How to Detect the Available IP Stack (IPv4/IPv6/Dual) on iOS and Android
This article explains how to determine whether a client device supports IPv4‑only, IPv6‑only, or dual‑stack networking on iOS and Android, covering gateway detection, DNS analysis, socket‑connect tests, and practical code samples for reliable IP‑stack identification.
Determine the Client's Available IP Stack
The goal is to identify whether the device can use an IPv4‑only, IPv6‑only, or IPv4‑IPv6 dual stack. This information is essential for correctly handling network requests on iOS and Android.
Getting Local IP and Gateway (iOS)
iOS can obtain the current gateway or route via sysctl. If only an IPv6 gateway is found, the network is IPv6‑only; if only an IPv4 gateway is found, it is IPv4‑only; if both are present, the situation is more complex.
Using getifaddrs can retrieve all active, non‑loopback interface addresses. By examining which interface (Wi‑Fi or Mobile) each gateway belongs to, one can infer the active IP stack, though edge cases (e.g., Wi‑Fi IPv4 gateway with Mobile IPv6 gateway) exist.
This iOS‑specific solution is tightly coupled to the platform and may require updates when iOS changes its networking logic.
DNS‑Based Approach
The idea is to resolve a domain (e.g., dns.weixin.qq.com) and check whether the returned address contains the NAT64 prefix 64:ff9b. If it does, the network is an IPv6‑only NAT64 environment.
in6_addr addr6_gateway = {0};
if (0 != getdefaultgateway6(&addr6_gateway))
return EIPv4;
if (IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway))
return EIPv4;
in_addr addr_gateway = {0};
if (0 != getdefaultgateway(&addr_gateway))
return EIPv6;
if (INADDR_NONE == addr_gateway.s_addr || INADDR_ANY == addr_gateway.s_addr)
return EIPv6;
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG|AI_V4MAPPED;
int error = getaddrinfo("dns.weixin.qq.com", "http", &hints, &res0);
if (0 != error) {
return EIPv4;
}
for (res = res0; res; res = res->ai_next) {
if (AF_INET6 == res->ai_addr.sa_family) {
if (is_nat64_address(((sockaddr_in6&)res->ai_addr).sin6_addr)) {
return EIPv6;
}
}
}
return EIPv4;This method relies on DNS resolution, which can be slow or fail, and it generates network traffic, making it unsuitable for real‑time detection.
Socket‑Connect Method (iOS 9 and Android)
Attempt a TCP connection to a known IPv4 address and a known IPv6 address; the result indicates which stack is usable.
int _test_connect(int pf, struct sockaddr *addr, size_t addrlen) {
int s = socket(pf, SOCK_STREAM, IPPROTO_TCP);
if (s < 0) return 0;
int ret;
do { ret = connect(s, addr, addrlen); } while (ret < 0 && errno == EINTR);
int success = errno;
do { ret = close(s); } while (ret < 0 && errno == EINTR);
return success;
}
static int _have_ipv6() {
static const struct sockaddr_in6 sin6_test = {
.sin6_len = sizeof(sockaddr_in6),
.sin6_family = AF_INET6,
.sin6_port = htons(0xFFFF),
.sin6_addr.s6_addr = {0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
sockaddr_union addr = { .in6 = sin6_test };
return _test_connect(PF_INET6, &addr.generic, sizeof(addr.in6));
}
static int _have_ipv4() {
static const struct sockaddr_in sin_test = {
.sin_len = sizeof(sockaddr_in),
.sin_family = AF_INET,
.sin_port = htons(0xFFFF),
.sin_addr.s_addr = htonl(0x08080808L) // 8.8.8.8
};
sockaddr_union addr = { .in = sin_test };
return _test_connect(PF_INET, &addr.generic, sizeof(addr.in));
}
enum TLocalIPStack { ELocalIPStack_None = 0, ELocalIPStack_IPv4 = 1, ELocalIPStack_IPv6 = 2, ELocalIPStack_Dual = 3 };
void test() {
int errno_ipv4 = _have_ipv4();
int errno_ipv6 = _have_ipv6();
int local_stack = 0;
if (errno_ipv4 != EHOSTUNREACH && errno_ipv4 != ENETUNREACH) local_stack |= ELocalIPStack_IPv4;
if (errno_ipv6 != EHOSTUNREACH && errno_ipv6 != ENETUNREACH) local_stack |= ELocalIPStack_IPv6;
}The approach suffers from unpredictable latency because it performs real network connections; successful connections consume traffic and may stress servers.
Mixed Approach (Mac OS, iOS, Linux, Android; Windows/WSL pending)
A combined method first checks default gateways, then uses the socket‑connect tests, and finally falls back to DNS‑server address inspection to differentiate IPv4 and IPv6 stacks without generating traffic.
TLocalIPStack local_ipstack_detect() {
in6_addr addr6_gateway = {0};
if (0 != getdefaultgateway6(&addr6_gateway)) return ELocalIPStack_IPv4;
if (IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway)) return ELocalIPStack_IPv4;
in_addr addr_gateway = {0};
if (0 != getdefaultgateway(&addr_gateway)) return ELocalIPStack_IPv6;
if (INADDR_NONE == addr_gateway.s_addr || INADDR_ANY == addr_gateway.s_addr) return ELocalIPStack_IPv6;
int have_ipv4 = _have_ipv4();
int have_ipv6 = _have_ipv6();
int local_stack = 0;
if (have_ipv4) local_stack |= ELocalIPStack_IPv4;
if (have_ipv6) local_stack |= ELocalIPStack_IPv6;
if (ELocalIPStack_Dual != local_stack) return (TLocalIPStack)local_stack;
int dns_ip_stack = 0;
std::vector<socket_address> dnssvraddrs;
getdnssvraddrs(dnssvraddrs);
for (int i = 0; i < dnssvraddrs.size(); ++i) {
if (AF_INET == dnssvraddrs[i].address().sa_family) dns_ip_stack |= ELocalIPStack_IPv4;
if (AF_INET6 == dnssvraddrs[i].address().sa_family) dns_ip_stack |= ELocalIPStack_IPv6;
}
return (TLocalIPStack)(ELocalIPStack_None == dns_ip_stack ? local_stack : dns_ip_stack);
}This solution is local, low‑cost, and avoids network traffic, though it cannot detect NAT64 support.
Additional Practical Tips
Prefer sockaddr_storage over platform‑specific structures when writing dual‑stack code.
A compact union of sockaddr, sockaddr_in, and sockaddr_in6 can reduce memory usage to 28 bytes.
When constructing URLs with literal IPv6 addresses, enclose the address in square brackets (e.g., http://[2001:db8::1]/).
On iOS, avoid using deprecated APIs such as gethostbyname; use getaddrinfo with AI_V4MAPPED and AI_ADDRCONFIG flags.
For reachability checks, create SCNetworkReachability objects for both IPv4 and IPv6 zero addresses, or use SCNetworkReachabilityCreateWithName for name‑based checks.
References
Apple’s “Supporting IPv6 DNS64/NAT64 Networks” guide, RFC 4038, Beej’s Guide to Network Programming, and various platform‑specific documentation provide deeper explanations of the concepts discussed.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
