IPv6 Address Present but No Connectivity: A Complete Diagnosis of an iStoreOS ip6class Mismatch
This article was last updated on: November 9, 2026 pm
I recently tested my home network with the “My NAT” and “NatTypeTester” tools and found that it has the worst NAT type of all: NAT4 - “Symmetric NAT”. As shown below:

This kind of NAT has extremely poor NAT traversal capability and has to be optimized. There are a few approaches:
- ❌ Ask the ISP for a public address: tried it, they won’t give one
- ✅ Switch the ONT to bridge mode: one less layer of NAT translation, which helps a lot
- ❌ Router configuration and firewall optimization: already optimized before testing, and the result was still NAT4 - “Symmetric NAT”. So it has nothing to do with this.
So I contacted the ISP and had the ONT switched to bridge mode over the past few days, using my own router - iStoreOS (based on OpenWrt) - to dial up. After the change:


🎉🎉🎉 At least it’s no longer the worst!
Then, a few days later, I noticed that after the change, the IPv6 line in Tailscale netcheck said no. All 5 machines on the same LAN showed no on that line.
It came down to a prefix delegation mismatch: the router’s dial-up mode had changed, but the IPv6-related configuration had not been updated in time.
📝Disclaimer: The public IPv6 prefixes in this article have been masked, with
xxxxstanding in for the actual values.
1. Analyzing the Tailscale Output
IPv6: no, but OS has support.
This is easily read as “this machine has no IPv6”. That is not what it means.
Behind this line, netcheck looks at at least three things:
- Whether the operating system has IPv6 capability,
- Whether packets can be sent out,
- Whether a STUN round trip can complete.
Looking at the raw fields with tailscale netcheck --format json, all 5 nodes produced identical results:
| Field | Value | Meaning |
|---|---|---|
OSHasIPv6 |
true | The system has an IPv6 stack |
IPv6CanSend |
true | There is a default route, so packets can be sent |
IPv6 |
false | Not a single STUN round trip completed |
GlobalV6 |
empty | No usable global address was detected |
IPv6CanSend corresponds to:
- a global address in
ip -6 addr(the /64 assigned by SLAAC), - a default in
ip -6 route show.
When both are satisfied, it is true.
2. The Evidence Chain: From 0.5ms to a Black Hole
Testing step by step:
- The LAN is fine: pinging the router and nodes on the same subnet gives 0.5ms and 0% packet loss.
traceroute6to2400:xxxx::1: the first hop is the router, and from the second hop on everything is*.tcpdump: only echo requests going out, with no ICMPv6 errors coming back.- ping6 from different sources on the router: ping6 from the router itself works, ping6 with the WAN source works, ping6 with the LAN source has 100% packet loss.
By step 4, the problem space had narrowed from “the ISP” to “the LAN forwarding path inside the router”.
Next, look at the egress device:
1 | |
The output shows the egress as via fe80::1 dev eth1.
eth1 is the bare physical port, not pppoe-wan. LAN IPv6 traffic was being thrown into an interface without PPPoE encapsulation, so even when it left there was nothing there to receive it.
3. Root Cause: A Mismatch Built from Three uci Settings
📝Notes: This section covers iStoreOS/OpenWrt uci configuration. Other systems use different names for the corresponding settings, but the mechanism is similar.
The ISP’s PD is 240e:xxxx:xxxx:xxxx::/60 (the actual prefix is masked), i.e. 16 /64s.
But the LAN was actually using a different 240e:xxxx:xxxx:xxxx::/64, which is not among those 16 /64s at all.
Three uci settings together produce this result:
network.lan.ip6class='wan6': the LAN only accepts prefixes offered by thewan6interface. Butwan6is bound to the bareeth1, not the PPPoEwan_6. This is the main cause. (⚠️ Before PPPoE, the WAN interface was calledwan6- no underscore; after PPPoE, the WAN interface is calledwan_6- with an underscore.)network.wan.delegate='0': prevents the /60 obtained via PPPoE from being delegated to the LAN.network.wan6.device='eth1': an extra DHCPv6 client bound to the physical port.
With all three stacked together, the LAN ended up holding a /64 outside the delegated range, paired with a default route pointing at the bare eth1.
4. Fix and Verification
- Back up
/etc/config/network. uci set network.lan.ip6class='wan_6'.uci set network.wan.delegate='1'.uci commit, thenifup lan.- Verify three things:
ip -6 addr show br-lan: check whether the prefix falls inside the ISP’s /60;ubus call network.interface.wan_6 status: check the delegated prefix;uci get network.lan.ip6class: confirm the class matches.
- Once confirmed working, set
network.wan6.disabled='1', thenifup wan6.
After the fix, the LAN prefix became 240e:xxxx:xxxx:xxxx::/64, and the default route goes via pppoe-wan.
netcheck went from IPv6: no to IPv6: yes. As shown below:
1 | |
ping6 went from 100% packet loss to 13.8ms to AliDNS.
With Tailscale enabled on my phone (which has an IPv6 address), it can now maintain a 100% stable direct connection to the other Tailscale devices at home. As shown below:

5. The Easiest Step to Miss: Stale Client Addresses
Even with the router fixed, the client’s netcheck may still say no.
The reason is that the old address assigned by SLAAC still has about 90 minutes of valid_lft left, and the kernel may still pick that old prefix when doing source address selection.
Clear it manually:
1 | |
Miss this step and you get the illusion that “it’s configured but doesn’t work”.
6. A Side Discovery: CGNAT and Hole Punching
While troubleshooting, I also took a look at pppoe-wan; its address is 100.x.x.x/32, which falls inside 100.64.0.0/10, the Shared Address Space defined by RFC 6598.
In other words, my China Telecom broadband has no public IPv4 address.
The IGNORED: private/reserved address message from miniupnpd on the router is not a fault: port forwarding could never reach the public internet in the first place, and UPnP in PortMapping can never work properly.
This CGNAT is cone-type (specifically port-restricted cone) (MappingVariesByDestIP: false), so IPv4 hole punching can still succeed. That is why, before the fix, direct IPv4 connections were never completely broken - they just worked intermittently.
There are still only two ways to fix it properly:
- ✅ Fix IPv6 (already done),
- ❌ Or apply to China Telecom for a public IP - impossible
Also, China Telecom changes the PD prefix on every redial, and the PD prefix did change this time (the actual prefix is masked). This is normal; once the configuration is written into uci, the LAN follows automatically, so there is no need to change it manually each time.
7. A Five-Step Troubleshooting Sequence
Here it is as a reusable sequence; next time, even on a different device, you can follow along:
ip -6 addr/ip -6 route show: is there a global address, is there a default route.traceroute6 2400:xxxx::1: at which hop it goes silent. Dying at hop 1 is a LAN problem; dying at hop 2 is a gateway problem.- On the router,
ping6 -I <WAN地址> <公网v6>: determine whether the ISP side is reachable. ip -6 route get <公网v6> from <LAN地址>: check whether the egress dev ispppoe-wanorwan.ubus call network.interface.wan_6 statusplusuci get network.lan.ip6class: verify that the delegated prefix and the LAN prefix, and the class, all match.
Conclusion
What makes IPv6: no, but OS has support so confusing is that it compresses three things into one line: system capability, packet-sending capability, and connectivity.
The first two are true and the third is false, so the output becomes a single no.
Break the three apart, then use ip -6 route get ... from ... to identify the egress device.
On iStoreOS, ip6class, delegate, and wan6.device are intertwined; changing just one is not enough. After the change, remember to clear the client’s old address - until the old address’s valid_lft expires, the client may keep holding on to the old prefix.
That’s all.