IPv6 Gating¶
SmokePing's FPing6 probe only produces meaningful data when the host can
actually reach the global IPv6 internet. Without it, every IPv6 target charts a
flat 100% loss — which looks like an outage but only means there is no IPv6
here. config-manager therefore checks global IPv6 reachability and omits IPv6
targets from the generated Targets file when it is unavailable.
Targets are not modified in the database. They stay exactly as configured and reappear on their own once a recheck sees IPv6 working.
Why "does the host have an IPv6 address" is not the test¶
Linux reports several kinds of address as scope global, and most of them
cannot reach the internet:
| Prefix | Kind | Globally routable |
|---|---|---|
2000::/3 |
Global unicast | yes |
fd00::/8 |
ULA (RFC 4193), including Tailscale's fd7a:115c:a1e0::/48 |
no |
fe80::/10 |
Link-local | no |
A router handing out a ULA prefix, or a Tailscale interface, is enough to make
ip -6 addr show scope global non-empty on a host with no IPv6 service at all.
The check¶
Three signals, cheapest first — all must pass:
- A global unicast address (
2000::/3). ULA and link-local do not count. - A default IPv6 route. Without one the kernel rejects the packet outright
(
connect: Network is unreachable) and nothing leaves the host.unreachable/prohibit/blackholedefault routes do not count. - A reply from a probe host. The only signal that proves end-to-end reachability rather than local configuration. Any one of the configured addresses replying is enough.
If steps 1 and 2 fail there is no point paying the ping timeout, so the probe is skipped.
Where it runs¶
The check executes inside the SmokePing container, via docker exec, not
in config-manager. SmokePing uses network_mode: host, so its namespace is
what its probes actually see. config-manager sits on a Docker bridge network
with no IPv6 whatsoever — a check made locally would report "no IPv6" on a
perfectly IPv6-capable host.
When the verdict changes¶
The check runs at startup (before the first config generation) and then every
IPV6_RECHECK_INTERVAL seconds. Configuration is regenerated only when the
verdict flips, so a stable host does no repeated work:
- IPv6 starts working → targets return, SmokePing reloads.
- IPv6 stops working → targets drop out instead of charting 100% loss.
A check that cannot run at all (SmokePing container down, Docker socket unavailable) is reported as an error and leaves the previous verdict in place — a transient failure must never silently drop targets. Likewise, before the first successful check the status is unknown, which keeps IPv6 targets.
Configuration¶
| Variable | Default | Purpose |
|---|---|---|
IPV6_MODE |
auto |
auto probes the host; force always emits IPv6 targets; off never does |
IPV6_PROBE_HOSTS |
2001:4860:4860::8888,2606:4700:4700::1111 |
Comma-separated addresses to ping |
IPV6_RECHECK_INTERVAL |
900 |
Seconds between background rechecks |
force is the escape hatch for a network the probe cannot classify (for
example, IPv6 reachable only through a proxy, or ICMP filtered outbound). The
recheck thread does not run unless the mode is auto.
Inspecting the current verdict¶
curl -H "Authorization: Bearer $CONFIG_API_TOKEN" \
http://localhost:5000/ipv6-status
{
"available": false,
"reason": "no global IPv6 address on this host (only ULA/link-local, which cannot route to the IPv6 internet)",
"addresses": [],
"mode": "auto",
"measurements_allowed": false,
"recheck_interval": 900
}
Force an immediate re-check (regenerates config if the verdict flips):
curl -X POST -H "Authorization: Bearer $CONFIG_API_TOKEN" \
http://localhost:5000/ipv6-status/refresh
When targets are being omitted, the generated Targets file says so near the
top, so the reason is visible where the effect is:
# IPv6 targets omitted: no global IPv6 address on this host ...
# They return automatically once a recheck sees global IPv6 working.
Interaction with alerting¶
The alerter's ipv6_down rule fires when every IPv6 target is at 100% loss
while IPv4 is healthy. With gating enabled that rule now means what it says —
"IPv6 was working and broke" — because a host that never had IPv6 stops
producing IPv6 measurements altogether, and the incident clears once the
existing points age out of the 15-minute window.