Qualcuno chiede che il browser usi il tunnel, e la richiesta torna come una regola del firewall che corrisponde alle porte di destinazione 80 e 443. Sembra precisa e non lo è. Una corrispondenza di porta seleziona i pacchetti in base alla loro intestazione di trasporto, non in base a quale programma ha aperto il socket, e la navigazione moderna non si limita a quelle due porte su TCP. La regola perderà parte del traffico che volevate includere e catturerà traffico che non avete mai intenzione di toccare. Prima di scriverla, decidete quale di tre confini diversi volete davvero.

La regola descrive una classe di trasporto, non un'applicazione

Su Linux, una corrispondenza di porta di destinazione ispeziona l'intestazione TCP e nulla sopra di essa. Marcare quei pacchetti e instradarli con una rotta di policy funziona esattamente come documentato, e nessuna di queste macchine sa che cosa significhi il carico utile né quale processo lo abbia inviato. Il marchio è un numero attaccato a un pacchetto, non un'identità attaccata a un'applicazione.

nft add rule inet mangle output tcp dport { 80, 443 } meta mark set 0x1
ip rule add fwmark 0x1 table 100
ip route add default dev wg0 table 100

Seguono subito due conseguenze. Un aggiornatore software che contatta TCP 443 riceve lo stesso trattamento del browser, il che può andar bene o può essere un'interruzione che attende il prossimo ciclo di aggiornamento. E un servizio web pubblicato di proposito su un'altra porta, diciamo 8443, resta del tutto fuori dalla regola pur essendo ancora il traffico web che interessava all'utente.

I numeri sono selettori legittimi quando il requisito riguarda davvero quelle porte. Diventano finzione quando nel ticket c'è scritto «browser» e nell'insieme di regole si implementa un intervallo di porte. Quel divario non è una finezza documentale. È la differenza tra una regola che potete difendere in una revisione e una che nessuno può testare.

HTTP/3 abbandona in silenzio la vostra regola TCP

HTTP/3 viaggia su QUIC, e QUIC è trasportato in UDP. Una regola che corrisponde solo a TCP non può vederlo, quindi lo stesso browser che supera la vostra pagina di prova può inviare una richiesta successiva su UDP e aggirare il tunnel senza che nessuno cambi un'impostazione. RFC 9114 descrive HTTP/3 come protocollo basato su QUIC, e QUIC usa UDP con porta predefinita 443 nel dispiegamento comune.

Se un dato browser tenti HTTP/3 dipende dalla sua configurazione, dal sito e dal fatto che un tentativo precedente sia riuscito. Questo lo rende una brutta cosa su cui ragionare a memoria. Chiedete invece allo strumento:

curl --http3 -sS -o /dev/null -w '%{http_version} %{time_total}\n' https://example.com
ss -tunp | grep -E ':443\b'

La riga curl stampa la versione negoziata, quindi un 3 conferma che è stato usato il percorso UDP. L'elenco dei socket mostra poi se quella connessione è legata all'interfaccia del tunnel o a quella fisica. Se HTTP/3 non è disponibile nella vostra build di curl, il flag fallisce rumorosamente invece di ripiegare in silenzio, il che è la sua piccola misericordia.

Il DNS è una decisione separata che dovete prendere

Una regola di porta per il traffico web non dice nulla sulla risoluzione dei nomi. Il browser può usare il resolver di sistema, un resolver configurato dentro il browser o quello spinto dall'ultima connessione. Ognuno di essi può inviare query su un percorso diverso da quello scelto dalla vostra regola, il che produce la classica scissione: la pagina si carica, il nome viene risolto in un posto che non vi aspettavate e il sito interno si risolve su un indirizzo pubblico o per niente.

Decidetelo esplicitamente. Se i nomi interni devono risolversi attraverso il tunnel, il resolver deve stare al suo interno, e l'insieme di regole deve consentire il DNS verso quel resolver tenendo fuori le altre query. Se il browser usa DNS over HTTPS verso un provider pubblico, la query aggira del tutto il vostro resolver e la regola di porta non può aiutare. Scrivete la scelta nello stesso record di modifica della regola di instradamento, perché chi eseguirà il debug del prossimo guasto legge entrambi insieme.

Poi verificate con il resolver invece che con la pagina:

resolvectl query intranet.example.internal
resolvectl status | grep -A3 'Current DNS Server'

Una risposta mancante è un problema di risoluzione anche quando il messaggio di errore del browser accusa la rete. Questi due guasti sembrano identici all'utente e sono ticket completamente diversi.

Tre confini, tre meccanismi diversi

La maggior parte della confusione sparisce appena nominate il confine che volete. Non sono intercambiabili, e ciascuno ha un modo di guastarsi diverso durante un'interruzione del tunnel.

What you actually wantMechanism that fitsWhat it does not cover
These transport ports use this exitFirewall mark plus policy routeApplications, HTTP/3 over UDP, DNS
This browsing session stays separateApplication-aware VPN, or a separate network namespace plus a proxyOther applications in the same session
Only these private sites are reachableDestination-based routes with private DNSPublic sites on the same address family

Un profilo del browser separato è la trappola della riga centrale. Separa cookie e cronologia, e non cambia nulla nella tabella di instradamento dell'host, quindi entrambi i profili prendono lo stesso percorso. L'isolamento richiede qualcosa che il kernel imponga, cioè uno spazio dei nomi, un container o un'applicazione che possieda la propria interfaccia di tunnel.

Qualunque riga si applichi, scrivete che cosa deve succedere quando il tunnel cade. Una rotta basata su marchio che sparisce con l'interfaccia di solito ricade sulla rotta predefinita fisica, che è un comportamento fail-open. Può essere accettabile per la navigazione e inaccettabile per tutto il resto, ed è una decisione, non un dettaglio implementativo.

Provate i flussi che dichiarate di coprire

Il test di accettazione deve seguire il confine che avete scritto, non i numeri di porta che erano comodi. Scegliete un flusso che deve passare dal tunnel, uno che non deve, e uno che usa un trasporto a cui non avete pensato:

ip route get 203.0.113.40
curl -sS -o /dev/null -w 'v4 %{http_version} %{remote_ip}\n' https://example.com
nft list ruleset | grep -A2 'dport'

La ricerca della rotta vi dice quale interfaccia usa davvero una destinazione, ed è il fatto che la maggior parte dei rapporti sbaglia. La riga curl conferma la versione negoziata e la famiglia di indirizzi. Il dump dell'insieme di regole intercetta la regola che avete aggiunto durante i test e dimenticato di delimitare, ed è così che un marchio temporaneo diventa instradamento fail-open permanente.

Registrate tutte e tre le uscite con una marca temporale e conservatele con la modifica. Quando più avanti qualcuno segnalerà che «la VPN ha rotto l'intranet», la differenza tra il confine previsto e quello implementato sarà visibile in quel registro, e la correzione sarà un cambio di regola e non una discussione.

Articoli correlati