Make OpenVPN Not Use VPN by Default
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 | $ echo "script-security 2" >>/etc/openvpn/client.conf |
How does OpenVPN make all traffic go through the VPN?
1 | $ ip route |
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 | #!/bin/sh |
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 | #!/bin/sh |
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 | $ ip addr show dev tun0 |
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?