Static IPv6 subnetting at home with dynamic prefix delegation

The problem

How to have IPv6 subnetting on a non-flat network at home when you are receiving a dynamic IPv6 prefix via DHCP6-PD (prefix delegation) via an ISP.

This is about a setup where you have (e.g.) a DSL line and a router that receives a prefix (e.g. a /56 prefix) via DHCP6-PD. Since the prefix is dynamic you cannot assign static IPv6 addresses, which means that you cannot possibly subnet this, which in turn means that you cannot have any kind of non-flat home network.

With IPv4 the problem doesn’t exist as you can have a non-flat home network with some routing involved and rely on NAT/Masquerading. But Masquerading doesn’t exists for IPv6 and most probably your home IPv6 router doesn’t do IPv6 NAT

The initial setup

Suppose you have something like this:

Internet <–> DSL Router (R1) <–> Another router (R2) <–> Some IPv6 subnets

The new setup

I solved this with a raspberry pi. You can use whatever you like as long as it has two Ethernet interfaces.

I used a Raspberry Pi and a USB ethernet adapter plugged to it. Then changed the home setup to something like this:

Internet <—>  DLS Router (R1) <—> Raspberry Pi (Pi) <—> Another Router (R2) <–> Home IPv6 Subnets

For simplicity I’ll use the above setup, even though my actual setup is a triangle between R1, Pi and R2 so that IPv4 traffic goes directly from R1 to R2 while IPv6 traffic goes through the Pi.

What needs to be done

Your whole home network will be exiting with a single /64 IPv6 network with random address mappings via NAT.

In order to achieve this you need to do the following:

  1. Have your DSL router provide addresses via SLAAC to the interface that connects to the Pi.
  2. Setup your IPv6 home network behind the Pi with static IPs. Use a static prefix that is allocated to you somehow (e.g. from sixxs) or ULA or something sane. This is not the dynamic prefix you get from your ISP.
  3. Have the Pi get a dynamic IPv6 address on the external interface.
  4. Have the Pi NAT your internal IPv6 addresses to external IPv6 addresses from the same subnet it belongs to.
  5. Have the Pi respond to ND requests for the NATed addresses.
  6. Have a script that reconfigured the NAT if it changed to accomodate for the dynamic IPv6 prefix you are assigned.

How to do it

First setup your DLS router to do SLAAC (stateless) IPv6 address assignments. I.e. not to assign addresses via DHCP6.

Then setup your internal network with static IPv6 addresses/subnets. Assuming you chose to use ULA (fd00::/8) for your static home network:

  • you use fd00:1::/64 for the connection Pi<–>R2
  • Pi will have fd00:1::1/64
  • R2 has the fd00:1::2/64 and a default IPv6 route via fd00:1::1
  • Your internal network behind R2 uses fd00::/48

It’s assumed below that eth0 is the external interface and eth1 is the internal interface of the Pi

Have the Pi get a dynamic IPv6 address

Assuming you’re using Debian:

Set the defaults for forwarding and autoconf by adding these to a file under /etc/sysctl.d:

net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1

net.ipv6.conf.all.autoconf = 0
net.ipv6.conf.default.autoconf = 0
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0

(make sure you reload these with /etc/init.d/procps restart)

Add this to the external interface’s iface config:

iface eth0 inet static
  ... ipv4 setup ...
  up echo 1 > /proc/sys/net/ipv6/conf/$IFACE/autoconf || true
  up echo 2 > /proc/sys/net/ipv6/conf/$IFACE/accept_ra || true
  up echo 0 > /sys/devices/virtual/net/$IFACE/bridge/multicast_snooping || true

You most probably want to also setup IPv4 over there. Note that these were set on “inet” and not on “inet6” as inet6 will be auto-configured. Feel free to adapt it.

You also need to setup static routes for the internal network on the pi:

iface eth1 inet static
  ... ipv4 setup ...
  echo 0 > /sys/devices/virtual/net/$IFACE/bridge/multicast_snooping || true

iface eth1 inet6 static
  address fd00:1::1
  netmask 64
  up ip -6 route add fd00::/48 via fd00:1::2 || true
  down ip -6 route del fd00::/48 via fd00:1::2 || true

Have the Pi do SNAT

Since the Pi receives an external address from an /64 IPv6 network and it’s the sole user, it’s ok to assume that you can use some more IPv6 addresses from that subnet for NAT 🙂

You can then do this:

PREFIX=$(ip -6 addr show $IFEX | grep inet6 | grep -v 'inet6 f[de]' | awk '{print $2}' | cut -f 1-4 -d : | tail -1)

Where PREFIX will hold the /64 prefix of the external (dynamically allocated) network

And then:

ip6tables -t nat -A POSTROUTING -o $IFEX -j SNAT --to-source ${from}-${to} --persistent

This will map your fd00::/48 addresses to 4096 IPv6 addresses from the dynamic prefix. You can obviously extend the range considerably, but don’t go nuts or you may end up with too many NAT and ND entries.

Have the Pi respond to ND requests

So far the Pi will happily do the NAT and will send the packets to your DSL router, but the DSL router won’t be able to send anything back as noone will be responding to ND requests for the NAT range.

To solve the problem we need to use ndppd and a dynamic configuration file.

Grab ndppd from, compile it and place the binary somewhere.

First ensure that proxy_ndp is enabled:

echo 1 > /proc/sys/net/ipv6/conf/$IFEX/proxy_ndp

Then create the appropriate ndppd.conf file:

cat << _KOKO > ndppd.conf
proxy $IFEX {
  rule ${PREFIX}::/64 {

Then fire up ndppd:

ndppd -d -v -c ndppd.conf

Test it

That’s it. If I didn’t forget anything then your home network should have IPv6 access to the rest of the world using the fd00::/48 prefix.

Script it

The final step is to script all of this and have it run via cron so that it adapts to IPv6 prefix changes. This is a slimmed down version of what I’m using, adjusted to the fd00::/48 prefix:




PREFIX=$(ip -6 addr show $IFEX | grep inet6 | grep -v 'inet6 f[de]' | awk '{print $2}' | cut -f 1-4 -d : | tail -1)
D0="/srv/ipv6" # A directory to work under


NDPPD="$D0/ndppd/ndppd" # Path to the ndppd executable



  $DEBUG && echo "$@"

  debug ip6tables -t nat "$@"
  ip6tables -t nat "$@"

# Test and set
  if ! I -C "$@" 2&gt; /dev/null ; then
    I -A "$@"

# Setup NAT
  local from to

  if $CHANGED ; then
    echo "Reseting NAT rules"
    do_nat_stop 2&gt; /dev/null


  I -N $TBL 2&gt; /dev/null
  ITS $TBL -s $PREFIX2 -o $IFEX -j SNAT --to-source ${from}-${to} --persistent

  while I -D POSTROUTING -j $TBL &gt; /dev/null ; do : ; done
  I -F $TBL
  I -X $TBL

# Do basic configuration
  echo 1 &gt; /proc/sys/net/ipv6/conf/$IFEX/proxy_ndp

  cat &lt;&lt; _KOKO &gt; $NCFGNEW
proxy $IFEX {
  rule ${PREFIX}::/64 {

  if ! test -e $NCFG || ! diff -q $NCFG $NCFGNEW &gt; /dev/null ; then
    debug "Things changed"
    test -e "$NCFG" && mv -f $NCFG $NCFGOLD
    mv -f $NCFGNEW $NCFG
    debug "Nothing changed"

# Start ndppd
  if $CHANGED ; then
   echo "New config. Reloading ndppd. Prefix: $PREFIX"
   killall ndppd
   sleep 1

  if ! pgrep ndppd &gt; /dev/null ; then
    if $DEBUG ; then
      $NDPPD -vvv -c $NCFG
      $NDPPD -d -v -c $NCFG

  if test -z "$PREFIX" ; then
    echo "No prefix"
    exit 1
    debug "Prefix: $PREFIX"


if $DEBUG ; then
  doit | logger -t 6nat

Have fun!


One comment

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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