OpenVPN, IPv6 with ULA and NAT

By Freeaqingme on Sunday 4 August 2013 13:56 - Comments (3)
Categories: Linux, Networking, Views: 20.068

Recent versions of OpenVPN provide IPv6 support, and as the world changes, so does my VPN setup.

My current VPS provider Edis provides a block of IPv6 addresses, but unfortunately it's only 1 /112 block. I wanted to use this VPS as my VPN gateway, and provide all devices behind this VPN with IPv6 addresses.

There is also the problem of renumbering; were I ever to choose a different VPS provider (it's unlikely I'll stick to the same one for eternity), I'd have to assign a different IP address to all devices that reside behind my VPN. RFC 4192 describes how you can go about this renumbering.

To overcome this problem, one would usually assign two addresses to each device: One ULA address, and one publicly routable address. The ULA address would be used for all internal traffic and the publicly routable addresses for traffic that also goes beyond the private network.

The problem here is that the smallest block of addresses that can be used by OpenVPN is a /112 subnet. That limit appears to be an arbitrarily chosen limit (I think it could also be /126), but it's one we need to deal with whatsoever. This limit means that we cannot assign a IPv6 subnet to the public interface of the gateway, and a smaller part of that subnet to the side of the VPN interface.
ULA
ULA, introduced in RFC 4193, provide an equivalent to the IPv4 Private Address Spaces like 192.168.0.0/16.

The idea is that everybody can use their own ULA-space. To accomplish this, you can generate your own /48 prefix to use within your network. By making sure you have a unique prefix, it should be possible to connect any two private networks together (e.g. by means of a lan2lan vpn).

A prefix can be generated based on your MAC-address and the current time, the first of which should by design be unique as well. Sixxs provides a simple generator to generate your own ULA prefix.

Because there's a slim chance that your generated prefix may collide with that of someone else, Sixxs has also set up a registry of prefixes. This registry seems to be the defacto standard. You can register your prefix on the same page as the generator.

As means of an example I will use the fd6c:62d9:eb8c::/48 ULA prefix in this blog post. The Sixxs registry ascribes this prefix to 'Example'. If you're so smart to just copy/paste this prefix, it's your loss - eventually.
Solution
In order to supply all devices behind my VPN with an IPv6 address, and provide them with connectivity to the internet (so not just a link-local address), and not having to renumber my internal addresses when I change ISP, I set up the following scheme:
- All devices behind my VPN use a ULA subnet only.
- The gateway performs 1:1 NAT, translating from my public /112, to a /112 subnet from my ULA /48 prefix.
- OpenVPN assigns addresses from a /112 subnet that are part of the ULA block.

"But NAT is bad!"

Well, that depends. When most people refer to NAT, they mean NAPT, Network Address Port Translation, as defined in RFC2663, section 4.1.2. With this type of NAT, one usually has 1 public ip address, and multiple private ip addresses. What packets are routed to what private ip is determined based on the destination port. However, all outgoing connections are required to be tracked by the NAT-gateway as well in order to know what private ip is meant to receive what incoming packet.

However, with the kind of NAT I describe here, the size of the subnet on the public side has the same size of that on the private side, both /112. All private addresses have a public counterpart, and therefore no tracking of connections is required (a firewall is!). Whenever a packet comes in, the public prefix is simply replaced by the ULA prefix, and the checksum is recalculated. The only disadvantage is that protocols that also carry addresses over layer 7 (e.g. SIP), may still be broken. But those protocols are rare, and most have already means to tackle problems like these.

http://imgs.xkcd.com/comics/nanobots.png
The steps
Some say that IPv6 has been around for almost 15 years now, since it was published in RFC 2460 in 1998. However, means to actually implement the protocol are still being published and released up to today.

Until recently, the Linux kernel didn't support IPv6 NAT support natively, although some patches have been around for some time. This functionality was implemented in the recently released version 3.7.

I'm on Ubuntu 12.04 (Precise), and by default this doesn't have 3.7 yet. That may be coming in two weeks though with the release of 12.04.3. You can however also install a new version of the kernel with hardly any dependencies, and you'll need an accompanying version of Netfilter (iptables) as well:


code:
1
2
3
4
5
apt-get install linux-generic-lts-raring
wget http://ftp.us.debian.org/debian/pool/main/i/iptables/libxtables10_1.4.19.1-1_amd64.deb
http://ftp.us.debian.org/debian/pool/main/i/iptables/iptables_1.4.19.1-1_amd64.deb
dpkg -i libxtables10_1.4.19.1-1_amd64.deb
dpkg -i iptables_1.4.19.1-1_amd64.deb



A reboot is required for the new kernel to come into effect.
OpenVPN
The next step is installing OpenVPN itself. For them IPv6 support is also relatively new, and you need version 2.3 to get all the bells and whistles:

code:
1
2
3
echo deb http://repos.openvpn.net/repos/apt/precise-snapshots precise main > /etc/apt/sources.list.d/repos.openvpn.net-precise-snapshots.list
apt-get update
apt-get install openvpn=2.3.*



I set up the following configs, based off this tutorial which may provide some additional pointers and how to generate your certificates used for authentication.

Server:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat /etc/openvpn/openvpn.conf
port 1194
proto udp
dev tun
ca ca.crt
cert server.crt
key server.key
dh dh4097.pem
ifconfig-pool-persist ipp.txt
keepalive 10 120
comp-lzo
user nobody
group users
persist-key
persist-tun
status openvpn-status.log
verb 3
client-to-client

server-ipv6 fd6c:62d9:eb8c::/112
server 172.31.253.0 255.255.255.128
push "route-ipv6 fd6c:62d9:eb8c::/48"
push "route-ipv6 2000::/3"



Client:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
remote server.example.com 1194
client
port 1194

ca server.ca.crt 
cert client.crt
key client.key

comp-lzo
dev tun
tun-ipv6
tls-client
proto udp
persist-key
persist-tun
user dolf
group dolf
mtu-test
status openvpn-status.log

Networking
Then it's required to allow traffic to be forwarded from the private side of things to the public side, set the following values in /etc/sysctl.conf:

code:
1
2
3
4
net.ipv6.conf.all.proxy_ndp = 1
  net.ipv6.conf.all.forwarding  = 1
  net.ipv6.conf.all.autoconf = 0
  net.ipv6.conf.all.accept_ra = 0



You can reload Sysctl by performing on the command line: sysctl -p

We also need to tell Iptables how to translate the ip addresses. Here, eth0 is the WAN interface, fd6c:62d9:eb8c::aa7 the address that OpenVPN assigns to my client, and 2606:2800:220:6d:26bf:1447:1097:aa7 the address that is mapped to.

code:
1
2
3
4
5
ip6tables -F
ip6tables -t nat -F
ip6tables -X

ip6tables -t nat -A POSTROUTING -o eth0 -s fd6c:62d9:eb8c::aa7 -j SNAT --to-source 2606:2800:220:6d:26bf:1447:1097:aa7



This is suboptimal, ideally Netfilter would allow to simply translate fd6c:62d9:eb8c::/112 to 2606:2800:220:6d:26bf:1447::/112. However, there are no plans to implement it; at least not anytime soon. Given that you now need 1 iptables rule per client, you'd ideally be able to script it using the client-connect directive from OpenVPN. However, currently OpenVPN doesn't yet pass the required ipv6 parameters to the script for the script to determine what client ip is was assigned, as described in this issue in the OpenVPN issue tracker. In my case this is not a real problem, since I only have a few devices on my personal VPN, but if you have many, you may want to look into fixing that issue yourself (and contribute back of course!).

In case you're interested, this is my networking config (/etc/network/interfaces):

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet static
    address 257.235.256.92
    netmask 255.255.255.0
    network 257.235.256.0
    broadcast 257.235.256.255
    gateway 257.235.256.1

iface eth0 inet6 static
    autoconf 0
    privext 0
    address 2606:2800:220:6d:26bf:1447:1097:1
    gateway 2606:2800:220::1
    netmask 48
    accept_ra 0
    dns-nameservers 2001:4860:4860::8888 2001:4860:4860::8844

Public Routing
Once you've got the VPN up and running, there's a connection between the VPS and datacenter gateway, and between the VPS and VPN clients. However, the datacenter gateway may not yet be aware where to route the addresses to. With IPv6, the Neighbor Discovery Protocol, NDP (RFC 3122) is used to locate devices within a network based on their ip address, just like ARP is used with IPv4.

By default, Linux only sends out NDP Advertisements for addresses that it has been assigned. I found ('tcpdump -nli any icmp6') that initially all traffic from VPN clients was going out successfully, but that the datacenter had no idea where to send the packets when they were returned from my destination.

The first thing I tried was configuring the ip address that should be routed to my VPS for a VPN client on my public interface:

code:
1
ip a a 2606:2800:220:6d:26bf:1447:1097:aa7 dev eth0



It worked, but seems like a major hack. A more appropriate solution turned out to be configuring the neighboring ip settings manually.

code:
1
ip -6 neigh add proxy 2606:2800:220:6d:26bf:1447:1097::aa7 dev eth0



This is more or less a correct way to do it, but still can be fairly tedious to maintain. There is also Neighbor Proxy Daemon IPv6, NPD6. This daemon simply sends out NDP Advertisements for all NDP Neighbor Solicitations for a given prefix.


code:
1
2
3
4
5
6
7
cd /usr/src
apt-get install subversion gcc
svn checkout http://npd6.googlecode.com/svn/trunk/ npd6-read-only
cd npd6-read-only/
apt-get install gcc
make
make install



Then configure the prefix directive in /etc/npd6.conf:

code:
1
prefix=2606:2800:220:6d:26bf:1447:1097:



And finally restart NPD6:

code:
1
/etc/init.d/npd6 restart



Initially, NPD6 didn't want to start. If that's the case, check for typos in your config. In my case, it turned out that the last colon on the end of the prefix is pretty mandatory.

Once you've covered these steps, you should have your VPN setup up and running. Enjoy your new VPN, and welcome to the IPv6 era!