IPv4-less Internet client setup

All of the pieces are now in place for the first time to allow setting up a network without IPv4. Since the Internet still exists as an IPv4 entity rather than IPv6, setting up machines to use only IPv6 clearly involves some sort of proxy agent translating the sessions at the border. Anyone used to using NAT under IPv4 won't find the concept to be any different than that. The IPv6 clients open TCPv6 sockets to special addresses that encapsulate the IPv4 address. This special prefix is fed through the translation layer and as a result a TCPv4 connection is opened to the other end.

The primary benefit of this scheme over NAT is that your client machines can interact with other IPv6 sites on the Internet in a completely native manner. Presuming you use 6to4 or have a connection to the 6bone or some other provider of real IPv6 connectivity, your experience with IPv6 sites will be native and pure. Your interaction with IPv4 sites will be simulated, but that's no different than having to hide behind NAT.

There are two pieces to the puzzle that you will need to set up. The first is the translation layer. FreeBSD (and probably other KAME derived IPv6 stacks) provides the faith/faithd system to translate IPv6 streams into IPv4 streams. But faith/faithd operate on one destination port at a time, sort of like a proxy. A much better solution is NATPT. NATPT is currently available for FreeBSD only in KAME snapshots, but it is pretty easy to extract the functionality from a snapshot and drop it into FreeBSD -stable or -current. here's how:

  1. Get the latest KAME snapshot.
  2. Look in kame/freebsd4/sys/netinet/ip_input.c. Copy all of the #ifdef NATPT changes into your own kernel source. Do the same for kame/freebsd4/sys/netinet/in_proto.c and kame/kame/sys/netinet6/ip6_input.c. Make sure that all 3 of these files include opt_natpt.h.
  3. If you're using a recent KAME snapshot, you'll notice some extra code in ip_input.c that calls ip6_setpktaddrs. You can put the declaration and set-up of sa6_src and sa6_dst and the call to ip6_setpktaddrs inside of #ifdef HAVE_IP6_SETPKTADDRS blocks. This call is not present in stock FreeBSD.
  4. Copy kame/kame/sys/netient6/natpt* into the netinet6 directory in your kernel source.
  5. Edit conf/options in your kernel source. Add two lines that say NATPT opt_natpt.h and NATPT_NAT opt_natpt.h.
  6. Edit conf/files and add an entry for each file that matches sys/netinet6/natpt*.c with the right side saying optional ipv6 natpt.
  7. Add option NATPT to your kernel configuration file.
  8. Rebuild, reinstall and boot your new kernel. You should note that it says NATPT initialized when it boots.
  9. Build and install natptconfig from the kame snapshot.

In order for the clients to use these mechanism, you need to have DNS lookups for Internet sites return AAAA records instead of A records, and the IPv4 address has to be encapsulated in a prefix that will send it through your NATPT prefix. The piece that makes this possible is the Trick-Or-Treat Daemon (totd). There is a FreeBSD port for it here.

So with NATPT set up on your border machine and a totd set up as the DNS server for all your client machines, it becomes possible to do completely without IPv4, presuming your applications will run correctly that way.

That begs the question, however. How do you migrate all of your applications?

The answer is a technique called "Bump in the API". It is described here. A Windows implementation is here (note that Mozilla does not properly render the page, which means the download link doesn't show up. Either view the source or use IE).

Here's how it works: IPv4 applications typically use the sockets API. You call gethostbyname() (or any of a few other mechanisms) to get the IP address of the host to which you are connecting, then call socket(), specifying the address family you want to use, then you call connect() with the sockaddr of the thing to which you want to connect.

You can modify the behavior of all of these routines using LD_PRELOAD to load a library that replaces these calls in libc with routines that subtly change the behavior.

We first rewrite gethostbyname() so that it instead calls getaddrinfo(), which is the new-and-improved address-family-agnostic API for host name translation. The problem is that the data structures returned by getaddrinfo() have way too much data in them (IPv6 addresses are 4 times larger than IPv4 addresses, among other things) to pass them back to callers of gethostbyname(). So we will instead cache the results in a list and return a phony IPv4 address to the caller.

We then intercept connect(). connect() will (hopefully) be presenting us with the dummy IP addresses we generated in our phonied-up gethostbyname() call. We look up the previously cached results, create our own *replacement* socket using the cached data, connect with it, then use dup2 to replace the user's socket with the newly created one.

Having done this, we should be able to transparently deal with IPv4 or IPv6 remote hosts from IPv4-only applications. Because of LD_PRELOAD, we don't even require the source code to the applications we're "repairing".

This technique breaks if the program uses the IPv4 address other than simply as an end-point in connect(). For example, ftp clients in some circumstances send IP addresses through their command streams to do various things. IM clients that attempt to do peer-to-peer communication fail in similar ways.

But for the general client-server case, the technique has been shown to work reasonably well.

Check out this bit of source

If you have a slightly more modest goal in mind, there are some shortcuts you can take. If your goal is to allow IPv4 programs to communicate with IPv4 sites, but without having to configure IPv4 on your network, then you could simply change all PF_INET sockets into PF_INET6 sockets and change all sockaddr_in addresses into sockaddr6_in addresses with your NATPT prefix tacked on the front.

Your IPv4 applications will not be able to talk to IPv6 sites this way, but it does preserve their legacy functionality while still pushing IPv4 out to your enterprise's border router.

Here's an implementation of the shortcut library. You must set an environment variable NATPT_PREFIX to be the "host 0" address of your NATPT region (that is, the NATPT address for 0.0.0.0). For example, 3ffe:1200:301b:4444::.

P.s. Here's a trick for you... Add fec0:0:0:ffff::1 as an alias on the machine that is your DNS server and use fec0:0:0:ffff::1 in the nameserver line on all of your clients. fec0:: is the 'site local' prefix and so long as your routers all know where fec0:0:0:ffff::1 is (you probably want to run route6d -l on that machine), that will always work, even if you change your prefix. You can even have multiple machines announce fec0:0:0:ffff::1 and RIP will make sure that the requests always go to the closest one. Just make sure you do NOT put link- or site-local addresses in your DNS zones - they won't work outside of your organization (by definition). Read more about this.