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_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:

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:

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.

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.