CreaRack-SL

tcp_service: Sonda TCP con Guardia SSRF

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: incluye ConnectionRefusedError (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í:

  1. Valida permisos y que target tenga tcp_port configurado.
  2. Si IP es privada → retorna último resultado conocido sin sondear.
  3. Si IP es pública → llama check_tcp(target.ip_address, target.tcp_port).
  4. Persiste resultado en target.last_tcp_up y target.last_tcp_check.
  5. Recalcula target.resolve_reachability() para estado combinado.
  6. Escribe métrica tcp_latency si UP.
  7. 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]]