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 LANfe80::/64
is the Link-Local route on interface eth0::/0
is the whole IPv6 address space, this is the gateway of last resortfe80::1
is the link-local address of the router of the LANff00::/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 tun0ff00::/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.
About link-local support ff00::/8
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 serverfe80::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 IETF2000::/4
is the first half part of the common GUA routed on internet3000::/4
is the second half part of the common GUA routed on internetfc00::/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
What a clear and well-structured explanation! Thank you!
many thanks, finally i understand how to customize IPv6 dynamic pool and how IPv6 routing is set.