Understanding OpenVPN 2.4 IPv6 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 IPv6 routing policy

Table of contents

Configuration reminder

Before diving into the IPv6 routing policy, a short reminder about the configuration used to enables IPv6 support on an OpenVPN infrastructure. The chosen configuration allows clients to redirect their whole traffic through the VPN tunnel over an Unique Local Address prefix . This configuration is composed of only two lines. Consider both of these lines as in used through the whole article.

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.

Line 1: enabling IPv6 through the tunnel

This first line of configuration is enough to enable a basic IPv6 support through the tunnel. For this example, it is needed to pick an IPv6 ULA prefix, fd42:feed:feed:feed::/64. It could have been any ULA /64 prefix included into fd00::/8. Here is what the line looks like:

server-ipv6 fd42:feed:feed:feed::/64

In reality, this configuration line is a kind of syntactic sugar and is transparently expanded by the server. Here is what it would look like if it was directly written into the configuration file:

tun-ipv6
push "tun-ipv6"
ifconfig-ipv6 fd42:feed:feed:feed::1 fd42:feed:feed:feed::2
ifconfig-ipv6-pool fd42:feed:feed:feed::1000/64

The two first lines enable the IPv6 capabilities at each end of the tunnel. The line tun-ipv6 enables IPv6 capabilities on the server side. While the line push "tun-ipv6" instructs the client to enable IPv6 capabilities on its side.

The two last lines give details on how to assign IPv6 addresses.
The line ifconfig-ipv6 assign an IPv6 address on the server side of the tunnel. The line ifconfig-ipv6-pool specifies the pool of IPv6 addresses to distribute amongst the clients.

About the second IPv6 address on the line starting with ifconfig-ipv6. Most of the time this address is unused by OpenVPN, it is the address fd42:feed:feed:feed::2 in the example. It is called the ipv6_remote and is only used on specific platform like Solaris. If the platform is Linux or Windows, this IPv6 address is unused.

The prefix fd42:feed:feed:feed::1000/64 may look a little bit odd at first, because the last hextet has a bit set and the prefix length is 64. In fact this is not a prefix but a pool of address to distribute. With fd42:feed:feed:feed::1000 being the first address to distribute inside the prefix fd42:feed:feed:feed::/64. In any case, the prefix length didn’t change and is still 64.

The parameter expansion is detailed in the helper_client_server function of the file helper.c (look after the following text: server-ipv6 2001:db8::/64)

Line 2: redirecting all the clients traffic inside the tunnel

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

push "redirect-gateway ipv6 bypass-dhcp"

The ipv6 argument instructs the client to redirect the whole IPv6 traffic into the tunnel, including Global Unicast traffic, Unique Local traffic and a few other IPv6 addresses traffic.
ATTENTION: the argument ipv6 of the command “redirect-gateway” has only been introduced since OpenVPN client 2.4.0.

The second argument, bypass-dhcp, instruct the client to create a special route to avoid traffic between the client and the server to go through the tunnel.

If the argument ipv6 exists without the argument bypass-dhcp, and if a client connects the server through a Global Unicast Address (GUA) or through a Unique Local Address (ULA), a traffic loop could occur on the client side.

When using redirect-gateway with IPv4 routes, there is no need to add the argument bypass-dhcp. Because OpenVPN automatically creates a route when needed to avoid tunnel traffic to go inside the tunnel itself. This behaviour is not implemented for IPv6 and that’s why the argument bypass-dhcp is needed.

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 to any Linux host with IPv6 support enabled.

user@openvpn:~# sudo route -6 -n
Kernel IPv6 routing table
Destination                    Next Hop Flag Met   Ref Use If
....
2001:db8::/64                  ::       U    600   2   0   eth0
fe80::/64                      ::       U    600   3   0   eth0
::/0                           fe80::1  UG   20600 7   0   eth0
ff00::/8                       ::       U    256   10  0   eth0
....

(other interface than eth0 have been voluntary ignored. )

  • 2001:db8::/64 is the on-link GUA /64 prefix on the LAN. This prefix depends on the internet connection settings. The server and the clients probably do not share the same value, except if they share the same LAN
  • fe80::/64 is the Link-Local route on interface eth0
  • ::/0 is the whole IPv6 address space, this is the gateway of last resort
  • fe80::1 is the link-local address of the router of the LAN
  • ff00::/8 is the Multicast route on interface eth0

Step 1: routes added by the Linux kernel at the tunnel interface creation

user@openvpn:~# sudo route -6 -n
Kernel IPv6 routing table
Destination                    Next Hop Flag Met  Ref Use If
....
fe80::/64                      ::       U    256  2   0   tun0
ff00::/8                       ::       U    256  1   0   tun0
....

(other interface than eth0 or tun0 have been voluntary ignored. )

  • fe80::/64 is the Link-Local route on interface tun0
  • ff00::/8 is the Multicast route on interface eth0

About the multicast route fe80::/64

The multicast route ff00::/8 always show up with kernel 4.9.0 or 5.0.0. But with kernel 4.4.0 (e.g. Ubuntu 16.04 LTS), it only shows up when ipv6 support is enabled.

The OpenVPN tunnel implementation just does not manage Link-Local traffic right now, it is not implemented within the 2.4.x version. It is on their roadmap for a future release, maybe version 2.5.x. (source)

That does not mean there is no Link-Local route fe80::/64 to the tunnel interface, neither a link-local address assigned on the tunnel interface. It depends of the Linux kernel version.

With a kernel version 4.9.0 or 5.0.0, the route is always added at the tunnel creation, even when IPv6 support is disabled inside OpenVPN configuration. And a Link-Local address is always assigned on the tunnel interface.

But with a kernel version 4.4.0 (e.g. Ubuntu 16.04 LTS), the Link-Local support does not seem to be implemented at all on tunnel interfaces. No Link-Local address is assigned for tunnel interfaces, even with IPv6 support enabled into the OpenVPN configuration. And the route fe80::/64 never shows up into the routing table.

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

During the tunnel initialisation, OpenVPN assigns ULA IPv6 addresses at both ends of the tunnel. With the same settings as described on the above sections, it assign the address fd42:feed:feed:feed::1 on the server side. And on the client side, an address is picked inside the pool fd42:feed:feed:feed::1000/64.

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 included into the pool fd42:feed:feed:feed::1000/64.

....
/sbin/ip -6 addr add fd42:feed:feed:feed::1/64 dev tun0
....

And the result into the routing table:

user@openvpn:~# sudo route -6 -n
Kernel IPv6 routing table
Destination                    Next Hop Flag Met  Ref Use If
....
fd42:feed:feed:feed::/64       ::       U    256  1   0   tun0
....

(other interface than eth0 or tun0 have been voluntary ignored. )

  • fd42:feed:feed:feed::/64 is the ULA prefix defined to communicate through the tunnel

Extra routing policy on the client side only

A few more steps occurs on the client side because of the redirect-gateway configuration. On the server routing table, nothing more is added of updated until the end of the tunnel creation.

Step 3: routes added by OpenVPN because of the bypass-dhcp argument

The goal of the bypass-dhcp argument is to create a direct route straight to the server.

....
/sbin/ip -6 route add 2001:db8::1/128 dev eth0 via fe80::1 metric 1
....

And the result into the routing table:

user@openvpn.client:~# sudo route -6 -n
Kernel IPv6 routing table
Destination                    Next Hop Flag Met  Ref Use If
....
2001:db8::1/128                fe80::1  UG   1    1   1   eth0
....

(other interface than eth0 or tun0 have been voluntary ignored. )

  • 2001:db8::1/128 is the IPv6 GUA of the VPN server
  • fe80::1 is the LLA of the router on the client LAN

ATTENTION: This route is not added if the client connects the OpenVPN server through its IPv4 address

Step 4: routes added by OpenVPN because of the ipv6 argument

The goal of the ipv6 argument is to redirect most of IPv6 traffic through the tunnel. Here are the commands used by OpenVPN on the client side:

....
/sbin/ip -6 route add ::/3 dev tun0
/sbin/ip -6 route add 2000::/4 dev tun0
/sbin/ip -6 route add 3000::/4 dev tun0
/sbin/ip -6 route add fc00::/7 dev tun0
....

And the result into the routing table:

user@openvpn.client:~# sudo route -6 -n
Kernel IPv6 routing table
Destination                    Next Hop Flag Met  Ref Use If
....
::/3                           ::       U    1024 0   0   tun0
2000::/4                       ::       U    1024 0   0   tun0
3000::/4                       ::       U    1024 0   0   tun0
fc00::/7                       ::       U    1024 0   0   tun0
....

(other interface than eth0 or tun0 have been voluntary ignored. )

  • ::/3 is a really weird prefix, it contains the Loopback address, the Unspecified address, Embedded IPv4 addresses, and a lot of addresses reserved by IETF
  • 2000::/4 is the first half part of the common GUA routed on internet
  • 3000::/4 is the second half part of the common GUA routed on internet
  • fc00::/7 is the ULA complete prefix

All these road are defined in the function do_init_route_ipv6_list in the file init.c

About ::/3, only 1/256 of it is specified by IANA. Routing only ::/80 would have been way more logical, it includes everything that exist into that prefix:

  • the Unspecified address ::
  • the Loopback address ::1
  • IPv4-Compatible ::/96 prefix
  • IPv4-Mapped ::ffff/96 prefix

But IPv4-Compatible addresses are obsolete, Unspecified and Loopback addresses shouldn’t be routed, so only IPv4-Mapped addresses ::ffff/96 are pertinent to be routed here.
Maybe the ::/3 is kept by OpenVPN to avoid breaking old setups and also to be ready for IPv6 future uses.

For more details about the purpose of these addresses or prefixes, see rfc4291

Conclusion

Two simple lines into the server configuration trigger a lot of changes on every hosts to be able to route the whole IPv6 traffic through the VPN tunnel.

Sources

  • https://tools.ietf.org/html/rfc4291
  • https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
  • https://github.com/OpenVPN/openvpn
  • https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
  • https://community.openvpn.net/openvpn/wiki/IPv6
  • https://wiki.linuxfoundation.org/networking/iproute2
  • https://linux.die.net/man/8/ip
  • https://tools.ietf.org/html/rfc3849

Related Posts

2 Comments

  1. What a clear and well-structured explanation! Thank you!

    Reply
  2. many thanks, finally i understand how to customize IPv6 dynamic pool and how IPv6 routing is set.

    Reply

Leave a Reply to DoPascCancel reply

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