Writing‎ > ‎

Setting up bridged OpenVPN on Freebsd

I spent a considerable amount of time getting OpenVPN working in bridged mode on my FreeBSD system. The problem turned out to be buggy bridging code in the NIC driver. However, I learned quite a bit in the process, including noticing that much of the information about bridging on FreeBSD (and OpenVPN in general) isn't exactly complete and up to date. In particular, they make setting up bridging for OpenVPN on FreeBSD seem much harder than it actually is once you've sorted everything out. Most of them also use older FreeBSD bridging tools, when the newer alternatives are both easier and more efficient.

This document is nota HOWTO. I'm going to provide details on how to set up a bridged FreeBSD server for a single client. However, I'm not going to simply provide a recipe – I'm going to explain why I do things the way I do them, and explain the alternatives as well. It should be usable as a HOWTO, but it should also be useful in setting up other configuration as well.

The goal for my setup is to minimize the number of things that I need to be maintain. This causes a number of things to be different from the recommended Linux configuration. In particular, rather than managing all the devices by hand in the OpenVPN startup script and leaving them all in place all the time, I use OpenVPN scripts to set up and destroy the devices automatically.

Install OpenVPN

The first thing you should do is install the OpenVPN port, or package if you prefer. The port is in /usr/ports/security/openvpn. Make sure you have 2.0.6 or later. It installs ${PREFIX}/etc/rc.d/openvpn.sh, which is what will start and stop the OpenVPN server.

Set up a tunneled VPN

The second thing to do is set up a working OpenVPN server that uses tunneling instead of bridging. This doesn't require FreeBSD-specific things after the installation, so you can pretty much follow the HOWTO. This will insure that everything but the bridging is correct before you tackle configuring the bridging server.

Don't worry about setting up routing for it on your local network, or extra policies and the like. Just make sure you can connect from the client to the server. To start and stop OpenVPN, use ${PREFIX}/etc/rc.d/openvpn.sh. You'll need to add the following three lines to /etc/rc.conf for that to work:

openvpn_enable="YES"
openvpn_configfile="/usr/local/etc/openvpn/openvpn.conf"
openvpn_if="tun"

openvpn_configfile will will need to be set to whatever you called the openvpn config file. openvpn_if lists the network pseudo interfaces that openvpn will be using, which is tun in this case, and ${PREFIX}/etc/rc.d/openvpn.sh will make sure they are loaded.

Testing the VPN

To properly test your VPN setup, you really need to connect the client to a different ISP than your LAN is connected to. This is the only way to make sure that the VPN and any routers and firewalls are set up properly. I'm fortunate enough to be dual-homed, and simply reconfigure my LAN so that my client is connected to the ISP that I'm not using as the endpoint for my VPN.

Failing that, the next thing would be if you have enough network gear to setup two separate networks on your LAN. That means two routers and a switch. You configure the two routers to use different LAN IP addresses – say 192.168.1.X and 192.168.2.X. You configure the switch on a third network – say 192.168.3.X. The two routers WAN ports are given addresses on that network, and plugged into the switch. The server is plugged into one router, which is configured to forward the OpenVPN protocol and port to the server. The client is plugged into the other router. When the VPN is down, you can't connect between the two machines. Bringing up the VPN should allow you to access all services on the server. I don't do this, but this is a common configuration for evaluating vpn software.

As a last resort, you can use your hosts firewalls to simulate network non-connectivity. Configure the two systems to block all packets to or from the other systems IP address, except for the VPN. Once the VPN is up, you should be able to reach all the services running on the other machine from either machine.

Kernel modules

To create a bridge in FreeBSD, you need to have the tap and bridge drivers in the kernel. The easiest way to do that is to let the ${PREFIX}/etc/rc.d/openvpn.sh script do it for you. Setting openvpn_if="tap bridge" in /etc/rc.conf will do that.

I like kernel modules that I use regularly to be compiled in. You can do that by adding device if_bridge and device tap to the kernel config file.

Creating the devices

The actual bridging is done by the if_bridge device. The bridge device isn't used by OpenVPN directly, so it won't create or configure it. While ${PREFIX}/etc/rc.d/openvpn.sh will load make sure the bridge module is loaded, it won't create the bridge device, or configure it. For our setup, the bridge device is only needed when we have a connection, so we can do that in the OpenVPN client-connect script.

Rather than maintaining scripts to create, configure and destroy the tap device, we'll let OpenVPN handle all of that. All we need to do is add the tap device to the bridge device. This can only happen if the bridge device actually exists, so we have to do it in the client-connect script.

Given those two things, the script we're going to use for client-connect is:

#!/bin/sh

ifconfig bridge0 create
ifconfig bridge0 addm if0 addm $dev up

This creates the bridge device, which we have to choose, and coordinate with any other bridge devices we're using on the system. It then adds the hardware NIC, which I've called if0 and the tap device, which is passed to the script in the environment variable $dev. Finally, it brings the bridge device up. That's all there is to it.

Shutting it down is even easier – all the client-disconnect script does is destroy the bridge device:

#!/bin/sh

ifconfig bridge0 destroy

This works fine with one server and one client. It does have the disadvantage that it destroys and creates the bridge device every time you connect. If you're going to be connecting a lot, you might want to consider the multiple client configuration discussed below. If you're happy with the single client config above, you can skip to the next section, because the rest of this one is going to discuss different setups.

If you're going be using multiple clients with your server, then you'll want to leave the bridge interface configured whenever the server is up. In that case, you want to use an up script to create the bridge device and do the initial configuration:

#!bin/sh

ifconfig bridge0 create
ifconfig bridge0 addm if0 up

The client-disconnect script above becomes the down script. You use the client-connect script to bridge the tap device used for each client:

#!/bin/sh

ifconfig bridge0 addm $dev

And you use the client-disconnect script to take the device out of the bridge:

#!/bin/sh

ifconfig bridge0 deletem $dev

If you're going to run multiple servers, you'll want to create and configure the bridge device external to OpenVPN. The easiest way to do this is to use the /etc/rc.conf cloned interface variables:

cloned_interfaces="bridge0"
ifconfig_bridge0="addm fi0 up"

This won't load the if_bridge kernel module for you, though. So you'll either have to compile that in, or load it via the /boot/loader.conf file. To use that file, add one line to it:

if_bridge_load="YES"

OpenVPN config

That was rather long, but most of it was discussing alternatives, each of which is actually short. What's left is the vpn config. Given a working tunneling OpenVPN config file, you need to delete the dev tun line, the server line, and any routing setup (hopefully, you didn't do that!). Then add the following lines to your OpenVPN config file:

server-bridge GATEWAY NETMASK IP-START IP-STOP
client-connect client-connect.sh
client-disconnect client-disconnect.sh

GATEWAY and NETMASK are the same as they are for if0. IP-START and IP-STOP are the IP address range on the local network that will be assigned to the client machines. Unfortunately, this will have to be coordinated with whatever else you're using to assign IP addresses. Note that these addresses must be on the same network as if0! That's the point of bridging – it makes the remote client appear to be on your local network.

Don't forget to change the client to dev tap as well.

Things you shouldn't have to worry about

ip addresses on the server

Various writeups on the web recommended setting ip addresses on the tap interface on the server. The Linux writeup sets the ip address on the bridge interface. Neither of these is needed on FreeBSD.

Firewall rules

I didn't recommend changing any firewall rules because it may not be necessary. All the packets coming from and going to the client go through the firewall rules, so those will work as you expect, without any action on your part. If your firewall rules for the local network doesn't refer to specific devices, they will work fine. I do this by using the any and me pseudo-interface in ipfw, and filtering source ip address for the local network.

If you need to specify device names in your firewall rules, the packets from the client will arrive on the bridge interface. Packets going to the client will go out via the if0 interface.

Older FreeBSDs: using bridge

The methods discussed here work in FreeBSD-5.5 and later. If you are using an older version of FreeBSD, you'll need to use the bridge module instead of the if_bridge pseudo-device. If you have both available, you should use if_bridge, as it works' better in the face of multiple interfaces, and is more efficient than bridge.

The considerations are the same as above, but some you manipulate the bridge differently. You can edit the various scripts, making the changes below to use the bridge module.

  • device if_bridge changes to options BRIDGE in the kernel config file.
  • Remove bridge from the openvpn_if variable in /etc/rc.conf, and add bridge_load="YES" to /boot/loader.conf.
  • ifconfig bridge0 create changes to sysctl net.link.ether.bridge=1 in the script. I would set this in /etc/sysctl.conf (see the sysctl.conf(5) man page).
  • ifconfig bridge0 destroy changes to sysctl net.link.ether.bridge=0. With the VPN up all the time, this is sort of pointless, so I'd just set this once in /etc/sysctl.conf.
  • ifconfig bridge0 addm if0 addm $dev up is spelled sysctl net.link.ether.bridge.config=$dev,if0.
  • ifconfig bridge0 deletem $dev is spelled sysctl net.link.ether.bridge.config=if0.

The major difference here is that reconfiguring the bridges requires careful setting of net.link.ether.bridge.config. See the bridge(4) manual page for details. The above examples are for the single client case.

Comments