All the bits, from anywhere.

Problem Statement: While OpenVPN has served me well over the past few years both for site-to-site and road-warrior style VPN connections, it always bugged me that I had to hack a config file, juggle certificates, and use a custom client that isn’t part of the base OS to bring up the links. My Android phone has a built-in L2TP/IPSec VPN client. My Macbook Pro OS X 10.9 laptop has both an IPSec and L2TP VPN client GUI wrapped around racoon. I run OpenBSD as my firewall/router gateway at home. There must be a solution here.

Goal: To allow all remote clients (both site-to-site and road-warrior) to connect and route all their traffic securely over the Internet through my OpenBSD machine at home.

Some of the hurdles I dealt with, and corners I knew I was cutting, to get the below solution working:

  • The devices I carry with me most of the time (Nexus 5 Android phone, OS X laptop) only support IKEv1, and not IKEv2. Therefore I could not use iked on OpenBSD, I had to use isakmpd.
  • I know that using client certificates is the more secure way to go when authenticating IPSec traffic, but I used a pre-shared key in this example for expediency and simplicity. I plan to migrate to certificates once I get my head wrapped around easily managing them.

On to the configuration. First, the PPP server on the OpenBSD machine. A simple configuration using npppd, handing out IPs on 10.40.0.0/24, using statically configured usernames and passwords.

npppd.conf(5) configuration:

set user-max-session 2  authentication LOCAL type local {     users-file "/etc/npppd/npppd-users" }  tunnel L2TP protocol l2tp {     listen on $external_ipv4_ip     l2tp-hostname $external_dns_A_record     idle-timeout 3600 # 1 hour }  ipcp IPCP {     pool-address "10.40.0.0/24"     dns-servers 10.40.0.1 }  interface tun1 address $vpn_endpoint_IP ipcp IPCP bind tunnel from L2TP authenticated by LOCAL to tun1 

Next was isakmpd, the daemon responsible for handling security associations (SA) and handling encrypted and authenticated network traffic.

isakmpd.conf(5) configuration:

ike passive esp transport    proto udp from $(external_IPv4_IP) to any port 1701    main auth hmac-sha1 enc aes group modp1024    quick auth hmac-sha2-256 enc aes group modp1024    psk dce930cbf010a35f336e640de0b7ff8e94b6b2a512d0ec41268e8e20a154fooo 

For my PSK (pre-shared key), I used OpenSSL to generate this random string. Don’t worry, the following is not my PSK, and you should not copy this verbatim.

$ openssl rand -hex 32 dce930cbf010a35f336e640de0b7ff8e94b6b2a512d0ec41268e8e20a1546044 

The below PF rules allow both authenticated and encrypted communication. I tried to be a specific as I could with all rules, having ‘from any to any’, or the like, was avoided at all costs. The last line is not specifically IPSec related, but I will explain it after.

$ext_if = "YOUR_EXTERNAL_INTERFACE_TO_THE_INTERNET" ipsec_if = "enc0" ipsec_tun = "tun1" table <ipsec_net> { 10.40.0.0/24 }  pass in proto { esp } from any to ($ext_if) pass in on $ext_if proto udp from any to any port {500, 1701, 4500} keep state pass in on $ipsec_if from any to ($ext_if) keep state (if-bound) pass in on $ipsec_tun from <ipsec_net> to any keep state (if-bound) pass out on $ext_if inet from <ipsec_net> to any nat-to ($ext_if) 

Ports:

  • 500: isakmpd key management
  • 1701: L2TP (used by npppd)
  • 4500: IPSec Nat-Traversal (used by isakmpd)

The last rule allows for remote road-warrior VPN clients to use NAT and route their traffic out my OpenBSD machine. The reason ‘$ipsec_tun:network” is not a viable macro to use in the NAT rule is that the interface created by nppd is not configured with a subnet attached to it. Try as I might, even with configuring /etc/hostname.tun1, when npppd comes up, the interface is configured as pasted below. The only solution I found here was specifying the network itself as either a table or a variable macro.

$ ifconfig tun1 tun1: flags=20043<UP,BROADCAST,RUNNING,NOINET6> mtu 1500     priority: 0     groups: tun     status: active     inet 10.40.0.1 netmask 0xffffffff 

Relevant rc.conf.local snippets

# IPSec Rules ipsec=yes ipsec_rules=/etc/ipsec.conf isakmpd_flags="-K" npppd_flags="-f /etc/npppd/npppd.conf" npppd_flags="" 

At this point, start npppd and isakmpd via their rc.d scripts. It is absolutely critical, given the ‘-K’ flag for isakmpd, you load the IPSec rules manually each time you restart isakmpd via ipsecctl. This bit me many times when testing connections, as isakmpd complains there are encryption mismatches between what the client sent, and what the server expected.

# ipsecctl -f /etc/ipsec.conf 

The above ipsecctl command is the magic incantation you must run manually (the above flags in rc.conf.local ensure it is run upon boot) every time you restart isakmpd.

At this point, I configured my client to use the above username, password, and pre-shared key to connect. I now had a working road-warrior style L2TP/IPSec VPN connection that I could use to access both my internal infrastructure and route traffic out through my Internet connection at home as if I was a client sitting on the internal network.

And now, some log snippets to show what it looks like when an active PPP/L2TP IPSec connection is made:

# npppctl session all Ppp Id = 3 Ppp Id : 3 Username : jforman Realm Name : LOCAL Concentrated Interface : tun1 Assigned IPv4 Address : 10.40.0.56 Tunnel Protocol : L2TP Tunnel From : $REMOTE_IP Start Time : 2015/04/25 15:05:40 Elapsed Time : 23 sec Input Bytes : 9029 (8.8 KB) Input Packets : 86 Input Errors : 2 (2.3%) Output Bytes : 345 Output Packets : 14 Output Errors : 0 (0.0%) 

Npppd logs:

Apr 25 15:05:38 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 logtype=Started RecvSCCRQ from=$(PUBLIC SOURCE IP OF VPN CLIENT):63771/udp tunnel_id=6/7 protocol=1.0 winsize=4 hostname=roadwarrior.theinter.net vendor=(no vendorname) firm=0000 Apr 25 15:05:38 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 SendSCCRP Apr 25 15:05:38 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 logtype=Started RecvSCCRQ from=$(PUBLIC SOURCE IP OF VPN CLIENT):63771/udp tunnel_id=6/7 protocol=1.0 winsize=4 hostname=roadwarrior.theinter.net vendor=(no vendorname) firm=0000 Apr 25 15:05:38 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 RecvSCCN Apr 25 15:05:38 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 SendZLB Apr 25 15:05:39 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 call=18525 RecvICRQ session_id=16020 Apr 25 15:05:39 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 call=18525 SendICRP session_id=18525 Apr 25 15:05:39 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 call=18525 RecvICCN session_id=16020 calling_number= tx_conn_speed=1000000 framing=async Apr 25 15:05:39 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 call=18525 logtype=PPPBind ppp=3 Apr 25 15:05:39 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=base logtype=Started tunnel=L2TP($(PUBLIC SOURCE IP OF VPN CLIENT):63771) Apr 25 15:05:39 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 call=18525 SendZLB Apr 25 15:05:39 VPNCONCENTRATOR npppd[9306]: l2tpd ctrl=6 call=18525 logtype=PPPBind ppp=3 Apr 25 15:05:42 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=lcp logtype=Opened mru=1360/1360 auth=MS-CHAP-V2 magic=145d130e/4efdd7cc Apr 25 15:05:42 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=chap proto=mschap_v2 logtype=Success username="$USERNAME" realm=LOCAL Apr 25 15:05:43 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=ccp CCP is stopped Apr 25 15:05:45 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=ipcp logtype=Opened ip=10.40.0.56 assignType=dynamic Apr 25 15:05:45 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=base logtype=TUNNELSTART user="$USERNAME" duration=6sec layer2=L2TP layer2from=$(PUBLIC SOURCE IP OF VPN CLIENT):63771 auth=MS-CHAP-V2 ip=10.40.0.56 iface=tun1 Apr 25 15:05:45 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=base Using pipex=yes Apr 25 15:05:45 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=base logtype=TUNNELSTART user="$USERNAME" duration=6sec layer2=L2TP layer2from=$(PUBLIC SOURCE IP OF VPN CLIENT):63771 auth=MS-CHAP-V2 ip=10.40.0.56 iface=tun1 Apr 25 15:05:45 VPNCONCENTRATOR /bsd: pipex: ppp=3 iface=tun1 protocol=L2TP id=18525 PIPEX is ready. Apr 25 15:05:45 VPNCONCENTRATOR npppd[9306]: ppp id=3 layer=base Using pipex=yes 

ipsecctl output showing flows and security associations:

# ipsecctl -s all 

Flows:

flow esp in proto udp from $(PUBLIC SOURCE IP OF VPN CLIENT) port 63771 to $(PUBLIC IPV4 VPN TERMINATOR IP) port l2tp peer $(PUBLIC SOURCE IP OF VPN CLIENT) srcid $(PUBLIC IPV4 VPN TERMINATOR IP)/32 dstid $(RFC1918 IP of VPN CLIENT)/32 type use flow esp out proto udp from $(PUBLIC IPV4 VPN TERMINATOR IP) port l2tp to $(PUBLIC SOURCE IP OF VPN CLIENT) port 63771 peer $(PUBLIC SOURCE IP OF VPN CLIENT) srcid $(PUBLIC IPV4 VPN TERMINATOR IP)/32 dstid $(RFC1918 IP of VPN CLIENT)/32 type require flow esp out from ::/0 to ::/0 type deny  SAD: esp transport from $(PUBLIC IPV4 VPN TERMINATOR IP) to $(PUBLIC SOURCE IP OF VPN CLIENT) spi 0x083cd308 auth hmac-sha1 enc aes-256 esp transport from $(PUBLIC SOURCE IP OF VPN CLIENT) to $(PUBLIC IPV4 VPN TERMINATOR IP) spi 0x5f9cd5a0 auth hmac-sha1 enc aes-256 

Sources:

Many many OpenBSD man pages: isakmpd(8), iked(8), ipsec.conf(5), npppd(8), npppd.conf(5)