Vuoi una VPN per lavoro (accesso LAN aziendale tramite tunnel) e allo stesso tempo che Netflix veda il tuo IP francese per offrirti il catalogo FR. Oppure vuoi che Steam scarichi direttamente (1 Gbps ISP) mentre tutto il resto passa attraverso la VPN. Soluzione: split-tunnel tramite tabelle di routing Linux, più potente dello split di AllowedIPs di WireGuard.
Questa guida implementa il pattern fwmark + ip rule + ip route che instrada applicazioni specifiche attraverso la VPN e altre direttamente, senza toccare il tunnel WireGuard stesso.
Perché lo split-tunnel con tabelle di routing Linux supera la modalità AllowedIPs
L'opzione AllowedIPs di WireGuard è elegante quando vuoi escludere interi intervalli IP dal tunnel — tipicamente la tua LAN locale (192.168.1.0/24) per mantenere l'accesso a NAS e stampanti senza passare attraverso la VPN. È sufficiente per l'80% degli usi domestici. Ma appena vuoi differenziare il routing per applicazione o per utente di sistema, AllowedIPs non può farlo. Steam e Netflix non hanno IP fissi — usano CDN con intervalli dinamici distribuiti globalmente. Mantenere manualmente l'elenco degli IP di Steam in AllowedIPs è ingestibile e si rompe a ogni aggiornamento CDN di Akamai o Fastly.
Il routing fwmark risolve questo problema marcando i pacchetti mentre escono dal processo utente (tramite cgroup o uid), prima che venga presa la decisione di routing IP. La tabella di routing 200 (VPN) o principale (ISP diretto) viene scelta in base al marchio, indipendentemente dalla destinazione. Così puoi dire "tutto il traffico dall'utente steam esce tramite ISP diretto, tutto il resto passa attraverso wg0" senza mai elencare un IP. È ciò che Mullvad fa internamente nella loro app Linux dal 2022, e ciò che Tor usa per la loro modalità bridge.
L'altro vantaggio: il fwmark sopravvive ai cambiamenti di rete. Se passi dal WiFi di casa al 4G mobile senza disconnetterti, il tuo split-tunnel rimane operativo perché la tabella di routing 200 punta a wg0 (l'interfaccia WireGuard, indipendente dall'interfaccia fisica sottostante). Con AllowedIPs non avresti problemi neanche, ma con un classico kill switch netfilter potresti finire con regole iptables che fanno riferimento a eth0 che non esiste su 4G mobile (l'interfaccia diventa wwan0).
Concetto in 4 passaggi
- Marca il traffico di un'app con
iptables -j MARK(basato su cgroup, utente o destinazione). - Crea una tabella di routing alternativa per il traffico marcato (es. tabella 200).
- Aggiungi una ip-rule dicendo "traffico con marchio X → usa tabella 200".
- Popola la tabella 200 con un percorso predefinito diverso (o
wg0o direttoeth0).
Risultato: il kernel ispeziona ogni pacchetto in uscita e sceglie la tabella 200 (VPN) o la tabella principale (ISP diretto).
Configurazione di base
Prerequisito: un tunnel WireGuard configurato e attivo. Consulta la guida alla configurazione di WireGuard su Contabo se parti da zero.
Prima controlla che iproute2 sia installato (dovrebbe esserlo su qualsiasi distribuzione recente):
ip -V
# iproute2-6.x.x
Caso A — Escludere Steam dalla VPN (download diretti ISP)
Steam si identifica tramite un cgroup quando viene lanciato da systemd. Instradiamo il suo traffico tramite l'interfaccia ISP eth0 invece di wg0.
Passaggio 1 — Crea un cgroup per Steam
sudo mkdir -p /sys/fs/cgroup/net_cls/steam
sudo bash -c 'echo 0x42 > /sys/fs/cgroup/net_cls/steam/net_cls.classid'
Passaggio 2 — Marca i pacchetti in uscita da quel cgroup
sudo iptables -t mangle -A OUTPUT -m cgroup --cgroup 0x42 -j MARK --set-mark 200
Passaggio 3 — Crea la tabella di routing 200 con percorso ISP
echo "200 isp_direct" | sudo tee -a /etc/iproute2/rt_tables
ISP_GW=$(ip route | awk '/default/ && /eth0/ {print $3}')
sudo ip route add default via $ISP_GW dev eth0 table isp_direct
sudo ip rule add fwmark 200 table isp_direct
Passaggio 4 — Avvia Steam in quel cgroup
cgexec -g net_cls:steam /usr/games/steam
Oppure tramite .slice di systemd per configurazioni Flatpak / Steam Snap.
Ora Steam scarica direttamente dall'ISP (fino a 1 Gbps in fibra) mentre il resto della tua macchina rimane sul tunnel WireGuard.
Caso B — Instradare Netflix tramite ISP per il catalogo FR
Vuoi che Netflix veda il tuo IP francese. Approccio per IP di destinazione, poiché Netflix utilizza più CDN.
Passaggio 1 — Ottieni gli intervalli BGP di Netflix
Netflix pubblica i suoi prefissi: AS2906. Puoi recuperarli con whois -h whois.radb.net -- '-i origin AS2906' o usare un elenco mantenuto:
curl -s https://api.bgpview.io/asn/2906/prefixes | jq -r '.data.ipv4_prefixes[].prefix' > /tmp/netflix-ranges.txt
Passaggio 2 — Marca il traffico verso quegli intervalli
while read range; do
sudo iptables -t mangle -A OUTPUT -d "$range" -j MARK --set-mark 200
done < /tmp/netflix-ranges.txt
Passaggio 3 — Usa la stessa tabella isp_direct (già configurata nel caso A).
Successivamente netflix.com, il DNS risolve, il pacchetto TCP parte con mark 200, il kernel invia tramite diretto eth0, Netflix vede il tuo IP francese e offre il catalogo corretto.
Caso C — Inverso: forza solo Slack/Zoom attraverso la VPN
Lavori dall'estero, vuoi che il traffico lavorativo (Slack, Zoom, GitHub) passi attraverso la VPN aziendale, tutto il resto diretto.
Configurazione simmetrica:
- Marca predefinito = diretto
- Marca app lavorative = tramite VPN
echo "201 work_vpn" | sudo tee -a /etc/iproute2/rt_tables
sudo ip route add default dev wg0 table work_vpn
sudo ip rule add fwmark 201 table work_vpn
# Marcatore per Slack (per destinazione)
for d in $(dig +short slack.com slack-edge.com app.slack.com); do
sudo iptables -t mangle -A OUTPUT -d "$d" -j MARK --set-mark 201
done
# Zoom (intervalli pubblicati da Zoom nei loro documenti amministrativi)
sudo iptables -t mangle -A OUTPUT -d 3.7.35.0/25 -j MARK --set-mark 201
sudo iptables -t mangle -A OUTPUT -d 3.21.137.128/25 -j MARK --set-mark 201
# ... completa con l'elenco ufficiale di Zoom
Importante: il percorso predefinito della tabella principale deve puntare a eth0 (ISP diretto), non a wg0. Controlla con ip route show table main.
Pratico: script completo split-tunnel.sh
Per evitare di riscrivere a ogni avvio, ecco uno script idempotente collegato a systemd:
#!/usr/bin/env bash
# /usr/local/bin/split-tunnel.sh
set -euo pipefail
ISP_IF="eth0"
VPN_IF="wg0"
ISP_GW=$(ip route | awk -v iface="$ISP_IF" '/default/ && $0 ~ iface {print $3; exit}')
grep -q '^200 isp_direct' /etc/iproute2/rt_tables || echo "200 isp_direct" >> /etc/iproute2/rt_tables
grep -q '^201 work_vpn' /etc/iproute2/rt_tables || echo "201 work_vpn" >> /etc/iproute2/rt_tables
ip route show table isp_direct | grep -q default || ip route add default via "$ISP_GW" dev "$ISP_IF" table isp_direct
ip route show table work_vpn | grep -q default || ip route add default dev "$VPN_IF" table work_vpn
ip rule show | grep -q "fwmark 0xc8 lookup isp_direct" || ip rule add fwmark 200 table isp_direct
ip rule show | grep -q "fwmark 0xc9 lookup work_vpn" || ip rule add fwmark 201 table work_vpn
mkdir -p /sys/fs/cgroup/net_cls/steam
echo 0x42 > /sys/fs/cgroup/net_cls/steam/net_cls.classid
iptables -t mangle -C OUTPUT -m cgroup --cgroup 0x42 -j MARK --set-mark 200 2>/dev/null \
|| iptables -t mangle -A OUTPUT -m cgroup --cgroup 0x42 -j MARK --set-mark 200
echo "Regole split-tunnel applicate."
unità systemd:
# /etc/systemd/system/split-tunnel.service
[Unit]
Description=Regole di routing split-tunnel
After=wg-quick@wg0.service
Wants=wg-quick@wg0.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/split-tunnel.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now split-tunnel.service
Caso D — Split per utente Linux
Vuoi che tutto il traffico dall'utente kid (l'account di tuo figlio) passi attraverso la VPN con filtro parentale, e il tuo principale eric diretto.
sudo iptables -t mangle -A OUTPUT -m owner --uid-owner kid -j MARK --set-mark 201
# Marchio 201 instradato tramite VPN (configurato sopra)
Ogni curl, firefox, apt lanciato sotto UID kid esce tramite wg0. Utile anche per un browser di test (Chromium in un altro utente) che mostra sempre un IP VPN diverso.
Diagnostica e debug
Mostra le regole attive
ip rule show
# Dovrebbe mostrare la tua regola fwmark 200/201 con la giusta priorità
ip route show table isp_direct
# Dovrebbe mostrare default via X dev eth0
ip route show table work_vpn
# Dovrebbe mostrare default dev wg0
Traccia il routing di un pacchetto
# Dalla shell dell'app per il debug (o avvolgi in cgexec)
ip route get 1.1.1.1 mark 200
# Dovrebbe mostrare "via 192.168.1.1 dev eth0 table isp_direct"
ip route get 1.1.1.1 mark 201
# Dovrebbe mostrare "dev wg0 table work_vpn"
Vedi il marchio in tcpdump
sudo tcpdump -i any -nn -e 'iptables' 2>/dev/null
# O più semplice:
sudo nft monitor trace
Marchio non applicato?
Controlla l'ordine delle catene iptables. La catena OUTPUT della tabella mangle viene eseguita prima del routing. Se la tua regola appare in iptables -t mangle -L OUTPUT -v -n ma il marchio non si applica, di solito è un cgroup non ereditato (lancia tramite cgexec esplicito).
Limiti e avvertenze
IPv6
Ogni comando sopra è solo per IPv4. Per IPv6, rispecchia con:
sudo ip6tables -t mangle -A OUTPUT ...
sudo ip -6 rule add fwmark 200 table isp_direct
sudo ip -6 route add default via FE80::1 dev eth0 table isp_direct
Se usi anche la nostra guida al kill switch, ricorda che le regole OUTPUT DROP del kill switch possono bloccare il traffico diretto dello split-tunnel. Correzione: whitelista i marchi 200/201 dal DROP globale.
Perdita DNS nello split-tunnel
Se dividi per IP di destinazione, il DNS viene ancora risolto dal tuo DNS principale. Se quel DNS è nel tunnel, Netflix potrebbe risolvere a un IP UK quando volevi FR. Correzione: configura un DNS diretto per i domini split (dnsmasq con resolver specifici per server).
Il kernel memorizza nella cache i percorsi
Dopo aver modificato una ip rule, esegui:
sudo ip route flush cache
Altrimenti le connessioni già aperte mantengono il loro vecchio routing.
Compatibilità Docker / container
Docker manipola iptables in modo aggressivo. Se coesistono split-tunnel + Docker, aspettati che le regole vengano sovrascritte al riavvio di Docker. Correzione: lancia split-tunnel.service con After=docker.service o usa nftables al posto di iptables.
Verdetto
Lo split-tunnel tramite fwmark + ip rule è la soluzione più flessibile su Linux. Puoi instradare per destinazione, utente, cgroup, processo. Costo: ~10 righe di configurazione e un servizio systemd. Molto superiore allo split di AllowedIPs di WireGuard che filtra solo per IP di destinazione.
Configurazione domestica consigliata:
- VPN predefinita per tutto
- Steam, Netflix, Time Machine → marcati
200→ ISP diretto - Kill switch netfilter con eccezione per il marchio 200
Configurazione lavorativa remota consigliata:
- ISP diretto predefinito
- Slack, Zoom, GitHub Enterprise, jumphost → marcati
201→ tramite VPN aziendale
E per la base: guida a WireGuard su Contabo, template pronti per l'uso, e monitoraggio Prometheus + Grafana per tracciare in tempo reale cosa esce da quale interfaccia.
Prima di costruire lo split-tunnel, usa il nostro generatore di configurazione WireGuard per ottenere il wg0.conf di base corretto — imposta AllowedIPs e PostUp correttamente fin dall'inizio così puoi concentrarti sul livello delle tabelle di routing. E se stai decidendo tra self-hosting e una VPN commerciale per motivi di budget, il nostro calcolatore dei costi VPN self-hosted fa i conti per 3 anni per te.
★ Datacenter GDPR di Norimberga · ✓ IPv4 dedicato incluso · 200+ Mbps garantiti
Self-host your VPN on your own VPS → ContaboFull root access · public IPv4 · pick your region→