Recently I've decided to give my FreeNAS box a few extra responsibilities. It's been used as a file server for years now running on a (now quite old) HP N40L MicroServer and has been running like a champ, but with my most recent home network overhaul replacing my OPNsense gateway laptop with a Ubiquiti USG, my ability to run VPNs via my gateway device is essentially gone.

So now I'm looking to create an iocage jails in FreeNAS that connects to a VPN service (I personally use Private Internet Access) that can act as a gateway for any device on my local network.

If possible I also want to configure the USG to provide a different gateway to a defined list of IP addresses, however I'll cover that in a separate post.


OpenVPN Gateway

Create the iocage jail

The main thing to be aware of here is you need to allow TUN devices to be used. If you are creating your jail through the FreeNAS UI, click the Advanced Jail Creation button when you create your jail. You don't need to mess with anything outside of the Basic Properties section if you don't want to, but the one thing you must set is allow_tun in the Custom Properties section.

It's also simplest if you enable the DHCP Autoconfigure IPv4 option in Basic Properties so your jail will automatically get an IP address from your gateway.

If this is the first time you've created a jail with this property enabled, you must reboot your FreeNAS box for it to take effect (the actual NAS, not the jail).

Once you've rebooted, connect to your FreeNAS via SSH and use the following command to connect to your iocage jail.

$ sudo iocage console <jailname>

This will prompt you for your password, enter it and you should be at the command prompt for your new jail.

Configure the OpenVPN Client

The first thing we need to do is install the OpenVPN client. (It's actually the same as the server package). Run the command below to install it. We'll also install curl for testing later.

$ pkg install openvpn curl

Since this is the first time running pkg in this jail, it will most likely prompt you to install pkg first. Say yes. Then once that's done it will ask for confirmation to install OpenVPN and its dependencies. Again, say yes.

We'll do a quick check on the public IP address seen by the jail. Make a note of this IP address.

curl -sSL https://api.ipify.org

Once OpenVPN is installed, put your configuration file into the /usr/local/etc/openvpn directory. Name it something descriptive, but make sure it ends with a .conf extension and not .ovpn as the init scripts expect it that way.

If you're using a service like Private Internet Access that has a username and password requirement, you'll need to put those into a file and amend your configuration script to read them. The file should be two lines, with the first having your username and the second having your password.

$ vim /usr/local/etc/openvpn/auth
my_vpn_username
my_vpn_password

Add/amend the following lines to your OpenVPN configuration file. We're assuming you named it as per the example above. Naming the tun device simplifies our setup later. Change the number to something other than 20 if desired, just make sure you use the same number later on in the firewall rules.

$ vim /usr/local/etc/openvpn/your_vpn_client.conf
...
dev tun20
auth-user-pass /usr/local/etc/openvpn/auth
redirect-gateway
...

Finally, we need to create a symlink to the OpenVPN init script in order to start the client. If you named your configuration file openvpn.conf then you can skip this step as the default script will pick it up. Replace your_vpn_client with whatever name you gave your configuration file, minus the .conf extension.

cd /usr/local/etc/init.d
ln -s openvpn your_vpn_client

Now we're going to test the OpenVPN connection. You can use the command below start the VPN client.

service your_vpn_client onestart

To check the status of the connection, check your logs using the command below. The excerpt below is from my setup after a successful connection.

$ cat /var/log/messages | grep your_vpn_client
Jan 01 12:00:00 vpnjail your_vpn_client[93222]: OpenVPN 2.4.7 amd64-portbld-freebsd11.2 [SSL (OpenSSL)] [LZO] [LZ4] [MH/RECVDA] [AEAD] built on Jul  7 2019
Jan 01 12:00:00 vpnjail your_vpn_client[93222]: library versions: OpenSSL 1.0.2o-freebsd  27 Mar 2018, LZO 2.10
Jan 01 12:00:00 vpnjail your_vpn_client[93223]: TCP/UDP: Preserving recently used remote address: [AF_INET]1.2.3.4:1198
Jan 01 12:00:00 vpnjail your_vpn_client[93223]: UDP link local: (not bound)
Jan 01 12:00:00 vpnjail your_vpn_client[93223]: UDP link remote: [AF_INET]1.2.3.4:1198
Jan 01 12:00:00 vpnjail your_vpn_client[93223]: WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Jan 01 12:00:00 vpnjail your_vpn_client[93223]: [bfc87ef49394c868cb8281422ae47f0e] Peer Connection Initiated with [AF_INET]1.2.3.4:1198
Jan 01 12:00:06 vpnjail your_vpn_client[93223]: TUN/TAP device /dev/tun20 opened
Jan 01 12:00:06 vpnjail your_vpn_client[93223]: /sbin/ifconfig tun20 10.0.0.2 10.0.0.1 mtu 1500 netmask 255.255.255.255 up
Jan 01 12:00:06 vpnjail your_vpn_client[93223]: Initialization Sequence Completed

The main line you're looking for is the last one that says "Initialization Sequence Completed". If you don't see that line then something went wrong with your VPN. Check your setup and make sure it's correct (the log file may provide you with some clues as to what went wrong).

To check the VPN is working run the following command and check your IP address.

$ curl -sSL https://api.ipify.org

The IP address should be different to the one from the one you recorded earlier if your traffic is being tunnelled through the VPN. To finish up we now set up our OpenVPN client to start when the jail does.

$ echo 'your_vpn_client_enable="YES"' >> /etc/rc.conf

Configure the firewall rules

Now that the OpenVPN client is configured and working, we need to set up our NAT filewall. This actually isn't too difficult once you know the correct firewall commands.

Firstly, you need your local IP range. If you're not sure what that is (it'll probably be something like 192.168.0.0/24 depending on your router), try running the following command inside your jail.

$ netstat -rn | grep -E "U[^A-Z]" | grep -v lo0 | awk '{print $1;}'
192.168.0.0/24

This works on my N40L, your mileage may vary. Write this down, we'll need it next. We're going to create an IPFW script to set up our NAT rules. Replace 192.168.0.0/24 with whatever your local network range is.

$ vim /usr/local/etc/ipfw.rules
ipfw -q -f flush
ipfw -q nat 1 config if tun20
ipfw -q add nat 1 all from 192.168.0.0/24 to any out via tun20
ipfw -q add nat 1 all from any to any in via tun20

Next we need to add the following lines to the /etc/rc.conf file.

$ vim /etc/rc.conf
...
cloned_interfaces="tun"
gateway_enable="YES"
firewall_enable="YES"
firewall_script="/usr/local/etc/ipfw.rules"

When that's done, we need to restart the IPFW firewall for the new rules to take effect.

$ service ipfw restart
Firewall rules loaded.

Now you should be able to set the gateway of any device on your local network to the IP address of the jail and it should be able to access the internet via the VPN.