Tirare fuori una sola porta dalla VPN richiede routing per policy
Una regola NAT riscrive gli indirizzi, ma non sceglie un collegamento in uscita. Ecco la regola, la tabella e il mark che tengono davvero una porta TCP fuori dal tunnel.
La richiesta è sempre una variante della stessa frase: tieni la VPN per la navigazione, ma lascia che SSH o un processo di backup usino la connessione normale. Il consiglio che circola in merito, inoltrare la porta con regole NAT di iptables, non lo fa. Una regola NAT riscrive indirizzi e porte all'interno di un flusso; non sceglie mai da quale interfaccia esce il pacchetto. Quella decisione spetta alla ricerca di instradamento, e la ricerca di instradamento ha bisogno di aiuto per vedere una porta.
Perché una rotta normale non può corrispondere a una porta
Linux sceglie una rotta cercando l'indirizzo di destinazione in una tabella, dopo aver filtrato le tabelle candidate con regole di policy. Quelle regole possono corrispondere a un mark del firewall, un indirizzo sorgente, un'interfaccia in ingresso o un UID. La voce di rotta in sé non ha un campo porta, perché la rotta viene scelta prima che l'intestazione di trasporto conti.
Gli iproute2 e i kernel attuali ampliano il selettore della regola: ipproto, sport e dport sono accettati, quindi ip rule add ipproto tcp dport 22 table 100 esprime l'intenzione in una riga. Esegui ip rule add help sull'host reale per vedere se la tua build elenca dport. Se sì, hai finito con un solo comando; se no, il percorso tramite mark qui sotto è l'equivalente.
In ogni caso la regola è solo un puntatore. La tabella 100 ha comunque bisogno di una rotta, ed è il pezzo che la gente dimentica.
Tre pezzi, o uno
La forma moderna è la regola singola qui sopra più una rotta nella tabella alternativa. La forma classica aggiunge netfilter nel mezzo: marca il pacchetto, poi fai corrispondere il mark in una regola. Entrambe finiscono nella stessa ricerca; differiscono in dove viene letta la porta.
L'approccio con mark esiste perché netfilter vede l'intestazione di trasporto e il codice di instradamento storicamente no. Questo è l'intero meccanismo, e spiega perché il ripiego siano tre pezzi che collaborano e non un comando astuto.
I tre pezzi in ordine
Primo, marca i flussi che vuoi deviare, in una catena che viene eseguita abbastanza presto perché il mark esista prima della decisione di rotta:
nft add table inet vpnexempt
nft 'add chain inet vpnexempt out { type route hook output priority mangle; }'
nft add rule inet vpnexempt out tcp dport 22 meta mark set 0x1Secondo, invia i pacchetti marcati a una tabella separata:
ip rule add fwmark 0x1/0x1 table 100Terzo, dai a quella tabella una rotta predefinita attraverso il collegamento fisico. Gli indirizzi sono illustrativi; sostituisci i tuoi:
ip route add default via 192.168.1.1 dev wlan0 table 100Salta il terzo passaggio e il pacchetto marcato non trova alcuna rotta nella tabella 100, quindi fallisce. Il sintomo sembra che la regola abbia rotto la rete, mentre in realtà ha rimosso l'unico percorso che restava al pacchetto.
Cosa lo tiene in funzione dopo il primo pacchetto
I mark si applicano ai pacchetti in uscita. La risposta arriva sull'interfaccia fisica con l'indirizzo assegnato da quel collegamento, e la tabella principale può ancora preferire il tunnel per quella destinazione. Due meccanismi mantengono coerente il percorso di ritorno: una regola basata sulla sorgente per l'indirizzo del collegamento, e il connection tracking che conserva la decisione per tutta la vita del flusso. Salvare il mark in conntrack e ripristinarlo sulla risposta è il modo abituale di rendere stabile un'eccezione con stato.
Il filtraggio del percorso inverso è l'altro killer silenzioso. Se il kernel valida rigorosamente il percorso di ritorno e la risposta arriva su un'interfaccia che la tabella principale non sceglierebbe, il pacchetto viene scartato senza messaggio. rp_filter i valori predefiniti variano per distribuzione e per il fatto che l'interfaccia sia inoltrata, quindi leggi il valore effettivo invece di supporre che sia disattivato.
Non marcare il trasporto del tunnel stesso. Un tunnel WireGuard o UDP OpenVPN non è TCP 22, quindi la ricetta qui sopra è sicura, ma un'eccezione scritta come tutto tranne la destinazione del tunnel può catturare i pacchetti cifrati e rompere il tunnel in un modo che sembra un loop di instradamento.
Verifica invece di supporre
Chiedi al kernel quale percorso seguirebbe, per la destinazione e per la porta, poi esegui il trasferimento reale:
ip route get 203.0.113.10
ip rule show
nft list rulesetSe il mark esiste ma i contatori non si muovono mai, la regola non corrisponde. I contatori sono la differenza fra una configurazione che hai scritto e una policy che il kernel applica.
Quando è lo strumento sbagliato
Le eccezioni basate sulla porta sono fragili per natura. I servizi cambiano porta, HTTP/3 arriva come UDP 443, e una singola applicazione può aprire più flussi di tipo diverso. Se il requisito reale è che questo programma usi il collegamento normale, lo strato di instradamento è il posto sbagliato; un network namespace o un proxy per applicazione si adattano meglio.
Se il requisito è che questa sottorete usi il collegamento normale, salta del tutto i mark e aggiungi una rotta più specifica. È più semplice, è visibile in una sola tabella, e la persona successiva può verificarla senza leggere un insieme di regole.
Scrivi quale dei tre requisiti hai davvero prima di toccare il firewall. Il meccanismo qui sopra è preciso, e la precisione applicata allo strato sbagliato è solo un'interruzione più lunga.