IPv6 Connectivity Issues: Dual-Stack Debugging

Debug IPv6 connectivity in dual-stack environments, including Router Advertisement problems, DHCPv6 vs SLAAC, Happy Eyeballs, and tunnel broker setup.

Why IPv6 Connectivity Is Tricky

Unlike IPv4, which relies on DHCP for address assignment and static routing tables, IPv6 supports multiple address assignment mechanisms (SLAAC and DHCPv6), introduces link-local addresses that can surprise administrators, and uses Router Advertisements (RAs) instead of DHCP for default gateway configuration. Any one of these can fail silently while IPv4 continues to work, making the issue invisible until a user hits an IPv6-only resource.

Is IPv6 Working? Quick Tests

Start with basic reachability tests before digging into configuration.

# Check IPv6 addresses assigned to your interfaces
ip -6 addr show
ifconfig | grep inet6   # macOS/BSD

# You should see at minimum:
# - Link-local address (fe80::/10) — always present if IPv6 is enabled
# - Global unicast address (2xxx: or 3xxx:) — required for internet access

# Ping the IPv6 loopback
ping6 ::1

# Ping a global IPv6 address (Google)
ping6 2001:4860:4860::8888

# Ping a hostname (tests DNS AAAA resolution)
ping6 ipv6.google.com

# Test from a website
curl -6 https://ipv6.google.com/
curl -6 https://ifconfig.co/

# Check your public IPv6 address
curl -6 https://ifconfig.me

If ping6 2001:4860:4860::8888 works but ping6 ipv6.google.com fails, the problem is DNS (missing AAAA resolution). If the global ping fails but link-local works, the problem is either routing or address assignment.

Router Advertisement Problems

In IPv6, the default gateway is configured through Router Advertisement (RA) messages sent by routers. If your device does not receive RAs, it will not have a default route.

# Check IPv6 routing table
ip -6 route show
# You need a default route: ::/0 via fe80::xxxx dev eth0

# If no default route, check if RAs are arriving
sudo tcpdump -i eth0 icmp6 and 'ip6[40] = 134'
# 134 = ICMPv6 type for Router Advertisement

# Check radvd (router advertisement daemon) on the router
sudo systemctl status radvd
sudo cat /etc/radvd.conf

# Manually trigger RA solicitation from client
sudo rdisc6 eth0   # Linux
# macOS: System Preferences -> Network shows IPv6 if RA received

Common RA issues:

Problem Symptom Fix
radvd not running No default route, no global address Start radvd: systemctl start radvd
Wrong prefix in RA Client gets wrong prefix Correct prefix in radvd.conf
RA rate too low Address lost after brief outage Reduce MinRtrAdvInterval
Firewall blocking ICMPv6 RA never reaches client Allow ICMPv6 type 133 and 134
# iptables: allow ICMPv6 (required for IPv6 to function)
sudo ip6tables -A INPUT -p icmpv6 -j ACCEPT
sudo ip6tables -A OUTPUT -p icmpv6 -j ACCEPT
sudo ip6tables -A FORWARD -p icmpv6 -j ACCEPT
# Blocking ICMPv6 breaks IPv6 completely — it is not optional

DHCPv6 vs SLAAC

IPv6 address assignment can happen through two mechanisms, and they are not mutually exclusive:

SLAAC (Stateless Address Autoconfiguration): The host generates its own address by combining the /64 prefix from the RA with a locally-generated interface ID (based on MAC or random). No DHCPv6 server needed.

DHCPv6 (Stateful): A DHCPv6 server assigns addresses explicitly, similar to IPv4 DHCP. Controlled by the M (Managed) flag in the RA.

DHCPv6 Stateless (Informational): SLAAC handles the address, but DHCPv6 provides other options like DNS server addresses. Controlled by the O (Other Config) flag.

# Check what flags the RA is advertising
sudo tcpdump -i eth0 -vv icmp6 2>/dev/null | grep -E "flags|prefix|M|O"

# Or use radvdump
sudo radvdump   # shows received RAs

# Linux: check current DHCPv6 client status
systemctl status dhclient
systemctl status wide-dhcpv6-client
cat /var/lib/dhcpv6/dhcp6c-eth0.conf

# Force SLAAC only (ignore DHCPv6)
sysctl net.ipv6.conf.eth0.accept_ra=1
sysctl net.ipv6.conf.eth0.autoconf=1

Happy Eyeballs Algorithm

Happy Eyeballs (RFC 6555) is a client algorithm that attempts both IPv6 and IPv4 connections in parallel, preferring IPv6 but falling back to IPv4 if IPv6 fails. This means IPv6 problems may not be immediately visible — the browser silently falls back to IPv4.

# Force IPv6 only with curl
curl -6 https://yourdomain.com

# Force IPv4 only with curl
curl -4 https://yourdomain.com

# Check which address was used
curl -v https://yourdomain.com 2>&1 | grep "Connected to"
# "Connected to yourdomain.com (2001:db8::1)" = IPv6
# "Connected to yourdomain.com (203.0.113.1)" = IPv4

# Measure actual connection time for each
time curl -6 -o /dev/null -s https://yourdomain.com
time curl -4 -o /dev/null -s https://yourdomain.com

If IPv6 is consistently slower than IPv4, the Happy Eyeballs delay (typically 250ms) is triggering. Fix the underlying IPv6 performance issue rather than disabling IPv6 entirely.

Disabling IPv6 (When Necessary)

Disabling IPv6 is a last resort — it breaks IPv6-only resources and may cause Happy Eyeballs delays. However, when a broken IPv6 stack is causing connectivity issues, temporary disabling helps isolate the problem.

# Disable IPv6 on a single interface (temporary)
sudo sysctl -w net.ipv6.conf.eth0.disable_ipv6=1

# Disable IPv6 system-wide (temporary)
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1

# Make permanent (add to /etc/sysctl.d/99-ipv6.conf)
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# Re-enable
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0

# macOS: disable via System Preferences
# Network -> interface -> Advanced -> TCP/IP -> Configure IPv6: Off

Tunnel Brokers

If your ISP does not provide native IPv6, a tunnel broker gives you IPv6 connectivity over IPv4. Hurricane Electric's free broker is the most widely used.

# Hurricane Electric tunnel setup (6in4 tunnel)
# After signing up at tunnelbroker.net:

# Create tunnel interface
sudo ip tunnel add he-ipv6 mode sit remote HE_SERVER_IPv4 local YOUR_IPv4 ttl 255
sudo ip link set he-ipv6 up
sudo ip addr add YOUR_CLIENT_IPv6/64 dev he-ipv6
sudo ip route add ::/0 dev he-ipv6

# Verify tunnel
ping6 HE_SERVER_IPv6
ping6 ipv6.google.com

# To make persistent (systemd-networkd):
# /etc/systemd/network/he-ipv6.netdev
[NetDev]
Name=he-ipv6
Kind=sit
[Tunnel]
Remote=HE_SERVER_IPv4
Local=YOUR_IPv4
TTL=255

Provider Support Check

Before deep debugging, verify your ISP actually supports IPv6:

# Check if your ISP announces IPv6 prefixes
# Visit https://bgp.he.net and look up your ISP's ASN

# Check if your router received an IPv6 prefix delegation
# On Linux router:
ip -6 addr show dev eth0 | grep "scope global"

# Most ISPs provide /64 prefix for home users, /56 or /48 for businesses

# Test ISP IPv6 support:
# IPv6 test site: https://test-ipv6.com
# Shows readiness score and detects partial IPv6 issues
curl -6 -s https://api6.ipify.org   # Returns IPv6 if native IPv6 works
ISP Type Expected IPv6 Support Typical Configuration
Major residential (US, EU) Native dual-stack DHCPv6 + RA
Business fiber Native dual-stack + PA/PI Static or DHCPv6
CGNAT residential IPv4-only or DS-Lite May need tunnel broker
Corporate WAN Often IPv4-only Contact network team