No description
  • JavaScript 57.5%
  • Go 32.5%
  • HTML 9.3%
  • Python 0.6%
  • Makefile 0.1%
Find a file
Delegao f7c618c0a0
Some checks failed
checks / check (1.25.x) (push) Has been cancelled
checks / check (stable) (push) Has been cancelled
feat(about): condition GeoLite notice on configured MMDB paths
2026-09-24 15:53:27 +02:00
.github/workflows docs: add nginx deployment, systemd service and CI checks 2026-09-22 23:06:38 +02:00
cmd/ipv6sx feat: serve IP formats and verified dual-stack connectivity probes 2026-09-22 23:06:06 +02:00
deploy docs: add nginx deployment, systemd service and CI checks 2026-09-22 23:06:38 +02:00
docs feat(stats): persist country-ASN intersections for country rankings 2026-09-24 15:49:53 +02:00
internal feat(about): condition GeoLite notice on configured MMDB paths 2026-09-24 15:53:27 +02:00
scripts feat(stats): add bundled interactive country map 2026-09-24 15:49:19 +02:00
web feat(about): condition GeoLite notice on configured MMDB paths 2026-09-24 15:53:27 +02:00
.gitattributes feat: integrate A1 branding, icons and social previews 2026-09-24 14:16:57 +02:00
.gitignore feat: add offline geolocation and persistent aggregate statistics 2026-09-22 23:00:34 +02:00
DESIGN.md feat(stats): persist country-ASN intersections for country rankings 2026-09-24 15:49:53 +02:00
go.mod feat: add offline geolocation and persistent aggregate statistics 2026-09-22 23:00:34 +02:00
go.sum feat: add offline geolocation and persistent aggregate statistics 2026-09-22 23:00:34 +02:00
Makefile docs: add nginx deployment, systemd service and CI checks 2026-09-22 23:06:38 +02:00
README.md feat(stats): persist country-ASN intersections for country rankings 2026-09-24 15:49:53 +02:00

ipv6.sx

A small IPv4/IPv6 connectivity checker written in Go. One binary serves the English interface, JSON/XML/text endpoints, statistics, and 88×31 web buttons. The interface uses plain HTML, CSS, and JavaScript, with no frontend build step, external fonts, analytics scripts, or runtime geolocation service.

The public deployment uses three origins:

Host DNS Purpose
ipv6.sx A + AAAA Website and the connection family selected by the browser
v4.ipv6.sx A only IPv4 address and latency probes
v6.ipv6.sx AAAA only IPv6 address and latency probes

Use direct DNS records, without a CDN proxy. Serve each hostname with its own single-host TLS certificate so the browser cannot reuse an HTTP/2 connection across the probe origins. See deployment.

Run locally

Install Go 1.25 or newer, then run:

go run ./cmd/ipv6sx

Open http://localhost:8080. The local defaults probe 127.0.0.1 and ::1; this checks your local loopback setup, not your ISP's IPv6 connectivity. The server must listen on both families for both local probes to succeed. Opening the site through another hostname requires changing IPV6SX_SITE_URL to match. For an entirely loopback setup, the content security policy permits the configured HTTP(S) schemes for probes and images, because browsers cannot match literal IPv6 addresses in CSP host sources. Production uses DNS hostnames and restricts those requests to the configured origins.

curl http://localhost:8080/text
curl http://localhost:8080/json
curl http://localhost:8080/xml
curl http://localhost:8080/api/stats

Build and check the application:

make build
make check

make check runs go test -race ./..., go vet ./..., and the dependency-free JavaScript tests with Node.js 22 or newer. Node is only needed for these tests; it is not needed to build or run the server. The binary is bin/ipv6sx.

What it measures

  • The address, IP family, and optional country/ASN for the request reaching the main origin; this is the displayed default family.
  • Connectivity to the IPv4-only and IPv6-only origins independently.
  • Six timed HTTP probes per family after one discarded warm-up, alternating which family runs first. A failed probe counts as a failure, never as zero ms.
  • Mean latency, variability, and the difference between IPv6 and IPv4 latency. These are browser HTTP timings, not ICMP ping. Browser scheduling, TLS, proxies, VPNs, and the server's location affect the result.
  • Aggregate results by UTC day, country, and ASN. Counts represent submitted tests, not unique people or devices, and repeated tests count again.
  • Filter /stats by country, ASN, or both and an inclusive UTC date range. The daily chart compares IPv6 availability with IPv6 used by default; missing observation days remain gaps. Filter URLs and JSON downloads preserve the selection.

Country and ASN come from optional local MaxMind-compatible MMDB files. Without them, connectivity and timing still work; attribution is shown as unknown. The application does not make geolocation requests to third parties.

See measurement and statistics semantics for precise definitions and limits, and the HTTP API for response examples.

Configuration

Configuration is read from environment variables at startup. The application does not load .env files automatically.

Variable Default Meaning
IPV6SX_LISTEN :8080 HTTP listener; use 127.0.0.1:8080 behind local nginx
IPV6SX_SITE_URL http://localhost:8080 Main public origin
IPV6SX_IPV4_URL http://127.0.0.1:8080 IPv4-only public origin
IPV6SX_IPV6_URL http://[::1]:8080 IPv6-only public origin
IPV6SX_STATS_FILE data/stats.json Aggregate JSON file; an empty value uses memory only
IPV6SX_RETENTION_DAYS 90 Number of UTC days retained, from 1 through 3660
IPV6SX_TRUSTED_PROXIES empty Comma-separated CIDRs allowed to provide X-Real-IP
IPV6SX_GEO_COUNTRY_DB empty Optional Country MMDB path
IPV6SX_GEO_ASN_DB empty Optional ASN MMDB path

Origins include the scheme and optional port, with no path, trailing slash, or query. Use HTTPS for all three origins in production. Set the main origin to exactly the address visitors use; the browser's probe requests use the configured origin policy. Providing an invalid database path fails startup rather than silently disabling an explicitly configured database.

Only one process may own a statistics file. The store persists aggregates, without raw IP addresses, tokens, or individual timing samples. Its JSON format is internal; use /api/stats to consume statistics. Your reverse proxy or host may have separate access/error logging policies.

Buttons and embedding

/embed provides copyable HTML for a static webring button and an IPv6 connectivity indicator. The latter layers an IPv6-only image over a fallback and needs no JavaScript. /badge.svg shows the family of the request fetching that image; /assets/webring.svg is a static link button.

The live badge reads IPv6 capable in green for IPv6 requests and IPv6 not detected otherwise. /assets/check-ipv6.svg adds a static Check your IPv6 connectivity! button. All buttons are 88×31 and have copyable HTML on /embed.

The indicator describes the visitor's ability to load the IPv6-only image. Image blocking, browser policies, or network filtering can prevent it loading. Loading a badge does not submit a test to the statistics store.

Branding

The complete A1 brand kit lives in web/assets/brand/, including vector masters, light/dark variants, PNG exports, favicons, app icons, social images, 88×31 buttons, and the Spanish usage guide. Browse its gallery at /assets/brand/. The gallery styles are external so it works with the site's content security policy; the supplied image assets are unchanged.

All four pages share the logo and icon/social metadata in web/brand.html. The header uses logo-light.svg because the interface has a fixed light background, while the SVG favicon follows the browser's color scheme. Social image URLs use IPV6SX_SITE_URL, including on self-hosted installations. The manifest retains its relative icon paths and root application scope.

/assets/favicon.svg and /assets/webring.svg remain available at their existing URLs with the new artwork. The live connectivity badges keep their protocol status labels. All assets are embedded in the Go binary: rebuild it when updating the brand kit. No frontend build or external asset service is needed.

Small nginx deployment example

The checked-in nginx configuration contains separate virtual hosts for the main site and the two probe hosts, plus a shared proxy snippet. The Go service listens on loopback:

IPV6SX_LISTEN=127.0.0.1:8080
IPV6SX_SITE_URL=https://ipv6.sx
IPV6SX_IPV4_URL=https://v4.ipv6.sx
IPV6SX_IPV6_URL=https://v6.ipv6.sx
IPV6SX_TRUSTED_PROXIES=127.0.0.1/32,::1/128
IPV6SX_STATS_FILE=/var/lib/ipv6sx/stats.json

nginx overwrites the supplied address when forwarding each request:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
}

Use the complete deployment instructions for DNS, certificates, systemd, persistent data, and verification commands. The snippets above alone do not configure the family-specific listeners or TLS.