I wanted to expose a single jail of my FreeBSD NAS to a network of a client via OpenVPN while it’s reachable both from my network and from the clients’ network. It should send all of its traffic through that VPN tunnel so that it appears like it is just another computer on that foreign network.
Luckily FreeBSD offers a great way to solve this by creating a separate routing table apart from my main routing table that is used when starting OpenVPN (so that it can populate it’s routes there) and when starting the jail (the jail in fact will consider that routing table as the only routing table available and therefore use it for anything).
I assume a setup using FreeBSD 10 or 11, ezjail for jail management, OpenVPN as a VPN technology and pf as a firewall.
First of all, install OpenVPN on the host (
pkg install openvpn should do), configure it so that you can connect from the host and set up your jail using ezjail. Just give it an internal IP, I use a bridged setup for my jails (see FreeBSD NAS: System Setup chapter 7 for more information on how to set it up), so my jail has the IP
10.0.0.100 on my local network.
Setting up a second routing table
FreeBSD has support for multiple routing tables that is called FIB (Forwarding Information Base). Therefore there is a tool called
setfib that can be used to wrap a command to bind it to a given routing table. But first we have to set up a second routing table. Because this is a kernel startup configuration we have to extend our
/boot/loader.conf file to include the following:
[...] net.fibs=2 [...]
The provided number is the amount of FIBs that should be set up. Here it’s to because I only need my normal one for my network and a second one for the VPN network. Afterwards reboot:
Be sure to have pf and gateway functionality enabled as well as your router (your real one to be able to establish a VPN connection) configured.
gateway_enable="YES" pf_enable="YES" defaultrouter="10.0.0.1"
pf if not already done.
Establish the VPN connection
I’m lazy, therefore I don’t use a rc-script. I establish my OpenVPN connection in rc.local in a
screen to have it running in background and to be able to monitor it.
/etc/rc.local looks like this, please not that the FIB
1 is the VPN routing table, while
0 is my main routing table for the rest of my stuff:
#!/bin/sh # Add default route to be able to establish a VPN connection to the clients' endpoint setfib -F 1 /sbin/route add default 10.0.0.1 # Start a screen with "setfib -F 1 openvpn ...", OpenVPN will use FIB 1 and therefore not interfere your normal traffic on FIB 0 /usr/local/bin/screen -d -m setfib -F 1 /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/my-vpn.conf
To check if that works without rebooting, just run:
Check if there’s a screen (
screen -ls) and if the connection is established correctly by attaching to it to check it.
Configure NAT on pf
OpenVPN will create a device called
tun0 at startup which represents the network device used for tunneling the network traffic. Now we have to set up a NAT between the jail and
tun0 so that all traffic is sent to that device.
/etc/pf.conf looks like this:
scrub in all # 10.0.0.100 = jail IP nat on tun0 from 10.0.0.100/32 to any -> ( tun0 ) # If you only want to put outgoing traffic through the VPN, you might also want to block any incoming traffic, this was not necessary for my use case ;-) # block in on tun0 all
pf (or start it), and we’re ready to go:
service pf restart
Test the VPN setup
For a easy test, we can now run
curl on our VPN routing table to see if we still have the public IP address of the normal internet connection or if it is the public IP provided by the VPN:
# This should give your normal public IP setfib -F 0 curl http://ipecho.net/plain # This should tive the public IP of the VPN setfib -F 1 curl http://ipecho.net/plain
For me, the public address of the VPN was returned, so the setup worked.
Configure the jail
I always recognize in FreeBSD that the tools integrate very well with each other, so of course
ezjail provides support for FIBs.
This is how my
/usr/local/etc/ezjail/myjail config looks like:
export jail_myjail_ip="10.0.0.100" export jail_myjail_fib="1"
After restarting the jail using
ezjail-admin restart myjail, the connection should be operational. To prove this, run
curl http://ipecho.net/plain one time from the host, and one time from within the jail. In the jail it should return the VPN public IP, while outside it should do everything as usual.