Fundamentals 13 min read

Mastering IPv6 Socket Programming: Handling NAT64, DNS64, and Mixed IP Stacks

This article explains IPv6 socket programming fundamentals, focusing on NAT64/DNS64 transition mechanisms, handling various IP‑stack combinations, and detecting client support, with practical code examples and workflow diagrams relevant to iOS 9 and broader platforms.

WeChat Client Technology Team
WeChat Client Technology Team
WeChat Client Technology Team
Mastering IPv6 Socket Programming: Handling NAT64, DNS64, and Mixed IP Stacks

Background

Research on IPv6 socket programming originated from iOS 9's requirement to support pure IPv6 networks; apps submitted to the App Store after 2016 must be compatible with IPv6‑only networks (iOS 9+).

Supporting IPv6 in iOS 9 – WWDC 2015 announced that iOS 9 supports pure IPv6 network services, and apps submitted to the App Store in 2016 must be compatible with pure IPv6 networks.

Although the motivation is iOS, most of the content applies to other platforms. IPv6 complexity includes compatibility with IPv4. This article focuses on NAT64, the most common pure IPv6 environment for mobile users, and on handling different IP‑stack combinations and detecting the client’s supported IP stack.

Problem Complexity

To simplify, we discard IPv4 sockets and use only IPv6 sockets, created with

AF_INET6

. iOS expects apps to work with NAT64/DNS64, so other transition mechanisms are omitted.

Socket API support RFC 4038 - Application Aspects of IPv6 Transition

v4 socket interface supports only IPv4 stack

v6 socket can support both IPv4 and IPv6 stacks

Server IP

returns v4 IP

returns v6 IP

User local IP stack

IPv4‑only

IPv6‑only

IPv4‑IPv6 dual stack

Various IPv6 transition mechanisms

NAT64/DNS64

64:ff9b::/96

– IPv6 network accesses IPv4 resources. RFC 6146 , RFC 6147

6to4

2002::/16

– for two IPv4‑public‑address subnets to communicate over IPv6. RFC 6343

Teredo tunneling

2001::/32

– tunnel between IPv6‑only subnets. RFC 4380

464XLAT – when an app uses only IPv4 sockets but the local network is IPv6‑only; requires CLAT service. RFC 6877

Other compatibility solutions (not covered)

Handling Different IP‑Stack Combinations

v4 IP + IPv4‑only or IPv4‑IPv6 Dual stack

Even when using an IPv6 socket, the socket must operate over the IPv4 protocol. IPv4‑mapped IPv6 addresses (e.g.,

::ffff:128.0.0.128

) allow a single socket API to handle both IPv4 and IPv6.

::ffff:0:0/96 — This prefix is designated as an IPv4‑mapped IPv6 address. With a few exceptions, this address type allows the transparent use of the Transport Layer protocols over IPv4 through the IPv6 networking application programming interface. Server applications only need to open a single listening socket to handle connections from clients using IPv6 or IPv4 protocols.

Example conversion and pseudo‑code:

local_addr.sin6_addr, v4mapped_str_local_addr, 64); //IPv4‑mapped IPv6 address sample
//address init 
const char* ipv4mapped_str ="::FFFF:14.17.32.211";
in6_addr ipv4mapped_addr = {0};
int v4mapped_r = inet_pton(AF_INET6, ipv4mapped_str, &ipv4mapped_addr);

sockaddr_in6 v4mapped_addr = {0};
v4mapped_addr.sin6_family = AF_INET6;
v4mapped_addr.sin6_port = htons(80);
v4mapped_addr.sin6_addr = ipv4mapped_addr;

//socket connect
int v4mapped_sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
std::string v4mapped_error;
if (0 != connect(v4mapped_sock, (sockaddr*)&v4mapped_addr, 28))
{
    v4mapped_error = strerror(errno);
}

//get local ip
sockaddr_in6 v4mapped_local_addr = {0};
socklen_t v4mapped_local_addr_len = 28;
char v4mapped_str_local_addr[64] = {0};
getsockname(v4mapped_sock, (sockaddr*)&v4mapped_local_addr, &v4mapped_local_addr_len);
inet_ntop(v4mapped_local_addr.sin6_family, &v4mapped_

v4 IP + IPv6‑only

This is the main scenario required by Apple. It involves NAT64/DNS64. See Apple’s “Supporting IPv6 DNS64/NAT64 Networks”.

NAT64 is a mechanism to allow IPv6 hosts to communicate with IPv4 servers. The NAT64 server is the endpoint for at least one IPv4 address and an IPv6 network segment of 32‑bits, e.g., 64:ff9b::/96 (RFC 6052, RFC 6146). The IPv6 client embeds the IPv4 address with which it wishes to communicate using these bits, and sends its packets to the resulting address. The NAT64 server then creates a NAT‑mapping between the IPv6 and the IPv4 address, allowing them to communicate. DNS64 describes a DNS server that when asked for a domain's AAAA records, but only finds A records, synthesizes the AAAA records from the A records. The first part of the synthesized IPv6 address points to an IPv6/IPv4 translator and the second part embeds the IPv4 address from the A record. The translator in question is usually a NAT64 server. The standard‑track specification of DNS64 is in RFC 6147. There are two noticeable issues with this transition mechanism: It only works for cases where DNS is used to find the remote host address; if IPv4 literals are used the DNS64 server will never be involved. Because the DNS64 server needs to return records not specified by the domain owner, DNSSEC validation against the root will fail in cases where the DNS server doing the translation is not the domain owner's server.

Typical NAT64 workflow:

Client performs

getaddrinfo

DNS lookup.

If DNS returns only an IPv4 address and the network is IPv6‑only, DNS64 prefixes it with

64:ff9b::/96

.

Client connects to the resulting IPv6 address.

Router recognizes the NAT64 prefix and forwards traffic to the IPv4 destination.

Returned IPv4 packets are translated back to IPv6 with the same prefix.

Apple documentation illustration:

//NAT64 address sample //address init const char* ipv6_str ="64:ff9b::14.17.32.211"; in6_addr ipv6_addr = {0}; int v6_r = inet_pton(AF_INET6, ipv6_str, &ipv6_addr); sockaddr_in6 v6_addr = {0}; v6_addr.sin6_family = AF_INET6; v6_addr.sin6_port = htons(80); v6_addr.sin6_addr = ipv6_addr; //socket connect int v6_sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); std::string v6_error; if (0 != connect(v6_sock, (sockaddr*)&v6_addr, 28)) { v6_error = strerror(errno); } //get local ip sockaddr_in6 v6_local_addr = {0}; socklen_t v6_local_addr_len = 28; char v6_str_local_addr[64] = {0}; getpeername(v6_sock, (sockaddr*)&v6_local_addr, &v6_local_addr_len); inet_ntop(v6_local_addr.sin6_family, &v6_local_addr.sin6_addr, v6_str_local_addr, 64); close(v6_sock);

If the client does not perform DNS resolution (e.g., custom DNS), it must manually prepend the

64:ff9b::/96

prefix when the network is IPv6‑only.

v6 IP + IPv4‑only

Connection typically fails with “network is unreachable” or “no route to host”. Exceptions exist if the server provides Teredo or 6to4 tunnels.

v6 IP + IPv6‑only or IPv4‑IPv6

Communication works directly, but address selection may involve RFC 3484 when DNS returns 6to4, Teredo, or native IPv6 addresses.

These are the basic principles of IPv6 socket programming; detailed implementation will be covered in the next part.

IPv6iOSnetworkingsocket programmingNAT64DNS64
WeChat Client Technology Team
Written by

WeChat Client Technology Team

Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.