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:

NAT4 - "Symmetric NAT"

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:

iStoreOS PPPoE dial-up

NAT3 - Port-Restricted Cone NAT

🎉🎉🎉 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 xxxx standing 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:

  1. Whether the operating system has IPv6 capability,
  2. Whether packets can be sent out,
  3. 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:

  1. The LAN is fine: pinging the router and nodes on the same subnet gives 0.5ms and 0% packet loss.
  2. traceroute6 to 2400:xxxx::1: the first hop is the router, and from the second hop on everything is *.
  3. tcpdump: only echo requests going out, with no ICMPv6 errors coming back.
  4. 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
ip -6 route get 2400:xxxx::1 from <LAN地址>

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:

  1. network.lan.ip6class='wan6': the LAN only accepts prefixes offered by the wan6 interface. But wan6 is bound to the bare eth1, not the PPPoE wan_6. This is the main cause. (⚠️ Before PPPoE, the WAN interface was called wan6 - no underscore; after PPPoE, the WAN interface is called wan_6 - with an underscore.)
  2. network.wan.delegate='0': prevents the /60 obtained via PPPoE from being delegated to the LAN.
  3. 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

  1. Back up /etc/config/network.
  2. uci set network.lan.ip6class='wan_6'.
  3. uci set network.wan.delegate='1'.
  4. uci commit, then ifup lan.
  5. 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.
  6. Once confirmed working, set network.wan6.disabled='1', then ifup 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
2
3
4
5
6
7
8
Report:
* Time: 2026-11-09 15:01:27.560476242+08:00
* UDP: true
* IPv4: yes, 49.76.xxx.xx:17338
* IPv6: yes, [240e:3a3:20bb:8xxx:xxxx:xxxx:xxxx:xxxx]:60572
* MappingVariesByDestIP: false
* PortMapping: UPnP, NAT-PMP, PCP
* CaptivePortal: false

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:

Phone maintains a 100% stable direct connection to home

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
sudo ip -6 addr flush dev enp37s0 scope global

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:

  1. ip -6 addr / ip -6 route show: is there a global address, is there a default route.
  2. 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.
  3. On the router, ping6 -I <WAN地址> <公网v6>: determine whether the ISP side is reachable.
  4. ip -6 route get <公网v6> from <LAN地址>: check whether the egress dev is pppoe-wan or wan.
  5. ubus call network.interface.wan_6 status plus uci 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.

📚️References


IPv6 Address Present but No Connectivity: A Complete Diagnosis of an iStoreOS ip6class Mismatch
https://e-whisper.com/posts/51252/
Author
east4ming
Posted on
November 9, 2026
Licensed under