Dual-stack and tunnels both assume IPv6 exists on both ends of a conversation. NAT64 solves the harder case: an IPv6-only client that must reach a server which only speaks IPv4. It does this by translating between the two protocols and, cleverly, by encoding the IPv4 server's address inside an IPv6 address so the client never has to know it is talking to a legacy host. This article explains how NAT64 works, walks the full stateful configuration on Cisco IOS XE 17.18, does the address-synthesis math by hand, and explains the DNS64 half that makes the whole thing usable. It also gives you an honest platform finding from the lab. It is part of the PingLabz IPv6 routing and services guide.
The problem NAT64 exists to solve
Imagine you have finally built an IPv6-only client network, no IPv4 addresses to hand out, no dual-stack to maintain. That is the clean end state everyone wants. The catch is that large parts of the internet, and plenty of internal legacy systems, still run IPv4-only. Your IPv6-only client has no way to put a packet on the wire toward 10.20.20.80, because it has no IPv4 stack and no IPv4 address. NAT64 sits at the boundary and translates: it takes IPv6 packets from the client, rewrites them as IPv4 packets toward the real server, and translates the replies back. The client stays purely IPv6; the server stays purely IPv4; the NAT64 gateway does the shapeshifting in the middle.
The synthesized address: the whole trick
NAT64's central idea is that an IPv4 address can be embedded inside an IPv6 address. You reserve an IPv6 /96 prefix for NAT64, and the last 32 bits of that prefix hold the IPv4 address of the target server. On the lab the NAT64 prefix is 2001:DB8:64::/96, and the IPv4-only server is 10.20.20.80. Convert each octet of the IPv4 address to hex:
10 . 20 . 20 . 80
0x0a 0x14 0x14 0x50 -> 0x0a141450Now append those 32 bits to the /96 prefix. The IPv6-only client reaches the IPv4 server at:
2001:db8:64::0a14:1450 (written by IOS as 2001:DB8:64::A14:1450)That single address is the entire trick of NAT64. The client sends a normal IPv6 packet to 2001:db8:64::0a14:1450. The NAT64 gateway recognises the packet is destined for its own /96 prefix, pulls the last 32 bits out (0x0a141450), reconstructs the IPv4 address 10.20.20.80, and forwards an IPv4 packet to the real server. To the client it was pure IPv6; to the server it was pure IPv4. The /96 length is deliberate: 128 minus 96 is exactly 32 bits, precisely enough to hold one IPv4 address.
The full stateful configuration
Here is the complete stateful NAT64 configuration from EDGE1 on the lab. Ethernet0/0 faces the IPv6-only client side, Ethernet0/1 faces the IPv4 side:
nat64 prefix stateful 2001:DB8:64::/96
nat64 v4 pool PLZ-POOL 203.0.113.1 203.0.113.10
ipv6 access-list PLZ-V6-CLIENTS
permit ipv6 2001:DB8:99:1::/64 any
nat64 v6v4 list PLZ-V6-CLIENTS pool PLZ-POOL overload
interface Ethernet0/0
nat64 enable
interface Ethernet0/1
nat64 enableEach piece has a job. nat64 prefix stateful 2001:DB8:64::/96 defines the translation prefix, the one whose last 32 bits carry the embedded IPv4 address. nat64 v4 pool PLZ-POOL 203.0.113.1 203.0.113.10 creates a pool of real IPv4 addresses that the gateway will use as the source of the translated packets (the server needs an IPv4 source to reply to). The ipv6 access-list selects which IPv6 clients are allowed to be translated, here the 2001:DB8:99:1::/64 client subnet. nat64 v6v4 list ... pool ... overload ties it together: translate IPv6-to-IPv4 for traffic matching the ACL, using the pool, with overload meaning PAT (many clients share the pool via port translation, exactly like IPv4 NAT overload). Finally, nat64 enable must be applied on both the ingress and egress interfaces, translation only happens on interfaces where it is switched on.
The infrastructure comes up correctly
With that configuration applied, the control plane builds everything it should. First, the NAT64 prefix gets a route, owned by the NAT64 virtual interface:
EDGE1#show ipv6 route 2001:DB8:64::A14:1450
Routing entry for 2001:DB8:64::/96
Known via "static", ...
::100.0.0.1, NVI0Look at what this confirms. A lookup for the synthesized address 2001:DB8:64::A14:1450 resolves to the 2001:DB8:64::/96 route, and that route points at NVI0, the NAT64 Virtual Interface. NVI0 is the internal pseudo-interface that owns the NAT64 prefix and hands matching packets to the translation engine. This is exactly the routing state you want: any IPv6 packet aimed at the prefix is steered into NAT64 rather than dropped. The prefix, the pool, the ACL, and this NVI0 route are all correctly in place.
An honest platform finding: the data plane did not translate
Now the honest part, because credibility matters more than a tidy story. With the IPv6-only host sourcing traffic correctly from 2001:db8:99:1::100 toward the synthesized address, the actual IPv6-to-IPv4 translation did not complete on this platform. The statistics stayed empty:
EDGE1#show nat64 statistics global
Total active translations: 0 ...
Packets translated (IPv6 -> IPv4)
Stateful: 0Zero active translations, zero packets translated. This is not a configuration error, the config, the pool, the ACL, the prefix, and the NVI0 route are all present and correct. It is a platform limitation. The lab runs on IOL-XE (IOS on Linux), which is a control-plane-focused simulation: it faithfully accepts configuration and shows control-plane state, but several data-plane features do not fully forward in it. On this same platform, uRPF drop counters and WRED behave the same way, they configure and report state but do not exercise the full forwarding path. NAT64 translation falls into that category here. So the responsible thing is to be clear: we proved the configuration and the routing infrastructure, and we did the address-synthesis math, but we did not capture a translated packet, and we are not going to pretend we did. A completed end-to-end NAT64 translation is best demonstrated on a full data-plane platform such as the Catalyst 8000V or physical hardware. Everything you can verify without forwarding, we verified; the one thing that needs a real data plane, we are honest about.
The DNS64 half of the story
NAT64 by itself has a usability gap: how does the client know to send its packet to 2001:db8:64::0a14:1450 in the first place? A user types a hostname, and DNS is supposed to return an address. The IPv4-only server has an A record (10.20.20.80) but no AAAA record, so an IPv6-only client doing a normal AAAA lookup gets nothing back. This is where DNS64 completes the design.
DNS64 is a special resolver behaviour. When an IPv6-only client asks for the AAAA record of an IPv4-only host, the DNS64 resolver notices there is no real AAAA, so it looks up the A record instead, takes the returned IPv4 address, and synthesises an AAAA record on the fly by embedding that IPv4 address into the NAT64 /96 prefix. For our server it would synthesise exactly 2001:db8:64::0a14:1450 and hand it back. The client just did an ordinary DNS lookup and received an ordinary-looking IPv6 address; it has no idea any translation is involved. It sends its packet to that address, the packet hits the NAT64 prefix, and the gateway translates. DNS64 and NAT64 are two halves of one mechanism: DNS64 manufactures the synthesized address at lookup time, NAT64 acts on it at forwarding time. Deploy one without the other and the design does not work in practice, because clients would have no way to discover the synthesized address on their own.
Stateful vs stateless NAT64
NAT64 comes in two flavours, and the distinction matters for design.
Stateful NAT64
Stateless NAT64
Stateful NAT64 (the configuration above) maps many IPv6 clients onto a small pool of IPv4 addresses using port translation, exactly like classic IPv4 NAT overload. It keeps per-flow state and conserves IPv4 addresses, which is the realistic case for an IPv6-only client network reaching the IPv4 internet. It requires DNS64 to hand clients the synthesized address. Stateless NAT64 maps addresses one-to-one with no pool and no state table, deriving each IPv4 address algorithmically from the IPv6 address. It scales beautifully because there is no state to track, but it only works with IPv6 addresses that are built to be IPv4-translatable, and it does not conserve IPv4 space (every translated host needs its own IPv4 address). Stateless suits a bank of servers with a fixed, predictable mapping; stateful suits a crowd of clients sharing scarce IPv4.
Verifying NAT64, in the order that finds problems fastest
When you deploy NAT64 and it does not work, check the pieces in dependency order rather than poking at random. First, confirm the prefix route exists and points at NVI0 with show ipv6 route on the synthesized address, as in the capture above; if that route is missing, the NAT64 prefix was never installed and nothing downstream can work. Second, confirm nat64 enable is on both the IPv6-facing and IPv4-facing interfaces, forgetting one side is the most common configuration mistake. Third, confirm the ACL actually matches your client source; a client outside the permitted subnet is silently ignored. Fourth, confirm the pool has free addresses and that the IPv4 side can route back to the pool range (the server replies to a pool address, so that address must be reachable from the server's perspective). Only after all four are green do you look at show nat64 statistics and show nat64 translations to watch flows build. If the infrastructure is correct and translations still read zero, and you are on a control-plane simulator, you have hit the same platform limit this lab did, move to hardware or a data-plane-capable image to see live translation.
What NAT64 breaks, and why it is a last resort
NAT64 works well for the common case of a client fetching content over HTTP, HTTPS, or other protocols that carry no embedded IP addresses. It struggles with anything that puts an IP literal inside the payload. Protocols that embed addresses in their application data (classic FTP in active mode, some SIP and peer-to-peer signalling, and any application that hands a bare IPv4 literal to the user) do not get those embedded addresses translated, because NAT64 only rewrites the packet headers, not arbitrary application payloads. An IPv6-only client handed an IPv4 literal by such an application has no way to reach it, since there is no synthesized address for a literal that never went through DNS64. This is the fundamental reason NAT64 is positioned as a last resort rather than a design goal: it is stateful, it is a translation choke point, and it quietly breaks a meaningful minority of applications. Dual-stack breaks nothing because both protocols are genuinely present; NAT64 is what you reach for only when you cannot dual-stack the client side and still must reach IPv4-only content.
Where NAT64 fits among the transition tools
It helps to place NAT64 next to its neighbors. Dual-stack runs IPv4 and IPv6 side by side and is the preferred end state; it has no translation and no tunnel, so nothing breaks, but it requires you to enable IPv6 everywhere and keep running IPv4 too. Tunnels carry IPv6 islands across an IPv4-only core you cannot upgrade; both ends still speak IPv6, so applications are unaffected, but you have taken on encapsulation overhead and MTU concerns. NAT64 is different in kind from both: it is the only one of the three that lets a host which does not speak IPv4 at all reach a host which does not speak IPv6 at all. That unique capability is also its cost, because bridging two protocols that were never designed to interoperate is inherently lossy. Use dual-stack where you can, tunnels where you must cross a hostile core, and NAT64 only where one side is genuinely single-protocol and cannot be changed.
Key Takeaways
- NAT64 lets an IPv6-only client reach an IPv4-only server by translating between the protocols at a gateway. The client stays pure IPv6, the server stays pure IPv4.
- The synthesized address is the core concept: a NAT64
/96prefix with the server's IPv4 embedded in the last 32 bits.10.20.20.80becomes0x0a141450, so the client targets2001:db8:64::a14:1450. - DNS64 is the other half: it synthesises exactly that
AAAArecord when an IPv6-only client looks up an IPv4-only host, so the client discovers the synthesized address with a normal DNS query. NAT64 without DNS64 is not usable in practice. - Stateful NAT64 uses an IPv4 pool with overload for many-to-few mappings and needs DNS64; stateless NAT64 is 1:1 with no pool, no state, but only for IPv4-translatable addresses.
- Honest lab note: the config, pool, ACL, prefix, and NVI0 route were all correct, but the data-plane translation did not complete on IOL-XE 17.18 (0 translations), consistent with it being a control-plane simulation. Demonstrate a completed NAT64 on Catalyst 8000V or hardware.
NAT64 is the last resort when one side is IPv6-only and the other is IPv4-only. The next article ties dual-stack, tunnels, and translation into a single decision framework. For the full picture, return to the IPv6 guide.