< Back to all hacks

#44 IPv6 DNS + autoSelectFamily

Network
Problem
DNS resolution fails intermittently in proot with IPv4-only resolv.conf.
Solution
Force IPv6 DNS servers in /etc/resolv.conf + autoSelectFamily: true in OpenClaw config.
Lesson
Proot's resolv.conf inherits Android's DNS which rotates. Pinning Google's IPv6 DNS and enabling dual-stack selection eliminates intermittent failures.

Context

The OpenClaw gateway makes HTTPS requests to multiple AI provider APIs (Kimi, Gemini, Groq, etc.). Each request starts with DNS resolution. Inside proot, the /etc/resolv.conf initially inherited Android's DNS configuration, which can include the router's local DNS (192.168.1.254) or carrier DNS servers. These DNS servers are unreliable in the proot environment for several reasons:

  • Android's DNS resolution goes through netd, but proot intercepts syscalls at the userspace level. The DNS resolver inside proot uses standard glibc getaddrinfo(), which reads /etc/resolv.conf directly instead of going through Android's DNS infrastructure
  • The router's local DNS (192.168.1.254) sometimes returns SERVFAIL for certain API domains, particularly for providers hosted on Cloudflare or Google Cloud
  • When the WiFi reconnects (after a drop or Doze wake), Android may briefly update DNS settings, but proot's resolv.conf is static

The fix has two parts: pin reliable DNS servers in resolv.conf, and enable Node.js's autoSelectFamily to try both IPv4 and IPv6 connections.

Implementation

Configure DNS inside the proot rootfs (or the native Termux environment):

# /etc/resolv.conf — pinned DNS servers
# Google Public DNS (IPv6 primary, IPv4 fallback)
nameserver 2001:4860:4860::8888
nameserver 2001:4860:4860::8844
nameserver 8.8.8.8

Write the file:

# Inside proot or native Termux
cat > /etc/resolv.conf << 'EOF'
nameserver 2001:4860:4860::8888
nameserver 2001:4860:4860::8844
nameserver 8.8.8.8
EOF

# Make it read-only to prevent Android from overwriting it
chmod 444 /etc/resolv.conf

Why IPv6 DNS servers first? On the Moto E2's network (WiFi behind a home router), Google's IPv6 DNS responds faster because the router supports IPv6 natively. The IPv4 entry (8.8.8.8) serves as a fallback if IPv6 is unreachable.

Enable autoSelectFamily in OpenClaw's configuration to handle dual-stack DNS responses:

{
  "network": {
    "autoSelectFamily": true,
    "autoSelectFamilyAttemptTimeout": 250
  }
}

This maps to Node.js's net.setDefaultAutoSelectFamily(true), introduced in Node.js v18.13. When a DNS query returns both A (IPv4) and AAAA (IPv6) records, Node.js tries to connect via the first address family that responds, rather than always preferring IPv4 or IPv6. The timeout of 250 ms means if the first family doesn't connect within 250 ms, Node.js simultaneously tries the other.

For the native Termux setup (post proot migration), the same resolv.conf approach works:

# Native Termux resolv.conf location
cat > $PREFIX/etc/resolv.conf << 'EOF'
nameserver 2001:4860:4860::8888
nameserver 2001:4860:4860::8844
nameserver 8.8.8.8
EOF
chmod 444 $PREFIX/etc/resolv.conf

Verification

# Test DNS resolution directly:
node22-icu -e "
  const dns = require('dns');
  dns.resolve4('api.openai.com', (err, addresses) => {
    console.log('IPv4:', err || addresses);
  });
  dns.resolve6('api.openai.com', (err, addresses) => {
    console.log('IPv6:', err || addresses);
  });
"
# Expected: at least one of IPv4/IPv6 returns addresses

# Test that autoSelectFamily is active:
node22-icu -e "
  const net = require('net');
  console.log('autoSelectFamily:', net.getDefaultAutoSelectFamily());
"
# Expected: autoSelectFamily: true

# Test an actual HTTPS request (e.g., to a known API endpoint):
node22-icu -e "
  const https = require('https');
  https.get('https://generativelanguage.googleapis.com', { timeout: 5000 }, (res) => {
    console.log('Status:', res.statusCode);
    res.destroy();
  }).on('error', (e) => console.log('Error:', e.message));
"
# Expected: Status: 404 (or similar — confirms DNS + TLS work)

# Verify resolv.conf is intact:
cat /etc/resolv.conf
# Expected: the three nameserver lines above

Gotchas

  • Android may overwrite /etc/resolv.conf on WiFi reconnect events, reverting to the router's DNS. Setting chmod 444 helps but is not foolproof inside proot. A cron job to re-check the contents every 5 minutes is a belt-and-suspenders approach
  • Some home routers block or intercept DNS traffic to non-standard servers (DNS hijacking). If pinned Google DNS stops working, check your router settings for DNS rebinding protection or forced DNS redirection
  • autoSelectFamily adds ~250 ms latency on the FIRST connection to each host if the preferred address family is unreachable. Subsequent connections use the cached successful family. This initial penalty is negligible for API calls that take 1-30 seconds
  • IPv6 DNS servers require the network path to support IPv6. If your WiFi network is IPv4-only with no IPv6 connectivity, put the 8.8.8.8 entry first
  • In the native Termux setup (no proot), Termux uses Android's DNS by default via getaddrinfo() through Bionic libc. The resolv.conf approach only works if Node.js is compiled against glibc (proot) or if you explicitly configure the dns module. With the native node22-icu build, Android's DNS resolution is used, which generally works better than proot's glibc resolver
  • Cloudflare DNS (2606:4700:4700::1111) is an alternative to Google DNS. Some users report better latency depending on location

Result

MetricBeforeAfter
DNS failures per day5-15 intermittent0
DNS resolverRouter (192.168.1.254)Google (2001:4860:4860::8888)
Address family selectionIPv4 onlyDual-stack (autoSelectFamily)
First-connection overheadN/A+250 ms max (one-time per host)
API request reliability~95%~99.9% (network permitting)
resolv.conf persistenceOverwritten by Androidchmod 444 (protected)