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:
- Netcat o /dev/tcp: fes servir netcat si està disponible i suporta -z i -w. Si no, /dev/tcp com a alternativa.
- 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.
- Control de temps d’ espera: sense timeout, tot es bloqueja en hosts que no responen. Millor curt i clar.
- 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
- Després de desplegar regles de firewall (iptables, firewalld, pf, etc.)
- Per verificar que els serveis en múltiples nodes de Kubernetes segueixen exposats
- Per validar configuracions de VIPs en balancejadors (HAProxy, LVS)
- Per comprovar si el proveïdor ha bloquejat ports sortints (freqüent en VPS barats)
- 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
- Escanejar des d’una VLAN equivocada, i assumir que els resultats són globals.
- Fer servir /dev/tcp sense timeout, i que l’script es bloquegi en IPs no assolibles.
- Paral·lelitzar sense wait, saturar el sistema i generar centenars de processos zombies.
- Confiar en nc sense verificar la seva versió i acabar amb sortides buides.
- 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.

