Some users of the LUG VPN hope to use the VPN only for certain specific IPs, while OpenVPN defaults to using the VPN for all. Perhaps my search skills are too poor, I didn’t Google a reliable answer. Readers without patience can directly look at my solution:

1
2
3
4
5
6
7
8
9
10
11
$ echo "script-security 2" >>/etc/openvpn/client.conf
$ echo "up /usr/local/bin/remove-ovpn-defroute" >>/etc/openvpn/client.conf

$ cat /usr/local/bin/remove-ovpn-defroute
#!/bin/sh
(
sleep 2 # wait for routing table to be flushed
ip route del 0.0.0.0/1 dev tun0
ip route del 128.0.0.0/1 dev tun0
) &
exit 0

How does OpenVPN make all traffic go through the VPN?

1
2
3
4
5
6
7
8
$ ip route
10.8.0.37 dev tun0 proto kernel scope link src 10.8.0.38
202.141.160.99 via 202.141.162.126 dev eth0
202.141.162.0/25 dev eth0 proto kernel scope link src 202.141.162.123
10.8.0.0/16 via 10.8.0.37 dev tun0
0.0.0.0/1 via 10.8.0.37 dev tun0
128.0.0.0/1 via 10.8.0.37 dev tun0
default via 202.141.162.126 dev eth0

In the above example,

  • 10.8.0.37 is the IP assigned by the OpenVPN server
  • 10.8.0.0/16 is the OpenVPN subnet (this VPN network is quite large, /24 may not be enough)
  • 202.141.160.99 is the IP of the OpenVPN server
  • 202.141.162.126 is the default gateway of the OpenVPN client
    As you can see, the priority of the 0.0.0.0/1 and 128.0.0.0/1 routing table entries is higher than the default route, and it covers all IPs. This is added by the OpenVPN client after a successful connection. If you start OpenVPN in the console, you can find it in the output log.

Why doesn’t OpenVPN directly overwrite the default route, but adds these two strange items? Because when the OpenVPN connection is disconnected, the routing table needs to be restored to its initial state. By using this 0.0.0.0/1 and 128.0.0.0/1 method, you only need to delete these two routing table entries when the VPN connection is disconnected, without storing the original default route.

How to prevent OpenVPN from adding these two rules when the connection is established? One online suggestion is to add a line of route no-pull in /etc/openvpn/client.conf (your client configuration file), and no routing table entries will be added. However, this will also not add the 10.8.0.0/16 routing table entry, and clients in the VPN network cannot communicate with each other (note: the OpenVPN server needs to configure client-to-client to enable client-to-client communication), and we can no longer know the IP range of the VPN subnet.

Another method is to delete these two newly added routing table entries after the connection is established. The OpenVPN client provides an up parameter, which can execute a specified command when the connection is established. If the command to be executed is an external script, you also need to specify the script-security 2 parameter to allow the execution of external scripts. Can you write it like this in an external script?

1
2
3
#!/bin/sh
ip route del 0.0.0.0/1 dev tun0
ip route del 128.0.0.0/1 dev tun0

No. Because the script specified by the up parameter is executed before modifying the routing table, although the VPN connection has been established at this time, these two commands actually delete non-existent rules, and will exit with an error code. The OpenVPN client will interrupt the connection when it detects an error code.

What about adding an exit 0 at the end of the script? It doesn’t work either. After the script is executed, there is no effect, and then the 0.0.0.0/1 and 128.0.0.0/1 routing table entries are added. This script will not be called again, so it cannot achieve the purpose of clearing the default route.

So I adopted a workaround: fork a script process, the main process exits normally, and the child process waits for 2 seconds (it is estimated that the routing rules have been set), and then deletes these two rules. In fact, it doesn’t take 2 seconds to set the routing rules, this is just a safety setting.

1
2
3
4
5
6
7
8
#!/bin/sh
(
sleep 2 # wait for routing table to be flushed
ip route del 0.0.0.0/1 dev tun0
ip route del 128.0.0.0/1 dev tun0
# insert your routes here
) &
exit 0

If users want to add some specific IPs to use the VPN, they can be written after the two ip route add commands. The IP of the VPN gateway can be grep/sed/awk from the output of ip addr show dev tun0, as in the following example 10.8.0.41.

1
2
3
4
5
6
7
8
9
10
11
$ ip addr show dev tun0
3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
link/none
inet 10.8.0.42 peer 10.8.0.41/32 scope global tun0

$ ip route
default via 192.168.0.1 dev eth0
10.8.0.0/16 via 10.8.0.41 dev tun0
10.8.0.41 dev tun0 proto kernel scope link src 10.8.0.42
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.101
202.141.160.99 via 192.168.0.1 dev eth0

The biggest drawback of this approach is that all traffic will go through the VPN for up to 2 seconds, and some TCP connections may be interrupted as a result. Besides route no-pull, is there a more harmonious solution?

Comments

2013-10-03