Come instradare il traffico dell’host attraverso un container Docker VPN
Un container VPN è uno spazio dei nomi di rete, non un proxy. Scegliete un netns condiviso, un vero listener SOCKS o un gateway instradato, poi verificate il percorso di uscita.
Il container del tunnel funziona, docker compose exec vpn curl -s https://ifconfig.me risponde con l’indirizzo del fornitore, eppure il browser sulla scrivania esce ancora dal vostro ISP. Quel divario è atteso. Un container che esegue un client VPN detiene uno spazio dei nomi di rete, rotte e un’interfaccia di tunnel, e nulla che parli HTTP o SOCKS; pubblicare una porta su di esso non può creare un listener che non è mai esistito.
Dove gira il client decide il progetto. Un container vicino può entrare nello spazio dei nomi del tunnel, un’applicazione desktop non può, e un router ha bisogno di rotte invece di un campo proxy. Se la mappatura è sbagliata, il sintomo è lo stesso: connessione rifiutata, o traffico che prende silenziosamente la vecchia strada.
Adattate la disposizione al punto in cui gira il client
Fate una domanda prima di modificare Compose: il client può essere avviato dentro lo spazio dei nomi di rete del container VPN? Tutto ciò che gira già come container Linux può. Un browser, un IDE o una CLI sulla scrivania non può, perché la sua impostazione proxy si aspetta un servizio di protocollo, non uno spazio dei nomi.
| Client | Arrangement | What must already exist |
|---|---|---|
| Another Compose service | Shares the vpn namespace via network_mode: service:vpn | A working tunnel and correct start order |
| Host browser, IDE or CLI | Proxy listener inside the namespace, published to host loopback | A configured SOCKS or HTTP proxy and its access rules |
| Router, VM or phone | The container as a layer-3 gateway | Forwarding, NAT and a return route |
Questi tre non si mescolano. Un campo proxy HTTP puntato all’indirizzo bridge del container non raggiunge nulla se un proxy non ascolta lì, e un listener proxy non instrada il traffico di un router. Decidete prima, poi seguite la sezione corrispondente.
Condividete lo spazio dei nomi del tunnel con un container vicino
Questa è l’opzione più economica perché non è coinvolto alcun protocollo proxy. L’applicazione entra nello spazio dei nomi del servizio vpn e ne eredita interfacce, rotte e spazio delle porte:
services:
vpn:
image: your-vpn-client
cap_add:
- NET_ADMIN
healthcheck:
test: ["CMD-SHELL", "ip link show tun0 >/dev/null"]
interval: 10s
retries: 5
app:
image: your-app
network_mode: service:vpn
depends_on:
vpn:
condition: service_healthyTre restrizioni derivano dal fatto che lo spazio dei nomi appartiene a un altro servizio. L’applicazione non ottiene una chiave networks propria, perché non ha uno spazio dei nomi a cui agganciarsi. La pubblicazione delle porte spetta a vpn, perché è lì che vive lo spazio dei nomi. E ricreare vpn distrugge lo spazio dei nomi a cui l’applicazione si è unita, quindi docker compose up -d --force-recreate vpn vi lascia ricreare anche app; un semplice riavvio non sempre basta. Il riferimento ai servizi Compose copre questa disposizione e la sua restrizione sulla pubblicazione delle porte.
Non date per scontato che il resolver arrivi con esso. /etc/resolv.conf è un file per container, non parte dello spazio dei nomi di rete, quindi l’applicazione può ancora interrogare il resolver dell’host mentre i suoi pacchetti escono dal tunnel. Leggetelo dall’interno del container e impostate dns: sull’applicazione quando i nomi privati devono essere risolti attraverso il tunnel.
docker compose exec app ip route
docker compose exec app cat /etc/resolv.conf
docker compose exec app curl -s https://ifconfig.meSe l’elenco delle rotte contiene l’interfaccia del tunnel e l’indirizzo è quello del fornitore, l’applicazione è dentro il tunnel. Se l’interfaccia manca, app è partita prima che vpn avesse un tunnel, quindi ricreate app appena vpn segnala di essere sano.
Mettete un vero listener proxy nello spazio dei nomi per i client dell’host
Un browser desktop non può unirsi a uno spazio dei nomi, quindi il tunnel deve essere esposto come servizio. Eseguite un vero proxy SOCKS o HTTP dentro lo spazio dei nomi vpn e pubblicate solo la sua porta:
services:
vpn:
ports:
- "127.0.0.1:1080:1080"
proxy:
image: your-proxy
network_mode: service:vpn
command: ["--socks", "0.0.0.0:1080"]L’indirizzo lato host è la metà importante di quella mappatura. 127.0.0.1:1080 a sinistra tiene il listener fuori dalla vostra LAN; un "1080:1080" nudo pubblica un proxy aperto a ogni dispositivo che può raggiungere la vostra macchina.
Il lato container ha la sua trappola. Le porte pubblicate vengono consegnate all’indirizzo del container sul bridge, non al suo loopback, quindi un proxy avviato con --socks 127.0.0.1:1080 dentro lo spazio dei nomi accetta connessioni dai vicini e rifiuta tutto ciò che arriva dalla porta pubblicata. Legatelo a 0.0.0.0 e affidatevi al binding loopback lato host per l’isolamento. La stessa regola vale per l’indirizzo di ascolto in un file di configurazione proxy.
Poi decidete dove vengono risolti i nomi. In curl, --socks5 risolve il nome host localmente e --socks5-hostname invia il nome al proxy; i browser espongono lo stesso interruttore con un’altra etichetta. La risoluzione locale fa trapelare la query anche quando la pagina si carica attraverso il tunnel.
Trattate il container come gateway solo con una rotta di ritorno
Router, VM e telefoni non useranno un campo proxy HTTP, e alcune applicazioni ignorano del tutto le variabili proxy. Instradare il loro traffico significa livello 3: il container inoltra i pacchetti e traduce gli indirizzi sull’interfaccia del tunnel.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADEDue cose sfuggono facilmente. Il client ha bisogno di una rotta che punti il suo gateway predefinito a questo host, e le risposte devono trovare la via del ritorno, o tramite una rotta statica sul router a monte o tramite NAT sull’interfaccia rivolta ai client. Senza una delle due, il tunnel porta la richiesta fuori e la risposta scompare.
Ha anche il raggio d’impatto più ampio. Un host che fa da gateway per tutta la LAN manda ogni client mal configurato attraverso il vostro account VPN, e una caduta del tunnel o oscura la LAN o torna all’ISP a seconda del kill switch.
Verificate il percorso di uscita, poi tornate indietro in ordine
Controllate in questo ordine e fermatevi al primo fallimento: indirizzo di uscita dentro lo spazio dei nomi, indirizzo di uscita dall’host attraverso il proxy, poi risoluzione dei nomi per un host privato.
docker compose exec vpn curl -s https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 http://internal.example/healthI primi due indirizzi devono corrispondere al fornitore, e il terzo deve risolversi attraverso il proxy invece di fallire con un errore di nome. Una prima riga corrispondente e una seconda che fallisce significano che il listener o la porta pubblicata è sbagliato, non il tunnel.
Tornare indietro è l’inverso della configurazione. Rimuovete le variabili d’ambiente proxy dal profilo della shell e da qualsiasi unità systemd, poi abbassate lo stack perché lo spazio dei nomi condiviso sparisca con il suo proprietario. Cancellate la porta pubblicata prima di cancellare il servizio proxy, altrimenti l’host conserva un listener loopback che nessuno legge.
Prima di proseguire, confermate quattro cose: il client che vi interessa raggiunge davvero il tunnel, solo il proprietario dello spazio dei nomi pubblica porte, i nomi privati si risolvono dove volevate, e nulla ascolta su un indirizzo host che non sia loopback. Ognuna di queste fallisce in silenzio in produzione, e ognuna è un controllo di una riga sopra.