OpenVPN routes dynamic NATting

Assume the following scenario: your {Open,Free}BSD pf-enabled (yes, I know what’s missing and it’s a pity, I am well aware of it) gateway connects to an OpenVPN server. This server pushes a couple of routes to your gateway that you’d like to be able to reach from within your own private network. As routers on the other end don’t have routes to your network(s), mandatory NAT is to be configured, but let’s also assume those routes are subject to change, and there’s more than a couple of them, some kind of dynamic rule adding should be considered.

Well luckily, OpenVPN’s third party script calling plus pf’s tables are a perfect match for such a task.

First up, we’ll need a script which will be passed the routes received by OpenVPN:

#!/bin/sh

PATH=/sbin:/usr/bin:/usr/local/bin

if [ "$#" -lt 1 ]; then
	echo "usage: $0 <table> [flush]"
	exit 1
fi

if [ "${2}" = "flush" ]; then
	pfctl -t ${1} -T flush
	exit 0
fi

i=1
while :
do
	eval routenet=\$route_network_${i}
	[ -z "${routenet}" ] && exit 0
	eval routemsk=\$route_netmask_${i}

	network=$(ipcalc -n ${routenet} ${routemsk}|awk '/^Network/ {print $2}')

	pfctl -t ${1} -T add ${network}

	i=$(($i + 1))
done

This script uses the route_network_{n} and route_netmask_{n} environmental variables sent by OpenVPN’s route-up command:

# openvpn configuration file
route-up "/home/imil/bin/routeup.sh myroutes"
down "/home/imil/bin/routeup.sh myroutes flush"

Then, in pf’s end, only the following rules are necessary:

tap_if="tap0"

table <myroutes> persist

nat on $tap_if from <mynet_allowed> to <myroutes> -> ($tap_if)

There you go, once your tunnel is up and routes are pushed, the script will push the routes to the myroutes pf table, and pf will NAT traffic to those destinations with our tunnel endpoint.