If you tried setting up an IPv6-capable VPN on a VPS provider that gave you an
IP range to play with, perhaps a /64 or larger, you would want to assign some
of the IPv6 addresses you have to your clients. In this post, we suppose that
you have the range
This should be a simple process: enable the
1 (or whatever the equivalent is on your
system), use DHCPv6 or SLAAC to assign the addresses to the clients, and
then your client should have working IPv6.
Unfortunately, this is not so simple. Most VPS providers are not actually
routing the entire subnet
2001:db8::/64 to you. Rather, they just connect
a number of VPSes onto the same virtual Ethernet network and rely on the
Neighbour Discovery Protocol (NDP) to find the router.
So for example, your VPS has a public interface
eth0 with public IPv6
2001:db8::1, and a private VPN interface
tap_vpn. Your VPN
client is assigned the IPv6 address
2001:db8::2. When the VPS provider
receives a packet for
2001:db8::2, their router sends out a neighbour
solicitation request over NDP, basically asking “who has IP
Your server receives the packet on
eth0 does not have the
2001:db8::2, it does not answer the solicitation request. No
other VPS on the network has it either, so no one answers it, and the
router sends back “host not found”.
The problem is that your VPS is the router for
2001:db8::2, and so, it
should answer the neighbour solicitation request in order to get the IP
Linux comes with a feature called NDP proxying. You can declare a list of IP addresses to answer neighbour solicitation requests for, and the system will answer them for you, allowing you to receive packets for them.
First, you have to enable this feature by setting the
1. You can do this by adding the following
/etc/sysctl.conf (or whatever the equivalent on your system is):
net.ipv6.conf.all.proxy_ndp = 1
Once you do this, run
sysctl -p as root to activate it immediately.
Then, for every IP address you wish the VPS to route, you have to run:
ip -6 neigh add proxy <ip> dev <interface>
For example, if you want to answer for
ip -6 neigh add proxy 2001:db8::2 dev eth0
Afterwards, your server will tell your external router, connected to
that it should receive packets for
2001:db8::2. Your server now routes
the traffic properly!
ip -6 neigh add proxy only allows you to add one IPv6
address at a time. There is no way to add an entire range of IP addresses.
You can pre-add a bunch of IP addresses and use DHCPv6 to only assign those to clients, but that’s rather ugly, and depending on how many addresses you added, may cause significant slowdowns.
The common wisdom is to run ndppd, a program that answers neighbour solicitation requests. It can be thought of as a replacement for the kernel’s NDP proxying feature. However, it has been relatively unmaintained, and multiple users reported that it does not work anymore. It did not work for me either.
Since I am using
dnsmasq already to announce the SLAAC prefix as well as
providing a DNS server for the VPN, it seems natural to check to see if
there are any additional features of
dnsmasq that I could use.
Turns out, it supports hook scripts that run when a new client connects or disconnects, and these hooks receive the IP(s) assigned to clients via DHCP.
So naturally, the solution is to switch
dnsmasq to assign IPs via DHCPv6
and use a hook script to automatically add and delete NDP proxy rules.
The following hook script does the job well:
#!/bin/bash action="$1" ip="$3" case "$ip" in 2001:db8::*) ;; *) exit ;; esac case "$action" in add|old) ip -6 neigh add proxy "$ip" dev eth0 ;; del) ip -6 neigh del proxy "$ip" dev eth0 ;; esac
You should replace the IP prefix and
eth0 with your own values.
To use this hook, add the following line to your
dnsmasq.conf is on your setup):
dhcp-script=<path to the shell script above>
Unfortunately, there is a downside to this. Since your IPv6 address is now assigned by DHCPv6, the privacy extensions no longer work. Your system cannot generate new IP addresses to use periodically.
On the other hand, since most services now identify you by your /64 prefix, knowing that the remaining bits change with privacy extensions, this does not make too much of a difference.
ndppd works for this use case, so you can give it a try.
You might have better luck than I did with it.
In any case, I hope you found this post useful. Have a nice day.