Monday, April 02, 2012

PPTP VPN and policy routing on user

The first part of this post describes how to use PPTP VPN on Linux, in command-line and not GUI. The second part, actually independent of VPN, describes how to set up policy routing for a user, in order to have all traffic from that user to go through a specific interface (e.g. the VPN interface).

VPN service: VPNoD

I'm using VPNoD (@vpnod) with a free beta account. It is PPTP, encrypted and they have instructions for all three major OS and phones. The Linux guide only shows the graphical configuration under Ubuntu. If you want command-line, you can use pptpclient.

PPTP client

Install package pptp-linux:
# aptitude install pptp-linux

Configure PPTP VPN:
# pptpsetup --create <name> --server <server> \
    --username <user> --password <password> [--encrypt]
For VPNoD, server is and choose --encrypt.

Start it in background, if it worked you should have a new interface named ppp0:
# pon <name>
# ifconfig ppp0

Stop if with command:
# poff <name>

If it failed you can debug with /var/log/syslog or just run it in foreground:
# pon <name> debug dump logfd 2 nodetach

Policy routing on user

Plenty of documentation out there on this topic: here are some links.

Basically, you need something like:
# mark all traffic generated by the user
iptables -t mangle -A OUTPUT -m owner --uid-owner <user> \
  -j MARK --set-mark <mark>

# masquerade all traffic from the user when going out
iptables -t nat -A POSTROUTING -m mark --mark <mark> -j MASQUERADE

# all traffic from the user goes into a dedicated routing table...
ip rule add fwmark <mark> table <table>

# ... for which we specify the gateway
ip route add default via <remote gateway> src <local ip> table <table>

echo 2 > /proc/sys/net/ipv4/conf/<interface>/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter # yes, also all

# any change to routes or reverse path filter requires a cache flush
ip route flush cache
The important detail is that you need to relax the Reverse Path Filter (RPF) to loose mode (2). If you do not do it, you notice a strange thing with tcpdump: user is able to send packets (e.g. TCP SYN), they go out to the interface (mark, ip rule and route), are properly masqueraded (nat), the remote end sends replies (e.g. TCP SYN/ACK) but the packets are never delivered to the application. Why? Because the RPF drops them since it does not expect to see external IP addresses on this interface.

Automation with ip-up, ip-down and ferm

pptpclient provides scripting at connection up and down, see /etc/ppp/ip-up and /etc/ppp/ip-down scripts. You can add your own scripts in /etc/ppp/ip-up.d and /etc/ppp/ip-down.d directories.

Example of my configuration with VPNoD: ip-up.d/vpnod and ip-down.d/vpnod.
# /etc/ppp/ip-up.d/vpnod
[ "${PPP_IPPARAM}" = "vpnod" ] || exit 0

ip rule add fwmark $MARK lookup $TABLE
ip route add default via $PPP_REMOTE src $PPP_LOCAL table $TABLE

echo 0 > /proc/sys/net/ipv4/conf/$PPP_IFACE/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter

ip route flush cache
# /etc/ppp/ip-down.d/vpnod
[ "${PPP_IPPARAM}" = "vpnod" ] || exit 0

ip rule del fwmark $MARK lookup $TABLE
ip route del default via $PPP_REMOTE src $PPP_LOCAL table $TABLE

echo 2 > /proc/sys/net/ipv4/conf/$PPP_IFACE/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter

ip route flush cache

Then, configure iptables via the following ferm rules: vpn.conf.
table mangle {
    chain OUTPUT { mod owner uid-owner vpn jump VPN_MARK; }
    chain VPN_MARK { MARK set-mark 0x4; ACCEPT; } # 3rd bit
    chain POSTROUTING { mod mark mark 0x4 jump VPN_FILTER; }
    chain VPN_FILTER { outerface ppp0 ACCEPT; DROP; }
  table nat {
    chain POSTROUTING { mod mark mark 0x4 MASQUERADE; }
The mangle postrouting rule matching on interface name (update if necessary) ensures traffic can only go out via VPN, avoiding leaks. Because of that and since there is no per-user DNS resolver setting (unfortunately), you probably need to fix your DNS resolver settings (/etc/resolv.conf) with a public one (such as or or or add a rule to explicitely allow your local resolver.

Reload ferm rules, start VPNoD PPTP and confirm that user vpn goes through the VPN:
# su vpn -c 'curl'


Your ping binary is most likely setuid:
# ls -lh /bin/ping
-rwsr-xr-x 1 root root 35K 2011-05-03 12:43 /bin/ping*
Which means if vpn user runs it, it runs as user root and as such, outside of the VPN.

The solution is to use POSIX file capabilities by removing the setuid and giving ping the NET_RAW capability:
# chmod u-s /bin/ping
# ls -l /bin/ping
-rwxr-xr-x 1 root root 35K 2011-05-03 12:43 /bin/ping*
# setcap cap_net_raw=ep /bin/ping
# getcap /bin/ping
/bin/ping = cap_net_raw+ep

Now try to ping inside the vpn, it is much slower which means the ping is going through the VPN.
Remember this for other networking setuid binaries.

No comments:

Post a Comment