La rotta predefinita IPv6 è sparita dopo aver attivato l'inoltro su Linux
Attivare l'inoltro IPv6 fa sì che il kernel ignori i router advertisement sul collegamento in uscita, e la rotta predefinita appresa scade. Correggilo con accept_ra.
Ho impostato net.ipv6.conf.all.forwarding=1 su un gateway Debian perché potesse inoltrare il traffico dei client VPN, e circa mezz'ora dopo la macchina non aveva più alcuna rotta predefinita IPv6. Il router a monte era attivo, il vicino link-local rispondeva ancora e il collegamento in uscita conservava il suo indirizzo globale. Ciò che si era fermato era la disponibilità del kernel ad accettare ancora router advertisement su quell'interfaccia. La rotta non è sparita quando ho digitato il sysctl; è sparita quando è scaduta la durata della rotta predefinita appresa dal RA e nulla l'ha rinnovata.
A prima vista nulla sembrava rotto. L'inoltro funzionava, i client passavano ancora finché la vecchia rotta era in cache, e il guasto è arrivato più tardi del cambiamento che lo aveva causato. Proprio quel ritardo fa sì che la colpa venga data al collegamento a monte o a una regola del firewall che non è mai stata coinvolta.
Leggi la tabella delle rotte prima di toccare qualsiasi sysctl
Inizia con ip -6 route show default e leggi gli attributi, non solo il gateway. Una rotta predefinita appresa dal RA è contrassegnata con proto ra, porta una preferenza e mostra un conto alla rovescia expires. Una rotta aggiunta a mano non ha né l'una né l'altra, e quella differenza ti dice quale meccanismo possiede la rotta.
$ ip -6 route show default
default via fe80::1 dev eth0 proto ra metric 1024 expires 1432sec hoplimit 64 pref mediumPoi guarda l'indirizzo: ip -6 addr show dev eth0. Un indirizzo globale con durate preferita e valida misurate in ore sopravvive a una rotta predefinita la cui durata del router si misura in minuti. Indirizzo presente e rotta predefinita assente è la firma di un problema di annunci. Se manca anche l'indirizzo, bisogna prima inseguire il collegamento, le informazioni sul prefisso o il rilevamento degli indirizzi duplicati.
Perché l'inoltro disattiva l'elaborazione dei RA
Un RA è un pacchetto ICMPv6 di tipo 134, inviato da una sorgente link-local con limite di salto 255. Prima di usarlo, il kernel consulta l'accept_ra dell'interfaccia. Il valore predefinito è 1, e 1 non significa sì. Significa: accetta solo finché questo host non inoltra. Imposta forwarding=1 e quella condizione non vale più, quindi l'annuncio viene scartato prima che venga considerato qualsiasi prefisso o router predefinito al suo interno.
Esistono tre valori, e solo il terzo sopravvive all'inoltro:
accept_ra | forwarding=0 | forwarding=1 |
|---|---|---|
| 0 | ignored | ignored |
| 1 (default) | accepted | ignored |
| 2 | accepted | accepted |
Altri due flag decidono che cosa viene effettivamente installato. accept_ra_defrtr controlla se il router predefinito dell'annuncio diventa una rotta, e accept_ra_pinfo controlla se le informazioni sul prefisso vengono usate per la configurazione dell'indirizzo. Disattivare accept_ra_defrtr è il modo consueto per mantenere indirizzi SLAAC su un host che inoltra senza apprendere una rotta predefinita. È una leva diversa da accept_ra, ed è quella a cui si ricorre quando si vogliono indirizzi ma non una rotta. La documentazione del kernel su IP sysctl li elenca tutti e tre.
Confermalo con tre comandi
Prima leggi i valori attivi invece del file che credi caricato:
sysctl net.ipv6.conf.eth0.forwarding net.ipv6.conf.all.forwarding \
net.ipv6.conf.eth0.accept_ra net.ipv6.conf.eth0.accept_ra_defrtrSecondo, dimostra che gli annunci arrivano ancora. Filtrare sul byte di tipo ICMPv6 basta e ti risparmia la lettura di un muro di traffico di scoperta dei vicini:
tcpdump -ni eth0 'icmp6 and ip6[40] == 134'Se per diversi minuti non compare nulla, accept_ra non è il tuo problema. Il collegamento a monte ha smesso di annunciare, qualcosa sul percorso filtra ICMPv6, oppure quell'interfaccia non è davvero sul segmento che pensi. Terzo, richiedi tu stesso un annuncio e leggi i suoi campi:
rdisc6 eth0rdisc6 è fornito nel pacchetto ndisc6 su Debian e Ubuntu. Nell'output guarda la durata del router e la preferenza prima di cambiare qualsiasi altra cosa. Un annuncio con durata del router 0 è l'istruzione a rimuovere il router predefinito, e nessun valore di accept_ra lo trasforma in una rotta utilizzabile. È anche così che si intercetta un CPE a monte in modalità bridge che distribuisce prefissi ma non si propone mai come router.
Rendi il collegamento in uscita l'unica interfaccia permissiva
Se il collegamento a monte deve essere il router predefinito, accetta lì gli annunci nonostante l'inoltro, e da nessun'altra parte:
# /etc/sysctl.d/60-ipv6-router.conf
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.eth0.accept_ra = 2
net.ipv6.conf.eth0.accept_ra_defrtr = 1L'ambito conta perché l'inoltro IPv6 in Linux è un solo interruttore globale, non uno per collegamento. Scrivi 1 in net.ipv6.conf.wg0.forwarding e poi leggi net.ipv6.conf.all.forwarding: il valore globale si inverte e ogni interfaccia comincia a inoltrare. Non puoi essere router sul tunnel e host normale sulla LAN. Quello che puoi limitare è l'accettazione. Mantieni accept_ra=2 sul collegamento in uscita e lascia le interfacce del tunnel e della LAN a 1 o 0, così un annuncio vagante che arriva dal tunnel non può installare una rotta predefinita che punta al suo interno.
Un sysctl -w temporaneo non sopravviverà al riavvio successivo, e sia systemd-networkd sia NetworkManager scrivono questi valori dalla propria configurazione. In networkd imposta IPv6Forwarding=yes e IPv6AcceptRA=yes nel file .network del collegamento in uscita; networkd documenta IPv6Forwarding come globale per lo stesso motivo lato kernel. Con NetworkManager è ipv6.method=auto sul collegamento in uscita a guidare il valore di accept_ra, quindi rileggi il sysctl dopo nmcli connection up invece di fidarti del primo controllo.
L'alternativa statica, ip -6 route add default via fe80::1 dev eth0, funziona davvero, ed è una risposta ragionevole quando il router a monte ha un indirizzo link-local stabile. Sii chiaro su ciò a cui rinunci. Una rotta predefinita statica non viene mai rinnovata né ritirata, quindi continua a puntare a un vicino morto dopo che il collegamento a monte è stato sostituito. La rotta appresa dal RA fallisce in modo sicuro; quella statica fallisce in silenzio.
Prova un pacchetto inoltrato, non quello del gateway stesso
I ping del gateway possono riuscire su una rotta che nessun client può usare, e possono riuscire anche perché il gateway è la sorgente. Prova da un client connesso, poi chiedi al gateway che cosa farebbe per l'indirizzo di quel client:
# on the VPN client
ping -6 -c 3 2001:4860:4860::8888
# on the gateway, using a client address
ip -6 route get 2001:4860:4860::8888 from fd42:20::2Poi aspetta una durata del router e guarda di nuovo. Lo stato stabile che vuoi è una rotta predefinita contrassegnata proto ra il cui valore expires si azzera di continuo, più all.forwarding=1. Se expires fa solo il conto alla rovescia, gli annunci vengono ancora scartati da qualche parte, e il colpevole abituale è un gestore di rete che riscrive accept_ra dopo che il sysctl di avvio è già stato eseguito.
Prima di chiudere, conferma quattro cose: la rotta predefinita è proto ra e si rinnova, accept_ra=2 vale solo per il collegamento in uscita, l'inoltro è ancora 1 dopo un riavvio e dopo un riavvio del gestore, e a completare un ping IPv6 è un client, non il gateway. Se il client fallisce ancora con una rotta sana, il prossimo sospetto è il percorso di ritorno o un firewall a monte, non l'annuncio.