Dimarts, febrer 10, 2026
SCRIPTING

Script Bash per a comprovar ports oberts en un rang d’IPs

Quan necessites comprovar ports en diversos hosts dins d’una xarxa, les eines no falten. Nmap, masscan, zmap… totes tenen el seu lloc. Però en entorns on no vols instal·lar res més enllà del mínim, o quan necessites un script ràpid que puguis revisar en dos minuts, Bash amb netcat o bash amb /dev/tcp continuen sent recursos sòlids.

Quan necessites això

Si estàs gestionant desenes de hosts, probablement tinguis ja alguna cosa muntada amb Ansible o Zabbix. Però per a validacions puntuals —per exemple després d’una reconfiguració de xarxa, una migració o un canvi de tallafocs— és pràctic tenir una forma ràpida de saber si determinat port està escoltant en una sèrie de màquines.

No substitueix una auditoria de xarxa. És una eina concreta, per a una necessitat específica. I quan saps el que estàs buscant (per exemple: “està el 22 obert a tots els nodes d’aquest /24?”), és més ràpid que pujar un escàner complet.

Decisions pràctiques

Abans de mostrar l’script, explico algunes decisions que eviten problemes després:

  1. Netcat o /dev/tcp: fes servir netcat si està disponible i suporta -z i -w. Si no, /dev/tcp com a alternativa.
  2. Sortida ordenada: que digui clarament si un port està obert o no. El que no es veu, no s’interpreta bé en logs automàtics.
  3. Control de temps d’ espera: sense timeout, tot es bloqueja en hosts que no responen. Millor curt i clar.
  4. IP incremental: evito dependre de nmap -sL, perquè pot fallar en subxarxes mal configurades o sense DNS.
  • Evitar processos zombies: compte amb els bucles en background sense control de processos. Si paral·lelitzes, fesservir wait amb límit de concurrència.

Script base amb netcat

Aquest script bàsic comprova un port donat en un rang d’ IPs. Senzill, llegible i suficient per a moltes tasques.

#!/bin/bash

START_IP="192.168.1.1"
END_IP="192.168.1.254"
PORT=22
TIMEOUT=1

function ip_to_int() {
    local IFS=.
    read -r i1 i2 i3 i4 <<< "$1"
    echo "$(( (i1 << 24) + (i2 << 16) + (i3 << 8) + i4 ))"
}

function int_to_ip() {
    local ip dec=$1
    for e in {1..4}; do
        ip=$((dec & 255))${ip:+.}$ip
        dec=$((dec >> 8))
    done
    echo "$ip"
}

START=$(ip_to_int "$START_IP")
END=$(ip_to_int "$END_IP")

for ((IP=START; IP<=END; IP++)); do
    CURRENT_IP=$(int_to_ip "$IP")

    nc -z -w $TIMEOUT "$CURRENT_IP" "$PORT" 2>/dev/null

    if [ $? -eq 0 ]; then
        echo "[OPEN] $CURRENT_IP:$PORT"
    else
        echo "[CLOSED] $CURRENT_IP:$PORT"
    fi
done

Netcat no és sempre el mateix

En alguns sistemes tens GNU Netcat, en d’altres tens OpenBSD Netcat. I hi ha diferències.

Per exemple, a Alpine o BusyBox, nc de vegades no suporta -z o -w. L’script anterior fallaria silenciosament. Ho detectes amb:

nc -z -w 1 127.0.0.1 22

Si veus que la connexió mai acaba o el paràmetre no és reconegut, tens una versió limitada. En aquest cas, millor fer servir /dev/tcp.

Alternativa sense netcat: /dev/tcp

Aquest és més portàtil, tot i que menys precís. Fa servir Bash directament per obrir el port i veure si respon.

#!/bin/bash

START_IP="192.168.1.1"
END_IP="192.168.1.254"
PORT=22
TIMEOUT=1

function ip_to_int() {
    local IFS=.
    read -r i1 i2 i3 i4 <<< "$1"
    echo "$(( (i1 << 24) + (i2 << 16) + (i3 << 8) + i4 ))"
}

function int_to_ip() {
    local ip dec=$1
    for e in {1..4}; do
        ip=$((dec & 255))${ip:+.}$ip
        dec=$((dec >> 8))
    done
    echo "$ip"
}

START=$(ip_to_int "$START_IP")
END=$(ip_to_int "$END_IP")

for ((IP=START; IP<=END; IP++)); do
    CURRENT_IP=$(int_to_ip "$IP")

    timeout $TIMEOUT bash -c "echo > /dev/tcp/$CURRENT_IP/$PORT" 2>/dev/null
    if [ $? -eq 0 ]; then
        echo "[OPEN] $CURRENT_IP:$PORT"
    else
        echo "[CLOSED] $CURRENT_IP:$PORT"
    fi
done

Compte: /dev/tcp només existeix a Bash. Si executes això en sh o dash, falla. Tampoc pots fer-lo servir en entorns que deshabiliten aquesta funcionalitat per seguretat (passa en alguns contenidors molt reduïts).

Afegir paral·lelisme sense perdre el control

Escanejar 254 IPs en sèrie triga massa. Però paral·lelitzar malament acaba en un sistema bloquejat o amb milers de processos zombi. Una forma senzilla és controlar la quantitat de processos en paral·lel amb una funció com aquesta:

MAX_PARALLEL=20
current_jobs=0

function wait_for_slot() {
    while [ "$(jobs | wc -l)" -ge "$MAX_PARALLEL" ]; do
        sleep 0.2
        jobs > /dev/null
    done
}

Y modificar el bucle:

for ((IP=START; IP<=END; IP++)); do
    CURRENT_IP=$(int_to_ip "$IP")
    wait_for_slot
    (
        nc -z -w $TIMEOUT "$CURRENT_IP" "$PORT" 2>/dev/null
        if [ $? -eq 0 ]; then
            echo "[OPEN] $CURRENT_IP:$PORT"
        else
            echo "[CLOSED] $CURRENT_IP:$PORT"
        fi
    ) &
done

wait

Quan fer servir aquest script

  1. Després de desplegar regles de firewall (iptables, firewalld, pf, etc.)
  2. Per verificar que els serveis en múltiples nodes de Kubernetes segueixen exposats
  3. Per validar configuracions de VIPs en balancejadors (HAProxy, LVS)
  4. Per comprovar si el proveïdor ha bloquejat ports sortints (freqüent en VPS barats)
  5. Per identificar nodes abandonats o no documentats que encara tenen serveis actius

Variacions que convé tenir a punt

Escanejar múltiples ports

En comptes d’ un port fix:

PORTS=(22 80 443 3306)

I dins del bucle:

for PORT in "${PORTS[@]}"; do
    wait_for_slot
    (
        nc -z -w $TIMEOUT "$CURRENT_IP" "$PORT" 2>/dev/null
        if [ $? -eq 0 ]; then
            echo "[OPEN] $CURRENT_IP:$PORT"
        fi
    ) &
done

Escaneig invers: comprovar connectivitat sortint

L’script pot viure dins d’un contenidor o node remot, i comprovar si pot accedir a cert host/port de destinació (per exemple, si surt al proxy o al servidor de base de dades).

Errors que poden aparèixer

  1. Escanejar des d’una VLAN equivocada, i assumir que els resultats són globals.
  2. Fer servir /dev/tcp sense timeout, i que l’script es bloquegi en IPs no assolibles.
  3. Paral·lelitzar sense wait, saturar el sistema i generar centenars de processos zombies.
  4. Confiar en nc sense verificar la seva versió i acabar amb sortides buides.
  5. Escanejar una xarxa /16 i no fer servir salts controlats (com fping -g) o filtres per ARP abans.

Què convé evitar

No facis servir ping previ. Molts hosts bloquegen ICMP, i això no significa que no estiguin oberts els ports. També evita acolorir sortides amb ANSI si l’script es farà servir en cron o logs de CI/CD. I és preferible no dependre d’eines externes com nmap, tret que s’estigui fent una anàlisi més seriosa.

Hi ha eines millors? Sí. Les fem servir sempre? No. Quan necessites una cosa simple, auditable, fàcil de portar entre servidors sense dependències, aquest tipus de script continua sent útil. La clau està en no improvisar-lo cada vegada, sinó tenir-ne un de clar, amb paràmetres ben definits i comportament predictible.

Deixa un comentari

L'adreça electrònica no es publicarà. Els camps necessaris estan marcats amb *