Volver a la wiki

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}")

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))

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))

Semántica de resultado

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

Véase también

Subir