Understanding OpenVPN 2.4 IPv4 routing policy

Introduction

Configuring an OpenVPN infrastructure does not require to understand every inner processes. As soon as the goals are reached, most of the time there is no need to go further into the software mechanisms. But through this article, I wanted to go into a more advanced understanding of:

  • the OpenVPN 2.4 IPv4 routing policy

Table of contents

Configuration

Before diving into the analysis of the IPv4 routing policy, a short look into the configuration used. With an OpenVPN infrastructure version 2.4 and bellow, IPv4 support is always enabled through the tunnel. Even if it can be minimalist, the IPv4 support has to be enabled for the tunnel to work. Maybe IPv4 would become optional with OpenVPN version 2.5, and thus maybe need more configuration to be enabled. (that’s a lot of maybe. source)

The chosen configuration can be found in most online documentation. A private network (rfc1918) is configured inside the tunnel to exchange data between clients and server. As a result, it creates a kind of Local Area Network with the server acting as a default gateway. On the server side a NAT44 is set to allow client to communicate on internet through the server IPv4 public address. It works exactly like a SOHO LAN with an internet box.

Do not forget that every configuration lines starting with push are only present into the server configuration file, but have impact on the client side. By the way, every configuration examples described through this article are from the server configuration file.

Part 1: configuring IPv4 through the tunnel

Configuration starts by choosing a topology and picking a private subnet. The chosen values come straight from official documentations.

topology subnet
server 10.8.0.0 255.255.255.0

About the line topology subnet, it is the recommanded value if both clients and server use OpenVPN version >= 2.4.0. Its purpose is to create a Local Area Network inside the tunnel. This LAN behaves mostly like a physical one but distributed across client tunnels. Broadcast operation is not implemented inside an OpenVPN tunnel.

About the line starting with server, it is a kind of syntactic sugar and is transparently expanded by the server. Disclaimer: it produces a lot of oddities. Here is what it could look like if it was directly written in the configuration file:

mode server
tls-server
ifconfig 10.8.0.1 255.255.255.0
ifconfig-pool 10.8.0.2 10.8.0.253 255.255.255.0
route-gateway 10.8.0.2
push "topology subnet"
push "route-gateway 10.8.0.1"

The two first lines enable server features on an OpenVPN instance. The line mode server instructs to listen to incoming connection. And the line tls-server specify that SSL/TLS authentication must be used through the communication channel.

The next two lines give details on how to assign IPv4 addresses. The line starting with ifconfig assigns an IPv4 address on the server side of the tunnel. The line starting with ifconfig-pool specifies the pool of IPv4 addresses to distribute amongst the clients. The setting ifconfig-pool sends a custom ifconfigpush directive to each client that connects the server.

The line route-gateway 10.8.0.2 instructs the server to use the address 10.8.0.2 as default destination for any custom route toward the tunnel.

The last two lines starting with the word push instruct the client which network configuration to use. It tells him first the topology subnet is in use. Then it tells him what route gateway to use inside the tunnel for custom routes: 10.8.0.1, it is the server address.

The parameter expansion is detailed in the helper_client_server function of the file helper.c (look after the text: server 10.8.0.0 255.255.255.0)

Oddities

The first oddity is the first available address in the pool: 10.8.0.2. This address is also the default route gateway assigned to the server. So if a custom route is added on the server side without a specific gateway or if the parameter route-gateway is not overridden, the custom routes traffic will have a client as next hop.
What could happen if the route-gateway wasn’t specified at all, OpenVPN would use the second argument of ifconfig as default destination, 255.255.255.0. This could seem strange, but it is a normal and needed behaviour to use the second ifconfig argument with net30 or p2p topologies.
By default, a server does not add custom route toward the tunnel, this line has just no effect in the end with the current configuration.
But if a custom route is needed on the server side, it is really important to set a specific gateway or to override parameter route-gateway.

The second oddity is the last available address in the pool, 10.8.0.253 and not 10.8.0.254. There is no technical reason explaining why the address ending with .254 should be kept free. I didn’t find any place in the code where this address is used. Removing this address from the pool does not impact the operations of an OpenVPN server, it just reduces the pool size. It is not a pool size limitation either, OpenVPN max pool size is 65536.

Pushing a route gateway to clients is the third and last oddity and is similar to the first oddity except it is on the client side this time.
From a client point of view, the only thing at the other end of a tunnel interface, is the server. So specifying a default gateway is a redundant information. But again, if no route gateway is specified, OpenVPN would use the second argument of ifconfig as default destination, 255.255.255.0. Remember this behaviour is useful for other topologies. So it is better to have a default route gateway even if it is a redundant information.

Part 2: redirecting all the clients traffic inside the tunnel

The next part of configuration has an interesting behaviour. It instructs the client which routing policy to apply on its side. Even if this line is not mandatory, it is considered as in used for the rest of the article.

push "redirect-gateway def1"

As soon as the setting redirect-gateway is used, a special route is automatically create to be sure the traffic toward the OpenVPN server is not sent through the tunnel. The goal is to avoid having any traffic loop by mistake. Be aware that this mechanism only exists with IPv4, not with IPv6.

The def1 argument instructs the client to redirect its whole IPv4 traffic inside the tunnel.

Routing policy on the client and the server sides of the tunnel

This section details step after step the evolution of the client and the server routing tables while enabling the OpenVPN infrastructure. During these first phases of the tunnel creation, both server and client share a similar routing table. Then only the client routing table continues to evolve.

Keep in mind that OpenVPN offers the possibility to avoid removing or updating existing route. It is preferred to add more specific routes than overriding existing routes. And it does not try to override existing routes by giving better metrics because metrics are not used by recent kernels.

Step 0: routing table before the OpenVPN activation

The following routing table is really common amongst any Linux host connected to a LAN with IPv4 support enabled.

user@openvpn:~# sudo route -4 -n
Kernel IP routing table
Destination  Gateway      Genmask         Flags Metric Ref Use Iface
....
0.0.0.0      192.168.1.1  0.0.0.0         UG    600    0   0   eth0
192.168.1.0  0.0.0.0      255.255.255.0   U     600    0   0   eth0
....
  • 0.0.0.0 is the whole IPv4 address space, this is the gateway of last resort
  • 192.168.1.1 is the private address of the router of the LAN
  • 192.168.1.0 is the private network defined on the LAN. This network depends of the local settings. The server and the clients may have different values

Step 1: route added by the ip command because OpenVPN has assigned an IPv4 address on the tunnel interface

During the tunnel initialisation, OpenVPN assigns IPv4 private addresses at both ends of the tunnel. With the settings described on the above sections, it assign the address 10.8.0.1 on the server side and picks an address inside the pool 10.8.0.2-10.8.0.253 for the client side.

OpenVPN uses the ip command from the package iproute2 to assign IP addresses and manipulate routes. This command directly interacts with the Linux kernel through the (rt)netlink interface. When adding an address on an interface, the ip command automatically adds a route associated to the address. The new route destination is the address prefix, and the destination interface is the interface where the address is added. In this case, the interface is the tunnel.

Here is what looks like the command called by OpenVPN when the server is initializing. It would have been exactly the same for clients but with an address from the pool.

....
/sbin/ip addr add dev tun0 10.8.0.1/24 broadcast 10.8.0.255
....

And the result into the routing table:

user@openvpn:~# sudo route -4 -n
Kernel IP routing table
Destination  Gateway      Genmask         Flags Metric Ref Use Iface
....
10.8.0.0     0.0.0.0      255.255.255.0   U     0      0   0   tun0
....
  • 10.8.0.0 is the private network assigned inside the tunnel

Extra routing policy on the client side only

A few more steps occurs on the client side because of the redirect-gateway setting. On the server routing table, nothing more is added or updated.

Step 2: routes automatically added by OpenVPN because of the use of redirect-gateway

The following route is automatically added by OpenVPN on the client side as soon as there is a use of the parameter redirect-gateway. It avoids pushing the tunnel traffic inside the tunnel itself and thus avoid creating loops.

....
/sbin/ip route add 203.0.113.1/32 via 192.168.1.1
....

And the result into the routing table:

user@openvpn.client:~# sudo route -4 -n
Kernel IP routing table
Destination  Gateway      Genmask         Flags Metric Ref Use Iface
....
203.0.113.1  192.168.1.1  255.255.255.255 UGH   0      0   0   eth0
....
  • 203.0.113.1 is the public address of the OpenVPN server
  • 192.168.1.1 is the private address of the default gateway on the client LAN

The creation of this route is done in the function redirect_default_route_to_vpn into the file route.c. (look after the comment “route remote host to original default gateway”)

ATTENTION: This route is not added if the client connects the OpenVPN server through an IPv6 address. In that case, the following warning is printed: ROUTE remote_host protocol differs from tunneled. It means there is no need to add the IPv4 route because the client is connected through an IPv6 address.

Step 3: routes added by OpenVPN because of the def1 argument

The goal of the argument def1 is to redirect the entire client traffic inside the OpenVPN tunnel. This is done by adding the following routes:

....
/sbin/ip route add 0.0.0.0/1 via 10.8.0.1
/sbin/ip route add 128.0.0.0/1 via 10.8.0.1
....

The ip address 10.8.0.1 associated to the via argument was pushed to the client with the route-gateway instruction. (see configuration sections) It could have been possible to use dev tun0 in place of via 10.8.0.1. It would have produced the exact same result into the routing table. It is a choice from developers to have used the via argument.

And here is the result inside the routing table:

user@openvpn.client:~# sudo route -4 -n
Kernel IP routing table
Destination  Gateway      Genmask         Flags Metric Ref Use Iface
....
0.0.0.0      10.8.0.1     128.0.0.0       UG    0      0   0   tun0
128.0.0.0    10.8.0.1     128.0.0.0       UG    0      0   0   tun0
....
  • 0.0.0.0 is the first half of the entire IPv4 space
  • 128.0.0.0 (in the destination column) is the second half of the entire IPv4 space
  • 10.8.0.1 is the private IPv4 address of the OpenVPN server inside the tunnel

The creation of this route is done in the function redirect_default_route_to_vpn in the file route.c. (look after 0x80000000) Be careful, IPv4 addresses are encoded into an hexadecimal format in the source code. So the address 0.0.0.0 becomes 0x00000000 and the address 128.0.0.0 becomes 0x80000000.

I decided to avoid talking too much about IPv4 Link-Local addresses, also called IP4LL. Simply because it is not relevant for IPv4 operations inside an OpenVPN tunnel. But also because route management has unpredictable behaviours and there is few or no documentation. IP4ALL actions seems to come from the Linux kernel, not OpenVPN.

Sometimes it is possible to see this kind of route showing up:

user@openvpn.client:~# sudo route -4 -n
Kernel IP routing table
Destination  Gateway      Genmask         Flags Metric Ref Use Iface
....
169.254.0.0  0.0.0.0      255.255.0.0     U     1000   0   0   tun0
....

But there is no Link Local address assigned on any sides of the tunnel, it is just a useless route.

Conclusion

Even if the routing policy is pretty straight forward in the end, many oddities result from previous OpenVPN version or from other topologies. That may increase the complexity of the overall understanding.

Sources

  • https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
  • https://community.openvpn.net/openvpn/ticket/208
  • https://tools.ietf.org/html/rfc1918
  • https://github.com/OpenVPN/openvpn
  • https://community.openvpn.net/openvpn/wiki/Topology
  • https://tools.ietf.org/html/rfc5737
  • https://linux.die.net/man/8/route

Related Posts

No Comments, Be The First!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.