Introduction
If you're even slightly security- or privacy-conscious, which in the present era of Big Data and tech oligarchs is a provident predilection, you should, if not already, be using a VPN (Virtual Private Network). And I don't mean one of the oft-advertised commercial offerings you see plastered about social media—but one that you control. Where every outgoing request that leaves your computer is tunneled through an encrypted private network owned and operated by you. Fortunately, the OpenBSD base tools make this a trivial task—and free if you already have a VPS. But even provisioning a new VM will likely be cheaper than the aforementioned commercial alternatives, with the added benefit of being more secure, and not only private in name but in practice; you can rest assured that only you and the endpoint will be cognizant of your traffic.
The following guide assumes you have a VPS running OpenBSD 6.6, but has also been tested on 6.5, 6.4, 6.3, and 6.2. If you don't already have a server, I highly recommend OpenBSD Amsterdam where a VM can be purchased for €60/year, and comes with OpenBSD preinstalled ready to go. Alternatively, Vultr also provides fast and reliable instances with OpenBSD images for US$5/month.
Enable Daemons
This L2TP over IPSec configuration uses the IKEv1 and IPSec protocols to
establish a tunnel for encrypted traffic between the client and server. This
requires the isakmpd
keying and npppd
tunneling daemons, which can be
enabled with rcctl
as a privileged user.
# rcctl enable ipsec isakmpd npppd # rcctl set isakmpd flags -K
Alternatively, open /etc/rc.conf.local
to add the following lines:
ipsec=YES isakmpd_flags="-K" npppd_flags=""
Before configuring these daemons, the system needs some tweaking to facilitate the intended use case.
Kernel Configuration
In order for the machine to act as a VPN gateway and perform traffic routing so
that packets can travel between network interfaces, IP forwarding and PPP frame
handling need to be enabled to forward IP packets in-kernel. In addition, if
the VPS is on a slow link but isn't resource constrained, consider enabling the
IP Compression Protocol (IPComp) to reduce datagram size and allow higher
throughput. To make these changes persistent across reboots, add the following
to /etc/sysctl.conf
.
net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1 net.pipex.enable=1 net.inet.ipcomp.enable=1
If there's no intent to use IPv6 or the node doesn't have limited bandwidth,
omit the inet6.ip6
and inet.ipcomp
lines, respectively.
Activate these changes now, by executing the following one liner as a privileged user:
# for c in $(cat /etc/sysctl.conf); do sysctl $c; done net.inet.ip.forwarding: 0 -> 1 net.inet6.ip6.forwarding: 0 -> 1 net.pipex.enable: 0 -> 1 net.inet.ipcomp.enable: 0 -> 1
Alternatively, two at a time:
# sysctl net.inet.ip.forwarding=1; sysctl net.pipex.enable=1 net.inet.ip.forwarding: 0 -> 1 net.pipex.enable: 0 -> 1 # sysctl net.inet6.ip6.forwarding=1; sysctl net.inet.ipcomp.enable=1 net.inet6.ip6.forwarding: 0 -> 1 net.inet.ipcomp.enable: 0 -> 1
With the system setup and requisite services enabled for IPSec, it's now time to configure the VPN.
Configure isakmpd
To setup a tunnel with a pre-shared key for authentication, isakmpd
needs to
be configured by adding a few simple lines to /etc/ipsec.conf
. Substitute
SECRETKEY
for the desired pre-shared key, which will need to be provided by
all connecting clients.
ike passive esp tunnel from "VPS_IP_ADDRESS" to any \ main group "modp1024" \ quick group "modp1024" \ psk "SECRETKEY"
Due to the sensitive nature of its contents, ipsec
requires that ipsec.conf
is neither group nor world read/writable; so lock down permissions to root only
access. Then parse the file to verify it's syntactically correct.
# chmod 600 /etc/ipsec.conf # ipsecctl -vnf /etc/ipsec.conf C set [Phase 1]:Default=peer-default force C set [peer-default]:Phase=1 force C set [peer-default]:Authentication=SECRETKEY force C set [peer-default]:Configuration=phase1-peer-default force C set [phase1-peer-default]:EXCHANGE_TYPE=ID_PROT force C add [phase1-peer-default]:Transforms=phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024 force C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:AUTHENTICATION_METHOD=PRE_SHARED force C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:HASH_ALGORITHM=SHA force C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:ENCRYPTION_ALGORITHM=AES_CBC force C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:KEY_LENGTH=128,128:256 force C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:GROUP_DESCRIPTION=MODP_1024 force C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:Life=LIFE_MAIN_MODE force C set [from-40.30.20.10-to-0.0.0.0/0]:Phase=2 force C set [from-40.30.20.10-to-0.0.0.0/0]:ISAKMP-peer=peer-default force C set [from-40.30.20.10-to-0.0.0.0/0]:Configuration=phase2-from-40.30.20.10-to-0.0.0.0/0 force C set [from-40.30.20.10-to-0.0.0.0/0]:Local-ID=from-40.30.20.10 force C set [from-40.30.20.10-to-0.0.0.0/0]:Remote-ID=to-0.0.0.0/0 force C set [phase2-from-40.30.20.10-to-0.0.0.0/0]:EXCHANGE_TYPE=QUICK_MODE force C set [phase2-from-40.30.20.10-to-0.0.0.0/0]:Suites=phase2-suite-from-40.30.20.10-to-0.0.0.0/0 force C set [phase2-suite-from-40.30.20.10-to-0.0.0.0/0]:Protocols=phase2-protocol-from-40.30.20.10-to-0.0.0.0/0 force C set [phase2-protocol-from-40.30.20.10-to-0.0.0.0/0]:PROTOCOL_ID=IPSEC_ESP force C set [phase2-protocol-from-40.30.20.10-to-0.0.0.0/0]:Transforms=phase2-transform-from-40.30.20.10-to-0.0.0.0/0-AES128-SHA2_256-MODP_1024-TUNNEL force C set [phase2-transform-from-40.30.20.10-to-0.0.0.0/0-AES128-SHA2_256-MODP_1024-TUNNEL]:TRANSFORM_ID=AES force C set [phase2-transform-from-40.30.20.10-to-0.0.0.0/0-AES128-SHA2_256-MODP_1024-TUNNEL]:KEY_LENGTH=128,128:256 force C set [phase2-transform-from-40.30.20.10-to-0.0.0.0/0-AES128-SHA2_256-MODP_1024-TUNNEL]:ENCAPSULATION_MODE=TUNNEL force C set [phase2-transform-from-40.30.20.10-to-0.0.0.0/0-AES128-SHA2_256-MODP_1024-TUNNEL]:AUTHENTICATION_ALGORITHM=HMAC_SHA2_256 force C set [phase2-transform-from-40.30.20.10-to-0.0.0.0/0-AES128-SHA2_256-MODP_1024-TUNNEL]:GROUP_DESCRIPTION=MODP_1024 force C set [phase2-transform-from-40.30.20.10-to-0.0.0.0/0-AES128-SHA2_256-MODP_1024-TUNNEL]:Life=LIFE_QUICK_MODE force C set [from-40.30.20.10]:ID-type=IPV4_ADDR force C set [from-40.30.20.10]:Address=40.30.20.10 force C set [to-0.0.0.0/0]:ID-type=IPV4_ADDR_SUBNET force C set [to-0.0.0.0/0]:Network=0.0.0.0 force C set [to-0.0.0.0/0]:Netmask=0.0.0.0 force C add [Phase 2]:Passive-Connections=from-40.30.20.10-to-0.0.0.0/0
The ipsec.conf(5) man page comprehensively explains the file format
and possible values, which can depend on the connecting client. The above
values are applicable to macOS and iOS clients, which require the modp1024
security group. Apart from that and the passive
mode, which instructs
isakmpd
to wait for connection requests, the default options are used.
Consult the man page and official documentation for settings suitable to other
peers.
Configure npppd
User authentication to the VPN is handled by the new Point-to-Point Protocol
daemon npppd
. Granting access to individual users requires adding two lines
for each user to the /etc/npppd/npppd-users
file; for example, to add users
johnnyb
and tommyg
with the passwords twogoatsonewolf
and onegoatnone
,
respectively:
johnnyb:\ :password=twogoatsonewolf: tommyg:\ :password=onegoatnone:
Note that all users will need to provide the abovementioned pre-shared key detailed in the
ipsec.conf
file, and their unique password listed in thenpppd-users
file.
That's the extent of configuration for the services managing authentication and
packet forwarding of the virtual private network. Changes can be made to
npppd
by editing /etc/npppd/npppd.conf
, and the npppd.conf(5)
man page is a concise resource for configuring the PPP daemon. For this
particular use case, however, the defaults suffice; a pppx(4)
interface and an IP address in the 10.0.0.0/24
subnet is allocated for each
session.
Configure pf
With the VPN now configured, all that remains server side is reconfiguring pf
to allow IPSec connections and packet forwarding, and handle the network
address translation of VPN traffic across disparate address spaces. The
following /etc/pf.conf
provides a minimal configuration to satisfy these
requirements (with only rules pertinent to the VPN shown).
ext_if = "vio0" vpn_if = "pppx" vpn_net = "10.0.0.0/24" # allow ESP protocol on public interface pass in on $ext_if proto esp # allow isakmpd UDP traffic through the public interface on ports 500 and 4500 pass in on $ext_if proto udp to port { isakmp, ipsec-nat-t } # filter all IPSec traffic on the enc interface pass on enc0 keep state (if-bound) # allow all trafic in on and out to the VPN network pass on $vpn_if from $vpn_net pass on $vpn_if to $vpn_net # nat VPN traffic going out on the public interface with the public IP match out on $ext_if from $vpn_net nat-to ($ext_if) set prio (3,4)
Before reloading the new ruleset, verify the syntax is valid.
# pfctl -vnf /etc/pf.conf ext_if = "vio0" vpn_if = "pppx" vpn_net = "10.0.0.0/24" pass in on vio0 proto udp from any to any port = 500 pass in on vio0 proto udp from any to any port = 4500 pass in on vio0 proto esp all pass on enc0 all flags S/SA keep state (if-bound) pass on pppx inet from 10.0.0.0/24 to any flags S/SA pass on pppx inet from any to 10.0.0.0/24 flags S/SA match out on vio0 inet from 10.0.0.0/24 to any set (prio(3, 4)) nat-to (vio0) round-robin # pfctl -f /etc/pf.conf
With pf
reconfigured and reloaded, it's time to start all the daemons.
Starting the VPN
OpenBSD's rcctl
makes this a cinch, and having previously enabled and
configured the requisite services, they should now start without complaint.
# rcctl start ipsec isakmpd npppd isakmpd(ok) npppd(ok)
Lastly, load the IPSec configuration file with ipsecctl
.
# ipsecctl -f /etc/ipsec.conf
Now that the VPN is up and running, switch over to the macOS client for some basic configuration before connecting.
macOS Setup
This can be done entirely through the macOS system settings graphical user interface.
Select the Network icon in System Preferences.
Figure 1: Open Network preferences Press the bottom left + icon.
Figure 2: Add new connection From the drop down menus, select the VPN interface, L2TP over IPSec VPN type, and provide a descriptive service name. Press Create.
Figure 3: L2TP over IPSec VPN Enter the IP address (or FQDN equivalent) from
ipsec.conf
and the username fromnpppd-users
in the Server Address and Account Name fields, respectively. Press Authentication Settings…Figure 4: Enter IP and username Input the
npppd-users
password and theipsec.conf
psk
value in the Password and Shared Secrect fields, respectively. Press OK.Figure 5: Enter password and pre-shared key Select Advanced…, then check Send all traffic over VPN connection and press OK.
Figure 6: Advanced options: send all traffic over VPN Press Apply in the bottom right corner, and then Connect.
Figure 7: Apply settings and connect!
The status should now show Connected with the allocated private IP address shown underneath. To the right, two progress bars that turn green when traffic is being sent or received will be displayed (see above screenshot).
On the server side, once a VPN session is established, ifconfig
will show the
connected client on the pppx
interface. Each session will be assigned its own
interface; that is, if multiple users are connected at the same time,
ifconfig
will display pppx0
, pppx1
, …, pppxN
.
# ifconfig lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 32768 index 3 priority 0 llprio 3 groups: lo inet6 ::1 prefixlen 128 inet6 fe80::1%lo0 prefixlen 64 scopeid 0x3 inet 127.0.0.1 netmask 0xff000000 vio0: flags=8b43<UP,BROADCAST,RUNNING,PROMISC,ALLMULTI,SIMPLEX,MULTICAST> mtu 1500 lladdr ab:cd:ef:a1:b2:c3 index 1 priority 0 llprio 3 groups: egress media: Ethernet autoselect status: active inet 40.30.20.10 netmask 0xffffff00 broadcast 10.20.30.255 inet6 aaaa:bbbb::cccc:dddd:eeee%vio0 prefixlen 64 scopeid 0x1 inet6 a1a1:b2b2:c3c3::ddd:eee prefixlen 64 enc0: flags=0<> index 2 priority 0 llprio 3 groups: enc status: active pflog0: flags=141<UP,RUNNING,PROMISC> mtu 33136 index 4 priority 0 llprio 3 groups: pflog pppx0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1360 description: johnnyb index 7 priority 0 llprio 3 groups: pppx inet 10.0.0.1 --> 10.0.0.232 netmask 0xffffffff
iOS Setup
iOS makes connecting to the VPN from an iPhone or iPad just as trivial.
Tap the Settings icon, enter General, then scroll down and select VPN.
Figure 8: Open VPN settings Tap Add VPN Configuration…
Figure 9: Add new VPN On the Add Configuration screen, do the following:
- change Type to
L2TP
; - provide a Description;
- specify the Server IP address (or FQDN equivalent) from
ipsec.conf
; - enter Account username and Password from
npppd-users
; - input the Secret pre-shared key from
ipsec.conf
; - enable Send All Traffic; then
- tap Done on the top right.
Figure 10: Enter VPN settings - change Type to
Toggle Status to connect to the VPN.
Figure 11: Connect to VPN
Conclusion
That concludes this short guide on how to establish an L2TP over IPSec VPN on
OpenBSD for macOS and iOS clients. The base tools provided on every default
install of OpenBSD make this a trivial undertaking, and the native macOS and
iOS network tools are also intuitively simple. While this enables a more secure
browsing experience by transmitting all traffic through an encrypted tunnel,
DNS requests are still being resolved by Google's nameservers. For a truly
private network, follow this guide to setup the local resolving name
server unbound
on OpenBSD, then check back soon for an upcoming article on
how to route all DNS requests through the VPN to be resolved by unbound
. This
will include how to configure DNSCrypt as another alternative solution for
preventing DNS leaks.