In the last post I introduced network namespaces and showed a practical example on how to share IPv4 connectivity between a network namespace and a host. Before that post, I also wrote a short tutorial on how to set up an IPv6 tunnel using Hurricane Electric broker service. This kind of service allow us to get into the IPv6 realm using an IPv4 connection.
In this article I continue exploring network namespaces. Taking advantage of the work done in the aforementioned posts, I explain in this post how to share IPv6 connectivity between a host and a network namespace.
Let’s assume we already have a SIT tunnel (IPv6-in-IPv4 tunnel) enabled in our host and we’re able to ping an external IPv6 address. If you haven’t, I encourage you to check out set up an IPv6 tunnel.
I need to write an script which will create the network namespace and set it up accordingly. I call that script ns-ipv6. If the script works correctly, I should be able to ping an external IPv6 host from the namespace. Such script looks like this:
ns-ipv6.sh
It actually works:
Let’s take a deeper look on how it works.
ULAs
The script creates a veth pair to communicate the network namespace with the host. Each virtual interface is assigned an IPv6 address in the ‘fd00::0/64’ network space (Lines 21 and 27). This type of address is known as ULA or Unique Local Address. ULAs are the IPv6 counterpart of IPv4 private addresses.
Before continuing, a brief reminder on how IPv6 addresses work:
An IPv6 address is a 128-bit value represented as 8 blocks of 16-bit (8 x 16-bit = 128-bit). Blocks are separated by a colon (‘:’). Unlike IPv4 addresses, block values are written in hexadecimal. Since each block is a 16-bit value, they can be written in hexadecimal as 4-digit numbers. Leading zeros in each block can be ommitted. On the same hand, when several consecutive block values are zero they can be ommitted too. In that case two colons (‘::’) are written instead, meaning everything in between is nil. For instance, the address ‘fd00::1’ is the short form of the much longer address ‘fd00:0000:0000:0000:0000:0000:0000:0001’.
RFC 4193 (section 3) describes Unique Local Addresses format as:
The RFC reserves the IPv6 address block ‘fc00::/7’ for ULAs. It divides this address in two subnetworks: ‘fc00::/8’ and ‘fd00::/8’. The use of the ‘fc00::/8’ block has not been defined yet, while the ‘fd00:/8’ block is used for IPv6 local assigned addresses (private addresses).
The address ‘fd63:b1f4:7268:d970::1’ is an example of a valid ULA. It starts by the ‘fd’ prefix followed by an unique Global ID (‘63:b1f4:7268’) and a Subnet ID (‘d970’), leaving 64 bits for the Interface ID (‘::1’). I recommend the page Private IPv6 address range to obtain valid random ULAs.
ULAs are not routable in the global Internet. They are meant to be used inside local networks and that’s precisely the reason why they exist.
NAT on IPv6
Lines 34-36 activate IPv6 forwarding and IP Masquerade on the source address. However, this solution is not optimal.
The Hurricane Electric tunnel broker service lends us a ‘::0/64’ block, with 2^64 - 2 valid hosts. NAT, Network Address Translation, grants a host in a private network external connectivity via a proxy that lends the private host its address. This is the most common use case of NAT, known as Source NAT. Besides IP addresses, NAT translates port numbers too and that’s why it’s sometimes referred as NAPT (Network Address and Port Translation). NAT has been an important technology for optimizing the use of IPv4 address space, although it has its costs too.
The original goal of IPv6 was solving the problem of IP address exhaustation. Mechanisms such as NAT are not needed because the IPv6 address space is so big that every host could have an unique address, reachable from another end of the network. Actually, IPv6 brings back the original point-to-point design of the IPv4 Internet, before private addresses and NAT were introduced.
So let’s try to get rid of NAT66 (IPv6-to-IPv6 translation) by:
- Using global IPv6 addresses.
- Removing MASQUERADING.
The new script is available as a gist here: ns-ipv6-no-nat.sh. There’s some tricky bits that are worth explaining:
First thing, is to replace the ULAs by IPv6 addresses which belong to /64 block leased by Hurricane Electric:
When setting up the interfaces, the host side should add a more restricted routing rule for the other end of the veth pair. The reason is that all addresses belong to the same network. If from the host side a packet needs to get to the network namespace side, it would be routed through the IPv6 tunnel unless there’s a more restricted rule.
Lastly, NAT66 can be removed but IP forwarding is still necessary as the host acts as a router.
When a packet arrives from the network namespace into the host, the destination address of the packet doesn’t match any of the interfaces of the host. If IP forwarding were disabled, the packet will simply be dropped. However, since IP forwarding is enabled, non-delivered packets get forwarded through the host’s default gateway reaching their destination, hopefully.
After these changes, the script still works:
DNS resolution
In the line above I’m pinging an IPv6 address directly (this address is actually ipv6.google.com). What happens if I try to ping a host name instead?
ns-ipv6> ping6 ipv6.google.com unknown host
When I ping ipv6.google.com from the network namespace, the /etc/resolv.conf file is queried to obtain a DNS nameserver address.
/etc/resolv.conf
nameserver 8.8.8.8
This address is an IPv4 address, but the network namespace has IPv6 connectivity only. It cannot reach any host in the IPv4 realm. However, DNS resolution works in the host since the host has either IPv6 and IPv4 connectivity. It is necessary to add a DNS server with an IPv6 address. Luckily, Google has DNS servers available in the IPv6 realm too.
nameserver 8.8.8.8 nameserver 2001:4860:4860::8888
Now I should be able to ping ipv6.google.com from the network namespace:
ns-ipv6> ping6 -c 1 ipv6.google.com PING ipv6.google.com(lis01s14-in-x0e.1e100.net) 56 data bytes 64 bytes from lis01s14-in-x0e.1e100.net: icmp_seq=1 ttl=57 time=85.7 ms --- ipv6.google.com ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 85.702/85.702/85.702/0.000 ms
Wrapping up
After all these changes we end up with a script that:
- Uses Hurricane Electric’s IPv6 network addresses, instead of ULAs.
- Doesn’t do NAT66 to provide external IPv6 connectivity to the network namespace.
It has been a lot of fun writing out this post, it helped me to understand many things better. I definitely encourage everyone interested to run some of the scripts above and try out IPv6, if you haven’t yet. The network namespace part is not fundamental but it makes it more interesting.
Lastly, I’d like to thank my colleague Carlos López for his unvaluable help as well as the StackOverflow community which helped me to figure out the script that gets rid of NAT66.