January 19, 2010
Here’s the deal.
We have an OpenVPN server, part of a network, for instance network 10.0.0.0/24, and server’s IP 10.0.0.15.
We connect to the OpenVPN server, using [b]UDP[/b], and a virtual [b]tap[/b] interface, let’s say, tap0.
After we’ve connected successfully with the vpn server, we run a dhcp client on the tap0 interface, and get an IP inside the 10.0.0.0/24 network, let’s say 10.0.0.55.
Along with the IP assignment, a new route will be added in the routing table, a route to the network 10.0.0.0/24, with no gateway(=link), through the tap0 interface.
Howerver, now we can’t contact the OpenVPN server. After the new route is added all of our packets to the VPN server, including the vpn packets, will be routed through the tap0 interface, and therefore VPN will stop working.
So, we add to the routing a table a route to the vpn server(10.0.0.15), via our local gateway(for instance 192.168.1.1), through our physical network interface(for instance eth0).
Now, we can communicate with every other host inside the 10.0.0.0/24 network over a VPN encrypted channel. But all of our connections to the VPN server will go through the unencrypted channel(192.168.1.1/eth0 route, bypassing the VPN/tap0 interafce).
But that’s not what we actually want.
Actually, we want to communicate with the VPN server over the VPN ‘tunnel'(and through tap0) for all the connections we make, except for the VPN connection.
That’s possible if we use iptables and iproute2.
We’ll mark the packets of the VPN connection using iptables(ie the packets using UDP, with destination address the VPN server, and destination port the port to which the server listens — port 1194 most likely).
iptables -t mangle -A OUTPUT -p udp -d 10.0.0.15 --dport 1194 -j MARK --set-mark 1
Now, we’ll create a rule with iproute2, which will route the marked packets using a different routing table.
First we create the new table.
echo 200 vpn.out >> /etc/iproute2/rt_tables
We add the rule.
ip rule add fwmark 1 lookup vpn.out
And we add the route for the vpn server to the vpn.out table.
ip route add 10.0.0.15 via 192.168.1.1 dev eth0 table vpn.out
One last thing.
With this configuration, there’s a problem in the selection of the source address for the vpn packets to the vpn server. Because the marking and the change of the route is done later, VPN will see the “10.0.0.0/24, no gateway, dev tap0” route in the main routing table, and will select the tap0 IP as the source address, which is obviously wrong since we want to get routed through the eth0 interface(with IP 192.168.1.2 for instance). This is fixed if we add the local 192.168.1.2 option in our vpn client configutaion file, so that OpenVPN binds to that address and selects it correctly as the source address.
We send only vpn packets through the 192.168.1.1/eth0 route, and everything else, including all other connections to the VPN server, are sent over vpn.
This ‘trick’ is very useful when you want to be able to ssh to the VPN server, but you want to prohibit ssh from IPs outside the local network.