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:
- Have your DSL router provide addresses via SLAAC to the interface that connects to the Pi.
- 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.
- Have the Pi get a dynamic IPv6 address on the external interface.
- Have the Pi NAT your internal IPv6 addresses to external IPv6 addresses from the same subnet it belongs to.
- Have the Pi respond to ND requests for the NATed addresses.
- 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:
IFEX=eth0 PREFIX=$(ip -6 addr show $IFEX | grep inet6 | grep -v 'inet6 f[de]' | awk '{print $2}' | cut -f 1-4 -d : | tail -1) PREFIX2="fd00::/48" NATFROM="::1:1000" NATTO="::1:2000"
Where PREFIX will hold the /64 prefix of the external (dynamically allocated) network
And then:
from="${PREFIX}${NATFROM}" to="${PREFIX}${NATTO}" 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 https://github.com/DanielAdolfsson/ndppd, 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 { static } } _KOKO
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:
#!/bin/bash PATH=/bin:/usr/bin:/sbin:/usr/sbin IFEX=eth0 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 PREFIX2="fd00::/48" NCFG="$D0/ndppd.conf" NCFGNEW="$D0/ndppd.conf.new" NCFGOLD="$D0/ndppd.conf.old" NDPPD="$D0/ndppd/ndppd" # Path to the ndppd executable NATFROM="::1:1000" NATTO="::1:2000" TBL=ip6nat DEBUG=${DEBUG:-false} CHANGED=${CHANGED:-false} debug() { $DEBUG && echo "$@" } I() { debug ip6tables -t nat "$@" ip6tables -t nat "$@" } # Test and set ITS() { if ! I -C "$@" 2> /dev/null ; then I -A "$@" fi } # Setup NAT do_nat_start() { local from to if $CHANGED ; then echo "Reseting NAT rules" do_nat_stop 2> /dev/null fi from="${PREFIX}${NATFROM}" to="${PREFIX}${NATTO}" I -N $TBL 2> /dev/null ITS $TBL -s $PREFIX2 -o $IFEX -j SNAT --to-source ${from}-${to} --persistent ITS POSTROUTING -j $TBL } do_nat_stop() { while I -D POSTROUTING -j $TBL > /dev/null ; do : ; done I -F $TBL I -X $TBL } # Do basic configuration do_cfg() { echo 1 > /proc/sys/net/ipv6/conf/$IFEX/proxy_ndp cat << _KOKO > $NCFGNEW proxy $IFEX { rule ${PREFIX}::/64 { static } } _KOKO if ! test -e $NCFG || ! diff -q $NCFG $NCFGNEW > /dev/null ; then debug "Things changed" CHANGED=true test -e "$NCFG" && mv -f $NCFG $NCFGOLD mv -f $NCFGNEW $NCFG else debug "Nothing changed" fi } # Start ndppd do_ndppd() { if $CHANGED ; then echo "New config. Reloading ndppd. Prefix: $PREFIX" killall ndppd sleep 1 fi if ! pgrep ndppd > /dev/null ; then if $DEBUG ; then $NDPPD -vvv -c $NCFG else $NDPPD -d -v -c $NCFG fi fi } doit() { if test -z "$PREFIX" ; then echo "No prefix" exit 1 else debug "Prefix: $PREFIX" fi do_cfg do_nat_start do_ndppd } if $DEBUG ; then doit else doit | logger -t 6nat fi
Have fun!
cheers, think this is gonna be the soluton to my use case at, due to wishing to keep ipv4 NAT on the infrastructure. Thanks 🙂
LikeLike