MAP66 (NAT from IPv6 to IPv6, NAT66) for Linux

Sven-Ola Tuecke

Freifunk

05-MAY-2013


Table of Contents

Installation
DKMS Integration
Configuration
Brief Version
Detailed Version
Mapping Single Address
Swapping Prefix with Hostbits
IPv6/IPv4 Precedence
Change gai.conf
Use Changed Internal Address
Motivation

These files implement a Linux netfilter target that changes the IPv6 address of packets. The address change is done checksum neutral, thus no checksum re-calculation for the packet is necessary. You can change the IPv6 source address of outgoing packets as well as the IPv6 destination address of incoming packets. This allows you to map an internal IPv6 address range to a second, externally used IPv6 address range. IPv6 address mapping is not very similar to IPv4 network address translation, but one can describe it as some sort of stateless NAT. The implementation is based on the expired IETF discussion paper published here:

http://tools.ietf.org/html/draft-mrw-behave-nat66-02

Warning

Using MAP66 rules together with connection tracking rules such as --ctstate is currently untested and may not work or may cause dysfunctions.

Installation

MAP66 implements two pieces of software: a shared library that extends the ip6tables command and a Linux kernel module. The shared library file adds the '-j MAP66' target to the ip6tables command. To build and install, you need ip6tables installed as well as the necessary headers. The Linux kernel module requires the Linux source file tree and kernel configuration files to compile. On a Debian / Ubuntu, the following command prepares the build environment:

sudo apt-get install build-essential linux-headers iptables-dev

Unpack the source tgz archive below /usr/src, change to the new sub-directory and issue "make" to build. If this compiles without errors, install the ip6tables extension with the following command:

sudo make install

Note

The kernel module (ip6t_MAP66.ko for Linux-2.6 or ip6t_MAP66.o for Linux-2.4) is not automatically installed nor loaded into the kernel. You can copy the kernel module file manually, e.g. with sudo cp ip6t_MAP66.ko /lib/modules/$(uname -r)/.

DKMS Integration

If the next system update needs to install a new kernel version, you also need to re-compile/re-install the MAP66 kernel module. With Debian / Ubuntu, this can be automated with the Dynamic Kernel Module Support Framework (DKMS). For this, the dkms.conf file is included with the MAP66 source file package. Install DKMS with the following command:

sudo apt-get install dkms

If not already in place, move/unpack the MAP66 source file archive below /usr/src/. To register the MAP66 source to DKMS and compile/install, issue these commands:

sudo dkms add -m ip6t_MAP66 -v 0.7
sudo dkms build -m ip6t_MAP66 -v 0.7
sudo dkms install -m ip6t_MAP66 -v 0.7

Tip

Read DKMS details here: https://wiki.kubuntu.org/Kernel/Dev/DKMSPackaging. Also, there's a pre-packed binary with DKMS for Debian / Ubuntu hosted on my packet repository: http://sven-ola.dyndns.org/repo/.

Configuration

Brief Version

You always need to add two ip6tables-rules to your netfilter configuration. One rule matches outgoing packets and changes their IPv6 source address. The second rule matches incoming packets and reverts the address change by altering their IPv6 destination address. To following commands correspond to the Address Mapping Example given in the IETF discussion paper:

ip6tables -t mangle -I POSTROUTING -o eth0 -s FD01:0203:0405::/48 -j MAP66 --src-to 2001:0DB8:0001::/48
ip6tables -t mangle -I PREROUTING  -i eth0 -d 2001:0DB8:0001::/48 -j MAP66 --dst-to FD01:0203:0405::/48

This example is also printed to the screen if you issue ip6tables -j MAP66 --help. By design, you cannot use an arbitrary prefix length. Only /112, /96 .. /16 are supported, because the MAP66 kernel module checksum re-calculation is made with 16-bit integers.

For each packet, the MAP66 kernel module also compares the packet's source address to all IPv6 addresses assigned to the outgoing interface. If a match is found, the packet's source address is not mapped. The same comparison happens on the incoming packet's destination address. The comparison requires some CPU resources, especially if the interface has a large number of assigned IPv6 addresses. If you are sure that the mapping cannot match the IPv6 address of the interface (e.g. the mapping rule defines a mapping prefix that cannot result in the interface address) you can switch off the comparison. Add the --nocheck parameter to the ip6tables command for this.

Detailed Version

The following explanation details a living example from the wireless mesh network that is mentioned under Motivation (see below). Throughout the mesh network, a private IP address range is used. The ULA prefix is fdca:ffee:babe::/64. All mesh nodes derive their IPv6 interface addresses by correlating the ULA prefix with the EUI48 (MAC address) of the respective network adapter.

There is a Debian based virtual machine that should act as one IPv6 Internet gateway for the mesh. You can reach the virtual machine's web service via IPv4 under http://bbb-vpn.freifunk.net. To experiment with IPv6, a SIXXS static tunnel setup has been added. The following /etc/network/interfaces file provides the configuration for IPv6:

auto sixxs
iface sixxs inet6 v4tunnel
    address  2001:4dd0:ff00:2ee::2
    netmask 64
    local 77.87.48.7
    endpoint 78.35.24.124
    ttl 64
    up ip link set mtu 1280 dev $IFACE
    up ip route add default via 2001:4dd0:ff00:2ee::1 dev $IFACE
    up ip addr add 2001:4dd0:fe77:ffff::1/48 dev $IFACE

As you can see, the virtual machine has an IPv6 prefix of 2001:4dd0:fe77::/48. The /48 prefix length includes 65536 /64 subnets where this setup occupies the ffff subnet. The machine is therefore reachable via http://[2001:4dd0:fe77:ffff:1]/. The netfilter setup of this machine is initialized with some basic precautions. The -j ACCEPT rules prevents any further matching on the POSTROUTING chain for local network packets. Most notably, we also block packets that leave the machine on the receiving interface so we cannot participate as a reflector in DDOS with the --dst-swap option used below.

ip6tables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
ip6tables -t mangle -A POSTROUTING -o sixxs -s 2001:4dd0:fe77::/48 -j ACCEPT
ip6tables -t mangle -A POSTROUTING -o sixxs -s 2001:4dd0:ff00:2ee::/64 -j ACCEPT
ip6tables -t filter -A FORWARD -i sixxs -o sixxs -j DROP

Tip

You may also stumble over the MSS-clamping rule. While IPv6 defines, that path MTU detection via ICMPv6 must be supported by any host, sometimes path MTU detection does not work. The SIXXS tunnel uses an MTU of 1280 byte. To get the following command working on my PC, I needed to add the above MSS-clamping rule on the gateway: wget --prefer-family=IPv6 -O - http://6to4.nro.net/.

The netfilter setup then includes the following command sequence to realize mapping from the private fdca:ffee:babe::/64 prefix to the globally valid IPv6 addresses and vice versa.

ip6tables -t mangle -A POSTROUTING -o sixxs -s fdca:ffee:babe::/64 \
    -j MAP66 --src-to 2001:4dd0:fe77:100::/64 --nocheck

ip6tables -t mangle -A PREROUTING  -i sixxs -d 2001:4dd0:fe77:100::/64 \
    -j MAP66 --dst-to fdca:ffee:babe::/64 --nocheck

Because for these IPv6 networks the external prefix length (/64) is smaller than the internal prefix length (/48), we are sure that mapped addresses cannot match the interface address. For example: 2001:4dd0:fe77:100::/64 cannot be mapped / converted to 2001:4dd0:fe77:ffff::1 in this context. For this reason, we can use the --nocheck speedup here.

Mapping Single Address

As an extension, the --csum option updates the packet's checksum instead of updating some of the IP address bits. This only works for some of the transported protocols (TCP, UDP, ICMP6, DCCP) because each protocol uses another offset for storing the checksum.

This option can be used to map exactly one IPv6 address to another, hence using --src-to or --dst-to with /128.

Swapping Prefix with Hostbits

In our mesh network, we have routers with old and new firmwares. While the old firmware uses a single /64 ULA prefix for all nodes and interfaces, the new firmware uses an arbitrary /64 prefix derived from the default route address. The new firmware always use ::1 for the host bits, e.g. 2002:abcd:efgh:xxxx::1/64 if the neighbour's default route is via 6to4, or fdca:ffee:babe:xxxx::1/64 if the neighbour's default route is via ULA. Anyhow, those routers waste 64 bits of address space because of the fixed host bits.

To map those arbitrary prefixes to the prefix used on the Internet gateway, we therefore can swap net bits with host bits and map the result to the gateway's prefix. Example: if a mesh node uses 2002:1234:5678:9abc::1/64 we can map this to 2001:4dd0:fe77:1:2002:1234:5678:9abc/64 and send to / receive from the Internet. For this, MAP66 support --src-swap and --dst-swap options. Here's the working example:

ip6tables -t mangle -A POSTROUTING -o sixxs \
    -m u32 --u32 "0x08 & 0xe0000000 = 0x20000000 && 0x10 = 0 && 0x14 & 0xffffff00 = 0" \
    -j MAP66 --nocheck --src-swap --src-to 2001:4dd0:fe77::/48

ip6tables -t mangle -A POSTROUTING -o sixxs \
    -m u32 --u32 "0x08 & 0xfe000000 = 0xfc000000 && 0x10 = 0 && 0x14 & 0xffffff00 = 0" \
    -j MAP66 --nocheck --src-swap --src-to 2001:4dd0:fe77::/48

ip6tables -t mangle -A PREROUTING -i sixxs \
    -m u32 --u32 "0x18 = 0x20014dd0 && 0x1c & 0xffffff00 = 0xfe770000 && 0x20 & 0xe0000000 = 0x20000000" \
    -j MAP66 --dst-swap --dst-to 0::/48 --nocheck

ip6tables -t mangle -A PREROUTING -i sixxs \
    -m u32 --u32 "0x18 = 0x20014dd0 && 0x1c & 0xffffff00 = 0xfe770000 && 0x20 & 0xfe000000 = 0xfc000000" \
    -j MAP66 --dst-swap --dst-to 0::/48 --nocheck

The above example uses the xtables u32 match to filter out packets with IPv6 addresses that uses host bits containing ULA (fc00::/7) or IANA Internet (2000::/3).

Note

Swapping / rotating 16 bit integers within IP packet headers is checksum neutral by design. Also, swapping / rotating 8 bit integers can be compensated with the following formula: addr[w] = add16(addr[w], add16(oldpartialcsum, ~byteswap(oldpartialcsum))).

IPv6/IPv4 Precedence

With Ubuntu and eventually with RedHat, you will notice that your browser does not show the IPv6 version of a web site that is multi-homed when using ULA addresses for your IPv6 Internet connection. The reason for this is an add on to the RFC 3484 rules that is compiled into the Ubuntu libc. The pre-installed /etc/gai.conf file will give you a hint on this.

In short: the getaddrinfo() library function rates a private IPv4 address higher than the ULA IPv6 address when choosing the transport protocol for a new Internet connection if this add on to the RFC 3484 rules is compiled in. For this reason, you may want to change the precedence rules within /etc/gai.conf (see Change gai.conf) or use another prefix (see Use Changed Internal Address).

Change gai.conf

The getaddrinfo() library function manages lists of label, precedence, and scope4 type entries. If the /etc/gai.conf file does not provide a single entry for a particular type, the compiled-in list is used. For this reason, you cannot uncomment a single entry to overwrite the default. You need to uncomment all entries of a particular type for this. The label lines compare source addresses, the precedence lines compare destination addresses.

Procedure 1. Change IPv6 Precedence

  1. Open the /etc/gai.conf file as root user, e.g. by executing sudo nano /etc/gai.conf.

  2. Remove the leading hash character from the 8 lines starting with #label.

  3. Re-add the hash character to the line stating #label fc00::/7 6.

  4. Save the file.

  5. Restart your browser and re-try to browse to a multi-homed web site.

The above procedure removes the difference between standard IPv6 source addresses and ULA type private IPv6 source addresses. Anything else is unchanged.

Use Changed Internal Address

As an alternative solution, you may use an arbitrary address prefix in your LAN that is not mentioned in the gai.conf file nor compiled in. This will work but introduces a double mapping: one map (Inet-ULA) on the Internet gateway router and a second map (ULA-Intern) on the internal router.

Note

While the well known IPv4 addresses 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 still exist, it is unlikely that their 6to4 counterparts 2002:0a00::/24, 2002:ac10::/28, and 2002:c0a8::/32 will be routed on the Internet. Sadly, the Ubuntu defaults penalize 6to4 addresses also.

If you already deployed ULA addresses in your network, you may be interested in a solution that runs on my Freifunk router. The router uses the IPv6 prefix that is reserved for documentation purposes on it's LAN interface. Within the OLSR-based mesh network, any interface uses an fdca:ffee:babe::/64 prefix. The following internal mapping is configured for this:

ip6tables -t mangle -I PREROUTING -i br0 -s 2001:0DB8::/64 -j MAP66 --src-to fdca:ffee:babe::/64 --csum
ip6tables -t mangle -I POSTROUTING -o br0 -d fdca:ffee:babe::/64 -j MAP66 --dst-to 2001:0DB8::/64 --csum

To prevent the mapped packets to vanish via the default route and to overcome mac address lookups during the routing process, I also added these prefixes to the router's /etc/radvd.conf as well as (host) routes pointing to the br0 interface for both prefixes.

Motivation

My Internet access at home is realized by a wireless community mesh network not owned by me. The mesh is operated with small embedded devices (nodes aka. WLAN routers) that are interconnected via radio links (WLAN IBSS / AdHoc). Routing is done with a specialized protocol such as Batman or OLSR. The routing protocol selects the nearest out of a dozen Internet gateways and configures a default route or an IPIP tunnel accordingly. Each Internet gateway is connected to a different ISP and provides the service with the help of IPv4 network address translation (NAT). Using NAT has the following effects:

  • Address amplification - something not necessary with IPv6 any more

  • Anonymization - nice to have as an option but not mission critical

  • ISP independence - no reverse routing, no "buy-a-number-range"

The last point is mission critical. One can obtain a provider independent IPv6 address range, but you need the cooperation of an ISP to use that address range for Internet connectivity. If you e.g. move to another ISP you need your address range to be re-routed to your new location.

ISP independence is also possible with some tunneling technique, such as VPN or mobile IP. Tunneling can be implemented on client PCs and Internet gateways/servers one day. But there is no need to implement the same tunneling technique on every mesh node. Why? Because the mesh nodes can use private IP addresses (or "ULA") to transport the tunnel data between the client PC and the gateway/server. Each tunneling technique typically needs a single instance (the "server") which forms a single point of failure. Rule-of-thumb1: avoid a SPOF for the infrastructure. Rule-of-thumb2: KISS (keep it simple stupid).

Using private IP addresses on the mesh nodes has a drawback: mesh node software updates e.g. a download via HTTP from an Internet server is not possible. This is where I start to think: hey, some kind of address mapping may be nice to have. While opening Pandora's NAT66 box, I discovered that IPv6 nerds do not like the acronym. It is always a good tactic in info wars to rename, hence the name "MAP66".

// Sven-Ola