Vai al contenuto
GLGiuseppe Lanza Scrivimi

VPN IPsec tra Cisco e Ubuntu con strongSwan

Collegare due sedi con una VPN IPsec IKEv2 a chiave condivisa tra un router Cisco IOS XE e Ubuntu con strongSwan: interfacce VTI e XFRM, algoritmi moderni, firewall, verifiche e problemi comuni.

Sistemi: Cisco IOS XE 17 · Ubuntu 24.04 LTS · strongSwan 5.9Livello: AvanzatoTempo: 60 minutiAggiornata: novembre 2026

Come funziona una VPN IPsec IKEv2

IPsec cifra e autentica i pacchetti IP tra due apparati. La negoziazione avviene con IKEv2 (RFC 7296) in due fasi:

  • IKE SA: i due capi si autenticano (qui con una chiave condivisa, PSK) e creano un canale protetto per la gestione. Usa UDP 500, oppure UDP 4500 se c’è un NAT di mezzo.
  • Child SA: dentro l’IKE SA si negoziano le chiavi per i dati veri. I dati viaggiano in ESP (protocollo IP 50), oppure in UDP 4500 con NAT-T.

Esistono due modi di decidere quale traffico entra nella VPN:

  • Basato su policy (crypto map sul Cisco): una access list elenca le coppie di reti da cifrare. È il metodo classico, ancora molto diffuso.
  • Basato su rotte (VTI sul Cisco, interfaccia XFRM su Linux): la VPN è un’interfaccia, e ci finisce dentro tutto ciò che instradi verso di essa. Aggiungere una rete significa aggiungere una rotta, non toccare le SA.

In questa guida uso il metodo basato su rotte, più moderno e più semplice da gestire. La crypto map è descritta più avanti come alternativa.

Lo scenario

È lo stesso della guida Tunnel GRE tra un router Cisco e Ubuntu Linux: sede A con router Cisco, sede B con un server Ubuntu che fa da router per la sua LAN. Entrambi hanno un IP pubblico statico. Al posto del tunnel GRE c’è un tunnel IPsec, con gli stessi indirizzi.

LAN sede A 192.168.10.0/24 Router Cisco Gi0/0 198.51.100.1 Internet UDP 500/4500 · ESP Ubuntu 24.04 ens18 203.0.113.10 LAN sede B 192.168.20.0/24 Tunnel IPsec IKEv2 · 10.255.255.0/30 Tunnel0 .1 ipsec0 .2 Gi0/1 .1 (gateway) ens19 .1 (gateway)
Il Cisco usa un’interfaccia VTI (Tunnel0), Ubuntu un’interfaccia XFRM (ipsec0). Le LAN si raggiungono con rotte statiche verso il tunnel.
ElementoSede A (Cisco)Sede B (Ubuntu)
IP pubblico198.51.100.1 su Gi0/0203.0.113.10 su ens18
LAN192.168.10.0/24, gateway .1 su Gi0/1192.168.20.0/24, gateway .1 su ens19
Interfaccia tunnelTunnel0 (VTI) – 10.255.255.1/30ipsec0 (XFRM) – 10.255.255.2/30
Identità IKE198.51.100.1203.0.113.10

Se hai già configurato il tunnel GRE della guida precedente, rimuovilo prima (o usa un’altra /30): le due soluzioni sono alternative.

I parametri da concordare

La causa numero uno delle VPN che non salgono è un parametro diverso tra i due lati. Fissali prima, in una tabella, e copia da lì:

ParametroValoreCisco IOS XEstrongSwan
Versione IKEIKEv2crypto ikev2 …version = 2
Cifratura IKEAES-256-GCMencryption aes-gcm-256aes256gcm16-prfsha256-ecp256
PRF IKESHA-256prf sha256
Gruppo DH19 (ECP 256 bit)group 19
Cifratura ESPAES-256-GCM, ICV 16 byteesp-gcm 256aes256gcm16-ecp256
PFS sulla Child SAgruppo 19set pfs group19
Durata IKE SA24 orepredefinita (86400 s)rekey_time = 24h
Durata Child SA1 orapredefinita (3600 s)rekey_time = 1h
AutenticazionePSKpre-shareauth = psk
DPDogni 10 sdpd 10 3 periodicdpd_delay = 10s

Con AES-GCM la cifratura include già l’integrità: per questo nella proposta IKE c’è solo la PRF e non c’è un algoritmo di integrità. Se un apparato non supporta GCM, usa AES-256-CBC con SHA-256: sul Cisco encryption aes-cbc-256 + integrity sha256 e esp-aes 256 esp-sha256-hmac; su strongSwan aes256-sha256-ecp256 per IKE e aes256-sha256-ecp256 per ESP.

Genera una chiave condivisa robusta e casuale:

openssl rand -base64 32
La chiave condivisa è la VPN

Chi conosce la PSK e un IP da cui presentarsi può tentare di impersonare un capo del tunnel. Usa una chiave lunga e casuale, diversa per ogni tunnel, e non mandarla per e-mail insieme agli indirizzi. Nella guida uso Cambia-Questa-Chiave-Esempio-0000: è un segnaposto, non usarlo mai. Sul Cisco attiva la cifratura delle chiavi nella configurazione con key config-key password-encrypt e password encryption aes; su Ubuntu proteggi il file con chmod 600.

Configurazione del router Cisco

La configurazione IKEv2 su IOS XE si costruisce a blocchi: proposta (algoritmi), policy (quali proposte usare), keyring (chiavi per peer), profilo (identità e autenticazione). Poi il profilo IPsec lega tutto all’interfaccia tunnel.

Cisco IOS XE – IKEv2 e IPsec
configure terminal
!
crypto ikev2 proposal PROP-GCM256
 encryption aes-gcm-256
 prf sha256
 group 19
!
crypto ikev2 policy POL-GCM256
 match address local 198.51.100.1
 proposal PROP-GCM256
!
crypto ikev2 keyring KR-SEDE-B
 peer UBUNTU-SEDE-B
  address 203.0.113.10
  pre-shared-key Cambia-Questa-Chiave-Esempio-0000
!
crypto ikev2 profile IKEV2-SEDE-B
 match identity remote address 203.0.113.10 255.255.255.255
 identity local address 198.51.100.1
 authentication remote pre-share
 authentication local pre-share
 keyring local KR-SEDE-B
 dpd 10 3 periodic
!
crypto ipsec transform-set TS-GCM256 esp-gcm 256
 mode tunnel
!
crypto ipsec profile IPSEC-SEDE-B
 set transform-set TS-GCM256
 set pfs group19
 set ikev2-profile IKEV2-SEDE-B

Poi l’interfaccia tunnel e la rotta verso la LAN remota:

Cisco IOS XE – interfaccia VTI
interface Tunnel0
 description VPN IPsec verso sede B (Ubuntu)
 ip address 10.255.255.1 255.255.255.252
 ip mtu 1400
 ip tcp adjust-mss 1360
 tunnel source GigabitEthernet0/0
 tunnel destination 203.0.113.10
 tunnel mode ipsec ipv4
 tunnel protection ipsec profile IPSEC-SEDE-B
!
ip route 192.168.20.0 255.255.255.0 10.255.255.2
!
end
write memory

tunnel mode ipsec ipv4 trasforma Tunnel0 in una VTI statica: niente GRE, solo ESP. Il Cisco negozia selettori di traffico “qualsiasi verso qualsiasi” (0.0.0.0/0 ↔ 0.0.0.0/0): cosa entra nel tunnel lo decide la tabella di routing. L’MTU a 1400 e l’MSS a 1360 sono valori prudenti che coprono l’overhead di ESP, di un eventuale NAT-T e di una linea PPPoE.

Se sull’interfaccia WAN hai una access list in ingresso, apri IKE, NAT-T ed ESP dal server Ubuntu:

Cisco IOS XE – access list WAN
ip access-list extended WAN-IN
 permit udp host 203.0.113.10 host 198.51.100.1 eq isakmp
 permit udp host 203.0.113.10 host 198.51.100.1 eq non500-isakmp
 permit esp host 203.0.113.10 host 198.51.100.1

Configurazione di Ubuntu con strongSwan

1. Installare strongSwan con swanctl

Su Ubuntu 24.04 installa il demone moderno charon-systemd e lo strumento swanctl. Non serve il vecchio ipsec.conf (pacchetto strongswan-starter).

sudo apt update
sudo apt install strongswan-swanctl charon-systemd

Il servizio si chiama strongswan.service; all’avvio carica in automatico i file /etc/swanctl/conf.d/*.conf.

2. Abilitare l’instradamento

Come per il tunnel GRE, Ubuntu deve inoltrare i pacchetti tra la LAN e la VPN:

/etc/sysctl.d/99-router.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
sudo sysctl --system

3. Creare l’interfaccia XFRM

L’equivalente Linux della VTI è un’interfaccia XFRM: i pacchetti instradati verso di essa vengono cifrati dalla SA con lo stesso identificativo (if_id, qui 42). La creo con systemd-networkd, che Netplan usa già come motore, con due file:

/etc/systemd/network/40-ipsec0.netdev
[NetDev]
Name=ipsec0
Kind=xfrm

[Xfrm]
InterfaceId=42
Independent=yes
/etc/systemd/network/40-ipsec0.network
[Match]
Name=ipsec0

[Link]
MTUBytes=1400

[Network]
Address=10.255.255.2/30

[Route]
# interfaccia punto-punto: basta indicare la destinazione
Destination=192.168.10.0/24
sudo networkctl reload
networkctl status ipsec0
Per una prova veloce, a mano

Gli stessi passaggi con ip (si perdono al riavvio):

sudo ip link add ipsec0 type xfrm dev ens18 if_id 42
sudo ip addr add 10.255.255.2/30 dev ipsec0
sudo ip link set ipsec0 mtu 1400 up
sudo ip route add 192.168.10.0/24 dev ipsec0

4. Configurare la connessione

/etc/swanctl/conf.d/sede-a.conf
connections {
  sede-a {
    version = 2
    local_addrs = 203.0.113.10
    remote_addrs = 198.51.100.1
    proposals = aes256gcm16-prfsha256-ecp256
    rekey_time = 24h
    dpd_delay = 10s

    local {
      auth = psk
      id = 203.0.113.10
    }
    remote {
      auth = psk
      id = 198.51.100.1
    }

    children {
      sede-a-lan {
        # selettori "qualsiasi": decide la tabella di routing
        local_ts = 0.0.0.0/0
        remote_ts = 0.0.0.0/0
        if_id_in = 42
        if_id_out = 42
        esp_proposals = aes256gcm16-ecp256
        rekey_time = 1h
        start_action = start
        dpd_action = restart
      }
    }
  }
}

secrets {
  ike-sede-a {
    id = 198.51.100.1
    secret = "Cambia-Questa-Chiave-Esempio-0000"
  }
}

I punti da notare:

  • id locale e remoto sono gli indirizzi pubblici, gli stessi di identity local e match identity remote sul Cisco.
  • if_id_in e if_id_out legano la Child SA all’interfaccia ipsec0. Senza, i selettori 0.0.0.0/0 catturerebbero tutto il traffico del server.
  • esp_proposals contiene il gruppo DH (ecp256): è il PFS, e deve coincidere con set pfs group19.
  • start_action = start avvia il tunnel al caricamento; dpd_action = restart lo rinegozia se il Cisco smette di rispondere.

Con le interfacce XFRM è meglio che strongSwan non installi rotte proprie (nella tabella 220): le rotte le gestisci tu. Crea un file di impostazioni:

/etc/strongswan.d/charon-local.conf
charon {
  install_routes = no
}

5. Avviare il servizio

sudo chmod 600 /etc/swanctl/conf.d/sede-a.conf
sudo systemctl enable --now strongswan
sudo systemctl restart strongswan   # rilegge anche strongswan.d
sudo swanctl --load-all             # dopo ogni modifica a swanctl

Firewall, NAT e MSS su Ubuntu

Con nftables (vedi la guida Firewall completo con nftables) servono tre cose: accettare IKE ed ESP dal Cisco, lasciar passare il traffico tra la LAN e ipsec0, e correggere l’MSS.

/etc/nftables.conf – estratto
table inet filtro {
  chain input {
    # ... regole esistenti ...
    ip saddr 198.51.100.1 udp dport { 500, 4500 } accept
    ip saddr 198.51.100.1 meta l4proto esp accept
    # ping e gestione dal tunnel verso il server stesso
    iifname "ipsec0" ip saddr { 10.255.255.0/30, 192.168.10.0/24 } accept
  }
  chain forward {
    # ... regole esistenti ...
    tcp flags syn tcp option maxseg size set rt mtu
    iifname "ens19" oifname "ipsec0" accept
    iifname "ipsec0" oifname "ens19" accept
  }
}

table ip nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    # il traffico verso la sede A non va mascherato
    ip saddr 192.168.20.0/24 ip daddr 192.168.10.0/24 accept
    oifname "ens18" masquerade
  }
}

Con l’interfaccia XFRM il traffico verso la LAN A esce da ipsec0 e non da ens18, quindi il masquerade non lo toccherebbe comunque. La regola di esclusione diventa indispensabile se passi alla variante con policy (vedi sotto), dove il pacchetto esce da ens18: se viene mascherato prima di essere cifrato, non corrisponde più alla policy e parte in chiaro, o non parte affatto.

Se usi UFW:

sudo ufw allow proto udp from 198.51.100.1 to any port 500,4500
sudo ufw allow proto esp from 198.51.100.1
sudo ufw route allow in on ens19 out on ipsec0
sudo ufw route allow in on ipsec0 out on ens19

L’alternativa: crypto map e policy

Se il router ha già altre VPN su crypto map, o il capo remoto supporta solo VPN basate su policy, puoi usare questo schema. Proposta, policy, keyring e profilo IKEv2 restano identici; cambiano la parte IPsec e l’interfaccia.

Cisco IOS XE – crypto map
ip access-list extended VPN-SEDE-B
 permit ip 192.168.10.0 0.0.0.255 192.168.20.0 0.0.0.255
!
crypto map CMAP-WAN 10 ipsec-isakmp
 set peer 203.0.113.10
 set transform-set TS-GCM256
 set pfs group19
 set ikev2-profile IKEV2-SEDE-B
 match address VPN-SEDE-B
!
interface GigabitEthernet0/0
 crypto map CMAP-WAN
!
ip access-list extended NAT-INSIDE
 deny   ip 192.168.10.0 0.0.0.255 192.168.20.0 0.0.0.255
 permit ip 192.168.10.0 0.0.0.255 any

Qui non servono Tunnel0 né la rotta via 10.255.255.2: basta che la LAN B sia instradata verso Gi0/0, di solito con la rotta predefinita. Il deny nella access list del NAT è obbligatorio, perché questo traffico esce dall’interfaccia esterna.

Su strongSwan togli if_id_in/if_id_out e l’interfaccia ipsec0, e restringi i selettori alle due LAN, speculari all’access list del Cisco:

/etc/swanctl/conf.d/sede-a.conf – children
    children {
      sede-a-lan {
        local_ts = 192.168.20.0/24
        remote_ts = 192.168.10.0/24
        esp_proposals = aes256gcm16-ecp256
        rekey_time = 1h
        start_action = start
        dpd_action = restart
      }
    }

In questo caso lascia install_routes al valore predefinito e ricordati la regola di esclusione dal masquerade. Il limite della crypto map è evidente: ogni nuova coppia di reti richiede di modificare l’access list da entrambe le parti, e i selettori devono restare speculari.

Verifiche

Sul router Cisco

show crypto ikev2 sa                    ! Status READY
show crypto ikev2 sa detailed           ! algoritmi, DH 19, durata, DPD
show crypto ipsec sa peer 203.0.113.10  ! #pkts encaps/decaps devono crescere
show crypto session detail
show interfaces Tunnel0                 ! up, line protocol is up
ping 10.255.255.2 source Tunnel0
ping 192.168.20.1 source GigabitEthernet0/1

Nella VTI il “line protocol” di Tunnel0 sale solo quando la Child SA è attiva: è già un buon indicatore dello stato della VPN.

Su Ubuntu

sudo swanctl --list-conns              # configurazione caricata
sudo swanctl --list-sas                # ESTABLISHED e INSTALLED
sudo swanctl --initiate --child sede-a-lan   # avvio manuale
sudo ip xfrm state                     # SA nel kernel
ip -s link show ipsec0                 # contatori dell'interfaccia
ping -c 3 -I 192.168.20.1 192.168.10.1
sudo journalctl -u strongswan -f       # log in tempo reale
sudo tcpdump -ni ens18 'udp port 500 or udp port 4500 or esp'

In --list-sas la Child SA deve mostrare AES_GCM_16-256/ECP_256: se manca ECP_256, il PFS non è attivo. Per un controllo dell’MTU senza frammentazione, da un client della LAN B:

ping -M do -s 1372 192.168.10.10   # 1372 + 28 = 1400: deve passare
ping -M do -s 1400 192.168.10.10   # deve fallire con "mtu = 1400"

Problemi comuni

SintomoCausa probabileCosa fare
NO_PROPOSAL_CHOSEN nei logAlgoritmi IKE o ESP diversi tra i due latiConfronta la tabella dei parametri; su strongSwan il log mostra le proposte ricevute e quelle configurate
AUTHENTICATION_FAILED o “no shared key found”PSK diversa, oppure identità che non corrispondonoControlla id in swanctl, identity local e match identity remote sul Cisco; ricopia la chiave senza spazi finali
“no peer config found” su strongSwanIl Cisco si presenta con un ID diverso da remote.idIl log riporta l’ID ricevuto: allinea identity local address sul Cisco
TS_UNACCEPTABLESelettori di traffico non compatibiliCon VTI usa 0.0.0.0/0 su strongSwan; con crypto map access list e local_ts/remote_ts devono essere speculari
Il tunnel sale, ma cade dopo circa un’oraPFS configurato da una sola parte: il primo scambio non usa DH, il rekey sìAllinea set pfs group19 e il gruppo in esp_proposals
Ubuntu dietro un router con NATL’IP visto dal Cisco non è quello di ens18NAT-T passa su UDP 4500 da solo; inoltra 500 e 4500 verso Ubuntu e usa come ID un nome (id = sede-b.example.org) con match identity remote fqdn sul Cisco
Dopo il riavvio di un capo il tunnel resta giùSA “orfane” senza DPDVerifica dpd sul Cisco e dpd_delay/dpd_action = restart su strongSwan
SA attive, ma le LAN non si vedonoRotta, inoltro o firewallip route get 192.168.10.1 deve indicare ipsec0; controlla la catena forward e i gateway dei client
Ping OK, ma siti e condivisioni si bloccanoMTU/MSSVerifica ip tcp adjust-mss 1360 sul Cisco e la regola MSS su Ubuntu

In produzione: certificati e passo successivo

Preferisci i certificati alla PSK

La chiave condivisa va bene per due sedi e per un laboratorio. In produzione, e appena i tunnel diventano più di uno, usa i certificati: ogni capo ha la sua chiave privata, la revoca è possibile e non c’è un segreto comune da custodire. Sul Cisco si configura un trustpoint PKI con authentication local rsa-sig (o ecdsa-sig); su strongSwan auth = pubkey, con certificati in /etc/swanctl/x509 e chiavi in /etc/swanctl/private.

Questa VPN trasporta solo unicast IP. Se ti servono OSPF o altri protocolli multicast tra le sedi, la soluzione è un tunnel GRE protetto da IPsec: lo spiego nella guida GRE su IPsec tra Cisco e Ubuntu. Per le basi del tunnel GRE vedi Tunnel GRE tra un router Cisco e Ubuntu Linux.

Prima di andare in produzione

Prova sempre la configurazione in laboratorio, salva quella attuale prima di modificarla e tieni un accesso di emergenza (console o accesso fuori banda) quando lavori su firewall e VPN da remoto.