====== Strongswan IPSec mediation feature (NAT hole punching) ====== {{description>IPSec tunnel between devices both behind NAT gateways}} This lab shows an interesting feature of Strongswan: IPSec tunnel between devices, both behind NAT gateways. ===== Presentation ===== ==== Network diagram ==== Lab build following [[documentation:examples:How to build a BSDRP router lab]]: 7 routers with full-meshed link and one shared LAN. Here is the logical and physical view: {{:documentation:examples:strongswan-ipsec-mediation.png}} ==== Setting-up the lab ==== === Downloading BSD Router Project images === Download BSDRP serial image (prevent to have to use an X display) on Sourceforge. === Download Lab scripts ==== More information on these BSDRP lab scripts available on [[documentation:examples:How to build a BSDRP router lab]]. Start the lab with full-meshed 7 routers and one shared LAN, on this example using bhyve lab script on FreeBSD: [root@FreeBSD]~# BSDRP-lab-bhyve.sh -i BSDRP-1.903-full-amd64-serial.img.xz -n 7 -l 1 VM 1 have the following NIC: - vtnet0 connected to VM 2 - vtnet1 connected to VM 3 - vtnet2 connected to VM 4 - vtnet3 connected to VM 5 - vtnet4 connected to VM 6 - vtnet5 connected to VM 7 - vtnet6 connected to LAN number 1 VM 2 have the following NIC: - vtnet0 connected to VM 1 - vtnet1 connected to VM 3 - vtnet2 connected to VM 4 - vtnet3 connected to VM 5 - vtnet4 connected to VM 6 - vtnet5 connected to VM 7 - vtnet6 connected to LAN number 1 VM 3 have the following NIC: - vtnet0 connected to VM 1 - vtnet1 connected to VM 2 - vtnet2 connected to VM 4 - vtnet3 connected to VM 5 - vtnet4 connected to VM 6 - vtnet5 connected to VM 7 - vtnet6 connected to LAN number 1 VM 4 have the following NIC: - vtnet0 connected to VM 1 - vtnet1 connected to VM 2 - vtnet2 connected to VM 3 - vtnet3 connected to VM 5 - vtnet4 connected to VM 6 - vtnet5 connected to VM 7 - vtnet6 connected to LAN number 1 VM 5 have the following NIC: - vtnet0 connected to VM 1 - vtnet1 connected to VM 2 - vtnet2 connected to VM 3 - vtnet3 connected to VM 4 - vtnet4 connected to VM 6 - vtnet5 connected to VM 7 - vtnet6 connected to LAN number 1 VM 6 have the following NIC: - vtnet0 connected to VM 1 - vtnet1 connected to VM 2 - vtnet2 connected to VM 3 - vtnet3 connected to VM 4 - vtnet4 connected to VM 5 - vtnet5 connected to VM 7 - vtnet6 connected to LAN number 1 VM 7 have the following NIC: - vtnet0 connected to VM 1 - vtnet1 connected to VM 2 - vtnet2 connected to VM 3 - vtnet3 connected to VM 4 - vtnet4 connected to VM 5 - vtnet5 connected to VM 6 - vtnet6 connected to LAN number 1 For connecting to VM'serial console, you can use: - VM 1 : cu -l /dev/nmdm1B - VM 2 : cu -l /dev/nmdm2B - VM 3 : cu -l /dev/nmdm3B - VM 4 : cu -l /dev/nmdm4B - VM 5 : cu -l /dev/nmdm5B - VM 6 : cu -l /dev/nmdm6B - VM 7 : cu -l /dev/nmdm7B ===== Configuration ===== Router 1 and Router 7 as a simple workstation, Router 2 and 6 as IPSec gateway, Router 4 as IPSec mediation, Router 4 and 5 as NAT gateway. ==== Router 1: Workstation ==== Router 1 is configured as a simple workstation. sysrc hostname=R1 sysrc gateway_enable=NO sysrc ipv6_gateway_enable=NO sysrc ifconfig_vtnet0="inet 192.168.1.1/24" sysrc defaultrouter="192.168.1.2" service hostname restart service netif restart service routing restart config save ==== Router 2: IPSec gateway ==== Router 2 is an IPSec gateway behind a NAT gateway that need to be directly reachable from Router 6 (then need to use a mediation server). Enable debug mode for IKEv2 into file /var/log/charon.log. sysrc hostname=R2 sysrc ifconfig_vtnet0="inet 192.168.1.2/24" sysrc ifconfig_vtnet1="inet 10.0.0.2/24" sysrc defaultrouter=10.0.0.3 sysrc strongswan_enable="yes" service hostname restart service netif restart service routing restart cat > /usr/local/etc/ipsec.conf <<'EOF' conn %default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 authby=secret keyexchange=ikev2 dpdaction=restart dpddelay=60s left=%defaultroute conn medsrv leftid=r2@medsrv.org leftauth=psk right=2.2.2.4 rightid=r4@medsrv.org rightauth=psk mediation=yes auto=add conn peer leftsubnet=192.168.1.0/24 leftid=r2@bsdrp.net right=%any rightid=r6@bsdrp.net rightsubnet=192.168.7.0/24 mediated_by=medsrv me_peerid=r6@medsrv.org auto=start 'EOF' cat > /usr/local/etc/ipsec.secrets <<'EOF' r2@medsrv.org : PSK "MediaServerPassword" r2@bsdrp.net r6@bsdrp.net : PSK "IPsecPassword" 'EOF' cat > /usr/local/etc/strongswan.d/charon-logging.conf <<'EOF' charon { filelog { /var/log/charon.log { default = 2 } } } 'EOF' service strongswan start config save ==== Router 3: NAT gateway ==== Router 3 is configured as a NAT gateway (pf with NAT using static-port option). Option static-port is important with pf: Without this option, NAT UDP hole will not work. sysrc hostname=R3 sysrc ifconfig_vtnet1="inet 10.0.0.3/24" sysrc ifconfig_vtnet6="inet 2.2.2.3/24" sysrc pf_enable="YES" service hostname restart service netif restart service routing restart cat > /etc/pf.conf <<'EOF' ext_if = "vtnet6" int_if = "vtnet1" localnet = $int_if:network nat on $ext_if from $localnet to any -> ($ext_if) static-port pass from { lo0, $localnet } to any keep state 'EOF' service pf start config save ==== Router 4: IPSec mediation server ==== Router 4 is an IPSec mediation server (and debug log enabled). sysrc hostname=R4 sysrc ifconfig_vtnet6="inet 2.2.2.4/24" sysrc strongswan_enable="yes" service hostname restart service netif restart service routing restart cat > /usr/local/etc/ipsec.conf <<'EOF' config setup conn %default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 authby=secret keyexchange=ikev2 mobike=no dpdaction=clear dpddelay=60s conn medsrv left=2.2.2.4 leftid=r4@medsrv.org leftauth=psk right=%any rightauth=psk mediation=yes auto=add 'EOF' cat > /usr/local/etc/ipsec.secrets <<'EOF' r2@medsrv.org : PSK "MediaServerPassword" r6@medsrv.org : PSK "MediaServerPassword" 'EOF' cat > /usr/local/etc/strongswan.d/charon-logging.conf <<'EOF' charon { filelog { /var/log/charon.log { default = 2 } } } 'EOF' service strongswan start config save ==== Router 5: NAT gateway ==== Router 5 has the same workstation mode configuration as R3. sysrc hostname=R5 sysrc ifconfig_vtnet6="inet 2.2.2.5/24" sysrc ifconfig_vtnet4="inet 10.0.0.5/24" sysrc pf_enable="YES" cat > /etc/pf.conf <<'EOF' ext_if = "vtnet6" int_if = "vtnet4" localnet = $int_if:network # ext_if IP address could be dynamic, hence ($ext_if) nat on $ext_if from $localnet to any -> ($ext_if) static-port pass from { lo0, $localnet } to any keep state 'EOF' hostname R5 service netif restart service routing restart service pf start config save ==== Router 6: IPSec gateway ==== Router 6 is like R2, an IPSec gateway using a mediation server (and debug log enabled). sysrc hostname=R6 sysrc defaultrouter="10.0.0.5" sysrc ifconfig_vtnet5="inet 192.168.7.6/24" sysrc ifconfig_vtnet4="inet 10.0.0.6/24" sysrc strongswan_enable="yes" service hostname restart service netif restart service routing restart cat > /usr/local/etc/ipsec.conf <<'EOF' conn %default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 authby=secret keyexchange=ikev2 dpdaction=restart dpddelay=60s left=%defaultroute conn medsrv leftid=r6@medsrv.org leftauth=psk right=2.2.2.4 rightid=r4@medsrv.org rightauth=psk mediation=yes auto=add conn peer leftsubnet=192.168.7.0/24 leftid=r6@bsdrp.net right=%any rightid=r2@bsdrp.net rightsubnet=192.168.1.0/24 mediated_by=medsrv me_peerid=r2@medsrv.org auto=start 'EOF' cat > /usr/local/etc/ipsec.secrets <<'EOF' r6@medsrv.org : PSK "MediaServerPassword" r2@bsdrp.net r6@bsdrp.net : PSK "IPsecPassword" 'EOF' cat > /usr/local/etc/strongswan.d/charon-logging.conf <<'EOF' charon { filelog { /var/log/charon.log { default = 2 } } } 'EOF' service strongswan start config save ==== Router 7: Workstation ==== Router 7 is, like R1, configured as a simple workstation. sysrc hostname=R7 sysrc gateway_enable=NO sysrc ipv6_gateway_enable=NO sysrc ifconfig_vtnet5="inet 192.168.7.7/24" sysrc defaultrouter="192.168.7.6" sysrc ifconfig_vtnet5="inet 192.168.7.7/24" service hostname restart service netif restart service routing restart config save ===== Testing ===== Status of IPSec tunnel: [root@R2]~# ipsec statusall Status of IKE charon daemon (strongSwan 5.5.1, FreeBSD 12.0-CURRENT, amd64): uptime: 4 minutes, since Apr 28 00:13:06 2017 worker threads: 11 of 16 idle, 5/0/0/0 working, job queue: 0/0/0/0, scheduled: 12 loaded plugins: charon aes des blowfish rc2 sha2 sha1 md4 md5 random nonce x509 revocation constraints pubkey pkcs1 pkcs7 pkcs8 pkcs12 pgp dnskey sshkey pem openssl f$ ps-prf xcbc cmac hmac attr kernel-pfkey kernel-pfroute resolve socket-default stroke vici updown eap-identity eap-md5 eap-mschapv2 eap-tls eap-ttls eap-peap xauth-gener$ c whitelist addrblock Listening IP addresses: 192.168.1.2 10.0.0.2 Connections: medsrv: %any...2.2.2.4 IKEv2, dpddelay=60s medsrv: local: [r2@medsrv.org] uses pre-shared key authentication medsrv: remote: [r4@medsrv.org] uses pre-shared key authentication medsrv: child: dynamic === dynamic TUNNEL, dpdaction=restart peer: %any...%any IKEv2, dpddelay=60s peer: local: [r2@bsdrp.net] uses pre-shared key authentication peer: remote: [r6@bsdrp.net] uses pre-shared key authentication peer: child: 192.168.1.0/24 === 192.168.7.0/24 TUNNEL, dpdaction=restart Security Associations (2 up, 0 connecting): peer[3]: ESTABLISHED 4 minutes ago, 10.0.0.2[r2@bsdrp.net]...2.2.2.5[r6@bsdrp.net] peer[3]: IKEv2 SPIs: da048fcc6b5080bd_i 474b56c815483bcf_r*, pre-shared key reauthentication in 49 minutes peer[3]: IKE proposal: AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_3072 peer{2}: INSTALLED, TUNNEL, reqid 1, ESP in UDP SPIs: cdec034c_i ced4f737_o peer{2}: AES_CBC_128/HMAC_SHA2_256_128, 0 bytes_i (0 pkts, 284s ago), 0 bytes_o (0 pkts, 284s ago), rekeying in 11 minutes peer{2}: 192.168.1.0/24 === 192.168.7.0/24 medsrv[2]: ESTABLISHED 4 minutes ago, 10.0.0.2[r2@medsrv.org]...2.2.2.4[r4@medsrv.org] medsrv[2]: IKEv2 SPIs: f7878154750454f8_i* 2077002ffa7ceaf9_r, pre-shared key reauthentication in 46 minutes medsrv[2]: IKE proposal: AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_3072 Ping: [root@R1]~# ping -c 3 192.168.7.7 PING 192.168.7.7 (192.168.7.7): 56 data bytes 64 bytes from 192.168.7.7: icmp_seq=0 ttl=62 time=0.627 ms 64 bytes from 192.168.7.7: icmp_seq=1 ttl=62 time=0.457 ms 64 bytes from 192.168.7.7: icmp_seq=2 ttl=62 time=0.594 ms --- 192.168.7.7 ping statistics --- 3 packets transmitted, 3 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 0.457/0.559/0.627/0.074 ms ===== Limitations ===== This mediation service works only if the NAT gateways try to preserve source UDP ports: Removing option "static-port" on pf configuration as example prevent to use this feature. ===== Testing others NAT engines ===== ==== FreeBSD ipfw (libalias) ==== Replacing pf nat by ipfw kernel-in-nat. R3 modification: sysrc pf_enable=no sysrc firewall_enable=yes sysrc firewall_nat_enable=yes sysrc firewall_script="/etc/ipfw.rules" cat > /etc/ipfw.rules <<'EOF' #!/bin/sh fwcmd="/sbin/ipfw -q" int_if="vtnet1" ext_if="vtnet6" ${fwcmd} -f flush ${fwcmd} nat 1 config if ${ext_if} same_ports deny_in unreg_only reset ${fwcmd} add pass ip from any to any via lo0 ${fwcmd} add pass ip from any to any via ${int_if} ${fwcmd} add nat 1 ip from any to any via ${ext_if} 'EOF' service pf onestop service ipfw start R5 modification: sysrc pf_enable=no sysrc firewall_enable=yes sysrc firewall_nat_enable=yes sysrc firewall_script="/etc/ipfw.rules" cat > /etc/ipfw.rules <<'EOF' #!/bin/sh fwcmd="/sbin/ipfw -q" int_if="vtnet4" ext_if="vtnet6" ${fwcmd} -f flush ${fwcmd} nat 1 config if ${ext_if} same_ports unreg_only reset ${fwcmd} add pass ip from any to any via lo0 ${fwcmd} add pass ip from any to any via ${int_if} ${fwcmd} add nat 1 ip from any to any via ${ext_if} 'EOF' service pf onestop service ipfw start ipfw is not able to display NAT session table, then we will check strongswan log on mediation server: [root@R4]~# tail -f /var/log/charon.log | grep ME_ENDPOINT 13[IKE] received HOST ME_ENDPOINT 192.168.1.2[4500] 13[IKE] received HOST ME_ENDPOINT 10.0.0.2[4500] 13[IKE] received SERVER_REFLEXIVE ME_ENDPOINT 2.2.2.3[4500] 13[IKE] received HOST ME_ENDPOINT 10.0.0.6[4500] 13[IKE] received HOST ME_ENDPOINT 192.168.7.6[4500] 13[IKE] received SERVER_REFLEXIVE ME_ENDPOINT 2.2.2.5[4500] All peers reach to connect to mediation server using their original UDP source port 4500: ipfw seems to correctly keep original source port. But IPSec tunnel is down: [root@R2]~# ipsec status peer Security Associations (2 up, 0 connecting): peer[1]: CREATED, %any[%any]...%any[%any] Checking with tcpdump on one Internet interface: [root@R3]~# tcpdump -pni vtnet6 host 2.2.2.5 and not icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on vtnet6, link-type EN10MB (Ethernet), capture size 262144 bytes 21:37:23.724744 IP 2.2.2.3.59172 > 2.2.2.5.4500: NONESP-encap: isakmp: child_sa inf2[I] 21:37:23.749477 IP 2.2.2.5.4500 > 2.2.2.3.4500: NONESP-encap: isakmp: child_sa inf2[I] 21:37:23.775475 IP 2.2.2.5.4500 > 2.2.2.3.4500: NONESP-encap: isakmp: child_sa inf2[I] 21:37:23.821481 IP 2.2.2.5.4500 > 2.2.2.3.4500: NONESP-encap: isakmp: child_sa inf2[I] ipfw nat engine is correctly keeping source port for packet going from: * R6 to R4 * R6 to R2 * R2 to R4 But it change source port for packet going from R2 to R6, and this broke mediation feature. There is a problem here: ipfw didn't reach to keept the "same_ports" because. The only cause it's because it's already used, but by what??? Digging further we found that Strongswan is generating IPSec packets using all addresses configured on R2, and the first try is using the : - The first packet generated by Strongswan is using source IP 192.168.1.2/24 (first interface, vtnet0), then consume on R3 the first NAT entry (R2, source port 4500 toward R6, destination port 4500) - The second packet generated by Strongswan is using source IP 10.0.0.2/24 (second interface, vnet1), then when R3 can't keept the same source port On R6, the first interface it tried is vtnet4 (with IP 10.0.0.6/24) before vtnet6 (192.168.7.6/24), then there is no problem of "already an entry into the NAT table". For fixing this problem we need to accept only local network address to ipfw configuration on R3 (same behavior as line "nat ... from $localnet" on pf configuration: cat > /etc/ipfw.rules <<'EOF' #!/bin/sh fwcmd="/sbin/ipfw -q" int_if="vtnet1" ext_if="vtnet6" ${fwcmd} -f flush ${fwcmd} nat 1 config if ${ext_if} same_ports deny_in reset ${fwcmd} add pass ip from any to any via lo0 ${fwcmd} add pass ip from any to any via ${int_if} ${fwcmd} add nat 1 ip from 10.0.0.0/24 to any xmit ${ext_if} ${fwcmd} add nat 1 ip from any to any recv ${ext_if} 'EOF' And this fix this Strongswan bug: [root@R2]~# ipsec status peer Security Associations (2 up, 0 connecting): peer[1]: ESTABLISHED 61 seconds ago, 10.0.0.2[r2@bsdrp.net]...2.2.2.5[r6@bsdrp.net] peer{1}: INSTALLED, TUNNEL, reqid 1, ESP in UDP SPIs: c76f1a74_i c01aa5a4_o peer{1}: 192.168.1.0/24 === 192.168.7.0/24 ==== Cisco IOS ==== For this test: - we insert 2 new Cisco router (named C3 and C5, emulated by dynamips) in parrallel of existing R3 and R5. - We modify R2 and R6 to uses the new C3 and C5 as default gateway Example if we are using the bhyve lab script (then on a FreeBSD hypervisor). First, need to found bridge interface used for the shared LAN (Internet): # ifconfig -a | egrep -B 1 'LAN_1$' bridge6: flags=8843 metric 0 mtu 1500 description: LAN_1 => We need to create 2 new TAP interfaces and add them it to this bridge6: They will be used on C3 and C5 as "Internet facing" interface Second, need to found LAN between R2 and R3. BSDRP lab script policy uses descrition name with "MESH_lower-router-id - higher-router-id". Then we need to found bridge with description "MESH_2-3": # ifconfig -a | grep -B 1 'MESH_2-3$' bridge7: flags=8843 metric 0 mtu 1500 description: MESH_2-3 => We need to create a new TAP interface and add it to bridge7: It will be used on C3 as "Internal" interface. And same for R5-R6 bridge: # ifconfig -a | grep -B 1 'MESH_5-6$' bridge19: flags=8843 metric 0 mtu 1500 description: MESH_5-6 => We need to create the last TAP interface and add it to bridge19: It will be used on C5 as "Internal" interface. Once done we start 2 dynamips instance (version 0.2.16 minimum for a working tap interface) emulating Cisco 3725 routers. Start dynamips on different tmux console. set C3_INT=`ifconfig tap create` set C3_EXT=`ifconfig tap create` set C5_INT=`ifconfig tap create` set C5_EXT=`ifconfig tap create` ifconfig bridge6 addm $C3_EXT ifconfig bridge6 addm $C5_EXT ifconfig bridge7 addm $C3_INT ifconfig bridge19 addm $C3_INT dynamips -P 3725 --idle-pc 0x602467a4 -r 256 -j -i 1 -s 0:0:tap:$C3_INT -s 0:1:tap:$C3_EXT c3725-adventerprisek9-mz.124-25d.bin dynamips -P 3725 --idle-pc 0x602467a4 -r 256 -j -i 2 -s 0:0:tap:$C5_INT -s 0:1:tap:$C5_EXT c3725-adventerprisek9-mz.124-25d.bin Configuring dynamips instance 1 (C3): en conf t hostname C3 access-list 1 permit 10.0.0.0 0.0.0.255 ip nat inside source list 1 interface fastEthernet 0/1 overload int fastEthernet 0/0 ip address 10.0.0.30 255.255.255.0 ip nat inside no shutdown int fastEthernet 0/1 ip address 2.2.2.30 255.255.255.0 ip nat outside no shutdown Configuring dynamips instance 2 (C5): en conf t hostname C5 access-list 1 permit 10.0.0.0 0.0.0.255 ip nat inside source list 1 interface fastEthernet 0/1 overload int fastEthernet 0/0 ip address 10.0.0.50 255.255.255.0 ip nat inside no shutdown int fastEthernet 0/1 ip address 2.2.2.50 255.255.255.0 ip nat outside no shutdown Change R2 default route for using C3: service strongswan stop sysrc defaultrouter="10.0.0.30" service routing restart service strongswan start Change R6 default route for using C5: service strongswan stop sysrc defaultrouter="10.0.0.50" service routing restart service strongswan start And testing: [root@R2]~# ipsec status peer Security Associations (3 up, 0 connecting): peer[3]: ESTABLISHED 40 seconds ago, 10.0.0.2[r2@bsdrp.net]...2.2.2.5[r6@bsdrp.net] peer{2}: INSTALLED, TUNNEL, reqid 1, ESP in UDP SPIs: cd81f3e1_i c209fd2f_o peer{2}: 192.168.1.0/24 === 192.168.7.0/24 peer[1]: ESTABLISHED 40 seconds ago, 10.0.0.2[r2@bsdrp.net]...2.2.2.5[r6@bsdrp.net] peer{1}: INSTALLED, TUNNEL, reqid 1, ESP in UDP SPIs: cd43f0d6_i ccc64d56_o peer{1}: 192.168.1.0/24 === 192.168.7.0/24 **It's working: The Cisco IOS nat engine try to preserve packet original source port.** And NAT translations table of C3 and C5 confirm it: C3#sh ip nat translations Pro Inside global Inside local Outside local Outside global udp 2.2.2.30:500 10.0.0.2:500 2.2.2.4:500 2.2.2.4:500 udp 2.2.2.30:4500 10.0.0.2:4500 2.2.2.4:4500 2.2.2.4:4500 udp 2.2.2.30:4500 10.0.0.2:4500 2.2.2.5:4500 2.2.2.5:4500 C5#sh ip nat translations Pro Inside global Inside local Outside local Outside global udp 2.2.2.50:500 10.0.0.6:500 2.2.2.4:500 2.2.2.4:500 udp 2.2.2.50:4500 10.0.0.6:4500 2.2.2.4:4500 2.2.2.4:4500 udp 2.2.2.50:4500 10.0.0.6:4500 2.2.2.30:4500 2.2.2.30:4500