Instradare il traffico dell'host attraverso una VPN in una VM: sei controlli
Sei controlli in ordine per trasformare una VM nel gateway VPN attraverso cui instrada il suo host: modalità dell'adattatore, inoltro, NAT, rotte dell'host, DNS, MTU.
Sei controlli, eseguiti in ordine, decidono se una VM può fare da gateway VPN per il proprio host. L'hypervisor non fa nulla di tutto ciò per te: l'ospite possiede il tunnel, l'host possiede una rotta, e la rete fra loro è instradamento ordinario. Tengo la lista perché saltare un passaggio produce sempre lo stesso sintomo, il silenzio, dove l'host fa ping all'ospite allegramente e non raggiunge nulla di interno.
Parti dal motivo. O il client aziendale si installa solo dentro l'ospite, o la policy lo tiene lì, e hai comunque bisogno del server di build interno dall'host. Se l'host può avere il proprio client, fai quello invece: un solo tunnel sull'host batte ogni volta due reti più un NAT.
Controllo 1: l'host ha bisogno di un collegamento a cui dare un indirizzo
L'host deve possedere un'interfaccia nello stesso prefisso del secondo adattatore dell'ospite. Host-only in VirtualBox, host-only o una VMnet personalizzata in VMware Workstation, un bridge interno o un dispositivo tap in QEMU/KVM e uno switch Internal in Hyper-V vanno tutti bene. Ospite 192.168.56.2, host 192.168.56.1, prefisso 192.168.56.0/24 è la disposizione che uso in laboratorio.
La rete QEMU in modalità utente (-netdev user, il backend SLIRP predefinito) e il NAT di VirtualBox falliscono questo controllo del tutto. I pacchetti dell'ospite arrivano a un processo proxy sull'host, così l'host non vede mai un dispositivo che porti gli indirizzi dell'ospite e non ha nulla su cui puntare una rotta. Il Default Switch di Hyper-V è la stessa storia. Se ping 192.168.56.2 dall'host fallisce, fermati: nessuna rotta aggiunta dopo lo riparerà.
Controllo 2: l'ospite inoltra e traduce
I pacchetti dall'host arrivano con un indirizzo sorgente host-only e una destinazione interna, quindi l'ospite deve inoltrarli e poi nasconderli dietro l'indirizzo del suo tunnel. Su un ospite Linux questo significa inoltro, una coppia FORWARD accept e source NAT verso il tunnel:
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -A FORWARD -i eth1 -o tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 192.168.56.0/24 -o tun0 -j MASQUERADE
La regola MASQUERADE è la parte che le persone omettono. I router aziendali raramente sanno che 192.168.56.0/24 esiste; con la regola, i server interni vedono l'indirizzo del tunnel dell'ospite e vi rispondono, e la risposta trova la via del ritorno verso l'ospite.
Un ospite Windows è più goffo. IPEnableRouter=1 sotto SYSTEM\CurrentControlSet\Services\Tcpip\Parameters attiva l'inoltro senza traduzione, quindi funziona solo dove l'instradamento aziendale punta già al tuo prefisso host-only. La condivisione della connessione a Internet traduce, ma rinumera l'adattatore condiviso su 192.168.137.1, quindi leggi l'indirizzo che ha effettivamente preso prima di scrivere le rotte dell'host.
Controllo 3: le rotte dell'host nominano l'ospite, non il tunnel
Manda attraverso l'ospite solo i prefissi che servono all'host. Su Linux una rotta per prefisso basta e si ritira in modo pulito:
sudo ip route add 10.20.0.0/16 via 192.168.56.2
ip route get 10.20.30.40
Su Windows l'equivalente è route add 10.20.0.0 mask 255.255.0.0 192.168.56.2. Lascia fuori -p finché stai ancora provando, così una rotta sbagliata muore al riavvio, e aggiungilo quando il percorso funziona. Non rimuovere la rotta predefinita dell'host a meno che ogni applicazione dell'host non debba usare il tunnel; se il tunnel completo è davvero il requisito, aggiungi le due rotte /1 e mantieni la rotta diretta verso la rete dell'hypervisor:
sudo ip route add 0.0.0.0/1 via 192.168.56.2
sudo ip route add 128.0.0.0/1 via 192.168.56.2
Su un host Windows controlla anche la metrica dell'interfaccia. Una rotta aggiunta mentre l'adattatore host-only era disattivato resta nella tabella ma non porta mai traffico, e un secondo adattatore con metrica più bassa può vincere la ricerca per lo stesso prefisso.
Controllo 4: il DNS segue la rotta, non il default del tunnel
Le rotte spostano pacchetti; il resolver decide quali nomi producono pacchetti. Limita il resolver aziendale alla zona aziendale invece di sostituire il resolver dell'host, e puntalo a un server DNS che la nuova rotta già raggiunge. Con systemd-resolved sull'host:
resolvectl dns eth1 10.20.0.53
resolvectl domain eth1 '~corp.example.com'
Su Windows una voce nella tabella dei criteri di risoluzione dei nomi fa lo stesso lavoro: Add-DnsClientNrptRule -Namespace ".corp.example.com" -NameServers 10.20.0.53. Se l'ospite esegue il proprio resolver sull'indirizzo host-only, usa quell'indirizzo invece, ma solo dopo aver confermato che risponde su 192.168.56.2 e non su loopback.
Poi controlla l'MTU prima di dare la colpa al DNS per le pagine lente. L'interfaccia del tunnel è più piccola dell'interfaccia da 1500 byte dell'host, quindi un host che invia segmenti a dimensione piena con il bit DF attivo li vede cadere da qualche parte dentro l'ospite. Il sintomo classico è un ping che funziona e un'applicazione web interna che si blocca dopo la stretta di mano TLS. Stringi l'MSS sul percorso di inoltro dell'ospite:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
Controllo 5: verifica da entrambe le estremità
Dall'host, traceroute o tracert verso un indirizzo interno dovrebbe mostrare 192.168.56.2 come primo salto, non il gateway fisico. Nell'ospite, osserva lo stesso tentativo uscire: tcpdump -ni any host 10.20.30.40 mostra la richiesta inoltrata sul lato host-only e quella tradotta sul tunnel. Una richiesta che esce, torna e lascia comunque l'host muto punta al percorso di ritorno.
Due guasti spiegano la maggior parte di questo. O il tunnel dell'ospite non porta alcuna rotta di ritorno per il prefisso host-only perché manca la regola NAT, o la rotta dell'host punta a un indirizzo che l'ospite non ha più dopo un riavvio. Rileggi gli indirizzi con ip -br addr e ip route get invece di fidarti delle note della sessione precedente.
Controllo 6: fallo sopravvivere a un riavvio e a un aggiornamento dell'hypervisor
Rendi persistenti entrambe le metà: configurazione delle rotte con netplan o NetworkManager sull'host, route add ... -p su Windows, e net.ipv4.ip_forward=1 in /etc/sysctl.d/ più un insieme di regole del firewall che si ricarica nell'ospite. Fai uno snapshot dell'ospite appena la configurazione è verificata, prima che arrivi il prossimo aggiornamento del client aziendale.
Le reti host-only vengono rinumerate quando un hypervisor ricrea i suoi adattatori, il che invalida in silenzio ogni rotta e regola NAT costruita su 192.168.56.0/24. Dopo un aggiornamento, riesegui i primi tre controlli in ordine; costa un minuto e intercetta il caso in cui l'host ha ancora la sua rotta mentre l'ospite è passato a una nuova sottorete.
IPv6 richiede il proprio passaggio. Le regole ip6tables, i record AAAA e una seconda famiglia di rotte dell'host non seguono la configurazione IPv4, e un servizio aziendale raggiungibile via IPv6 sembrerà permanentemente guasto mentre IPv4 funziona.
Superato o no, in ordine: l'host può fare ping all'indirizzo host-only dell'ospite; l'ospite inoltra e fa MASQUERADE verso il tunnel; ip route get restituisce l'ospite come gateway per il prefisso interno; i nomi aziendali si risolvono tramite il resolver limitato; traceroute mostra l'ospite per primo; e le stesse sei risposte sopravvivono a un riavvio.