Impilare un secondo client OpenVPN su un solo host Linux fallisce in uno di due punti. O il client interno non riesce a raggiungere il proprio server perché il tunnel esterno ha catturato quel trasporto, oppure il secondo client riscrive la tabella di routing e il primo tunnel smette di funzionare. Entrambi i problemi stanno nel routing, non nella cifratura, e entrambi si risolvono prima che il nuovo tunnel trasporti traffico applicativo. Gli indirizzi qui sono illustrativi: tun0 è il tunnel esterno, 203.0.113.9 è il server interno e TCP 9999 è il flusso che deve usare il percorso interno.

Due modalità di guasto, una tabella di routing

Un client interno apre un socket UDP verso il proprio server, e quel socket segue la tabella di routing corrente come qualsiasi altro. Se il tunnel esterno possiede già la rotta predefinita, i pacchetti di handshake finiscono nel tunnel esterno. Funziona solo quando il server esterno può raggiungere il server interno e il lato opposto accetta di trasportare il flusso; altrimenti l'handshake si blocca e il client riprova.

Il secondo guasto viene dal push del server. Il push abituale è redirect-gateway def1, che installa 0.0.0.0/1 e 128.0.0.0/1 invece di sostituire la rotta predefinita. Due client che installano ciascuno la propria coppia /1 quindi si scontrano, e la coppia più recente vince per quasi ogni destinazione. Cerca quei due prefissi nella tabella quando un tunnel muore dopo che il secondo si connette, perché la rotta predefinita stessa potrebbe non essere mai cambiata.

Marca il flusso e dagli una tabella

Dirigi il traffico selezionato contrassegnandolo nella tabella mangle e facendo corrispondere il marchio in una regola di policy. La regola punta a una tabella dedicata la cui rotta predefinita è il dispositivo interno, così solo i pacchetti marcati prendono il percorso interno.

iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200

La maggior parte dei guasti in questo insieme di regole deriva da due errori. Il valore del marchio impostato dal firewall e il valore confrontato dalla regola devono essere identici, perché una discrepanza non produce né errore né effetto. E PREROUTING vede solo i pacchetti che arrivano dall'esterno, quindi una connessione avviata su questo host richiede una regola OUTPUT con la stessa corrispondenza prima di essere marcata del tutto.

Togli la proprietà delle rotte al secondo client

Due opzioni impediscono al secondo client di modificare la tabella, e agiscono in punti diversi. route-nopull ignora le opzioni di rotta inviate, quindi non viene installato nulla. route-noexec accetta le opzioni inviate nell'ambiente del client ma lascia la modifica della tabella a un hook, ed è quello che vuoi quando devi vedere cosa ha offerto il server.

L'hook viene eseguito con i valori inviati nel proprio ambiente, e dovrebbe aggiungere solo le voci che hai approvato. Rendilo idempotente: svuota la tabella che possiede prima di ricostruirla, e non dare per scontato che l'esecuzione precedente abbia lasciato uno stato pulito. Una riconnessione richiama l'hook, e un hook che aggiunge senza pulire accumula rotte duplicate.

Scegli un solo proprietario e mettilo per iscritto. Un profilo che imposta entrambe le opzioni, o un hook che compete con uno script manuale, produce una tabella che nessuno saprà spiegare in seguito.

Fissa entrambi gli indirizzi dei server

Il server interno ha bisogno di una rotta host attraverso il dispositivo esterno prima che il client interno si connetta. È quella voce che tiene il trasporto interno dentro il tunnel esterno invece di lasciarlo seguire la rotta predefinita.

ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9

Aggiungi una rotta host corrispondente anche per il server esterno nella tabella principale, altrimenti una riconnessione può instradare il trasporto del client esterno nel suo stesso tunnel. L'ordine non è facoltativo: aggiungi queste voci dopo che l'interfaccia pertinente esiste, cioè un hook o uno script che viene eseguito dopo la connessione. Una rotta che punta a un dispositivo spento viene rimossa o ignorata, quindi una voce scritta all'avvio non sopravvive alla sequenza che hai ipotizzato.

Controlla la ricerca invece di leggere tutta la tabella. L'output della ricerca di rotta indica il dispositivo e l'indirizzo sorgente che il kernel userà, ed è esattamente ciò che riceve il client quando apre il proprio socket.

Prova la catena e rendi visibile il guasto

Cattura sull'interfaccia esterna mentre il client interno si connette. L'handshake interno dovrebbe apparire come payload cifrato dentro il tunnel esterno; se appare come UDP in chiaro verso 203.0.113.9, manca la rotta host e la catena non è mai stata costruita.

Una catena che torna silenziosamente sull'uplink fisico è il risultato peggiore, perché lo stato dell'interfaccia non annuncia nulla e il traffico interno esce dall'indirizzo sorgente sbagliato. Se il secondo client può riconnettersi direttamente quando il tunnel esterno cade, disattiva quel percorso o vincolalo all'interfaccia esterna in modo che il guasto sia visibile.

Chiudi con due flussi e un dump delle regole: una connessione marcata e una non marcata dovrebbero seguire percorsi diversi, e ip rule show dovrebbe elencare la preferenza che intendevi. Se entrambi i flussi seguono lo stesso percorso, il marchio non viene applicato dove pensi.

Articoli correlati