Files
fn_registry/python/functions/cybersecurity/ping_host.py
T
egutierrez 935008ec3f feat(recon): grupo de reconocimiento de red + servicios + fingerprint web
Añade el capability group `recon` (dominio cybersecurity + pipelines, Python),
con la política de archivado OSINT y página madre docs/capabilities/recon.md.

Lookups y sondeo (wrappers de CLI):
- whois_lookup, rdap_lookup, dns_records, ping_host, traceroute_host, nmap_scan
- save_scan_to_osint (sink común) + recon_osint (pipeline one-shot scan+archivado)

Escaneo de puertos/servicios nativo (stdlib, sin nmap ni sudo):
- scan_tcp_ports: connect-scan TCP concurrente (open/closed/filtered)
- grab_service_banner: banner grab + identificación de servicio/versión real
- identify_port_service: puro, puerto -> servicio IANA esperado (~120 puertos)
- scan_port_services: pipeline one-shot (scan -> identify + banner por puerto abierto)

Fingerprint de tecnología web (estilo Wappalyzer), patrón pura/impura:
- fetch_http_fingerprint: GET stdlib, recoge headers/html/cookies (solo nombres)
- detect_web_tech: puro, matchea ~50 firmas regex -> tecnologías por categoría
- fingerprint_web_stack: pipeline one-shot url -> tecnologías

Todas devuelven dict {status} sin lanzar. Tests: 43 verdes, sin red externa.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:12:07 +02:00

131 lines
4.2 KiB
Python

"""Sondeo de disponibilidad ICMP de un host via el binario `ping` (Linux).
Funcion IMPURA: ejecuta `ping -c <count> -w <timeout_s> <host>` como subprocess
y parsea la salida (resumen de paquetes y linea rtt). Un host inalcanzable o
con ICMP filtrado NO es error: se reporta `status:"ok"` con `loss_pct=100` y
rtt None. Solo es error si el binario falla, el host esta vacio o hay timeout
duro del subprocess. El campo `raw` siempre esta presente.
"""
import re
import subprocess
_LOSS_RE = re.compile(r"([\d.]+)%\s+packet\s+loss")
_TX_RX_RE = re.compile(r"(\d+)\s+packets\s+transmitted,\s+(\d+)\s+(?:packets\s+)?received")
_RTT_RE = re.compile(
r"(?:rtt|round-trip)\s+min/avg/max(?:/mdev)?\s*=\s*"
r"([\d.]+)/([\d.]+)/([\d.]+)"
)
def ping_host(host: str, count: int = 4, timeout_s: int = 30) -> dict:
"""Hace ping ICMP a un host y parsea el resumen de paquetes y latencia.
Args:
host: Hostname o IP a sondear (ej. ``"1.1.1.1"`` o ``"google.com"``).
count: Numero de echo requests a enviar (`ping -c`).
timeout_s: Deadline total del comando ping (`ping -w`) y a la vez el
timeout duro del subprocess (este ultimo con +5s de margen).
Returns:
Dict de estado. En exito (incluido host inalcanzable)::
{
"status": "ok",
"host": <host>,
"packets_sent": <int>,
"packets_recv": <int>,
"loss_pct": <float>,
"rtt_avg_ms": <float|None>,
"rtt_min_ms": <float|None>,
"rtt_max_ms": <float|None>,
"raw": <stdout>,
}
En fallo (binario ausente, host vacio, timeout duro)::
{"status": "error", "error": <str>, "host": <host>, "raw": <stdout|"">}
"""
if not host or not host.strip():
return {"status": "error", "error": "ping_host: host vacio", "host": host, "raw": ""}
host = host.strip()
hard_timeout = float(timeout_s) + 5.0
try:
proc = subprocess.run(
["ping", "-c", str(count), "-w", str(timeout_s), host],
capture_output=True,
text=True,
timeout=hard_timeout,
)
except FileNotFoundError:
return {
"status": "error",
"error": "ping_host: binario `ping` no encontrado en PATH (paquete iputils-ping)",
"host": host,
"raw": "",
}
except subprocess.TimeoutExpired as exc:
partial = exc.stdout or ""
if isinstance(partial, bytes):
partial = partial.decode(errors="replace")
return {
"status": "error",
"error": f"ping_host: timeout duro del subprocess tras {hard_timeout}s",
"host": host,
"raw": partial,
}
raw = proc.stdout or ""
packets_sent: int | None = None
packets_recv: int | None = None
loss_pct: float = 100.0
rtt_min = rtt_avg = rtt_max = None
m_txrx = _TX_RX_RE.search(raw)
if m_txrx:
packets_sent = int(m_txrx.group(1))
packets_recv = int(m_txrx.group(2))
m_loss = _LOSS_RE.search(raw)
if m_loss:
loss_pct = float(m_loss.group(1))
m_rtt = _RTT_RE.search(raw)
if m_rtt:
rtt_min = float(m_rtt.group(1))
rtt_avg = float(m_rtt.group(2))
rtt_max = float(m_rtt.group(3))
return {
"status": "ok",
"host": host,
"packets_sent": packets_sent,
"packets_recv": packets_recv,
"loss_pct": loss_pct,
"rtt_avg_ms": rtt_avg,
"rtt_min_ms": rtt_min,
"rtt_max_ms": rtt_max,
"raw": raw,
}
if __name__ == "__main__":
try:
result = ping_host("1.1.1.1", count=3, timeout_s=10)
print(result["status"])
if result["status"] == "ok":
print(
f"loss={result['loss_pct']}% "
f"recv={result['packets_recv']}/{result['packets_sent']} "
f"avg={result['rtt_avg_ms']}ms"
)
print("--- raw ---")
print(result["raw"])
else:
print("error:", result.get("error"))
except Exception as exc: # smoke: tolera cualquier fallo de red sin romper
print("smoke fallo (tolerado):", exc)