Propósito
Módulo monitoring/services/tcp_service.py que implementa la lógica de sondeo TCP — intenta establecer una conexión a un host:puerto, mide latencia del handshake, y retorna resultado estructurado. Usada por el endpoint /api/monitoring/targets/{id}/tcp/check (SaaS público) para verificar si equipos con ICMP bloqueado tienen servicios vivos.
Defensa de seguridad: incluye guardia SSRF que rechaza direcciones privadas (RFC1918, loopback, link-local). Solo se sondean destinos públicos desde el SaaS; la LAN la maneja el Local Agent.
Firma
Función pública
def check_tcp(
host: str,
port: int,
timeout: float = DEFAULT_TIMEOUT_S # 3.0
) -> TcpResult:
"""Intenta conexión TCP a host:port y devuelve (status, latency_ms, error)."""
Resultado (dataclass)
@dataclass
class TcpResult:
status: str # "up" | "down"
latency_ms: float | None # ms si up, else None
error: str | None # mensaje si error
Lógica paso a paso
1. Validación de SSRF
safe, reason = validate_destination_host(host)
if not safe:
return TcpResult(status="down", latency_ms=None, error=f"Blocked destination: {reason}")
- Rechaza: RFC1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback (127.0.0.1, ::1), link-local (169.254.0.0/16, fe80::/10), multicast.
- Caso de uso: evitar que un atacante use el SaaS como proxy para probar la LAN interna.
2. Validación de puerto
if not (1 <= int(port) <= 65535):
return TcpResult(status="down", latency_ms=None, error=f"Invalid port: {port}")
3. Intento de conexión
start = time.monotonic()
with socket.create_connection((host, int(port)), timeout=timeout):
latency_ms = (time.monotonic() - start) * 1000.0
return TcpResult(status="up", latency_ms=round(latency_ms, 2))
- Usa
socket.create_connection()para resolver DNS + conectar (SYN). - Timeout configurable (default 3.0s).
- Si completa: status “up”, latencia en ms.
4. Manejo de excepciones
except TimeoutError:
return TcpResult(status="down", latency_ms=None, error="Connection timed out")
except OSError as e:
return TcpResult(status="down", latency_ms=None, error=str(e))
TimeoutError: exceso de tiempo (timeout o sin respuesta).OSError: incluyeConnectionRefusedError(puerto cerrado),HostUnreachableError,NetworkUnreachableError, socket errors generales.
Semántica de resultado
- “up”: puerto ACEPTA la conexión (SYN-ACK recibido, handshake completado).
- “down”: port RECHAZA (RST), timeout, o error de red. Nota: en términos del usuario, “el puerto no responde”.
Integración con endpoint
El endpoint POST /api/monitoring/targets/{target_id}/tcp/check en monitoring/api/operations.py usa check_tcp() así:
- Valida permisos y que target tenga
tcp_portconfigurado. - Si IP es privada → retorna último resultado conocido sin sondear.
- Si IP es pública → llama
check_tcp(target.ip_address, target.tcp_port). - Persiste resultado en
target.last_tcp_upytarget.last_tcp_check. - Recalcula
target.resolve_reachability()para estado combinado. - Escribe métrica
tcp_latencysi UP. - Notifica via WebSocket.
Constantes
DEFAULT_TIMEOUT_S = 3.0
Igual que el timeout del ping (ICMP). No debería tardar más que el sondeo del Agente.
Tests relacionados
tests/api/test_tcp_probe.py: 15 tests — validación, guardia SSRF, socket real, etc.
Véase también
- [[entity—monitoring—endpoint—tcp-check]]
- [[entity—monitoring—model—monitoring-target]]
- [[entity—terminal—service—sentinel-tcp-check]]
- [[feature—monitoring—sonda-tcp]]
- [[concept—monitoring—cns]]