Quando aggiungi un prefisso IPv6 a un tunnel da sede a sede, il pacchetto non viaggia come suggerisce lo schema. Il gateway locale prende l’intero pacchetto IPv6, lo cifra e mette davanti un header IP esterno la cui famiglia di indirizzi è decisa dagli indirizzi degli endpoint, non da ciò che sta dentro. Il gateway remoto rimuove quell’header esterno e inoltra il pacchetto originale sulla sua LAN, dove torna a essere un normale pacchetto IPv6. Tunnel esterno e traffico interno sono due conversazioni separate che condividono una chiave, una coppia di endpoint e nulla più.

Due header, due famiglie di indirizzi

In modalità tunnel IPsec il pacchetto IPv6 interno diventa il payload dietro un header ESP. L’header esterno che trasporta quel pacchetto ESP può essere IPv4 o IPv6, e segue local_addrs e remote_addrs. WireGuard fa lo stesso con meno cerimonia: il pacchetto interno è il payload di un datagramma UDP verso l’endpoint del peer. Entrambi lasciano intatto l’header interno, ed è per questo che il lato remoto può instradarlo dopo la decapsulazione.

Il percorso tra le sedi vede solo UDP 4500 quando IPsec usa il NAT traversal, o UDP 51820 per un listener WireGuard predefinito. Ogni router lungo il percorso può essere solo IPv4 e il traffico IPv6 attraversa comunque, perché non appare mai come pacchetto IPv6 su quel percorso. Questo dà una prima separazione netta: tcpdump -ni eth0 udp port 51820 mostra il percorso cifrato, tcpdump -ni wg0 ip6 mostra il traffico interno. Una cattura esterna intensa con una interna silenziosa significa che il kernel non mette IPv6 nel tunnel; è instradamento o policy, non stato del link.

Il gateway decrementa anche il limite di salti interno mentre inoltra, quindi gli endpoint contano come router sul percorso IPv6. Conteggio dei salti esterno e limite di salti interno si muovono in modo indipendente, e un pacchetto che muore al gateway remoto è morto nell’inoltro, non nella cifratura.

Selettori e AllowedIPs sono la lista dei permessi

IKEv2 negozia quali prefissi interni una child SA può portare. Su strongSwan 5.9 e successivi, scriverli in swanctl.conf appare così:

children {
  net6 {
    local_ts  = 2001:db8:aa::/48
    remote_ts = 2001:db8:bb::/48
    mode = tunnel
    esp_proposals = aes256gcm16-prfsha384-ecp384
    start_action = trap
  }
}

Omettere local_ts e remote_ts e strongSwan li deriva dalle sottoreti dell’interfaccia che seleziona. Su un host dual-stack quella scorciatoia produce spesso una coppia solo IPv4: la SA sale e IPv6 viene rifiutato prima di raggiungere il codice ESP. Scrivere entrambi i prefissi rende la SA idonea a portarli, e start_action = trap la avvia al primo pacchetto corrispondente.

WireGuard ha una sola lista che svolge due compiti. Nel blocco peer, AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48 è sia la rotta installata all’invio sia il filtro applicato alla ricezione: un pacchetto decifrato la cui origine interna cade fuori da quella lista viene scartato in silenzio. wg-quick aggiunge una rotta per voce, quindi il blocco peer qui sotto non ha bisogno di un comando ip -6 route separato.

[Interface]
Address = 2001:db8:aa:ff::1/64, fd00:aa:ff::1/64
ListenPort = 51820

[Peer]
PublicKey = <site-b-public-key>
Endpoint = 203.0.113.20:51820
AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48

Entrambe le liste devono essere speculari: la sede B accetta il prefisso della sede A come selettore remoto e lo instrada al ritorno, e il tunnel funziona solo quando le due direzioni concordano. Una lista asimmetrica produce il sintomo che costa più tempo — una stretta di mano sana, contatori che si muovono da un lato, e un ping da A a B che riesce mentre nulla da B raggiunge A.

Inoltro, rotte e il prefisso di ritorno

Linux non inoltra IPv6 finché non glielo dici, e l’impostazione è per namespace e non per interfaccia:

sysctl -w net.ipv6.conf.all.forwarding=1
ip -6 route add 2001:db8:bb::/48 dev wg0
ip -6 route get 2001:db8:bb::10 from 2001:db8:aa::10

La seconda riga è quella che si dimentica nei progetti basati su rotte. IPsec basato su policy installa una policy XFRM che corrisponde ai prefissi, ma un setup basato su rotte con vti6 o WireGuard ha bisogno di una vera rotta IPv6 che punti a quel dispositivo. Rendi persistente il sysctl sotto /etc/sysctl.d/; altrimenti un riavvio riporta silenziosamente l’inoltro a disattivato.

L’inoltro ha un effetto collaterale che vale la pena conoscere: il kernel ignora allora gli router advertisement a meno che net.ipv6.conf.<wan>.accept_ra non sia 2, quindi un gateway il cui indirizzo WAN arriva via SLAAC può perdere la rotta predefinita nel momento in cui abiliti l’inoltro. Imposta accept_ra = 2 su quell’interfaccia nello stesso file e la rotta sopravvive. Il core della sede B deve anche apprendere 2001:db8:aa::/48 dal gateway del tunnel, tramite rotta statica o un protocollo di routing sul tunnel, altrimenti la risposta muore dentro la rete di B.

Un controllo in quattro passi che localizza la rottura

Lavora in quest’ordine e fermati al primo passo che fallisce, perché ogni passo successivo dipende dai precedenti.

  1. Selettori: swanctl --list-sas dovrebbe mostrare la coppia di prefissi IPv6 sulla child SA, oppure wg show wg0 allowed-ips il prefisso remoto con un latest-handshakes recente.
  2. Rotta del kernel: ip -6 route get 2001:db8:bb::10 deve rispondere con il dispositivo del tunnel, non con l’interfaccia WAN.
  3. Cattura interna: tcpdump -ni wg0 ip6 mentre fai ping tra host LAN reali. Pacchetti che escono ma nessuno che torna indicano la rotta o il firewall di B; nessun pacchetto affatto riporta ai passi 1 o 2.
  4. Destinazione: sull’host B, ss -6 -ltn conferma che il servizio ascolta su IPv6, e i contatori locali del firewall mostrano se il pacchetto è stato accettato o scartato.

L’MTU rientra nella stessa passata. I router IPv6 non frammentano, quindi un pacchetto troppo grande viene scartato e Packet Too Big torna al mittente. Se le strette di mano e i ping piccoli funzionano ma un trasferimento di file si blocca, riduci il payload finché non passa: ping -6 -s 1400 -c 3 2001:db8:bb::10. Le interfacce WireGuard hanno per impostazione predefinita una MTU di 1420 byte e IPsec su IPv4 sottrae di più, quindi impostare la MTU interna batte sperare nella scoperta della MTU di percorso attraverso un firewall che scarta ICMPv6.

Modalità di guasto che sembrano problemi di firewall

Il più comune non è affatto un firewall: il servizio si lega a 0.0.0.0 invece di ::, il record AAAA esiste, e il client riceve un rifiuto rapido su IPv6 che un browser dual-stack nasconde tornando a IPv4. Se falliscono solo alcune applicazioni, controlla la famiglia in ascolto prima di toccare un set di regole.

Il secondo è il filtraggio ICMPv6. Neighbor Discovery e Packet Too Big fanno parte del funzionamento normale, non della diagnostica, e una policy che scarta tutto ICMPv6 produce un tunnel che si connette, si autentica e poi si blocca su qualunque cosa più grande di un pacchetto piccolo. Consenti i tipi da cui dipende il percorso e lascia i contatori attivi così uno scarto è visibile.

Il terzo è un prefisso aggiunto al tunnel ma mai al piano di indirizzamento della sede che lo possiede, così nessun router lo annuncia e nessun host può rispondere da lì. Parti con un prefisso e una coppia di host, dimostra entrambe le direzioni con i quattro passi, poi aggiungi il resto; un prefisso rotto resta una riga in una configurazione invece di una riprogettazione. L’unico riferimento ufficiale che vale la pena tenere aperto è RFC 8200, che definisce l’header che il tunnel trasporta.

Articoli correlati