---

nat46.ko: 13 years later

Inexplicably, a sizable portion of my professional life is intertwined with address translators of various kinds: first, as a TAC engineer in 2000s, helping customers make them work on PIX firewalls, then more actively exploring translation between IPv4 and IPv6 as a means of helping IPv6 transition, and even making some cartoons about them in the process !

This article will be revisiting an 13-years old fun project of mine, a kernel module which performs IPv4 to IPv6 (and back!) static translation. Partially as a review, and partially as a context for a write-up about a different topic, which will involve this module as part of the main cast.

When NATs are truly a force of good

First - where is it all coming from ? Back in the days of me spending most of my time on IPv6 transition, namely on a MAP-T and MAP-E protocols, I spent some bit of time playing with various implementations for interop testing.

For those that are not familiar with these protocols, the abbreviation stands for "Mapping of Addresses and Ports", and is a family of protocols based on the idea colloquially known as "A+P" (RFC6346 - https://www.rfc-editor.org/rfc/rfc6346.html), in order to scalably share scarce IPv4 addresses for the consumers, over an IPv6-only carrier network.

Here's a picture that I borrowed from one of the RFCs to illustrate:


       User N
       Private IPv4
      |  Network
      |
   O--+---------------O
   |  |  MAP CE       |
   | +-----+--------+ |
   | NAPT44|  MAP   | |
   | +-----+        | |\     ,-------.                      .------.
   |       +--------+ | \ ,-'         `-.                 ,-'       `-.
   O------------------O  /              \   O---------O  /   Public   \
                        /    IPv6-only  \  |  MAP    | /     IPv4      \
                       (    Network      --+  Border +-     Network    )
                        \  (MAP Domain) /  |  Relay  | \               /
   O------------------O  \              /   O---------O  \            /
   |    MAP   CE      |  /".         ,-'                 `-.       ,-'
   | +-----+--------+ | /   `----+--'                       ------'
   | NAPT44|  MAP   | |/
   | +-----+        | |
   |   |   +--------+ |
   O---+--------------O
       |
        User M
      Private IPv4
        Network

The goal of the whole game is to get rid of the IPv4 in the carrier network, get rid of the big bad stateful CGNs (Carrier Grade NATs), and preferrably improve sharing of the IPv4 addresses - such that one address could be used by multiple subscribers.

The gist of the approach is that from a big centralized NAT it moves back to the situation of each user having their own small NAT on their router, however, with this NAT using only portion of the port space. There is some bit math in how this port space is divided (because, dividing the ports in the sequential fashion would not work for many reasons), so this CPE NAT ends up using multiple (maybe many) "slices" of port space, and then at the same time the IPv4 packets are transformed into a format suitable for transmission over IPv6-only carrier network, using one of the two mechanisms:

What is great about this is that all the state can be pushed where it always was - on the customer CPEs, and the service provider side the devices can be entirely stateless, thus making the scaling and operations of the protocol much much simpler.

Your best code is the one you do not have to write

While testing, it struck me that a couple of the available CPE implementations at a time focused a lot on the protocol as a whole - both the "partial NAT" and "encapsulation" were done in one place. Having dealt by then for almost a decade of supporting the NAT implementations in PIX firewalls, I did not fancy the idea of reinventing the wheel, and having a lot of new code in the process. There already seemingly was NAT support on linux, can not we use that ? The question - could it do "partial NAT" ? It turns out - with a bit of iptables it was very possible, and thus I set out to experiment with my own approach - where the NAT would be entirely done by the Linux kernel, then the case of MAP-E would be entirely supportable by the existing kernel code with zero changes! For MAP-T, however, there was no appropriate tool in the toolbox.

When the no-code approach is not enough, you still have to code

This is how the kernel module was born, I first called it "mapmint" - "MAP minimal translation". For the mental model I have decided to go with a "black box" abstraction: imagine a box connected to your linux system, via a wire that on the linux side is a virtual interface. You send an IPv6 packet into this wire, you get back IPv4 packet after translation (if it is not dropped). If you send IPv4 packet - then if the translation succeeds, you get back an IPv6 packet. This model has its limitations, namely, that you can not separate IPv4 and IPv6 into different namespaces, but since the target was the CPE, where the IPv4 and IPv6 would be in the same namespace, I decided to go with this.

How to represent the rules for the translation ? While all I needed was the particular kind of the translation, the temptation to design something more generic was big. So, I decided to implement a simple rule-based translation engine: an array of rules, each rule telling exactly how and when to do the IPv4-IPv6 translation for both of the addresses in the packet, and the first rule that "knows" how to perform the translation wins. Walk the array for either IPv4 or IPv6 packets. This way the code would be very simple to understand and reason about.

Each of the two addresses within the packet can be translated using different "styles" of rules - from a simple 1:1 address translation, to a fancy bit math used in MAP-T, and with the possibility to easily adding new styles if needed. Somewhere around that time, it occurred to me - this can do more than a half-of-MAP-T - and I renamed the module into nat46.

The first tests indeed showed that this was a workable model, and after some tests, it ended up being used inside the OpenWRT implementation, with Steven Barth building all of the OpenWRT magic required to actually make it usable.

Success stories that I am proud of

It is very rewarding to see when your ideas put to use. What started as a fun "How far can we go with with how little code ?" exercise, turned out into something that is actually used by the others, and it always feels me with joy thinking about this.

In its primary function, the module is serving quite a few real-world users: https://www.ripe.net/media/documents/Richard_Patterson_-_Sky_Italia_and_MAP-T_-_RIPE_Open_House_2021.pdf.

With a slightly tweaked configuration - exactly as I hoped for - it can be also used as part of CLAT (client translator for IPv6-only networks) - https://github.com/toreanderson/clatd/commit/6d2ad96c2f89c86d6d93e1dfc83fd03045754e54 - or, you can also use it as a frontend to expose your IPv6-only servers to the legacy internet.

If you want to tinker with the code - it's here: https://github.com/ayourtch/nat46. A couple of levels deep there is the linux kernel module itself, and it includes the configuration options.

Want to know how to easily experiment with a kernel module, without wrecking havoc on your machine ? Stay tuned, this will be exactly the topic for another article I mentioned in the beginning.

Press any key to continue...

If you came hear via LinkedIn - then you can now let me know what you think.

Alternatively - you can comment on BlueSky.

Files in 2025-10-25-nat46-13-years-later:


../
HEADER.txt                                         25-Oct-2025 17:20                8885

(c) Andrew Yourtchenko