Question in short: How do I get traffic from a host on an internal network, using NAT behind a router, to use a VPN connection set up on the router?

I have a FreeBSD 9 router with 4 network interfaces. One external connected to my ISP and the others connected to internal subnets. The internal interfaces use NAT (by pf) to connect to the external world.

Now, I would like to have all traffic from a host on one of the subnets use a PPTP VPN connection set up on the FreeBSD machine. The end result would be that a host on the internal network uses a VPN without having to use a VPN client on the host itself.

I've managed to set up the VPN client with mpd5. Connecting from the router to the VPN server with mpd5, I get a new network interface — ng0 — with an ip address.

My problem is that I don't know where to go from here. There's just an interface (ng0) with an IP address. How do I make traffic from the internal, NATed, network use the VPN connection?

If you're trying to answer or point me in the right direction, you can start with the assumption I don't know or understand anything about VPNs, routing, NAT or pf — which I clearly almost don't. (Adding a route from where to where? Adding pf rules doing what to incoming and outgoing packets? etc.)


This is right after connecting to the VPN server from the router.

My network

My /etc/pf.conf:

ext_if = "em0"
local_if = "em1"
wifi_if = "em2"
misc_if = "em3"
localnet = "{" $local_if:network $wifi_if:network $misc_if:network "}"
nat on $ext_if from $localnet to any -> ($ext_if)
pass in all
pass out all keep state

Routing table:

Destination        Gateway            Flags    Refs      Use  Netif Expire
default          UGS         0   998893    em0        link#10            U           0   159240    em1           link#10            UHS         0      358    lo0        link#11            U           0   503719    em2           link#11            UHS         0        0    lo0        link#12            U           0  3773731    em3           link#12            UHS         0        0    lo0       link#15            UH          0        0    ng0      link#15            UHS         0        0    lo0          link#13            UH          0        0    lo0     link#9             U           0        0    em0       link#9             UHS         0        0    lo0

To connect to the VPN server I have this in /usr/local/etc/mpd/mpd.conf:

    create bundle static B1
    set bundle yes compression
    set ipcp ranges
    set ccp yes mppc
    set mppc yes e128
    set mppc yes stateless

    # Not using these yet, as I don't know what to put in them ...
    #set iface up-script /usr/local/etc/mpd5/ip-up.sh
    #set iface down-script /usr/local/etc/mpd5/ip-down.sh

    create link static L1 pptp
    set link action bundle B1
    set auth authname XXXXXXXXX
    set auth password XXXXXXXXX
    set link max-redial 0
    set link mtu 1460
    set link keep-alive 20 75
    set pptp peer
    set pptp disable windowing
  • Can I clarify - you want all the hosts on your network to go out your em0 interface except for a specific host which you want to go out the VPN? Or do you want to send all your internal network hosts out the VPN? – Zanchey Feb 13 '12 at 4:12
  • I'd like a specific internal host or maybe an entire internal subnet — the important part is that it's not everything — to go out the VPN. All other internal traffic should go out the normal em0 way. – Erik Tjernlund Feb 13 '12 at 8:41

The key question here is, "Did you try the VPN yet?" Everything seems fine. If your FreeBSD box can already route to the remote side of the VPN, your job is almost done. That means that the connection is valid and it is aware of the route to the remote network.

The host behind the router shouldn't require any changes at all to communicate "through the VPN." If it has the router set as its default gateway, then all traffic to remote networks (including the other side of the VPN) should go through the router, which as we stated, is already aware of the appropriate route. If you want to be very explicit, add a static route to the routing table of the host in question to ensure that it is aware of the correct route to the remote side of the VPN.

  • As far as I can tell the VPN connection works. The output from mpd5 looks good and stuff like show link indicates there's a proper connection. I get a network interface – ng0 – with an ip address. But my problem, as I've clearly explained, is that I don't know how to get any traffic to use the VPN connection. There's no route, no pf rules, nothing. So how could it work? (And it doesn't). That's my whole question: how do I get traffic to use the VPN. – Erik Tjernlund Feb 11 '12 at 12:59
  • I should clarify my previous comment. No, the VPN doesn't work in full (that's a big part of my original question). There's an network interface after starting mpd5 (ng0), but there's no routing information (neither manual nor any script run by the mpd5 configuration — see above from mpd.conf). So when I say that the "VPN connection works", I mean that mpd5 connects to the VPN server and I get a ng0 interface on the router, but that's it. – Erik Tjernlund Feb 11 '12 at 14:53

To check that your VPN is working as expected, you can start by pinging the other side of your VPN tunnel from your FreeBSD box.


The main issue once you are sure that your VPN is ok is "routing" all the traffic coming from your inside interfaces to the gn0 interface and doing NAT as well on this interface. You cannot do that by simply modifying the default router or you would shoot you in the foot, as the VPN requires its packets to route through your ISP ;)

The word here is "divert", but as far as I know (see " Does PF support divert like IPFW? "), this is not supported by the current FreeBSD "pf" port (this port is always older than the OpenBSD "pf", as being the original). Maybe in the future ...

Here are some hints for a solution which imply you changing from "pf" to "ipfw" which support "divert":

  • read the FreeBSD official ipfw documentation, as this is a good start for the next steps.
  • rebuild your kernel with "ipfw" and "divert" enable (chapter 31.6.2 of the "ipfw" documentation)
  • enable "ipfw" and set the "natd" out interface from your "/etc/rc.conf" file:

    natd_flags="-f /etc/natd.conf"
  • write your own "/etc/rc.firewall-myown" (you can use the default "/etc/rc.firewall" as a starting point):

    ipfw -f flush 
    ipfw add 10002 divert natd all from any to any via em2
    ipfw add 10003 divert natd all from any to any via em3
    ipfw add 10004 divert natd all from any to any via em4

The "natd" daemon configuration is quite straightforward and is mostly used to set port forwarding.

I did not check everything, especially the fact that you have 3 interfaces to NAT but I use "ipfw" for my own FreeBSD routing box and I did some VPN diverting in the past.

Enjoy !

  • Firstly, yes, I can ping the VPN address ( That address replies to ping even without the VPN, but I think my pings are going through the tunnel because I can see the traffic if i do a tcpdump -i ng0. Lots of trying stuff has led me to believe, like you suggest, that it's the NAT that's the problem. I'll have a closer look at the ipfw stuff you suggest ... – Erik Tjernlund Feb 12 '12 at 22:43
  • Good to know that your VPN is working. Now comes the hard part: "routing", that is "diverting" in your case ;) Just remember for testing that your FreeBSD box will be blind, only the client hosts will be able to go through your VPN. – Ouki Feb 13 '12 at 0:23
  • Actually, I'm pretty sure you can just modify the default route, as long as you insert a more specific route to ensure you can reach the VPN server (which you can do with a pre-up script). – Zanchey Feb 13 '12 at 4:31
  • Of course you can. But the question is to make traffic only from the internal interfaces go through the VPN. Maybe I was a bit too literal in my answer ;) – Ouki Feb 13 '12 at 9:56

I don't know if FreeBSD has ip command, but there most like is something similar to accomplish this.

# create special routing table
echo 7 special >> /etc/iproute2/rt_tables
# add default route for special table
ip route add table special default via
# see special table
ip route show table special
# add source based routing rule
ip rule add from table special
# + add your nat rules unless the other end knows how to route back
# to your network

@see ip layer network admin guide (linux)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.