Roadwarrior IPsec on VMM

This guide is designed to help you simulate a roadwarrior configuration with VMM using the IPsec roadwarrior guide. If you lack real hardware to test with, you can simulate the setup inside vmm.

This guide assumes you have read the IPsec roadwarrior guide. It only covers those parts that are specific to the setup on VMM.

192.0.2.1 (NAT)      198.51.100.1     198.51.0.1
  init <---------------> resp <------> Internet
10.0.5.0/24            10.0.5.1

Configuring the host

First, we set up the host's interfaces:

host# cat /etc/hostname.vport11
inet 198.51.0.1 0xffffff00
!route add -inet 198.51.100.1 -cloning -link -iface vport11
!route add -inet 198.51.100/24 198.51.100.1
!route add -inet 192.0.2.1 -cloning -link -iface vport11
!route add -inet 192.0.2/24 192.0.2.1
up
host# cat /etc/hostname.veb11
add vport11
up

Next we configure vm.conf:

socket owner :vmdusers

switch "switch11" {
    locked lladdr
    interface veb11
}

bsdiso="/home/iso/install75.iso"

vm "init" {
        owner $USER
        memory 1G
        cdrom $bsdiso
        disk /home/$USER/init.qcow2 format qcow2
        interface tap11 {
                locked lladdr e8:8b:11:11:11:11
                switch "switch11"
        }
}

vm "resp" {
        owner $USER
        memory 1G
        cdrom $bsdiso
        disk /home/$USER/resp.qcow2 format qcow2
        interface tap22 {
                locked lladdr e8:8b:22:22:22:22
                switch "switch11"
        }
}

We need to add a firewall rule similar to this one on the host:

match out on egress from !(egress:network) to any nat-to (egress:0)

Make sure to create each of the qcow2 images and install OpenBSD.

Then inside the virtual machines, we configure the interfaces:

init# cat /etc/hostname.vio0
inet 192.0.2.1 0xffffff00
!route add -inet 198.51.0.1 -cloning -link -iface vio0
!route add -inet 198.51.0/24 198.51.0.1
!route add -inet default 198.51.0.1
init# cat /etc/hostname.lo1
up

resp# cat /etc/hostname.vio0
inet 198.51.100.1 0xffffff00
!route add -inet 198.51.0.1 -cloning -link -iface vio0
!route add -inet 198.51.0/24 198.51.0.1
!route add -inet default 198.51.0.1
resp# cat /etc/hostname.vether0
inet 10.0.5.1 0xffffff00

We now configure iked(8) as mentioned in the IPsec roadwarrior guide.

Exchange public keys. First, copy the public key from resp to init:

init$ ssh 198.51.100.1 'doas cat /etc/iked/local.pub' | doas tee /etc/iked/pubkeys/fqdn/resp.example.com

Replace resp.example.com with resp's fully qualified domain name.

Next, copy the public key from init to resp:

init$ cat /etc/iked/local.pub | ssh 198.51.100.1 'doas tee /etc/iked/pubkeys/fqdn/in
it.example.com'

Configure iked.conf(5) for both resp and init:

init# cat /etc/iked.conf
gateway = "198.51.100.1"
srcname = "init.example.com"
destname = "resp.example.com"
pool = "10.0.5.0/24"
dns = "198.51.100.1"

ikev2 $destname active esp \
        from dynamic to any \
        peer $gateway \
        srcid $srcname dstid $destname \
        request address any \
        iface lo1

resp# cat /etc/iked.conf
gateway = "198.51.100.1"
hostname = "resp.example.com"
pool = "10.0.5.0/24"
dns = "198.51.100.1"

ikev2 $hostname passive esp \
        from any to dynamic \
        local $gateway peer any \
        srcid $hostname \
        config address $pool \
        config name-server $dns \
        tag "ROADW"

Tighten permissions for iked(8):

init# chmod 0600 /etc/iked.conf

resp# chmod 0600 /etc/iked.conf

Appropriate sysctls should be enabled on resp:

resp# cat /etc/sysctl.conf
net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
net.inet.ipcomp.enable=1
net.inet.esp.enable=1
net.inet.ah.enable=1

resp# sysctl net.inet.ip.forwarding=1
resp# sysctl net.inet6.ip6.forwarding=1
resp# sysctl net.inet.ipcomp.enable=1
resp# sysctl net.inet.esp.enable=1
resp# sysctl net.inet.ah.enable=1

Adjust /etc/pf.conf? for resp:

resp="198.51.100.1"
ext_if="vio0"
pass in on $ext_if proto udp to $resp port {isakmp, ipsec-nat-t} tag IKED
pass in on $ext_if proto esp to $resp tag IKED
pass on enc0 inet tagged ROADW
match out on $ext_if inet tagged ROADW nat-to $ext_if
match in quick on enc0 inet proto { tcp, udp } to port 53 rdr-to 127.0.0.1 port 53

Reload packet filter:

resp# pfctl -f /etc/pf.conf

Start iked:

init# rcctl enable iked
init# rcctl start iked
iked(ok)

resp# rcctl enable iked
resp# rcctl start iked
iked(ok)

Confirm IPsec flows with ipsecctl and verify security associations with ikectl:

init# ipsecctl -sa
FLOWS:
flow esp in from 0.0.0.0/0 to 10.0.5.117 peer 198.51.100.1 srcid FQDN/init.example.com dstid FQDN/resp.example.com type require
flow esp out from 10.0.5.117 to 0.0.0.0/0 peer 198.51.100.1 srcid FQDN/init.example.com dstid FQDN/resp.example.com type require

SAD:
esp tunnel from 198.51.100.1 to 192.0.2.1 spi 0x24ce827e enc aes-128-gcm
esp tunnel from 192.0.2.1 to 198.51.100.1 spi 0x7c42210d enc aes-128-gcm

resp# ikectl show sa
iked_sas: 0xceb3d56a780 rspi 0x964bd34b809388a9 ispi 0x9b48ee25a0b56672 198.51.100.1:500->192.0.2.1:500<FQDN/init.example.com>[10.0.5.117] ESTABLISHED r udpecap nexti 0x0 pol 0xcebae8df000
  sa_childsas: 0xceb3d55e780 ESP 0x7c42210d in 192.0.2.1:500 -> 198.51.100.1:500 (LA) B=0x0 P=0xceb3d556c00 @0xceb3d56a780
  sa_childsas: 0xceb3d556c00 ESP 0x24ce827e out 198.51.100.1:500 -> 192.0.2.1:500 (L) B=0x0 P=0xceb3d55e780 @0xceb3d56a780
  sa_flows: 0xceb3d571400 ESP out 0.0.0.0/0 -> 10.0.5.117/32 [0]@-1 (L) @0xceb3d56a780
  sa_flows: 0xceb3d57c800 ESP in 10.0.5.117/32 -> 0.0.0.0/0 [0]@-1 (L) @0xceb3d56a780
iked_activesas: 0xceb3d556c00 ESP 0x24ce827e out 198.51.100.1:500 -> 192.0.2.1:500 (L) B=0x0 P=0xceb3d55e780 @0xceb3d56a780
iked_activesas: 0xceb3d55e780 ESP 0x7c42210d in 192.0.2.1:500 -> 198.51.100.1:500 (LA) B=0x0 P=0xceb3d556c00 @0xceb3d56a780
iked_flows: 0xceb3d57c800 ESP in 10.0.5.117/32 -> 0.0.0.0/0 [0]@-1 (L) @0xceb3d56a780
iked_flows: 0xceb3d571400 ESP out 0.0.0.0/0 -> 10.0.5.117/32 [0]@-1 (L) @0xceb3d56a780
iked_dstid_sas: 0xceb3d56a780 rspi 0x964bd34b809388a9 ispi 0x9b48ee25a0b56672 198.51.100.1:500->192.0.2.1:500<FQDN/init.example.com>[10.0.5.117] ESTABLISHED r udpecap nexti 0x0 pol 0xcebae8df000

Testing

From init, ping resp to confirm:

init# ping 10.0.5.1
PING 10.0.5.1 (10.0.5.1): 56 data bytes
64 bytes from 10.0.5.1: icmp_seq=0 ttl=255 time=0.695 ms
64 bytes from 10.0.5.1: icmp_seq=1 ttl=255 time=0.793 ms

resp# tcpdump -ne -i enc0
tcpdump: listening on enc0, link-type ENC
01:43:23.420194 (authentic,confidential): SPI 0x2b3e01ae: 10.0.5.117 > 10.0.5.1: icmp: echo request (encap)
01:43:23.420216 (authentic,confidential): SPI 0x41576324: 10.0.5.1 > 10.0.5.117: icm
p: echo reply (encap)
01:43:24.420194 (authentic,confidential): SPI 0x2b3e01ae: 10.0.5.117 > 10.0.5.1: icm
p: echo request (encap)
01:43:24.420214 (authentic,confidential): SPI 0x41576324: 10.0.5.1 > 10.0.5.117: icm
p: echo reply (encap)

From init, ping an external IP address and confirm that all packets pass through

 resp's enc0 interface:
init# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
64 bytes from 1.1.1.1: icmp_seq=0 ttl=52 time=8.297 ms
64 bytes from 1.1.1.1: icmp_seq=1 ttl=52 time=8.191 ms

resp# tcpdump -ne -i enc0
tcpdump: listening on enc0, link-type ENC
01:44:24.830185 (authentic,confidential): SPI 0x2b3e01ae: 10.0.5.117 > 1.1.1.1: icmp
: echo request (encap)
01:44:24.837528 (authentic,confidential): SPI 0x41576324: 1.1.1.1 > 10.0.5.117: icmp
: echo reply (encap)
01:44:25.830193 (authentic,confidential): SPI 0x2b3e01ae: 10.0.5.117 > 1.1.1.1: icmp
: echo request (encap)
01:44:25.837375 (authentic,confidential): SPI 0x41576324: 1.1.1.1 > 10.0.5.117: icmp
: echo reply (encap)

Configuring DNS

Change the following values in resp's /var/unbound/etc/unbound.conf:

outgoing-interface: 198.51.100.1
access-control: 10.0.0.0/8 allow

Make sure to enable and start unbound:

resp# rcctl enable unbound
resp# rcctl start unbound

You may want to configure domain blacklists to block unwanted traffic.

If init is running OpenBSD, you can verify if DNS lookup is working properly:

init# host ircnow.org 
ircnow.org has address 198.251.82.194
ircnow.org has IPv6 address 2605:6404:2d3::
ircnow.org mail is handled by 10 mail.ircnow.org.

resp# tcpdump -ne -i enc0 
tcpdump: listening on enc0, link-type ENC
01:25:54.119770 (authentic,confidential): SPI 0xb5cda039: 10.0.5.117.33324 > 8.8.8.8
.53: 63988+ A? ircnow.org.(28) (encap)
01:25:54.119882 (authentic,confidential): SPI 0x6f31fa50: 8.8.8.8.53 > 10.0.5.117.33
324: 63988 1/0/0 A 198.251.82.194(44) (encap)
01:25:55.139849 (authentic,confidential): SPI 0xb5cda039: 10.0.5.117.37113 > 8.8.8.8
.53: 10591+ AAAA? ircnow.org.(28) (encap)
01:25:55.139942 (authentic,confidential): SPI 0x6f31fa50: 8.8.8.8.53 > 10.0.5.117.37
113: 10591 1/0/0 AAAA 2605:6404:2d3::(56) (encap)
01:25:56.159754 (authentic,confidential): SPI 0xb5cda039: 10.0.5.117.1698 > 8.8.8.8.
53: 43204+ MX? ircnow.org.(28) (encap)
01:25:56.159846 (authentic,confidential): SPI 0x6f31fa50: 8.8.8.8.53 > 10.0.5.117.16
98: 43204 1/0/0 MX mail.ircnow.org. 10(49) (encap)

IPsec Statistics

init# netstat -s -p esp
esp:
        144 input ESP packets
        150 output ESP packets
        0 packets from unsupported protocol families
....
        0 raw ESP packets for encapsulating TDB received
        19976 input bytes
        20406 output bytes
init# netstat -s -p ah
ah:
        0 input AH packets
        0 output AH packets
        0 packets from unsupported protocol families
...
        0 output packets could not be sent
        0 input bytes
        0 output bytes

As expected, there are no AH packets being sent because the IPsec flows are configured to use ESP for confidentiality. For more information, see ipsec(4).