Hacking together a dual-stack lite AFTR

(ab)using connmarks and policy routing

April 17th, 2018

Recently I was bored at 1AM and wanted to try and understand dual-stack lite properly. In general, I've always believed that you need to run a certain technology to understand it, so after a quick read over the RFC I decided to hack together a DS-lite AFTR on a linux box.

Note: Please don't actually deploy this. There appear to be some proper open source implementations of a DS-lite AFTR for linux (eg by the ISC), and various vendors have their own implementations. If you actually want to try this, please note that this doesn't include anything to filter routed traffic, so this should all be done on a firewalled off network.

A quick primer: how DS-lite works

DS-lite is a fairly simple idea - on the B4 router (Basic Bridging BroadBand, typically a consumer router) you encapsulate all IPv4 packets inside IPv6 packets, so they can then be routed over an ISP's IPv6-only network to an AFTR (Address Family Translation Router) which decapsulates the packets and does NAT.

The key thing to note is that the B4 does not do NAT, it simply routes all packets out over the 4in6 tunnel, and the AFTR will typically receive IPv4 packets with source addresses in RFC1918 ranges. For further reading, Juniper have a great explanation, or you can just go and read the RFC.

Doing this on linux

Encapsulating IPv4 in IPv6 on linux is very simple, you can just create a 4in6 tunnel as below:

# ip tunnel add dslite1 mode ip4ip6 local 2001:678:634:103:192::1 remote 2001:678:634:103:192::2
# ip l set dslite1 up
# ip a add 192.0.0.1 dev dslite1

As per RFC6333, the AFTR has the v4 IP 192.0.0.1 on all 4in6 tunnels, and the B4 router has 192.0.0.2.

You can then easily enable forwarding, and set the router to NAT traffic going to the internet:

# sysctl -w net.ipv4.ip_forward=1
# iptables -t nat -A POSTROUTING -o uplink -j MASQUERADE

This assumes that you have a single link to the internet called "uplink". In my case this is another 4in6 tunnel to get back from my v6-only home network where I'm running these lab routers to a router on the edge of my nework with IPv4 connectivity, but this would typically just be your WAN interface for a simple router.

If you do a packet capture at this point, you'll see any traffic from the network behind the B4 router being decapsulated and NATed out the uplink. However, any responses will not be routed properly back to the source, since there's no entry in the routing table for the source private IPv4 addresses (ignoring the default route).

Fun with policy routing

The solution to this is to create a second routing table, which routes all traffic down the tunnel:

# echo "1 dslite1" >> /etc/iproute2/rt_tables
# ip r add default dev dslite1 table dslite1

Connmarks can then be used with iptables to mark all incoming connections from a specific tunnel, and save that mark:

# iptables -t mangle -A PREROUTING -i dslite1 -j MARK --set-mark 1
# iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark

Conntrack tracks connections, so any saved marks can be restored for packets related to an existing connection. If any response packets are identified based on source interface and the original mark, those are routed via the second routing table:

# iptables -t mangle -I PREROUTING -j CONNMARK --restore-mark
# ip rule add fwmark 1 iif uplink table dslite1

The key thing here is that this can be repeated many times, and linux will be able to route traffic back down different tunnels as required.

Additionally, a rule can be added to do the same routing with packets originating from the router itself (eg ICMP ping replies, or TTL expired ICMP messages), and a kernel parameter set to preserve marks for replies:

# sysctl -w net.ipv4.fwmark_reflect=1
# ip rule add fwmark 1 iif lo table dslite1

Actually testing this

Of course you can easily set up another linux machine, add a 6in4 tunnel and set it to route traffic out over that interface. However, that only confirms that this specific method of running an IPv4 network behind an IPv6-only network works, and not that DS-lite has actually been implemented!

My first thought was to try and get hold of a real consumer router with DS-lite support and try it out, but I realised there was a much easier solution - OpenWrt/LEDE has support for DS-lite, and can easily be installed on many existing consumer routers, or in a VM. It even has support for setting it up through the GUI:

At this point, IPv4 hosts behind the LEDE router should be able to communicate through the router, over the tunnel and through NAT on the AFTR to reach the IPv4 internet. Personally I still prefer NAT64 (and 464XLAT where necessary) and will continue to run that on my network, but this was a fun experiment!