--- name: scan_tcp_ports kind: function lang: py domain: cybersecurity version: "1.0.0" purity: impure signature: "def scan_tcp_ports(host: str, ports: str | list[int] = 'common', timeout_s: float = 1.0, workers: int = 100) -> dict" description: "Connect-scan TCP concurrente de un host sobre una lista o rango de puertos usando SOLO stdlib (socket + ThreadPoolExecutor). NO requiere nmap ni sudo: es un connect-scan simple (full handshake) que clasifica cada puerto en open/closed/filtered y los corre en paralelo con threads. Complementa a nmap_scan para escaneo rapido en Python puro; NO detecta version de servicio. Acepta ports como lista de ints, preset 'common', rango '1-1024' o CSV '22,80,443'. NO lanza: devuelve dict status ok/error con campo raw legible para evidencia OSINT." tags: [recon, cybersecurity, port-scan, tcp, network] params: - name: host desc: "Hostname o IP objetivo a escanear (ej. 'scanme.nmap.org', '127.0.0.1', '192.168.1.10'). Se resuelve a IP con socket.gethostbyname; si no resuelve devuelve status error. Vacio devuelve status error." - name: ports desc: "Especificacion de puertos. Cuatro formas: lista de ints [22,80,443]; string preset 'common' (~30 puertos comunes: 21,22,23,25,53,80,110,135,139,143,443,445,993,995,3306,3389,5432,5900,6379,8080,8443,27017... default); string rango '1-1024'; string CSV '22,80,443' (admite rangos mezclados '22,80,8000-8010'). Se normaliza a lista ordenada de ints unicos en 1..65535. Spec invalida devuelve status error." - name: timeout_s desc: "Timeout por conexion TCP en segundos (float). Default 1.0. Valor bajo en redes lentas puede marcar puertos realmente abiertos como filtered." - name: workers desc: "Numero de hilos concurrentes del ThreadPoolExecutor. Default 100. Se acota internamente a >=1 y al numero de puertos a escanear. Valores muy altos pueden saturar descriptores de archivo o la red local." output: "dict de estado. ok: {status:'ok', host, ip (resuelta), ports_scanned:int, open:[int] (ordenada), closed_count:int, filtered_count:int, results:[{port:int, state:'open'|'closed'|'filtered'}] (ordenado por puerto), raw:str (bloque PORT/STATE legible con open+filtered, omite closed)}. error (host no resuelve, spec invalida, host vacio): {status:'error', error:str, host}. Nunca lanza excepciones." uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_py_core" imports: [] tested: true tests: ["test_parse_ports_common", "test_parse_ports_rango", "test_parse_ports_csv", "test_parse_ports_lista", "test_scan_localhost_puerto_abierto", "test_scan_host_no_resuelve_error", "test_scan_host_vacio_error", "test_scan_spec_invalida_error"] test_file_path: "python/functions/cybersecurity/scan_tcp_ports_test.py" file_path: "python/functions/cybersecurity/scan_tcp_ports.py" --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from cybersecurity import scan_tcp_ports # 1) Puertos comunes contra el host oficial de pruebas de nmap (legal escanear). res = scan_tcp_ports("scanme.nmap.org", ports="common", timeout_s=1.0) if res["status"] == "ok": print(res["ip"], "abiertos:", res["open"]) # ej. 45.33.32.156 abiertos: [22, 80] print(res["raw"]) # bloque PORT/STATE para el vault else: print("error:", res["error"]) # 2) Rango de puertos concreto en localhost. res = scan_tcp_ports("127.0.0.1", ports="1-1024", timeout_s=0.3, workers=200) print(res["open"]) # 3) Lista explicita de puertos. res = scan_tcp_ports("192.168.1.10", ports=[22, 80, 443, 8080]) ``` Invocacion directa por el registry: ```bash # Via MCP (preferido): # mcp__registry__fn_run id="scan_tcp_ports_py_cybersecurity" args=["scanme.nmap.org"] # Via CLI: ./fn run scan_tcp_ports scanme.nmap.org ``` ## Cuando usarla Usala cuando quieras saber rapidamente que puertos TCP estan abiertos en UN host sin depender de nmap ni de sudo: escaneo en Python puro, scriptable y headless. Ideal en entornos donde no puedes instalar nmap o quieres un sondeo ligero de la superficie expuesta (un puñado de puertos o un rango pequeño) antes de pasar a herramientas mas pesadas. A diferencia de `nmap_scan_py_cybersecurity`: este NO da version ni nombre del servicio, solo el estado del puerto (open/closed/filtered). Si necesitas deteccion de version (-sV), scripts NSE, OS detection, UDP o barrido de subred, usa `nmap_scan`. Para un check rapido "que puertos responden" en 1 host, esta es mas directa. ## Gotchas - Funcion impura: abre conexiones TCP reales (full three-way handshake). Es un connect-scan, por lo que NO es sigiloso: queda en los logs del objetivo y es facilmente detectable por IDS/firewalls. Sin sudo no hace SYN-scan (half-open). - LEGAL: escanear puertos de hosts de terceros sin autorizacion puede ser delito. Escanea solo objetivos propios o con permiso explicito. `scanme.nmap.org` es el host oficial de pruebas de nmap (legal escanear). - `timeout_s` bajo en redes lentas o con alta latencia puede marcar puertos realmente ABIERTOS como `filtered` (la conexion no completa a tiempo). Sube `timeout_s` si dudas de los resultados; bajalo para escanear rangos grandes mas rapido a costa de falsos filtered. - `workers` muy alto (miles) puede agotar descriptores de archivo del proceso o saturar la red local / el objetivo. Se acota internamente al numero de puertos, pero un rango grande con workers altos sigue siendo agresivo. - Distincion de estados: `open` = connect exito; `closed` = RST / connection refused (host vivo, puerto cerrado); `filtered` = timeout / inalcanzable (probable firewall que descarta el paquete). Un host detras de firewall drop-all puede devolver TODO filtered aunque tenga servicios. - Solo IPv4: usa `socket.gethostbyname` (resuelve a A record). Para IPv6 usar otra ruta. No lanza: revisa siempre `res["status"]` antes de leer `open`/`results`.