tech_surveillance2633 wordsRead on Arc Codex

How NetworkManager uses eBPF to support CLAT and IPv6

IPv6 has been around for more than thirty years, but we are still far from an IPv6-only world. According to Google statistics, only about 50% of its users are accessing its services using IPv6. So far, transitioning to IPv6 has been driven primarily by dual-stack networks, with IPv4 and IPv6 running in parallel. However, this approach prolongs the reliance on IPv4 and its limited address space, providing little incentive to move to IPv6-only. A new approach, known as IPv6-mostly, is emerging as an alternative. What is IPv6-mostly? With IPv6-mostly, a capable client can operate in IPv6-only mode while legacy clients continue to use IPv4. Instead of maintaining full dual-stack operations indefinitely, this approach provides a practical migration path toward IPv6-only. How does IPv6-mostly work? NetworkManager 1.58 in Red Hat Enterprise Linux 10.3 implements an IPv6-mostly architecture using customer-side translator (CLAT) and eBPF to make it possible. While a dual stack remains the most common transition mechanism, it fails to solve the IPv4 address exhaustion problem because it still requires assigning an IPv4 address to every device. Furthermore, the availability of IPv4 on every host masks underlying IPv6 network problems: Mechanisms like Happy Eyeballs (RFC 8305) automatically fall back to IPv4, should IPv6 fail, leaving network administrators unaware of underlying IPv6 connectivity issues. In practice, these characteristics turn the dual stack from a transitional mechanism into a long-term operating model. IPv4 remains necessary for every host, while IPv6 problems go unnoticed because traffic simply falls back to IPv4. As a result, many networks have little incentive to move beyond a dual stack. IPv6-mostly was introduced to address this problem. An IPv6-mostly network is defined in RFC 8925: A network that provides NAT64 (possibly with DNS64) service as well as IPv4 connectivity and allows the coexistence of IPv6-only, dual-stack, and IPv4-only hosts on the same segment. Such a deployment scenario allows operators to incrementally turn off IPv4 on end hosts, while still providing IPv4 to devices that require IPv4 to operate. But IPv6-only-capable devices need not be assigned IPv4 addresses. This approach offers a clear migration path to IPv6-only. At the beginning all hosts run IPv4 and IPv6 in parallel (a dual stack). At some point, the network operator enables IPv6-mostly capabilities and related signaling options on the network. Modern operating systems detect these signals and automatically disable their IPv4 stack on that interface, freeing local IPv4 addresses. Older, legacy hosts that do not understand the new options continue to request and use IPv4 addresses normally on the same segment. As legacy systems are updated or phased out over time, the remaining pool of IPv4 addresses shrinks until the administrator can turn off IPv4 entirely (full IPv6-only). IPv6-mostly relies on these specific standards and mechanisms: - DHCPv4 option 108 "IPv6-only preferred" (RFC 8925): An IPv6-mostly network advertises this option in its DHCPv4 responses. If a client supports this option and receives it, it interprets this as a directive to disable IPv4 assignment on the interface. This frees up the IPv4 address pool for legacy devices that actually require it. - DNS64 and NAT64 (RFC 6146 / RFC 6147): When an IPv6-only client needs to reach an IPv4-only domain, DNS64 synthesizes an IPv6 address for it. The client's traffic is sent to this IPv6 address and is translated to IPv4 by the network's NAT64 gateway. This mechanism handles most of the standard traffic transparently and doesn't require specific support from the client. The primary drawback of this mechanism is its incompatibility with client-side DNSSEC verification. Because DNS64 alters DNS responses by generating artificial AAAA records, it breaks the cryptographic chain of trust. - 464XLAT (RFC 6877): While DNS64 handles name-based connections, 464XLAT provides IPv4 connectivity for applications that use literal IPv4 addresses or bypass standard DNS. It works by using a local translator (the CLAT) to convert an application's IPv4 traffic into IPv6 directly on the user's device. This traffic is sent across the carrier's network and translated back to IPv4 by a provider-side translator (PLAT/NAT64) before entering the global internet. - PREF64 Option (RFC 8781): This router advertisement option tells clients which IPv6 prefix they must use for IPv6 traffic translated from IPv4 by the CLAT, so that it is properly converted by the NAT64 gateway. How does 464XLAT work? The purpose of 464XLAT is to provide IPv4 connectivity across an IPv6-only network, even for applications that rely on IPv4 literals or older socket APIs. It achieves this by splitting the translation responsibility into two distinct components: - Provider-side translator (PLAT): This sits at the edge of the ISP's network. It is a stateful NAT64 (defined in RFC 6146) and it translates IPv6 packets from the customer network into public IPv4 packets for the internet. - Customer-side translator (CLAT): This runs on the endpoint itself. It is a stateless translator that performs a 1:1 translation of packet headers. Both components rely on the stateless IP/ICMP translation (SIIT) algorithm defined in RFC 7915. This standard specifies how to convert an IPv4 header into an IPv6 header (and the other way round), by mapping the individual fields and recalculating checksums. To understand how 464XLAT works, let's trace the path of a packet from a local application trying to reach a public IPv4 server at 198.51.100.1 (Figure 1). The application opens a standard IPv4 socket and sends a packet. The source address is the private IPv4 address 192.0.0.1 (the next section discusses how this address is generated). The destination is the target server 198.51.100.1.The application is unaware that it is running on an IPv6-only network. The CLAT running on the host intercepts the packet and rewrites its headers using the SIIT algorithm. It replaces the private IPv4 source address with a native IPv6 address assigned to the host (2001:db8:a::42). It also creates a synthetic IPv6 destination address by embedding the target IPv4 address (198.51.100.1) into the well-known NAT64 prefix (64:ff9b::). The resulting IPv6 packet travels on the provider's network and reaches the PLAT. The PLAT replaces the packet IPv6 header with an IPv4 one and sets the source address to a public IPv4 address from the provider's pool. It records this stateful mapping before sending the packet to the public IPv4 internet. On the return path, the PLAT uses its state table to translate the response back to IPv6, and the CLAT performs the final stateless translation to IPv4. In this way, the response can reach the original host. Discovery and preparation with NetworkManager All of this must work automatically without user intervention. To achieve this, NetworkManager discovers when the network supports CLAT, derives the necessary parameters and programs the data plane so that the translation happens seamlessly. The process begins with discovery. NetworkManager listens for IPv6 router advertisements (RAs) on the interface. For 464XLAT to function, the network must advertise the PREF64 option, which contains the NAT64 prefix and its lifetime. There are other mechanisms available to discover the NAT64 prefix, like the DNS-based one defined in RFC 7050. However, the PREF64 option remains the recommended one, as clarified by the recent informational RFC 9872 ("Recommendations for Discovering IPv6 Prefix Used for IPv6 Address Synthesis"). A special value for the NAT64 prefix is 64:ff9b::/96, which is documented as the "well-known prefix" in RFC 6052. It is often used as the default prefix, especially in mobile networks. It does come with limitations (for example, it can't be used to represent a private IPv4 address) so enterprise admins often choose a dedicated network-specific prefix (NSP). Once the NAT64 prefix is discovered, NetworkManager needs a dedicated IPv6 address to use as source for the translated traffic. It's important that this address differs from the IPv6 address used for native IPv6 traffic. When packets come back from the NAT64 gateway, their source address belongs to the NAT64 prefix. Because native applications using DNS64 also communicate with the same NAT64 prefix, the CLAT would have no way of knowing which packets must be translated back to IPv4 and which belong to native applications. By giving the CLAT its own IPv6 address, it knows that any packet arriving at that address requires translation. The IPv6 address dedicated for CLAT is obtained through SLAAC, by choosing a different interface identifier (for example, the last 64 bits) than the IPv6-native address. Local applications sending native IPv6 traffic should use the native IPv6 address and not the CLAT IPv6 address. To enforce this, the CLAT address is not configured on the interface and is known only by NetworkManager and the CLAT program. For this reason, it is not visible in the output of the ip addr show command. Even if the network does not provide IPv4, NetworkManager still needs to configure a local IPv4 address on the interface so that legacy applications can open IPv4 sockets. The address is selected by NetworkManager in the 192.0.0.0 - 192.0.0.7 range, which is reserved by IANA for CLAT (RFC 7335). A dedicated range ensures that the address doesn't conflict with RFC 1918 private networks like 192.168.1.0/24 that might be active on other interfaces. Once NetworkManager has determined all the necessary parameters for translation - the NAT64 prefix and the dedicated IPv4 and IPv6 addresses - it is ready to start the CLAT. Those parameters can be inspected by using nmcli , the NetworkManager command-line tool: $ nmcli [...] wlp0s20f3: connected to home "Intel 6 AX201" wifi (iwlwifi), 62:42:12:02:61:12, hw, mtu 1500 ip4 default inet4 192.0.0.1/32 inet6 2001:db8:a::ff2b:c70d:c5b1:fbfe/64 inet6 fe80::c060:8caf:f69b:e41a/64 clat inet4 192.0.0.1 inet6 2001:db8:a::42 pref64 64:ff9b::/96 [...] This last line shows all the three key components together: The CLAT IPv4 address (192.0.0.1), the IPv6 CLAT address (2001:db8:a::42), and the NAT64 prefix (64:ff9b::/96). CLAT with eBPF The CLAT functionality in NetworkManager is implemented with an eBPF program attached to the network interface. This choice to use eBPF was made for two reasons: The Linux kernel lacks a native NAT64 implementation, and userspace implementations introduce performance overhead due to context switching for every packet. The program uses eBPF hooks in the traffic control (TC) subsystem, intercepting packets in both ingress and egress paths. It performs stateless packet translation as defined in RFC 7915, processing each packet individually to rewrite headers and map addresses. NetworkManager passes the configuration parameters to the program as BPF global variables, a mechanism that allows sharing data between userspace and a BPF program in the kernel. The configuration contains the IPv4 and IPv6 CLAT addresses, the NAT64 prefix and the interface type. The interface type is an important parameter because it allows the program to work both on layer 2 interfaces like ethernet and Wi-Fi, where packets include a MAC header, and pure layer 3 interfaces like WWAN modems and IP tunnels. The primary job of the eBPF program is to rewrite the packet headers. Since IPv4 and IPv6 headers have different default lengths (20 bytes versus 40 bytes), the program calls the helper function bpf_skb_change_proto() provided by the kernel. This function adjusts the packet layout by adding or removing space as necessary. Once the buffer is resized, the program writes the new IP header, recalculates the checksums and lets the packet continue its journey through the network stack. Figure 2 provides an overview of this process. Recomputing checksums Checksums are used in packet headers to verify that data was not corrupted during transmission. Because the eBPF program modifies these headers, it must also update the corresponding checksums. At the network layer, there is a difference between IPv4 and IPv6: An IPv4 header contains a dedicated checksum field, while an IPv6 header does not. Therefore, during the translation of an IPv4 packet to IPv6, there is no need to update the network checksum. When translating an IPv6 packet back to IPv4, the eBPF program must compute a new IP checksum. However, because this checksum covers only the header itself, it can be computed very quickly. At the transport layer, the logic gets slightly more complex. Both a TCP and UDP header includes a checksum that covers not only its payload but also a "pseudo-header" containing the source and destination addresses from the IP header. Because the CLAT modifies these IP addresses, the original TCP and UDP checksums become invalid. In theory, this recalculation could be avoided by generating checksum-neutral CLAT addresses. This would require careful selection of the dedicated IPv4 and IPv6 addresses so that their net contribution to the checksum is zero. NetworkManager's current implementation does not use checksum-neutral addresses, meaning it must update the TCP and UDP checksums for every packet. Fortunately, this does not create a performance bottleneck. Instead of recalculating the checksum over the entire packet payload, the eBPF program performs an incremental update, applying only the mathematical difference between the old and new IP addresses to the existing checksum, as illustrated in figure 3. Handling fragmentation Another layer of complexity arises with packet fragmentation. When inbound IPv6 fragmented packets arrive, the CLAT eBPF program must translate them into corresponding IPv4 fragments. The program identifies the first fragment in the sequence based on the offset field of the IPv6 fragmentation header and recomputes the checksum in the layer 4 header (TCP or UDP) contained in the fragment. Translating ICMP error messages Suppose you send a UDP packet to a host that's not listening on that UDP port. The receiving host generates an ICMP "port unreachable" message, with the payload containing a copy of the original message including the IP and UDP headers. The kernel uses the payload of the received ICMP to map the packet to the correct socket and to generate an error for the application. When using 464XLAT, both the PLAT and CLAT must translate not only the outer IP and ICMP headers, but also the inner headers in the payload. In the CLAT eBPF program, this complicates things because it requires changing the size of the inner IP header, moving the packet content and recalculating multiple checksums. However, this is necessary so that error signaling works properly. Furthermore, this is needed by diagnostic tools such as ping and traceroute . ICMP translation is illustrated in figure 4. To display a list of next hops to a given destination, traceroute relies on the "time exceeded" ICMP error messages. When a host uses 464XLAT to reach the IPv4 internet, the next hops can be IPv6 routers (in the path before the NAT64 gateway) or IPv4 routers (in the path after the NAT64 gateway). The addresses of the IPv4 routers appear as regular IPv6-mapped IPv4 addresses to the CLAT and can be translated back to the original IPv4 address. IPv6 routers have addresses outside of the NAT64 prefix and can't be translated to an IPv4 address. Without any intervention, the CLAT would simply drop the "time exceeded" messages from these routers and traceroute would simply display a timeout (* * * ) for these next hops. The CLAT eBPF program implements the method described in draft-ietf-v6ops-icmpext-xlat-v6only-source, translating the unmappable IPv6 source address to the address 192.0.0.8, designed by IANA as the IPv4 dummy address. This allows the next hop to be displayed, albeit with a fixed IPv4 address. Future iterations may include ICMP extensions to expose the real IPv6 address. Observability and eBPF Because packet translation happens transparently in the kernel, debugging connectivity issues could become difficult. To assist users troubleshooting the translation process, the eBPF program maintains counters for different traffic types and dropped packets in both the ingress and egress directions. These statistics are printed to the NetworkManager debug logs when the connection is terminated, providing visibility into the CLAT program's activity. Conclusion Support for native CLAT and DHCPv4 Option 108 in NetworkManager represents an important contribution to the transition to IPv6. By implementing translation logic with eBPF, NetworkManager avoids userspace context switching, promoting low resource usage and high performance. Network administrators and Linux users can try those new features in NetworkManager 1.58 release, shipping in Red Hat Enterprise Linux 10.3.

How it works

Once you click Generate, Ollama reads this article and crafts 5 comprehension questions. Your answers are graded against the article content — general knowledge won't be enough. Score 70+ to count toward your certificate.

Questions are cached — you'll always get the same 5 for this article.