We’re an all-Apple campus on the client side (we mostly run Linux on the server side). We noticed something interesting on our macs while testing IPv6-only connectivity using the NAT64 servers that we set up for testing IPv4 legacy protocols with IPv6-only connections.

One day I absent-mindedly tried to ping an IPv4 address literal (8.8.8.8). Normally, this doesn’t work with NAT64 because a literal address doesn’t involve a query to DNS, so DNS64 doesn’t get the chance to synthesize the v4-embedded address.

But in this case, I was able to ping and get responses! I thought perhaps I had turned v4 on by accident (I had manually turned it off to test native IPv6), but I confirmed it was still off. Then, I noticed this in ifconfig:

en0: flags=88e3<UP,BROADCAST,SMART,RUNNING,NOARP,SIMPLEX,MULTICAST> mtu 1500
	options=6463<RXCSUM,TXCSUM,TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
	ether <redacted>
	inet6 fe80::<redacted>%en0 prefixlen 64 secured scopeid 0xf 
	inet6 <redacted> prefixlen 64 autoconf secured 
	inet 192.0.0.2 netmask 0xffffffff broadcast 192.0.0.2
	inet6 <redacted> prefixlen 64 clat46
	nat64 prefix 64:ff9b:: prefixlen 96
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active

That 192.0.0.2 address is pulled from the DS-Lite range, and represents a locally-assigned address that only works on the host. Checking the route tables, you can see that this address has a next-hop gateway that’s in the same range:

% netstat -f inet -rn  
Routing tables

Internet:
Destination        Gateway            Flags           Netif Expire
default            192.0.0.1          UGScg             en0       
127                127.0.0.1          UCS               lo0       
127.0.0.1          127.0.0.1          UH                lo0       
192.0.0.1/32       link#15            UCS               en0      !
192.0.0.1          link#15            UHLWIir           en0      !
192.0.0.2/32       link#15            UCS               en0      !
192.0.0.2          f0:2f:4b:13:ed:bd  UHLWIi            lo0       
224.0.0/4          link#15            UmCS              en0      !
224.0.0.251        1:0:5e:0:0:fb      UHmLWI            en0       
239.255.255.250    1:0:5e:7f:ff:fa    UHmLWI            en0       
255.255.255.255/32 link#15            UCS               en0      !

You’ll note that the 192.0.0.1 address is the default gateway for v4, so as far as the node is concerned, it has a default route to the v4 internet. Traffic to that address is handled by a client site translator (CLAT) process that converts the packets from v4 to v6 and embeds the v4 destination in the v6 address (just like DNS64 synthesis would). The benefit here is that no DNS is required, and the host has (what appears to be) a valid IPv4 address, so applications that are v4-only on the host continue to operate correctly even though the host doesn’t have a routable v4 address.

I wasn’t the only one to see this behavior. Ondřej Caletka of the RIPE NCC observed this CLAT behavior in macOS and documented it in his article about IPv6-mostly networks. However, at the time of his article (2022), the CLAT was only activated in response to DHCPv4 providing the IPv6-only Preferred option, along with a PREF64 Advertisement in the v6 Router Advertisement.

In my case, I don’t have these set up (yet). So in 2023 it seems Apple has relaxed the requirements for activating the CLAT. My best guess is that when macOS detects both the absence of a v4 address (in my case, because I’ve explicitly disabled it) and detects a PREF64 value per RFC7050 it activates the CLAT. This is good, and helpful because the RA flag has not been adopted by any router vendors we have tested (Juniper, Arista, Aruba). I reached out to Ondřej and he confirmed my findings; he also said that he observed macOS 13 (Ventura) complaining unless it had a valid IPv4 address or a functioning CLAT/NAT64 (so it seems Apple is requiring IPv4 functionality to consider itself “connected”).

It’s worth noting that this feature is not 100% reliable. If I have multiple network connections enabled (ethernet and wifi), and then disconnect from ethernet, the CLAT doesn’t automatically pick up on wifi. I usually have to bring wifi down and back up, and sometimes toggle v4 on and back off before the functionality returns. I’ve noticed that when it’s broken I sometimes see the CLAT output in ifconfig but netstat shows no default v4 route installed, so the packets won’t flow correctly.

Even though it looks like macOS doesn’t quite have all the kinks worked out, I’m still excited about this feature! I was prepared to deal with the loss of IPv4 literals when we switch to pure IPv6 with NAT64. However, having this CLAT functionality will help the transition by preventing immediate breakage of programs that still rely on raw IPv4. On the downside, it means that broken apps can stay broken longer and there will be less impetus to update them. But, given that some apps may not be able to be upgraded (e.g., because they’re discontinued), this prevents those apps from not working at all and possibly preventing a migration to IPv6-only.