Skip to main content
  1. Blog/

Il Tunnel che Sceglie: Split vs Full VPN con WireGuard

Alessio Barnini
Author
Alessio Barnini
Table of Contents

TL;DR
  • WireGuard: VPN moderna basata su Curve25519 e ChaCha20, integrata direttamente nel kernel Linux come interfaccia di rete virtuale (wg0).
  • VPN Concentrator: Il dispositivo (in questo lab Ubuntu) che termina il tunnel cifrato, decifra il traffico e lo instrada verso la rete interna.
  • Split Tunnel (AllowedIPs = 10.0.0.0/24): Solo il traffico destinato alla subnet della VPN passa nel tunnel; il traffico internet esce in chiaro tramite il gateway locale.
  • Full Tunnel (AllowedIPs = 0.0.0.0/0): Tutto il traffico, incluso quello internet, viene convogliato nel tunnel cifrato e richiede IP forwarding e MASQUERADE sul concentratore.
  • Visibilità di rete: tcpdump/tshark mostrano solo pacchetti UDP cifrati sull'interfaccia fisica (enp0s1), mentre svelano il traffico ICMP/IP decifrato su quella virtuale (wg0).
$ history
  • apt install wireguard -y
  • wg genkey | tee privatekey | wg pubkey > publickey
  • wg-quick up wg0
  • wg-quick down wg0
  • ip route show
  • traceroute 8.8.8.8
  • tcpdump -i wg0
  • tcpdump -i enp0s1 udp port 51820
  • tshark -r capture.pcap

Configurare un tunnel WireGuard tra due VM e vedere con i propri occhi la differenza tra split tunnel e full tunnel. Non teoria - routing table e traceroute che lo dimostrano empiricamente.

Rappresentazione di Split Tunnel vs Full Tunnel

Obiettivo del laboratorio
#

In questo articolo vedremo come configurare e analizzare una VPN basata su WireGuard, analizzando in tempo reale il comportamento del traffico di rete tra queste tre entità del nostro laboratorio virtuale:

Ubuntu (192.168.64.3)   → VPN concentrator (wg0: 10.0.0.1)
Kali   (192.168.64.200) → client VPN       (wg0: 10.0.0.2)
Mac    (192.168.64.1)   → gateway

Cos'è WireGuard
#

WireGuard è un protocollo VPN moderno integrato nel kernel Linux. Crea un'interfaccia di rete virtuale (wg0) e cifra tutto il traffico che ci passa usando Curve25519 (scambio chiavi), ChaCha20 (cifratura) e Poly1305 (integrità).

Per il kernel, wg0 è una NIC (Network Interface Card) come le altre - ma con un layer di cifratura trasparente sotto. NIC è qualsiasi interfaccia di rete: fisica come enp0s1 o eth0, o virtuale come wg0. Le applicazioni non sanno che il traffico viene cifrato: lo vedono come una rete normale.

Rispetto a IPsec: meno configurazione, codice più piccolo (< 4000 righe vs centinaia di migliaia), più veloce su hardware moderno. Rispetto a OpenVPN: non usa TLS, gira nel kernel invece che in userspace.

VPN non è WireGuard. VPN è il concetto - un tunnel cifrato su rete pubblica. WireGuard, IPsec, OpenVPN sono i protocolli che lo implementano. Quando una domanda Security+ chiede "quale protocollo per VPN site-to-site?" la risposta è IPsec, non VPN.

WireGuard vs IPsec - quando si usa quale
#

WireGuard - quando puoi scegliere tu:

  • Setup moderno, funziona su Linux/Mac/Windows/mobile
  • Performance alta (gira nel kernel, usa ChaCha20)
  • Configurazione semplice, pochi peer
  • Tipico: accesso remoto dipendenti, home lab, VPN personale

IPsec - quando non puoi scegliere:

  • Standard enterprise da 30 anni - Cisco, Juniper, Fortinet lo parlano tutti
  • Site-to-site tra router/firewall di vendor diversi (interoperabilità garantita)
  • Ambienti regolamentati che richiedono standard certificati (FIPS, NSA Suite B)
  • Tipico: VPN tra sedi aziendali, connessioni verso AWS/Azure VPN Gateway

Trappola Security+: quando vedi "VPN tra due sedi aziendali" pensa IPsec. WireGuard è troppo recente per dominare le domande d'esame - il protocollo site-to-site standard rimane IPsec.

Cos'è un VPN concentrator
#

Il VPN concentrator è il terminatore del tunnel: riceve la connessione cifrata, autentica il client, decifra il traffico e lo instrada nella rete interna. Senza di lui il tunnel non ha dove terminare.

┌──────────────────┐                       ┌─────────────────────────────┐
│  Kali            │                       │  Ubuntu                     │
│  client VPN      │                       │  VPN concentrator           │
│  192.168.64.200  │                       │  192.168.64.3               │
│  wg0: 10.0.0.2   │                       │  wg0: 10.0.0.1              │
└────────┬─────────┘                       └──────────┬──────────────────┘
         │                                            │
         │   protocollo: WireGuard                    │
         │   trasporto:  UDP 51820                    │
         │   cifratura:  ChaCha20                     │
         │════════════════════════════════════════════│
         │             traffico cifrato               │
                                               riceve │
                                             autentica│  ← verifica chiave pubblica
                                              decifra │  ← ChaCha20
                                             instrada ↓  ← routing verso rete interna
                                              rete interna

In questo lab Ubuntu è il concentrator. Il concentrator non è per forza hardware dedicato:

FormaEsempi
Hardware dedicatoCisco ASA, Juniper SRX
NGFW con VPN integrataPalo Alto, Fortinet, pfSense
Software su serverOpenVPN, WireGuard - come in questo lab
Cloud gatewayAWS VPN Gateway, Azure VPN Gateway

Cisco VPN Concentrator Hardware

Nel lab vs nel mondo reale
#

In un'azienda reale il concentrator ha una "gamba" su internet e una "gamba" sulla rete interna - e sta nella DMZ (screened subnet), non direttamente sulla LAN:

Internet
    │  tunnel cifrato
[DMZ] Ubuntu concentrator (192.168.64.3)
    │  decifra → instrada verso...
    ├── file server   (10.10.0.5)
    ├── database      (10.10.0.10)
    └── intranet      (10.10.0.20)

Nel nostro lab Ubuntu non è in una DMZ - è sulla stessa rete flat di Kali (192.168.64.x). E non c'è nulla "dietro" di lui: Ubuntu è allo stesso tempo il concentrator e la rete interna. Funziona per capire il tunnel, ma nella realtà il concentrator sarebbe il punto di ingresso verso risorse interne separate.


Installazione
#

apt install wireguard -y
wg --version

Su entrambe le VM.


Le chiavi
#

wg genkey | tee server_private | wg pubkey > server_public

Che cosa sono queste chiavi?

WireGuard usa Curve25519 - crittografia a curva ellittica. Il comando fa tre cose in pipe: wg genkey genera 256 bit random (chiave privata). tee la salva su file e la passa avanti. wg pubkey deriva la chiave pubblica dalla privata - operazione a senso unico, non si torna indietro. La pubblica si condivide con i peer. La privata non esce mai dalla macchina.

Errore su Kali: sudo non si propaga nel pipe
#

$ sudo wg genkey | tee client_private | wg pubkey > client_public
tee: client_private: Permission denied

Soluzione:

sudo su -
wg genkey | tee client_private | wg pubkey > client_public

sudo su - apre una shell root completa. Il - carica l'ambiente di root (home /root, PATH, variabili) - senza - hai i permessi ma l'ambiente dell'utente precedente.


Configurazione tunnel
#

Ubuntu (/etc/wireguard/wg0.conf):

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <server_private>

[Peer]
PublicKey = <client_public>
AllowedIPs = 10.0.0.2/32

Kali (/etc/wireguard/wg0.conf):

[Interface]
Address = 10.0.0.2/24
PrivateKey = <client_private>

[Peer]
PublicKey = <server_public>
Endpoint = 192.168.64.3:51820
AllowedIPs = 10.0.0.0/24

Come funziona il routing tra le due interfacce
#

wg-quick up wg0 crea wg0 ma non sostituisce eth0 - entrambe le interfacce restano attive contemporaneamente. È il kernel che decide quale usare in base alla destinazione:

ip route su Kali:
  default via 192.168.64.1 dev eth0    ← tutto il resto → eth0 (fisico)
  10.0.0.0/24 dev wg0                  ← 10.0.0.x      → wg0 (tunnel)
  192.168.64.0/24 dev eth0             ← rete locale    → eth0 (fisico)

Non sei tu che scegli l'interfaccia - scegli la destinazione, ci pensa il kernel:

  • ping 10.0.0.1 → kernel vede 10.0.0.x → usa wg0 → cifra e manda nel tunnel
  • ping 8.8.8.8 → kernel non trova route specifica → usa default → esce da eth0 diretto

Ubuntu è raggiungibile in due modi contemporaneamente: 192.168.64.3 via eth0 (in chiaro) e 10.0.0.1 via wg0 (cifrato). Stessa macchina, due percorsi diversi.

Come funziona la crittografia
#

Kali non cifra con la sua chiave privata nel senso classico. WireGuard usa ECDH (Elliptic Curve Diffie-Hellman):

Kali:   chiave_privata_kali  + chiave_pubblica_ubuntu → chiave di sessione
Ubuntu: chiave_privata_ubuntu + chiave_pubblica_kali  → stessa chiave di sessione

Entrambi arrivano alla stessa chiave simmetrica senza mai mandarsela in rete. Poi ChaCha20 cifra il traffico con quella chiave. Le chiavi pubbliche servono solo per il handshake iniziale.

Ubuntu ha la chiave pubblica di ogni client nel [Peer] del suo config. Quando arriva un pacchetto cifrato, controlla quale peer può averlo mandato. Per aggiungere un nuovo client basta aggiungere un blocco [Peer] con la sua chiave pubblica - nessun certificato, nessuna CA.

AllowedIPs: la whitelist doppia
#

AllowedIPs = 10.0.0.2/32 su Ubuntu non è solo un filtro - è una whitelist che funziona in entrambe le direzioni:

  • In ingresso: accetta pacchetti da questo peer solo se dichiarano sorgente 10.0.0.2. Un pacchetto che dice di venire da un altro IP viene scartato.
  • In uscita: instrada verso 10.0.0.2 solo tramite questo peer.

Su Kali, AllowedIPs = 10.0.0.0/24 dice al kernel: "tutto il traffico verso 10.0.0.x va nel tunnel verso Ubuntu". È questo parametro che crea la differenza tra split e full tunnel - cambiarlo in 0.0.0.0/0 sposta il default route nel tunnel e tutto il traffico segue.

wg-quick up wg0

Output su Ubuntu:

[#] ip link add wg0 type wireguard
[#] ip -4 address add 10.0.0.1/24 dev wg0
[#] ip link set mtu 1420 up dev wg0

MTU 1420 invece di 1500: WireGuard aggiunge ~80 byte di header cifrato, il kernel riduce l'MTU per evitare frammentazione.

Tunnel up - ping da Kali a 10.0.0.1 risponde.


Split Tunnel
#

Con AllowedIPs = 10.0.0.0/24 nel config di Kali, solo il traffico verso la rete interna 10.0.0.x entra nel tunnel. Tutto il resto esce diretto.

ip route su Kali lo mostra chiaramente:

default via 192.168.64.1 dev eth0 onlink
10.0.0.0/24 dev wg0 proto kernel scope link src 10.0.0.2
192.168.64.0/24 dev eth0 proto kernel scope link src 192.168.64.200

La riga default punta ancora al Mac gateway (192.168.64.1), non a Ubuntu. Qualsiasi IP che non matcha 10.0.0.0/24 usa il default - esce diretto da casa.

La prova: traceroute 8.8.8.8 da Kali con tunnel attivo:

 1  192.168.64.1 (192.168.64.1)       ← Mac gateway - NON Ubuntu
 2  myfastgate.lan (192.168.1.254)    ← router di casa
 3  192.168.128.1                     ← ISP (Fastweb)
 4  * * *
 5  172.30.25.193
 ...
14  dns.google (8.8.8.8)

Il primo hop è 192.168.64.1 - il Mac. Il traffico verso Google non ha mai visto Ubuntu. La ricerca Google di Kali parte da casa, non dall'ufficio. Il tunnel esiste, ma sceglie cosa passarci dentro.

Confronto: traceroute 10.0.0.1 (Ubuntu via tunnel):

 1  10.0.0.1 (10.0.0.1)  14.067 ms

Un solo hop. Il tunnel WireGuard appare come un cavo diretto punto-punto - non importa che fisicamente il traffico passi per eth0 e la rete 192.168.64.x. Dal punto di vista del routing, wg0 è un link diretto tra Kali e Ubuntu.


Full Tunnel
#

Una riga cambia tutto. In /etc/wireguard/wg0.conf su Kali:

AllowedIPs = 0.0.0.0/0, ::/0

0.0.0.0/0 significa "tutti gli IP" - il kernel instraderà tutto il traffico nel tunnel, incluso il default route verso internet.

Su Ubuntu - due comandi obbligatori prima di riavviare il tunnel:

echo 1 > /proc/sys/net/ipv4/ip_forward
sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o enp0s1 -j MASQUERADE

Il primo abilita il forwarding dei pacchetti tra interfacce. Il secondo aggiunge il NAT: i pacchetti di Kali (10.0.0.0/24) che escono da enp0s1 vengono mascherati con l'IP di Ubuntu - Google vede Ubuntu, non Kali.

Riavvia il tunnel su Kali:

wg-quick down wg0 && wg-quick up wg0

Questa volta wg-quick aggiunge regole di policy routing:

[#] ip -4 rule add not fwmark 51820 table 51820
[#] ip -4 rule add table main suppress_prefixlength 0
[#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820

ip route mostra ancora default via 192.168.64.1 - ma è fuorviante. Il default è nella tabella main, il full tunnel usa la tabella 51820:

ip route show table 51820
# default dev wg0 scope link

ip rule show
# 32764: from all lookup main suppress_prefixlength 0
# 32765: not from all fwmark 0xca6c lookup 51820
# 32766: from all lookup main

La logica anti-loop: WireGuard marca i propri pacchetti con fwmark 0xca6c. La regola 32765 dice "vai in tabella 51820 solo se NON sei un pacchetto WireGuard". Così i pacchetti UDP del tunnel escono da eth0 normalmente, senza rientrare nel tunnel.

La prova
#

traceroute 8.8.8.8 con full tunnel:

 1  10.0.0.1 (10.0.0.1)  8.207 ms

Primo hop: Ubuntu. Non più il Mac.

E ping 8.8.8.8 funziona - ma solo dopo aver aggiunto il MASQUERADE. Senza di esso, il traffico arriva a Ubuntu ma non sa come tornare a Kali:

# senza MASQUERADE: 27 packets transmitted, 0 received, 100% packet loss
# con MASQUERADE:   5 packets transmitted, 5 received, 0% packet loss, time 4011ms

Il MASQUERADE era il tassello mancante. Ubuntu riceveva i pacchetti di Kali, li mandava a Google - ma Google rispondeva all'IP interno 10.0.0.2 che non è raggiungibile da internet. Con MASQUERADE, Ubuntu sostituisce 10.0.0.2 con il proprio IP pubblico prima di mandare il pacchetto fuori. Google risponde a Ubuntu, Ubuntu gira la risposta a Kali.


tcpdump - due finestre, due versioni dello stesso ping
#

Su Ubuntu, due terminali in parallelo mentre Kali fa ping 10.0.0.1.

Terminale 1 - enp0s1 (interfaccia fisica, traffico sul cavo):

sudo tcpdump -i enp0s1 udp port 51820
192.168.64.200.49430 > barno-server.51820: UDP, length 148   ← handshake
barno-server.51820 > 192.168.64.200.49430: UDP, length 92    ← handshake risposta
192.168.64.200.49430 > barno-server.51820: UDP, length 128   ← ping cifrato
barno-server.51820 > 192.168.64.200.49430: UDP, length 128   ← pong cifrato

Solo UDP. Nessun ICMP visibile, nessun IP interno. Un intercettatore sulla rete vede blob opachi.

Terminale 2 - wg0 (interfaccia tunnel, dopo la decifratura):

sudo tcpdump -i wg0
10.0.0.2 > barno-server: ICMP echo request, id 22872, seq 1, length 64
barno-server > 10.0.0.2: ICMP echo reply, id 22872, seq 1, length 64

Contenuto reale: ICMP ping tra i due IP del tunnel, perfettamente leggibile.

Kali (10.0.0.2)                                    Ubuntu (10.0.0.1)
      │                                                    │
      │  quello che vede wg0 (dopo decifratura):           │
      │  ICMP echo request, length 64                      │
      │ ─────────────────────────────────────────────────► │
      │  ICMP echo reply, length 64                        │
      │ ◄───────────────────────────────────────────────── │
      │                                                    │
      │  quello che vede enp0s1 (sul cavo fisico):         │
      │  UDP 192.168.64.200 → 192.168.64.3:51820           │
      │  length 128  [payload cifrato - illeggibile]        │
      │ ─────────────────────────────────────────────────► │
      │  UDP 192.168.64.3:51820 → 192.168.64.200           │
      │  length 128  [payload cifrato - illeggibile]        │
      │ ◄───────────────────────────────────────────────── │

L'overhead: il ping ICMP è 84 byte su wg0. Sul cavo fisico diventa 170 byte - 86 byte di overhead (Ethernet 14 + IP 20 + UDP 8 + WireGuard header + Poly1305 tag).

tshark - dentro la struttura del protocollo
#

tcpdump mostra solo "UDP blob". tshark decodifica la struttura WireGuard frame per frame, anche senza poter aprire il payload cifrato.

Su wg0 - contenuto completo visibile:

Frame 1: 84 bytes - Protocols: raw:ip:icmp:data
  IP Src: 10.0.0.2  Dst: 10.0.0.1
  ICMP Type: 8 (Echo request)  Seq: 1
  Data: 10 11 12 13 14 15 16 17...   ← payload del ping in chiaro

Frame 2: 84 bytes - Protocols: raw:ip:icmp:data
  IP Src: 10.0.0.1  Dst: 10.0.0.2
  ICMP Type: 0 (Echo reply)  Seq: 1
  Response time: 0.935 ms

Tutto leggibile: IP sorgente, destinazione, tipo ICMP, sequence number, persino i byte del payload in esadecimale.

Su enp0s1 - struttura WireGuard visibile, contenuto no:

Frame 1: 190 bytes - Handshake Initiation (Kali → Ubuntu)
  Sender: 0x6b362a51
  Ephemeral: VORjGlFLYZNri0fC8rKSwPMJ...   ← chiave temporanea
  Encrypted Static                           ← chiave statica di Kali, cifrata
  Encrypted Timestamp                        ← anti-replay

Frame 2: 134 bytes - Handshake Response (Ubuntu → Kali)
  Sender: 0xebef2b9b
  Receiver: 0x6b362a51                       ← risponde a Kali
  Ephemeral: fWt0VsQBI/Sp4paMN/nkm7SM...    ← chiave temporanea di Ubuntu
  Encrypted Empty                            ← conferma handshake

Frame 3: 170 bytes - Transport Data (Kali → Ubuntu)
  Receiver: 0xebef2b9b
  Counter: 0                                 ← anti-replay: primo pacchetto dati
  Encrypted Packet                           ← ICMP echo request - invisibile

Frame 4: 170 bytes - Transport Data (Ubuntu → Kali)
  Receiver: 0x6b362a51
  Counter: 0
  Encrypted Packet                           ← ICMP echo reply - invisibile

Il disegno completo dei 4 frame:

Kali (192.168.64.200)                        Ubuntu (192.168.64.3)
        │                                            │
        │──── Frame 1: Handshake Initiation ────────►│
        │     190 bytes                              │
        │     Ephemeral key + Encrypted Static       │
        │     [negoziazione chiave di sessione]      │
        │                                            │
        │◄─── Frame 2: Handshake Response ───────────│
        │     134 bytes                              │
        │     Ephemeral key + Encrypted Empty        │
        │     [sessione stabilita - chiavi pronte]   │
        │                                            │
        │──── Frame 3: Transport Data ───────────────►│
        │     170 bytes                              │
        │     Counter: 0 / Encrypted Packet          │
        │     [dentro: ICMP echo request - cifrato]  │
        │                                            │
        │◄─── Frame 4: Transport Data ───────────────│
        │     170 bytes                              │
        │     Counter: 0 / Encrypted Packet          │
        │     [dentro: ICMP echo reply - cifrato]    │
        │                                            │
   vede enp0s1                               vede wg0
   (attaccante)                              (dopo decifratura)
   UDP blob opachi                           ICMP in chiaro

Tre cose che tshark rivela:

1. Handshake in 2 soli messaggi. IPsec (IKE) ne usa 6+. WireGuard è radicale: iniziazione + risposta, fatto.

2. Ephemeral keys - forward secrecy. Le chiavi temporanee cambiano ad ogni sessione. Se un attaccante cattura tutto il traffico oggi e ruba le chiavi statiche domani, non può decifrare le sessioni passate: le chiavi effimere sono già state buttate via.

3. Counter anti-replay. Parte da 0 e sale per ogni pacchetto. Se qualcuno intercetta un pacchetto e lo rimanda, il ricevitore lo scarta: quel counter è già stato processato.

La vista dell'attaccante
#

enp0s1 è esattamente quello che vede un attaccante che fa ARP poisoning sulla rete fisica - intercetta il cavo ma è fuori dal tubo VPN:

Rete fisica (192.168.64.x)
      ┌─────────────┴─────────────┐
      │       ATTACCANTE          │
      │   (ARP poisoning, MITM)   │
      │                           │
      │  vede:                    │
      │  192.168.64.200           │
      │    → 192.168.64.3:51820   │
      │  UDP, length 128          │
      │  [blob cifrato opaco]     │
      │                           │
      │  NON vede:                │
      │  ICMP echo request        │
      │  10.0.0.2 → 10.0.0.1     │
      └───────────────────────────┘
      │                           │
   Kali                        Ubuntu
 (10.0.0.2)                 (10.0.0.1)
      │                           │
      └──── tubo WireGuard ───────┘
             cifrato con
             ChaCha20 + Poly1305

L'attaccante sa che c'è traffico tra i due IP e sa la dimensione dei pacchetti - ma non il contenuto. Questo è esattamente il threat model che la VPN risolve: proteggere i dati su una rete non fidata (wifi pubblico, ISP compromesso, rete aziendale con un insider).


exit 0
#

Alla fine del lab, tre concetti che sembravano astratti diventano concreti:

VPN non è una modalità di comunicazione - è una tecnologia. Crea un canale privato cifrato sopra una rete pubblica. WireGuard, IPsec, OpenVPN sono le implementazioni. La VPN è il concetto.

Split tunnel: decide il kernel, non l'interfaccia. AllowedIPs crea delle route nella tabella di routing. Il kernel confronta la destinazione di ogni pacchetto con quelle route e sceglie l'interfaccia - wg0 se la destinazione è nella lista, eth0 altrimenti. Non è l'interfaccia che "capisce" - è il routing table che decide.

Full tunnel: non c'è whitelist, c'è tutto. AllowedIPs = 0.0.0.0/0 non è una lista selettiva - è "tutto senza eccezioni". La whitelist esiste solo in split tunnel, dove AllowedIPs è una lista specifica di reti. In full tunnel quella lista include ogni IP possibile, quindi il concetto di selezione scompare.

Il concentrator fa tre cose: riceve il traffico cifrato su UDP 51820, lo decifra con la chiave di sessione derivata dall'ECDH, e lo instrada verso la rete interna. In questo lab Ubuntu fa tutto da solo - in produzione avrebbe una rete di server, database e risorse interne dietro di lui, e starebbe in DMZ con un IP pubblico esposto su internet.

WireGuard non parte da solo. wg-quick è manuale. Per renderlo persistente: systemctl enable wg-quick@wg0. Senza questo, al riavvio il tunnel non esiste.

Una cosa che tcpdump non può mostrare ma tshark sì: il handshake in 2 messaggi, le ephemeral key, il counter anti-replay. La cifratura è opaca al contenuto ma trasparente nella struttura - e quella struttura racconta tutto sul protocollo.


Comandi usati: wg-quick · ip · tcpdump · tshark · iptables Concetti correlati: vpn-concentrator · wireguard · split-tunnel-full-tunnel Approfondimento: cap-04-securing-your-network · lab-ipsec-strongswan · lab-vpn-dmz-windows-internal

Related