Quattro domande, gli strumenti giusti
Quando un server rallenta, quando un servizio smette di rispondere o quando nei log compare qualcosa di strano, le domande sono sempre le stesse: quanti sono collegati, chi apre più connessioni, che cosa sta facendo e chi bussa senza entrare. Questa guida raccoglie i comandi per rispondere a ciascuna, dal più rapido al più dettagliato.
Due strumenti fanno quasi tutto il lavoro:
ssmostra i socket del server: le connessioni che terminano su questa macchina, con stato, porte e processo. È già installato (pacchetto iproute2) e sostituisce il vecchionetstat.conntrackmostra la tabella dei flussi che il firewall sta seguendo: anche UDP e ICMP, anche il traffico che il server inoltra se fa da gateway, con byte e pacchetti per flusso.
Accanto a loro trovi gli strumenti classici che molti di noi usano da anni, come netstat, lsof, iptables e nmap, e i comandi per vedere chi è stato bloccato da fail2ban e come sta il proxy Squid.
Gli indirizzi negli esempi sono di documentazione (203.0.113.0/24, 198.51.100.0/24) e gli output sono indicativi.
Preparazione
sudo apt update
sudo apt install conntrack iftop nethogs tcpdump
ip -br link # il nome dell'interfaccia: eth0, ens18, enp1s0...
Negli esempi l’interfaccia è eth0: sostituiscila con la tua.
Una funzione per ripulire gli indirizzi
ss scrive l’indirizzo remoto insieme alla porta, e in forme diverse: 203.0.113.45:51122, [2001:db8::7]:443 e, per i servizi che ascoltano sia in IPv4 sia in IPv6, [::ffff:203.0.113.45]:51122. Questa funzione toglie porta, parentesi e prefisso ::ffff:, così lo stesso client viene contato una volta sola:
solo_ip() { sed -E 's/:[0-9*]+$//; s/^\[//; s/\]$//; s/^::ffff://'; }source ~/.bashrc
echo '[::ffff:203.0.113.45]:51122' | solo_ip # 203.0.113.45
Con un filtro di stato (ss -tn state established) l’output non ha la colonna dello stato: l’indirizzo locale è la colonna 3 e quello remoto la 4. Senza filtro di stato (ss -tan) la prima colonna è lo stato e gli indirizzi diventano la 4 e la 5. L’opzione -H toglie la riga d’intestazione, che altrimenti finirebbe nei conteggi.
Quanti indirizzi sono collegati
Il riassunto in un comando
ss -s
Total: 412
TCP: 389 (estab 214, closed 131, orphaned 0, timewait 128)
Transport Total IP IPv6
RAW 1 0 1
UDP 9 5 4
TCP 258 171 87
estab sono le connessioni attive; timewait quelle appena chiuse, che il kernel tiene ancora per qualche decina di secondi. Molte timewait sono normali su un server web.
Indirizzi diversi con almeno una connessione attiva
ss -Htn state established | awk '{print $4}' | solo_ip | sort -u | wc -l
Solo su un servizio
# client collegati al sito (porta 443)
ss -Htn state established '( sport = :443 )' | awk '{print $4}' | solo_ip | sort -u | wc -l
# client collegati in SSH
ss -Htn state established '( sport = :22 )' | awk '{print $4}' | solo_ip | sort -u
sport è la porta sorgente vista dal server, cioè la porta locale del servizio. Per le connessioni che il server apre verso l’esterno (aggiornamenti, backup, API) si filtra invece con dport.
Distinguere entrate e uscite
# porte su cui il server ascolta
ss -Htln | awk '{print $4}' | grep -oE '[0-9]+$' | sort -un | paste -sd' '
# connessioni in entrata: la porta locale è una di quelle in ascolto
ss -Htn state established | awk '{print $3}' | grep -oE '[0-9]+$' | sort | uniq -c | sort -rn | head
Nel secondo comando le porte “alte” (sopra 32768) con un conteggio alto sono di solito connessioni in uscita: il server ha aperto lui la connessione da una porta temporanea.
Con conntrack: anche UDP e il traffico inoltrato
# flussi seguiti in questo momento e limite massimo
sudo conntrack -C
cat /proc/sys/net/netfilter/nf_conntrack_max
# flussi per protocollo
sudo conntrack -L 2>/dev/null | awk '{print $1}' | sort | uniq -c
# indirizzi sorgente diversi nella tabella
sudo conntrack -L 2>/dev/null | grep -oP '^.*?\bsrc=\K\S+' | sort -u | wc -l
Ogni riga di conntrack -L contiene due volte src=: la prima riguarda la direzione originale, la seconda la risposta. L’espressione ^.*?\bsrc=\K\S+ prende solo la prima, cioè chi ha avviato il flusso. Il messaggio finale di conntrack (N flow entries have been shown) va su stderr, per questo c’è 2>/dev/null.
conntrack -C si avvicina a nf_conntrack_maxQuando la tabella è piena il kernel scarta le nuove connessioni e nel journal compare nf_conntrack: table full, dropping packet. Capita durante un attacco o su un gateway molto carico. Si vede con journalctl -k --grep 'table full'; il rimedio è trovare chi la riempie (sezioni seguenti) e, se il traffico è legittimo, alzare il limite con sysctl net.netfilter.nf_conntrack_max.
Quante connessioni fa un singolo IP
La classifica
ss -Htn state established | awk '{print $4}' | solo_ip | sort | uniq -c | sort -rn | head -15
186 203.0.113.45
24 198.51.100.7
6 192.0.2.10
3 198.51.100.120
Un browser normale apre da 2 a 10 connessioni verso un sito. Decine o centinaia dallo stesso indirizzo indicano un programma automatico, un proxy aziendale che raccoglie molti utenti o un tentativo di saturare il servizio.
Tutti gli stati, non solo le connessioni attive
ss -Htan | awk '{print $5}' | solo_ip | sort | uniq -c | sort -rn | head -15
Qui entrano anche SYN-RECV, TIME-WAIT e FIN-WAIT: chi apre e chiude connessioni a raffica compare in questa classifica ma non nella precedente.
Un indirizzo preciso
IP=203.0.113.45
# connessioni attive
ss -Htn state established | awk '{print $4}' | solo_ip | grep -cxF "$IP"
# suddivise per stato
ss -Htan | awk -v ip="$IP" '{a=$5; sub(/:[0-9*]+$/,"",a); gsub(/[][]/,"",a); sub(/^::ffff:/,"",a); if (a==ip) print $1}' \
| sort | uniq -c
# il dettaglio, con processo e byte scambiati
sudo ss -tnpi dst "$IP"
# i flussi nella tabella del firewall (anche UDP)
sudo conntrack -L -s "$IP" 2>/dev/null | wc -l
sudo conntrack -L -s "$IP" 2>/dev/null | head
Il filtro dst di ss confronta l’indirizzo remoto. Su alcuni servizi in ascolto su [::] il client IPv4 compare come ::ffff:203.0.113.45: se ss dst non trova nulla, prova ss -tnpi dst "::ffff:$IP".
Quante connessioni nuove al minuto
Il numero di connessioni aperte in un istante dice poco su chi le apre e le chiude di continuo. Per misurare la frequenza si ascoltano per un minuto gli eventi di conntrack:
sudo timeout 60 conntrack -E -e NEW 2>/dev/null \
| grep -oP '^.*?\bsrc=\K\S+' | sort | uniq -c | sort -rn | head
744 203.0.113.45
38 198.51.100.7
5 192.0.2.10
Il risultato è il numero di nuove connessioni nell’ultimo minuto per indirizzo. Per un indirizzo solo: sudo timeout 60 conntrack -E -e NEW -s 203.0.113.45 2>/dev/null | wc -l.
Che tipo di traffico
Su quali servizi
# connessioni attive per porta locale (il servizio)
ss -Htn state established | awk '{print $3}' | grep -oE '[0-9]+$' | sort | uniq -c | sort -rn
# per un indirizzo preciso: quali porte usa
sudo conntrack -L -s "$IP" 2>/dev/null | grep -oP '^.*?\bdport=\K\d+' | sort | uniq -c | sort -rn
# quale processo risponde a quell'indirizzo
sudo ss -Htnp state established | grep -F "$IP" | grep -oP 'users:\(\("\K[^"]+' | sort | uniq -c
| Porta | Servizio tipico | Che cosa può indicare un volume alto |
|---|---|---|
| 22 | SSH | Tentativi di accesso, oppure backup e rsync legittimi |
| 25, 465, 587 | Posta | Tentativi di usare il server come relay, o di indovinare le password SMTP |
| 53 | DNS | Su un server pubblico: uso come amplificatore di attacchi |
| 80, 443 | Web | Traffico normale, crawler, scanner o tentativi di saturazione |
| 3306, 5432 | Database | Non dovrebbero essere raggiungibili da Internet |
| 3389, 445 | RDP, SMB | Scansioni in cerca di sistemi Windows esposti |
Banda: chi sta consumando
# banda per coppia di indirizzi, senza risolvere i nomi, con le porte
sudo iftop -i eth0 -nNP
# solo il traffico di un indirizzo
sudo iftop -i eth0 -nNP -f "host 203.0.113.45"
# banda per processo
sudo nethogs eth0
In iftop le tre colonne a destra sono le medie su 2, 10 e 40 secondi. Con t si cambia la visualizzazione, con q si esce. nethogs risponde a una domanda diversa: quale programma del server sta usando la rete.
La velocità della linea
sudo apt install speedtest-cli
speedtest-cli --simple
Misura download e upload verso un server di speedtest.net. Serve a capire se il collo di bottiglia è la linea: se iftop mostra un traffico vicino al valore misurato, la linea è satura. Il test stesso consuma tutta la banda per qualche secondo, quindi non lanciarlo nei momenti critici.
Byte e pacchetti per flusso
# attiva il conteggio dei byte in conntrack (vale fino al riavvio)
sudo sysctl -w net.netfilter.nf_conntrack_acct=1
# dopo qualche minuto: i flussi di un indirizzo con pacchetti e byte
sudo conntrack -L -s 203.0.113.45 2>/dev/null
tcp 6 431990 ESTABLISHED src=203.0.113.45 dst=192.0.2.80 sport=51122 dport=443 packets=1840 bytes=96412 src=192.0.2.80 dst=203.0.113.45 sport=443 dport=51122 packets=2911 bytes=4108230 [ASSURED] mark=0 use=1
I primi packets e bytes sono quelli inviati dal client, i secondi quelli della risposta. Qui il client ha mandato 96 kB e ricevuto 4 MB: un download, non un attacco. Il conteggio vale solo per i flussi nati dopo l’attivazione.
Guardare i pacchetti
# i primi 50 pacchetti da e verso un indirizzo
sudo tcpdump -nni eth0 -c 50 host 203.0.113.45
# solo i SYN, cioè le richieste di nuove connessioni TCP
sudo tcpdump -nni eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'
# le porte di destinazione colpite da un indirizzo, su 1000 pacchetti
sudo tcpdump -nni eth0 -q -c 1000 src host 203.0.113.45 2>/dev/null \
| awk '{print $5}' | sed 's/:$//' | awk -F. '{print $NF}' | sort | uniq -c | sort -rn | head
# salvare un minuto di traffico per analizzarlo con Wireshark
sudo timeout 60 tcpdump -nni eth0 -w /tmp/cattura.pcap host 203.0.113.45
In ogni riga di tcpdump l’indirizzo e la porta sono uniti da un punto: 203.0.113.45.51122 > 192.0.2.80.443. Per questo l’ultimo comando separa sul punto e prende l’ultimo campo. Se un indirizzo tocca decine di porte diverse, sta facendo una scansione.
Porte aperte e servizi in ascolto
Prima di chiedersi chi si collega, conviene sapere dove può collegarsi: quali servizi ascoltano e su quali indirizzi.
# moderno: ss (sempre presente)
sudo ss -tulpn
# classico: netstat (pacchetto net-tools)
sudo apt install net-tools
sudo netstat -tulpen
sudo netstat -plnut
# lsof: solo i socket TCP in ascolto, senza risolvere nomi e porte
sudo lsof -nP -iTCP -sTCP:LISTEN
sudo lsof -nP -iUDP
| Opzione | Significato |
|---|---|
-t / -u | Socket TCP / UDP |
-l | Solo quelli in ascolto (listen) |
-p | Processo e PID che possiedono il socket (serve sudo per vederli tutti) |
-n | Numeri al posto di nomi: più veloce, e mostra le porte reali |
-e | Solo netstat: utente e inode del socket |
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3))
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("squid",pid=1104,fd=14))
tcp LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=950,fd=21))
Conta soprattutto l’indirizzo locale: 0.0.0.0, [::] o * vogliono dire “su tutte le interfacce”, quindi raggiungibile dalla rete se il firewall lo permette; 127.0.0.1 o [::1] vogliono dire “solo da questa macchina”. Un database su 0.0.0.0 è quasi sempre un errore.
La prova dall’esterno con nmap
Quello che il server dice di ascoltare non è detto che sia raggiungibile: in mezzo c’è il firewall. La verifica vera si fa da un’altra macchina:
sudo apt install nmap
# connessione TCP completa sulle 1000 porte più comuni (non serve root)
nmap -sT 192.168.1.6
# tutte le 65535 porte TCP
nmap -sT -p- 192.168.1.6
# le 50 porte UDP più comuni (lento, serve root)
sudo nmap -sU --top-ports 50 192.168.1.6
Attenzione alla sintassi: l’opzione è -sT, con il trattino. Nel risultato, open vuol dire che il servizio risponde, closed che la porta è raggiungibile ma non ascolta nessuno, filtered che il firewall scarta i pacchetti. Usa nmap solo sui tuoi sistemi o su quelli per cui hai un’autorizzazione scritta.
Regole e contatori del firewall
I contatori delle regole dicono quanti pacchetti e byte hanno colpito ciascuna regola: sono il modo più rapido per sapere se una regola sta lavorando.
# tabella filter: regole numerate, contatori, indirizzi senza risoluzione DNS
sudo iptables -L -n -v --line-numbers
# tabella nat (port forward, masquerade) con i valori esatti
sudo iptables -t nat -L -n -v -x
# le regole nel formato dei comandi che le hanno create
sudo iptables -S
sudo iptables -S | grep f2b # solo le catene di fail2ban
# lo stesso per IPv6
sudo ip6tables -L -n -v --line-numbers
-vaggiunge le colonnepktsebytes;-xle scrive per intero invece di arrotondarle in K, M e G.--line-numbersserve per togliere una regola precisa:sudo iptables -D INPUT 5cancella la regola 5 della catena INPUT.- Nella tabella
nat, la catenaPREROUTINGcontiene i port forward (DNAT) ePOSTROUTINGil mascheramento verso Internet (MASQUERADE). Se i contatori di un DNAT restano a zero, il traffico non arriva fino al server. sudo iptables -Zazzera i contatori: utile per misurare da adesso in poi, ma perdi lo storico.
Dalla 20.10 il comando iptables di Ubuntu è iptables-nft: scrive le regole dentro nftables. Le regole create con iptables compaiono anche in sudo nft list ruleset, ma non vale il contrario: quelle scritte direttamente con nft (come la tabella blocchi o f2b-table di fail2ban con banaction nftables) non si vedono con iptables -L. Nel dubbio, sudo nft list ruleset mostra tutto.
Chi ha tentato a vuoto
“A vuoto” può voler dire cose diverse, e ognuna si vede in un posto diverso: connessioni mai completate, pacchetti verso porte chiuse, autenticazioni fallite.
Connessioni mai completate (SYN senza risposta)
# quante connessioni sono ferme a metà
ss -Htn state syn-recv | wc -l
# da chi arrivano
ss -Htn state syn-recv | awk '{print $4}' | solo_ip | sort | uniq -c | sort -rn | head
In condizioni normali sono zero o poche unità. Centinaia o migliaia indicano un SYN flood: il client manda la richiesta di connessione e non completa mai la stretta di mano. Gli indirizzi sorgente di un SYN flood sono spesso falsificati, quindi la classifica va presa con cautela.
I contatori del kernel
nstat -az TcpAttemptFails TcpOutRsts TcpExtListenDrops TcpExtListenOverflows TcpExtSyncookiesSent
| Contatore | Che cosa conta |
|---|---|
TcpAttemptFails | Connessioni fallite prima di essere stabilite |
TcpOutRsts | Reset inviati dal server, per esempio a chi bussa su una porta chiusa che il firewall non scarta |
TcpExtListenDrops, TcpExtListenOverflows | Connessioni scartate perché la coda del servizio era piena |
TcpExtSyncookiesSent | Il kernel si sta difendendo da un SYN flood con i SYN cookie |
I valori sono totali dall’avvio. Per vedere quanto crescono, esegui nstat senza -a due volte a qualche secondo di distanza: la seconda volta mostra solo l’incremento.
Pacchetti scartati dal firewall
Se il firewall registra i pacchetti scartati con un prefisso (per esempio log prefix "nft-drop: ", come nella guida Firewall completo con nftables), il journal del kernel dice chi ha bussato e dove:
# indirizzi respinti nell'ultima ora, con il numero di pacchetti
sudo journalctl -k --since "1 hour ago" --grep 'nft-drop' -o cat \
| grep -oP 'SRC=\K\S+' | sort | uniq -c | sort -rn | head
# quanti indirizzi diversi
sudo journalctl -k --since "1 hour ago" --grep 'nft-drop' -o cat \
| grep -oP 'SRC=\K\S+' | sort -u | wc -l
# le porte più cercate
sudo journalctl -k --since "1 hour ago" --grep 'nft-drop' -o cat \
| grep -oP 'DPT=\K\d+' | sort | uniq -c | sort -rn | head
Senza registrazione, i contatori delle regole dicono almeno quanti pacchetti sono stati scartati: sudo nft list ruleset | grep counter.
Autenticazioni fallite
# SSH: tentativi falliti per indirizzo nelle ultime 24 ore
sudo journalctl -u ssh --since "24 hours ago" -o cat \
| grep -E 'Failed password|Invalid user|invalid user|authenticating user' \
| grep -oP '\S+(?= port \d+)' | sort | uniq -c | sort -rn | head
# quanti indirizzi diversi ci hanno provato
sudo journalctl -u ssh --since "24 hours ago" -o cat \
| grep -E 'Failed password|Invalid user|invalid user|authenticating user' \
| grep -oP '\S+(?= port \d+)' | sort -u | wc -l
# quanti di questi sono poi riusciti a entrare (dovrebbe essere vuoto)
comm -12 \
<(sudo journalctl -u ssh --since "24 hours ago" -o cat | grep -E 'Failed|[Ii]nvalid user|authenticating user' | grep -oP '\S+(?= port \d+)' | sort -u) \
<(sudo journalctl -u ssh --since "24 hours ago" -o cat | grep Accepted | grep -oP 'from \K\S+' | sort -u)
L’ultimo comando confronta i due elenchi e stampa gli indirizzi presenti in entrambi. È normale trovarci il tuo indirizzo dopo una password sbagliata; qualsiasi altro indirizzo va controllato subito.
# web: indirizzi che hanno ricevuto solo errori (nessuna risposta 2xx o 3xx)
sudo awk '{tot[$1]++} $9 ~ /^[45]/ {err[$1]++}
END {for (ip in tot) if (err[ip] == tot[ip]) print tot[ip], ip}' /var/log/nginx/access.log \
| sort -rn | head
# quanti sono
sudo awk '{tot[$1]++} $9 ~ /^[45]/ {err[$1]++}
END {for (ip in tot) if (err[ip] == tot[ip]) n++; print n+0}' /var/log/nginx/access.log
Un visitatore vero riceve almeno qualche pagina con codice 200. Un indirizzo che colleziona solo 404 e 403 sta cercando qualcosa che non c’è.
Gli indirizzi bloccati da fail2ban
# le jail attive
sudo fail2ban-client status
# una jail: tentativi in corso, ban attuali e totali, elenco degli IP
sudo fail2ban-client status sshd
sudo fail2ban-client status squid
# solo l'elenco degli IP bloccati, uno per riga
sudo fail2ban-client get sshd banip | tr ' ' '\n'
# con l'ora del ban e quella dello sblocco
sudo fail2ban-client get sshd banip --with-time
# sbloccare un indirizzo in una jail, oppure in tutte
sudo fail2ban-client set squid unbanip 192.168.31.9
sudo fail2ban-client unban 192.168.31.9
# ban registrati nel log, per jail e per indirizzo
grep -c '\[sshd\] Ban ' /var/log/fail2ban.log
grep '\[sshd\] Ban ' /var/log/fail2ban.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head
Due dettagli che fanno perdere tempo:
getlegge,setagisce. Per sbloccare si usaset <jail> unbanip <ip>.get … actionunbaneset … actionunbanriguardano invece il comando che l’azione esegue allo sblocco: leggerlo è innocuo, ma “impostarlo” a un indirizzo IP rompe l’azione della jail fino al riavvio di fail2ban.- Estrarre gli IP con un’espressione regolare come
grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}'funziona solo con IPv4.get <jail> baniprestituisce già l’elenco pulito, IPv6 compresi.
La jail squid legge /var/log/squid/access.log e blocca chi accumula richieste negate: in una rete aziendale capita che sia un PC dell’ufficio, magari con un programma che insiste su un sito bloccato. Aggiungi la rete interna a ignoreip in /etc/fail2ban/jail.local (per esempio ignoreip = 127.0.0.1/8 ::1 192.168.31.0/24) e riavvia fail2ban.
Copiare jail.conf in jail.local funziona, ma dopo un aggiornamento ti ritrovi con centinaia di righe vecchie. Meglio un jail.local con le sole righe che cambi. Lo stesso per i filtri: invece di modificare filter.d/sshd.conf, crea filter.d/sshd.local con le sole righe nuove, così l’aggiornamento del pacchetto non le cancella. I dettagli sono nella guida Fail2ban su Linux.
Un pannello dei ban sempre aperto
Per tenere d’occhio i ban su un secondo monitor o in una finestra tmux, questo script mostra per ogni jail gli indirizzi bloccati e i minuti che mancano allo sblocco, più lo stato del server. Si aggiorna ogni 10 secondi.
#!/bin/bash
# Pannello dei ban di fail2ban: IP bloccati, minuti allo sblocco, stato del server.
# Uso: sudo visualizza-ban [jail ...] es.: sudo visualizza-ban sshd squid
# Esci con Ctrl+C.
JAIL=("$@"); [[ ${#JAIL[@]} -eq 0 ]] && JAIL=(sshd)
AGGIORNA=10
LOG=/var/log/fail2ban.log
[[ $EUID -eq 0 ]] || { echo "Eseguilo come root: sudo $0 ${JAIL[*]}"; exit 1; }
BLU=$'\e[1;44m'; VERDE=$'\e[1;32m'; GIALLO=$'\e[1;33m'; NORM=$'\e[0m'
trap 'tput cnorm; echo; exit 0' INT TERM
tput civis # nasconde il cursore
minuti_a() { # minuti da adesso a una data
local fine; fine=$(date -d "$1" +%s 2>/dev/null) || { echo "?"; return; }
echo $(( (fine - $(date +%s) + 59) / 60 ))
}
pannello() {
printf '%s Indirizzi bloccati da fail2ban %s %s\n' "$BLU" "$NORM" "$(date '+%d/%m/%Y %H:%M:%S')"
for j in "${JAIL[@]}"; do
local elenco n tot i=0
elenco=$(fail2ban-client get "$j" banip --with-time 2>/dev/null)
n=$(grep -c . <<< "$elenco")
tot=$(zgrep -h "\[$j\] Ban " "$LOG"* 2>/dev/null | wc -l)
printf '\n%sJail %s%s bloccati ora: %d ban nei log: %d\n' "$VERDE" "$j" "$NORM" "$n" "$tot"
printf ' ┌─────┬──────────────────────────────────────────┬───────────┐\n'
printf ' │ N. │ Indirizzo │ Sblocco │\n'
printf ' ├─────┼──────────────────────────────────────────┼───────────┤\n'
while read -r riga; do
[[ -z $riga ]] && continue
local ip durata fine resta
ip=${riga%%[[:space:]]*}
durata=$(awk '{for (k=1; k<NF; k++) if ($k == "+") print $(k+1)}' <<< "$riga")
fine=$(awk '{print $(NF-1), $NF}' <<< "$riga")
if [[ $durata == -1 ]]; then resta="sempre"; else resta="$(minuti_a "$fine") min"; fi
printf ' │ %3d │ %-40s │ %9s │\n' $((++i)) "$ip" "$resta"
done <<< "$elenco"
(( n == 0 )) && printf ' │ │ %-40s │ │\n' "nessuno"
printf ' └─────┴──────────────────────────────────────────┴───────────┘\n'
done
local temp=""
[[ -r /sys/class/thermal/thermal_zone0/temp ]] &&
temp=$(awk '{printf "%.1f °C", $1/1000}' /sys/class/thermal/thermal_zone0/temp)
printf '\n%sServer%s\n' "$GIALLO" "$NORM"
printf ' Carico : %s\n' "$(cut -d' ' -f1-3 /proc/loadavg)"
[[ -n $temp ]] && printf ' Temperatura : %s\n' "$temp"
printf ' Disco / : %s\n' "$(df -h / | awk 'NR==2 {print $5 " usato"}')"
printf ' Memoria : %s\n' "$(free -m | awk 'NR==2 {print $3 " / " $2 " MB"}')"
printf ' Utenti : %s\n' "$(who | awk '{print $1}' | sort -u | paste -sd, -)"
printf ' Connessioni : %s attive\n' "$(ss -Htn state established | wc -l)"
}
while true; do
schermo=$(pannello) # prepara tutto, poi disegna in un colpo
tput clear; printf '%s\n' "$schermo"
sleep "$AGGIORNA"
donesudo chmod 750 /usr/local/sbin/visualizza-ban
sudo visualizza-ban sshd squid
Indirizzi bloccati da fail2ban 13/11/2026 10:42:07
Jail sshd bloccati ora: 2 ban nei log: 318
┌─────┬──────────────────────────────────────────┬───────────┐
│ N. │ Indirizzo │ Sblocco │
├─────┼──────────────────────────────────────────┼───────────┤
│ 1 │ 203.0.113.45 │ 97 min │
│ 2 │ 2001:db8:bad::17 │ 112 min │
└─────┴──────────────────────────────────────────┴───────────┘
Rispetto alle versioni che si trovano spesso in giro, con un tempo di ban fisso nel codice e la ricerca dell’IP nel log con grep, questo script:
- legge ora del ban e scadenza da fail2ban (
banip --with-time, disponibile da fail2ban 0.11), quindi i minuti sono giusti con qualsiasibantimee anche con i ban progressivi; - con nessun indirizzo bloccato mostra 0, non 1:
echo "" | wc -lconta una riga anche quando la variabile è vuota,grep -c .no; - non confonde gli indirizzi: un
grep 1.2.3.4nel log trova anche11.2.3.45, perché il punto vale “qualsiasi carattere”; - conta i ban della singola jail anche nei log ruotati (
zgrepsufail2ban.log*); - prepara il pannello e poi lo disegna tutto insieme, così lo schermo non sfarfalla e non servono sequenze di escape per cancellare le righe.
Il proxy Squid
Su un proxy Squid ogni navigazione degli utenti passa per il server, quindi le domande “chi è collegato” e “quanto” si fanno anche al proxy stesso.
# statistiche generali (qui il proxy ascolta sulla porta 80; il valore predefinito è 3128)
squidclient -p 80 mgr:info
# i file descriptor: quanti ne può usare, quanti ne usa, quanti restano
squidclient -p 80 mgr:info | grep -i 'file desc'
# senza squidclient, con l'interfaccia di gestione via HTTP
curl -s http://127.0.0.1:80/squid-internal-mgr/info | grep -i 'file desc'
# client collegati al proxy, con il numero di connessioni
ss -Htn state established '( sport = :80 )' | awk '{print $4}' | solo_ip | sort | uniq -c | sort -rn | head
# richieste negate (TCP_DENIED) per client
sudo awk '$4 ~ /DENIED/ {print $3}' /var/log/squid/access.log | sort | uniq -c | sort -rn | head
# i siti più richiesti
sudo awk '{print $7}' /var/log/squid/access.log | awk -F/ '{print $3}' | sort | uniq -c | sort -rn | head
Maximum number of file descriptors: 65536
Largest file desc currently in use: 412
Number of file desc currently in use: 388
Available number of file descriptors: 65148
Reserved number of file descriptors: 100
Ogni connessione che passa dal proxy occupa almeno due file descriptor, uno verso il client e uno verso il sito. Se i disponibili si avvicinano a zero, nel cache.log compare Your cache is running out of filedescriptors e Squid smette di accettare connessioni. Si alza il limite con max_filedescriptors in squid.conf e, se serve, con LimitNOFILE in un override del servizio (sudo systemctl edit squid).
Nel formato di log nativo di Squid il terzo campo è il client, il quarto il risultato (TCP_MISS/200, TCP_DENIED/403…) e il settimo l’URL. L’interfaccia di gestione risponde di norma solo da localhost (regola http_access allow localhost manager).
chmod +sSe al proxy è collegato il filtro URL ufdbGuard, la sua procedura di installazione può chiedere chmod +s /usr/sbin/ufdbsignal: il bit setuid fa girare il programma con i diritti del suo proprietario, così Squid, che gira come utente proxy, può mandare il segnale di ricarica al demone. Un eseguibile setuid di root è un punto delicato: controlla con ls -l /usr/sbin/ufdbsignal che proprietario e permessi siano quelli attesi (-rwsr-xr-x root), non dare mai il bit setuid ad altri programmi per “far funzionare” qualcosa e ricontrollalo dopo ogni aggiornamento. Per un elenco di tutti gli eseguibili setuid: sudo find / -xdev -perm -4000 -type f -ls.
Uno script di riepilogo
Tutti i conteggi principali in un colpo solo, da lanciare quando qualcosa non va o da far eseguire periodicamente.
#!/bin/bash
# Riepilogo delle connessioni: quanti, chi, su quali servizi, chi tenta a vuoto.
set -uo pipefail
solo_ip() { sed -E 's/:[0-9*]+$//; s/^\[//; s/\]$//; s/^::ffff://'; }
DA="${1:-1 hour ago}" # intervallo per i log, es.: chi-e-collegato "24 hours ago"
titolo() { printf '\n== %s ==\n' "$1"; }
titolo "Connessioni attive"
ss -Htn state established | wc -l
titolo "Indirizzi diversi collegati"
ss -Htn state established | awk '{print $4}' | solo_ip | sort -u | wc -l
titolo "Indirizzi con più connessioni attive"
ss -Htn state established | awk '{print $4}' | solo_ip | sort | uniq -c | sort -rn | head -10
titolo "Connessioni attive per porta locale"
ss -Htn state established | awk '{print $3}' | grep -oE '[0-9]+$' | sort | uniq -c | sort -rn | head -10
titolo "Connessioni a metà (SYN-RECV)"
ss -Htn state syn-recv | wc -l
if command -v conntrack >/dev/null; then
titolo "Flussi seguiti dal firewall / massimo"
echo "$(conntrack -C) / $(cat /proc/sys/net/netfilter/nf_conntrack_max)"
fi
titolo "SSH: indirizzi con tentativi falliti (da: $DA)"
journalctl -u ssh --since "$DA" -o cat \
| grep -E 'Failed password|Invalid user|invalid user|authenticating user' \
| grep -oP '\S+(?= port \d+)' | sort | uniq -c | sort -rn \
| tee >(wc -l | sed 's/^/indirizzi diversi: /' >&2) | head -10
titolo "SSH: accessi riusciti (da: $DA)"
journalctl -u ssh --since "$DA" -o cat | grep Accepted || echo "nessuno"
titolo "Firewall: indirizzi respinti (da: $DA)"
journalctl -k --since "$DA" --grep 'nft-' -o cat 2>/dev/null \
| grep -oP 'SRC=\K\S+' | sort | uniq -c | sort -rn | head -10sudo chmod 750 /usr/local/sbin/chi-e-collegato
sudo chi-e-collegato # ultima ora
sudo chi-e-collegato "24 hours ago" # ultimo giorno
Lo script usa set -uo pipefail ma non -e: un grep che non trova nulla non deve interrompere il riepilogo. Il numero di indirizzi diversi per SSH viene scritto su stderr da tee, quindi può comparire qualche riga più in alto o più in basso della classifica.
Come leggere i numeri
| Quello che vedi | Probabile significato | Che cosa fare |
|---|---|---|
Un IP con centinaia di connessioni ESTAB sulla 443 | Bot, scraper o tentativo di saturazione; a volte un proxy aziendale | Controlla con conntrack -E la frequenza; limitalo nel firewall o nel server web |
Migliaia di SYN-RECV | SYN flood | Verifica che i SYN cookie siano attivi (sysctl net.ipv4.tcp_syncookies = 1); per attacchi grandi serve il provider |
| Un IP che tocca decine di porte | Scansione | Blocca l’indirizzo per un periodo limitato |
| Molti IP diversi con pochi tentativi SSH ciascuno | Attacco distribuito a bassa intensità | Il blocco per IP serve poco: accesso SSH con sole chiavi e solo da indirizzi noti |
Tante TIME-WAIT | Normale su server web e proxy | Nessuna, salvo esaurimento delle porte in uscita |
| Tutto il traffico da uno o due indirizzi | Davanti al server c’è un proxy, un bilanciatore o una CDN | Leggi l’IP reale nei log del server web (X-Forwarded-For), non in ss |
Il passo successivo
Questi comandi servono a capire. Per agire, cioè bloccare automaticamente chi supera una soglia, la guida Rilevare le violazioni su Ubuntu e bloccare gli IP per 120 minuti usa le stesse informazioni per alimentare un set di nftables con scadenza, fail2ban o uno script con timer.
Prova sempre la configurazione in laboratorio, salva quella attuale prima di modificarla e tieni un accesso di emergenza (console o accesso fuori banda) quando lavori su firewall e VPN da remoto.