IPv6-only Network based on Jool

8 min read Original article ↗

In this post, I will share experiences of my experiment of how to setting up the IPv6-only network on Linux using the Jool1.

Network Topology

Here is the classical topology of home network. All the hosts can access the Internet through one router. The router has dual stack of v4/v6. And all the LAN hosts have one /64 IPv6 prefix.

Internet
    |
+---+----+  10.0.0.1/32
| Router |  2001:db8::/64
+---+----+
    |
    +-------+-------------------+
            |2001:db8:1::/64     | 2001:db8:2::/64
      +-----+-----+       +-----+-----+
      |   Host 1  |       |   Host 2  |
      +-----+-----+       +-----+-----+

There are two problems we need to fix. Firstly, let the LAN hosts access the IPv4 Internet without mutual interference. Secondly, support IPv4-to-IPv6 port mapping so that the IPv4-only host in the Internet can reach the services hosted on the IPv6-only LAN hosts.

Jool vs TAYGA

The two tasks mentioned above can be both achieved through Jool.

Besides the Jool, you may find the TAYGA2. TAYGA is an out-of-kernel stateless NAT64 implementation for Linux that uses the TUN driver to exchange IPv4 and IPv6 packets with the kernel.

TAYGA is not the proper choice because it only support the stateless NAT64, which means you need several public IPv4 addresses to to the IPv4-to-IPv6 mapping. Another drawback of TAYGA is that it uses the TUN devices, which may have performance issue.

Moreover, the latest release of TAYGA is published on 2011, which means there are more than 15 years without new development.

On the contrary, the Jool is another Open Source Ipv4/IPv6 Translator with active development. Jool support both stateless and stateful NAT64.

Install Jool

Jool contains two parts, jool-dkms is for kernel modules, and jool-tools for user space tools.

The Debian/Ubuntu user can install Jool by apt

apt install jool-dkms jool-tools

The OpenWrt user can install the jool-tools-netfilter package. This will install kmod-jool-netfilter and other dependencies.

I am using the Arch Linux. So I have to build Jool from source.

Install the tools used by compiling

pacman -S base-devel linux-headers dkms tar

Get the source code

wget https://github.com/NICMx/Jool/releases/download/v4.1.13/jool-4.1.13.tar.gz
tar -xzf jool-4.1.13.tar.gz

Compiling the kernel modules

dkms install jool-4.1.13/

Compile and install the userspace applications

cd jool-4.1.13/
./configure
make
make install

We need to add the /etc/modules-load.d/jool.conf to let Arch Linux load the jool module automatically after booting.

echo jool > /etc/modules-load.d/jool.conf

Now we can enable the stateful-nat64.

Stateful-NAT64

The NAT64 use the fixed prefix of 64:ff9b::/96. All the IPv4 address should be embed into this prefix and be treated as normal IPv6 address in the LAN hosts.

In the router side, run the following command3 to create the NAT64 instance named after z64.

jool instance add "z64" --netfilter --pool6 64:ff9b::/96

The name of instance is z64. And the IPv6 address pool is 64:ff9b::/96. All packet with the destination address of prefix of 64:ff9b::/96 will be translated into IPv4 packet with the address of default outing NIC. Jool will detect the outgoing network card automatically.

Then you need to enable the ip forwarding option of kernel.

sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv6.conf.default.forwarding=1
sysctl -w net.ipv4.ip_forward=1

and run the below command to load them

Now the router is ready to do the NAT64 translation.

In your LAN hosts, you can ping 1.1.1.1 with the following commands

ping -c1 64:ff9b::1.1.1.1
PING 64:ff9b::1.1.1.1 (64:ff9b::101:101) 56 data bytes
64 bytes from 64:ff9b::101:101: icmp_seq=1 ttl=56 time=1.42 ms

--- 64:ff9b::1.1.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.419/1.419/1.419/0.000 ms

The pseudo address of 64:ff9b::1.1.1.1 has been translated into 64:ff9b::101:101. And the ICMP echo package with destination address of 64:ff9b::101:101 will be convert into 1.1.1.1 by Jool.

It works like charming.

DNS64

With the help of Jool, we can ping the pseudo ipv4 address. However, we need to access the IPv4 services by the DNS name. It will be very convenient if the DNS can convert the IPv4 address (A records) into the pseudo IPv6 address. We can use the DNS64 service to do this job.

If one DNS resolver support DNS64, it will try to query the AAAA records of the domain name. If there is only A records (IPv4 address), the resolver will return the pseudo IPv6 address with the 64:ff9b::/96 prefix as the AAAA record.

Both the Public DNS of Google and Cloudflare support the DNS64 service.

# google
2001:4860:4860::64
2001:4860:4860::6464
# cloudflare
2606:4700:4700::64
2606:4700:4700::6400

I recommend you to put both resolvers into your /etc/resolv.conf file to obtain the high availability.

Warning

Do not set the DNS64 resolver into the /etc/resolv.conf of Jool host.

Because the Jool only hooks itself to PRE_ROUTING. It does not attach itself to LOCAL_OUT. This means it can only translate traffic that inbounds from some interface (physical or otherwise). It does not intercept packets sourced from its own network namespace.

And now you can ping github.com which is IPv4-only until 2024,

ping -c1 github.com
PING github.com (64:ff9b::8c52:7403) 56 data bytes
64 bytes from 64:ff9b::8c52:7403: icmp_seq=1 ttl=52 time=23.6 ms

--- github.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 23.624/23.624/23.624/0.000 ms

If you only want to let the LAN host access the IPv4 Internet, you can finish here. But if you want to run service on you LAN host and need to let the IPv4 host outside to access your service, you need continue read next section.

Port Mapping

Suppose you are running web server on port of 3000 on Host 1. And you need to let the client access your web server via IPv4 network. You have to set the IPv4-to-IPv6 port mapping for your service.

By Jool, you can use the bib4 command to do this job:

jool -i z64 bib add 2001:db8::1#3000 10.0.0.1#3000 --tcp

This will add one port mapping for tcp traffic. All traffic with destination of 10.0.0.1 and port of 3000 will be translated into 2001:db8::1 and port of 3000 and be forwarded to Host 1.

However, if you try to run the aforementioned command, you will encounter the following error:

Error: The kernel module returned error 22: Transport address '10.0.0.1#3000'
does not belong to pool4.
Please add it there first.

As it reads, the port of 10.0.0.1#3000 does not belong to pool4. So what is in the pool4 currently? You can use the pool4 subcommand to check it.

You will see nothing. The pool4 is empty.

Every IPv6-to-IPv4 translation need the address pool of both v4 and v6. We only setup the pool6 in the previous section. If the pool4 is empty, how could Jool accomplish the traffic translation?

According the document of pool45, an empty pool4 behaves differently from a populated pool4. Empty pool4 defaults to use ports 61001-65535 of whatever universe-scoped IPv4 addresses the node’s interfaces have.

Then Jool is behaving as if pool4 were configured as follows:

+------------+-------+--------------------+-----------------+-------------+
|       Mark | Proto |     Max iterations |         Address |       Ports |
+------------+-------+--------------------+-----------------+-------------+
|          0 |   TCP |       1024 ( auto) |        10.0.0.1 | 61001-65535 |
+------------+-------+--------------------+-----------------+-------------+
|          0 |   UDP |       1024 ( auto) |        10.0.0.1 | 61001-65535 |
+------------+-------+--------------------+-----------------+-------------+
|          0 |  ICMP |       1024 ( auto) |        10.0.0.1 | 61001-65535 |
+------------+-------+--------------------+-----------------+-------------+

You can only mapping the port int the range of 61001-655535. Let’s adjust our bib command:

jool -i z64 bib add 2001:db8:1::#3000 10.0.0.1#63000 --tcp

Now you can access the web service on Host 1 via the destination 10.0.0.1:63000.

Warning

The port range of pool4 should not overlap with the ephemeral port range.

The default ephemeral port range is 32768-61000, this is why Jool choose the 61001-65535 as it’s default mapping range.

If you need to support more than 4534 simultaneous connections, you have to adjust the port range of pool4 and the ephemeral port range kernel.

sysctl -w net.ipv4.ip_local_port_range="32768 40000"

jool pool4 add 10.0.0.1 40001-61000 --tcp
jool pool4 add 10.0.0.1 40001-61000 --udp
jool pool4 add 10.0.0.1 40001-61000 --icmp

Persistence

All the above configurations will be reset after reboot. If you need toe persist these functions, you need do add some configuration file.

First, load the jool module automatically.

echo jool > /etc/modules-load.d/jool.conf

Then enable the forwarding for both IPv6 and IPv4:

cat <<EOF > /etc/sysctl.d/99-ipv6.conf
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding=1
net.ipv4.ip_forward=1
EOF

Third, setup the systemd service for Jool.

cat <<EOF > /etc/systemd/system/jool.service
[Unit]
Description=Jool NAT64/SIIT Daemon
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/jool file handle /etc/jool/jool.conf

[Install]
WantedBy=multi-user.target
EOF

The Type of this unit is oneshot, which means it will be triggered once. In the ExecStart part, we use the jool file handle to load the /etc/jool/jool.conf.

{
    "instance": "z64",
    "framework": "netfilter",

    "global": {
        "pool6": "64:ff9b::/96"
    },
    "pool4": [
        {
            "protocol": "TCP",
            "prefix": "10.0.0.1/32",
            "port range": "50001-65535"
        },
        {
            "protocol": "UDP",
            "prefix": "10.0.0.1/32",
            "port range": "50001-65535"
        },
        {
            "protocol": "ICMP",
            "prefix": "10.0.0.1/32",
            "port range": "50001-65535"
        }
    ],
    "bib": [
        {
            "protocol": "TCP",
            "ipv4 address": "10.0.0.1#53000",
            "ipv6 address": "2001:db8:1::#3000"
        }
    ]
}

Finally, enable the service unit:

systemctl enable --now jool

Then you can reboot the system. And all the NAT64 will work as plan.

💡Tip

More details of running IPv6 only network on OpenWRT can be found at https://ripe87.ripe.net/wp-content/uploads/presentations/8-IPv6-mostly_on_OpenWRT.pdf

OK, this post will finish here. With Jool, we can setup pure IPv6 network for both outgoing and ingoing traffic. If you have any problem all advice, fell free to leave message to me.


  1. https://nicmx.github.io/Jool/en/index.html↩︎

  2. http://www.litech.org/tayga/↩︎

  3. https://nicmx.github.io/Jool/en/usr-flags-instance.html↩︎

  4. https://nicmx.github.io/Jool/en/usr-flags-bib.html↩︎

  5. https://nicmx.github.io/Jool/en/usr-flags-pool4.html↩︎