Routing a FreeBSD Jail through OpenVPN

Tue 30 June 2015 by feld

I decided I wanted to concoct a solution where I could force all applications in a jail or jails through a VPN connection without affecting the internet connectivity of other daemons on the system. After some headbanging I was able to make this work. The OS version being used in this example is 10.1-RELEASE.

This post assumes you know how to setup an OpenVPN client & server as well as being familiar with jails.

Enable multiple routing tables

Multiple routing tables (fibs) are available out of the box these days but require you define them on boot. I only need two fibs, one for the system and one used by this jail. As many jails/applications as you want can share a fib; you don't need to make a new one for each new setup.

/boot/loader.conf

net.fibs=2

Set the default route for the fib

My OpenVPN setup handles redirecting the default gateway and adding routes to the client's routing table upon connection. In order for this to work successfully it needs to detect a default route. Here's the proper syntax so it starts on boot:

/etc/rc.conf

static_routes="vpn"
route_vpn="default 172.16.1.1 -fib 1"

Let's get the OpenVPN client running. This OpenVPN client exists outside the jail. This is required because you cannot alter routing tables from within a jail.

When starting the OpenVPN client you will need to execute it via setfib so it is operating under the correct routing table. The openvpn-client doesn't daemonize, so I'll let you solve that whichever way you prefer. I have mine running under daemontools to keep it alive.

server# setfib -F 1 /usr/local/sbin/openvpn-client /usr/local/etc/openvpn.conf

My OpenVPN client provisions static IPs for each new client, so the IP address is not going to change. I believe this is standard behavior for OpenVPN, but you may want to consult their docs if you run into issues.

The jail's IP address needs to be our end of the OpenVPN tunnel.

/etc/jail.conf

vpnjail {
    host.hostname = "vpnjail";
    ip4.addr = "10.8.0.14";
    exec.fib = 1;
    allow.raw_sockets;
}

edit: if your IP is dynamically allocated by your VPN provider, use ip4.addr = inherit;

Now when you start the jail it will use the new fib.

Please note if you use jexec to enter the jail you will be tricked into executing everything with the wrong fib! This is not well documented behavior. When the jail is launched and things automatically start they will use the correct fib, but if you jexec into the jail and run things from the shell it will not use the correct fib unless you setfib -F1 jexec when entering the jail!

When in doubt, check your fib with sysctl net.my_fibnum !

At this point you should have a fully functional FreeBSD jail with all network connectivity being pushed over the VPN.

You may be asking why I didn't bother using a VNET jail with its own network stack and run the openvpn-client within the jail, too. The reason is that I want the network connectivity to completely fail if the vpn goes down. I do not want any chance of the traffic leaking. You may be able to do a VNET jail and solve this problem with some firewall rules, but that is additional complexity I did not want to introduce to this environment.

Stay safe :-)