VPN tunneling on a linux router

After battling VPN on linux for over a week, I have what I think is the best solution out there for those of you using linux firewalls as your primary home routers. For completeness sake, I'll post what I did to connect my VPNs as tunnels on my linux router instead of using pptp-config or clobbering my default gateway. While this isn't the easiest approach, it is most defiantly the easiest to use when you have more than one PC on the network and your VPN only allows for one login.

I am not discouraging the use of PPTP-config. Their documentation and their hard work on their documentation is what provided me the resources I needed to troubleshoot many of the issues I was dealing with (esp with the major differences in 2.4.0 and 2.4.2 in pptp). If PPTP-config works for you, awesome, this how-to how to transparently provide VPN connectivity to your private (and hopefully secured) network via tunnels and the linux routing table.

If you are reading this, I am going to make the following assumptions:

  1. You understand basic routing
  2. You know iptables well enough to have a linux router
  3. You aren't dumb and can install software w/o crying about dependencies
  4. You will install PPP/PPTP 2.4.2 and not anything lower (check)
  5. You can translate Gentoo-ish style into your favored distro of choice

Ok, now that we are all on the same same page...lets start to get into the details of this project.

Home network: 172.16.22.0/24 (I'm not greedy)
Internal router interface: eth1 172.16.22.10 (omg, I didn't use 1!!!oneoen)
External router interface: eth0 72.181.255.245 (Cable modem, not DSL)
VPN endpoints: A Cisco PIX (cisco.bornl33t.net) and a Microsoft VPN server (msvpn.bornl33t.net). Both are obviously fake, but they are labeled like that for clarity sake only. (and I don't want you to know where I work).

Configuration files

Lets make two peers for pptp: touch /etc/ppp/peers/{cisco,msvpn}
Lets makes two options files for pptp: touch /etc/ppp/options.{cisco,msvpn}

Configuring the Cisco PIX VPN

/etc/ppp/peers/cisco

pty "pptp cisco.bornl33t.net --nolaunchpppd"
name crackerjm
file /etc/ppp/options.cisco
lcp-echo-interval 10
lcp-echo-failure 10
ipparam cisco

This file tells pppd how to create a PPTP link to what host and which username to authenticate with. Lets dig into that options file.

/etc/ppp/options.cisco

### CISCO VPN
name crackerjm  # auth name in chap-secrets
remotename cisco  # must match ipparam and name of file in /etc/ppp/peers
lock
noauth
nodeflate
nobsdcomp
mtu 1490
mru 1490
lcp-echo-failure 5
lcp-echo-interval 120
idle 86400 # never drop the link
noipdefault # hax for cisco

Add the user to chap-secrets:
echo "cracjerjm    cisco  l4m3P4ssw0rd  *" >> /etc/ppp/chap-secrets

Configuring the MS VPN

/etc/ppp/peers/msvpn

pty "pptp msvpn.bornl33t.net --nolaunchpppd"
name cjackmack 
file /etc/ppp/options.msvpn
lcp-echo-interval 10
lcp-echo-failure 10
nodeflate
nobsdcomp
ipparam msvpn

This file tells pppd how to create a PPTP link to what host and which username to authenticate with. Lets dig into that options file.

/etc/ppp/options.msvpn

### MS VPN
name cjackmack   # auth name in chap-secrets
remotename msvpn  # must match ipparam and name of file in /etc/ppp/peers
lock
noauth
nodeflate
nobsdcomp
mtu 1490
mru 1490
lcp-echo-failure 5
lcp-echo-interval 120
refuse-eap    #
refuse-chap   #  These aren't good enough for us
refuse-pap    #
refuse-mschap # neither is this one
require-mppe  # this is our compression method, ask the server to use this only
idle 86400 # don't timeout

The big difference you'll notice in the options file here is that I am refusing a lot of authentication methods. This is because microsoft VPN requires MPPC compression, which requires MPPE (aka mschap-v2). We need to make sure our VPN connection is strict enough to try only to use mschap-v2.
Add the user to chap-secrets:
echo "cjackmack     msvpn   b0rnl337  *" >> /etc/ppp/chap-secrets

Note:If it is the same username password (both using an LDAP/AD backend), then you can replace cisco with a glob (*) or just copy the entry twice and change the server name. In my example, the VPN tunnels I connect to are completely seperate and have different ACLs associated with each. So I have two logins, but some places make this easy and give you a single login. A word of caution to PPPoE users, this will probably override your credentials. I haven't messed with this myself, but a comment would be appreciated.

Testing the VPN

Open another terminal on your router and run one of the following commands:
Cisco test: pon cisco debug dump logfd 2 nodetach
MSvpn test: pon msvpn debug dump logfd 2 nodetach
^c will close the tunnel cleanly

Check ifconfig (or ip addr show) to see if you have a new ppp? interface (ppp0 for me). If it worked, ^c and ppp0 should disappear. This means that your VPN connection was successful! We don't want to test it with ping or traceroute just yet. If it wasn't, I'm sorry, you may want to read this. Since your VPN works, lets move onto setting up those routes and cleaning up that VPN connection.

Making the VPN tunnels available to your network

On both my VPN connections, I had the horrible experience of a routing loop. Not the most awesome experience ever, but a helpful one at that. To prevent it from ever happening again, I ended up added a few rules to a script to fix it. The routing loop happens when the tunnel peer supplies itself as a host through your new tunnel interface. Obviously you can't re-route your tunnel traffic through the tunnel!!

Lets prevent that routing perversion as well as prepare to setup our additional routes as well. After a link is successfully established, pppd runs /etc/ppp/ip-up with 6 options passed to it: ppp-device, tty-device, speed, local-ip, remote-ip, ipparam. remote-ip is the peer endpoint (not your VPN ip). In bash, we could delete a host from the routing table using /sbin/route del -host $5 dev $1. However, we don't want to add that little hack to /etc/ppp/ip-up as that file is provided via package management. Lets instead abstract this to make it easy to configure multiple tunnels.

littlenemo ~ # tail -n3 /etc/ppp/ip-up
fi

[ -f /etc/ppp/ip-up.local ] && . /etc/ppp/ip-up.local "$@"

Oh look! a built-in clause for /etc/ppp/ip-up.local! Lets add our little hack to this file so that way it is run on every ppp? connection. If the route doesn't exist on that interface then nothing will happen.

/etc/ppp/ip-up.local

#!/bin/bash

# Link routes tunnel endpoint to itself, remove it
# to prevent a routing loop
/sbin/route del -host $5 dev $1

#look for a file called ip-up.pppX, and if it exists, runs it, passing the connection parameters
[ -f /etc/ppp/ip-up.$1 ] && . /etc/ppp/ip-up.$1 "$@"

Cool, so prevented that damned routing loop and now it will run /etc/ppp/ip-up.$1 if it exists and passes all of it's calling parameters to the script. So, for ppp0 (which we will assume is cisco), lets setup our files to look like the following.

/etc/ppp/ip-up.ppp0

#!/bin/bash
# Supply a metric (usually 40+) to allow for
# preferred routes and locally attatched network
# to take priority
METRIC=50

# Add routes for private networks
/sbin/route add -net 172.16.0.0 netmask 255.255.0.0 metric $METRIC dev $1
/sbin/route add -net 192.168.254.0 netmask 255.255.255.0 metric $METRIC dev $1

/etc/ppp/ip-up.ppp1

#!/bin/bash
# Supply a metric (usually 40+) to allow for
# preferred routes and locally attatched network
# to take priority
METRIC=60

# Add routes for private networks
/sbin/route add -net 10.0.0.0 netmask 255.0.0.0 metric $METRIC dev $1
/sbin/route add -net 192.168.0.0 netmask 255.255.0.0 metric $METRIC dev $1

Wait, what!? I know, those are some funny routing rules huh? Ok, so if you understand routing metrics, this isn't all that odd. Since our local network is 172.16.22.0/24, our metric is 0. If I add a route of 172.16.0.0/16 with a metric of 50, then the routing tables will see that 172.16.22.0/24 has a lower cost to travel along eth1 instead of ppp0 and all other 172.16.0.0/16 will travel out of ppp0. Think of routing like electricity, it always travels the path of least resistance. The same rule applies to 192.168.254.0/24 with a metric of 50 through ppp0 and 192.168.0.0/16 with a metric of 60 through ppp1. All 192.168.0.0/16 will flow through ppp1 unless it's 192.168.254.0/24 which will flow through ppp0. If there are specific hosts that are using the same network as you, things get a bit sticky, but I seriously hope you AND your vpn endpoint aren't using the same network and mask... You can also add hosts in the same manner you add networks /sbin/route add -host 192.168.9.242 metric $METRIC dev $1.

This is a small snip of my iptables-save file /var/lib/iptables/rules-save

# Making NAT work!
-A POSTROUTING -o eth0 -j MASQUERADE

# For transparent VPN tunneling
# this is needed when you are using peer to net and not
# when using net to net.
-A POSTROUTING -o ppp0 -j MASQUERADE
-A POSTROUTING -o ppp1 -j MASQUERADE

You have to make sure to MASQUERADE all outbound traffic on ppp0 and ppp1 because the tunnel only supplied an single endpoint (one IP) and didn't advertise a new route. So, any traffic coming from 172.16.22.0/24 needs to be masked so that connections are able to be established. This is a vital piece for those with single endpoint VPNs (which most likely means you).

If you need access to private DNS servers, then just make some forward zones on your caching servers. I'm not going to show you how to do this, go look it up as it is super simple to accomplish for both BIND (kinda easy) and djbdns (too easy).

Making links persistant (very gentoo-ish)

This is the quick and dirty. Basicly it binds a peer to a particular interface and be added to startup scripts and the like. If you want dial on demand, just use demand instead of persist in your /etc/conf.d/net.

/etc/conf.d/net

# This blank configuration will automatically use DHCP for any net.*
# scripts in /etc/init.d.  To create a more complete configuration,
# please review /etc/conf.d/net.example and save your configuration
# in /etc/conf.d/net (this file :]!).
config_eth1=( "172.16.22.10/24" )
config_eth0=("dhcp")
dhcpcd_eth0="-R"

config_ppp0=( "ppp" )
pppd_ppp0=( "persist"
            "call cisco"
            "holdoff 10"
   )
link_ppp0="pty \" pptp cisco.bornl33t.net file /etc/ppp/options.cisco --nolaunchpppd\""

config_ppp1=( "ppp" )
pppd_ppp1=( "persist"
            "call msvpn"
            "holdoff 10"
   )
link_ppp1="pty \" pptp msvpn.bornl33t.net file /etc/ppp/options.msvpn --nolaunchpppd\""

Configuring your workstation(s)

NONE! Your done, just make sure your default route is 172.16.22.10 and your set! If using DHCP your job is even easier.

Closing

So what we have now is a fully functioning transparent VPN tunneling solution for your home network. All normal internet traffic travels through eth0 (like normal) and does what it is suppose to. The only time the tunnels will be utilized is when routing requests are made to any of the private networks we placed into our routing tables. Playing WoW, streaming pron, or just surfing the net will all be unaffected by your VPN project. Don't have to worry about work calling you about your massive VPN bandwidth usage and you don't have to re-connect in Gaim every time you VPN into work to check on your stuff there.

I hope this guide was helpful to you. And thanks for reading this!

Sources for this project

http://pptpclient.sourceforge.net
http://gentoo-wiki.com/HOWTO_Set_up_a_vpn_client_with_mppe_encryption
http://gentoo-wiki.com/HOWTO_PPTP_tunnels_with_kernel_2.6
A config script I found in a mailing list (sorry guy, I lost the link but it got me started)