Releases: 1mb-dev/natcheck
v0.1.4 — Sparrow
First in the natcheck birds-themed release series. Small, darting — like the hairpin probe.
Added
- RFC 5780 §4.3 hairpinning detection. New JSON field
"hairpinning": true | false | null(additive);nulldistinguishes "not tested" from "tested false." Probe uses two dedicated unconnected UDP sockets, STUN-probed in parallel against the first--serverentry, then a tagged loopback packet from socket A to mapped-B. Listen window default 1s. Wall-clock parallel cost ≤200ms in the common case; runs concurrent with mapping probes. - New warning constant
hairpin_untestedemitted when local socket setup or STUN probe failed. - Human-format report adds a
Hairpinning: true|falseline when the probe produced a value.
Fixed
warningTextno longer falls through to the raw warning ID formixed_address_family_probes(#19). Reads "Mapping classification spans IPv4 and IPv6; each family observes its own NAT."WarnInsufficientProbestext refined to "Insufficient probes for one or more address families." Under v0.1.3 combine semantics the warning can fire alongside a confident combined verdict; the old text implied no verdict was reached.
Changed
classify.Classifysignature gains a third parameter*probe.HairpinningResult. Callers passing nil get the same behavior as v0.1.3 plus the newhairpin_untestedwarning.docs/design.mdv0.2 staged-sequence labels reconciled: hairpinning is v0.1.4,natcheck serveris v0.1.5. Hairpinning cost claim updated to reflect actual ≤200ms parallel cost.
Migration note
JSON consumers reading warnings[] will see a new hairpin_untested value when hairpin probe failed setup. Consumers reading hairpinning must handle null as "did not test." Additive only; no removals.
Validation
Captured against a destroy-after coturn droplet on the v0.1.3 residential ISP — see docs/samples/hairpinning.{txt,json} and the validation log entry.
Install: go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.4
v0.1.3 — cross-address-family mapping classification fix
Closes #14. First 3-segment-semver tag after the v0.1.2.x patch incident — go install ...@v0.1.3 works correctly via the Go module proxy.
Fixed
- Cross-address-family probe sets no longer produce a wrong ADM/symmetric verdict. Previously, mixing IPv6-resolved hostname servers (e.g.,
stun.l.google.com) with IPv4-literal servers (e.g., a self-hosted coturn) compared mapped endpoints across address families and reported ADM because the endpoints differed by construction (each family observes its own NAT). The classifier now groups successes by address family, classifies each group independently, and combines under the rule "Unknown is absence of information, not disagreement": matching verdicts win, two confident verdicts that differ produce Unknown, a confident verdict beats Unknown from the other group.
Added
- New warning value
mixed_address_family_probesinwarnings[]. Emitted whenever successful probes span both IPv4 and IPv6 address families. Additive to the JSON schema.
Migration
JSON consumers checking nat_type == "ADM" for cross-family probe sets will see "Unknown" on the same input under v0.1.3 — the previous verdict was incorrect. Forecast-checking consumers (webrtc_forecast.direct_p2p) are mostly unaffected: the dominant cross-family disagreement case stays exit 1 (Unknown → 1, was ADM → 1). The verdict-flip case (genuinely agreed EIM across families, previously ADM, now EIM → exit 0) is the bug being fixed.
Install
brew tap 1mb-dev/tap
brew upgrade natcheck # if already installed
brew install natcheck # fresh installor
go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.3v0.1.2.2 — coturn validation works on more provider topologies
Note for
go installusers: this tag is 4-segment (v0.1.2.2), which Go module proxy treats as invalid semver and silently substitutes a pseudo-version. The Go binary is byte-identical tov0.1.2— no Go source changed in v0.1.2.1 or v0.1.2.2, the deltas are conf + docs + script that ship via Homebrew or repo clone, not viago install. Usego install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.2(same binary) orbrew install 1mb-dev/tap/natcheck(binary + corrected assets). v0.1.3 will use 3-segment semver.
Patch release. Closes #15 and #16. No code or JSON schema delta — same binary as v0.1.2 / v0.1.2.1.
Fixed
examples/coturn-natcheck.confnow uses the two-listening-ip+ two-external-ip-pair form explicitly. v0.1.2.1'sexternal-ip=PUBLIC/PRIVATEonly worked on AWS/GCP-style topologies where the two IPs differ naturally. On single-public-IP providers (DigitalOcean basic droplet, Linode Nanode, Hetzner single-IP), eth0's IP IS the public IP —external-ip=A/Adoesn't satisfy coturn's "two distinct IPs" requirement and coturn silently logsWARNING: ... only one IP address is providedwhile natcheck reportsfiltering: untested.docs/coturn-setup.mdadds a per-provider topology table (AWS/GCP / DO basic / bare metal) with a worked DigitalOcean Reserved IP example (ip addr add SECOND_IP/32 dev eth0).
Added
-
scripts/validate-coturn.sh— one-shot SSH-pipe provisioner that installs coturn, writes the conf, opens the firewall, starts coturn in tmux, and verifies the startup log for the two specific warning lines that signal a misconfigured §4.4 path. Exits non-zero withFAIL: ...if either appears, so misconfigured droplets don't silently producefiltering: untestedsamples. AcceptsSECOND_IP=<addr>env var for single-public-IP providers — aliases the IP to the NIC and writes the multi-IP conf.# AWS/GCP topology: ssh root@<vm-ip> 'bash -s' < scripts/validate-coturn.sh # Single-public-IP provider, after attaching a second IP: ssh root@<vm-ip> "SECOND_IP=<reserved-ip> bash -s" < scripts/validate-coturn.sh
Verified
End-to-end against a real DigitalOcean basic droplet (coturn 4.6, Ubuntu 24.04, primary public IP + Reserved IP aliased to eth0). Canonical filtering verdict reproduces across runs; classification + warnings + exit code stable. tcpdump confirmed coturn responds to RFC 5780 §4.4 Test 2 + Test 3 with routable public source IPs.
Known follow-up
- #14 — when the default-server hostnames resolve via IPv6 and a custom
--serveris IPv4 literal, the classifier compares mapped endpoints across address families and produces wrong ADM verdicts. Affects users followingdocs/coturn-setup.mdwho pass the natural probe set. Larger surface (Go code change + new schema warning + tests). v0.1.3.
Install
brew tap 1mb-dev/tap
brew upgrade natcheck # if already installed
brew install natcheck # fresh installor
go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.2.2v0.1.2.1 — coturn config fix
Note for
go installusers: this tag is 4-segment (v0.1.2.1), which Go module proxy treats as invalid semver and silently substitutes a pseudo-version. The Go binary is byte-identical tov0.1.2— no Go source changed in v0.1.2.1, the deltas are conf + docs that ship via Homebrew or repo clone, not viago install. Usego install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.2(same binary) orbrew install 1mb-dev/tap/natcheck(binary + corrected assets). v0.1.3 will use 3-segment semver.
Patch release. Fixes the bundled coturn config so that v0.1.2's filtering classification actually runs against a default-recipe coturn.
Fixed
examples/coturn-natcheck.confnow setsrfc5780(coturn 4.x defaults RFC 5780 NAT behavior discovery to OFF; without this directive, coturn silently omitsOTHER-ADDRESSfrom Binding responses and natcheck reportsfiltering: untestedwithWarnFilteringSkippedNoChangeRequest).examples/coturn-natcheck.confswitchesexternal-ip=YOUR_PUBLIC_IP→external-ip=YOUR_PUBLIC_IP/YOUR_PRIVATE_IP. A bare single-valueexternal-iptriggersSTUN CHANGE_REQUEST not supported: only one IP address is providedeven on a single-NIC VM.docs/coturn-setup.mddocuments both requirements and adds a verification step (step 4) to grep coturn's stdout for the two specific warning lines.
Not changed
No code, no JSON schema, no exit-code mapping. go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.2 and @v0.1.2.1 produce the same binary. The patch tag exists so the Homebrew formula and changelog can point at the corrected setup story.
Why it shipped broken
v0.1.2's pre-tag review covered the in-process internal/stunserver 4-corner test (which natcheck fully controls) and read the conf for "is the YOUR_PUBLIC_IP gotcha framed right." The framing was right; the conf itself was missing the rfc5780 directive that coturn 4.x requires. Discovered by trying to validate v0.1.2 end-to-end against a real coturn 4.10.0 immediately after release.
Install
brew tap 1mb-dev/tap
brew upgrade natcheck # if already installed
brew install natcheck # fresh installor
go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.2.1v0.1.2 — RFC 5780 §4.4 filtering classification
Added
- RFC 5780 §4.4 filtering classification when the target STUN server advertises
OTHER-ADDRESS. natcheck runs the three-step CHANGE-REQUEST sequence and reportsendpoint-independent,address-dependent,address-and-port-dependent, oruntestedfiltering. - Top-level
"filtering"object in--jsonoutput. Always present.tested_againstfield omitted when behavior isuntested. - WebRTC forecast value
"possible"now emitted for EIM mappings combined with restrictive (address-dependent or address-and-port-dependent) filtering. examples/coturn-natcheck.conf+docs/coturn-setup.md: minimum coturn config for filtering classification and a one-page setup guide.internal/stunserverpackage (foundation for v0.1.4'snatcheck serversubcommand).
Changed
- Default-server users (Google, Cloudflare) see no extra latency: filtering classification is skipped when no probe response advertises
OTHER-ADDRESS. coturn /natcheck serverusers get filtering automatically. --timeoutflag help: now notes that filtering classification adds up to 1.5s when applicable.
JSON schema (additive — strict consumers update)
- New top-level key:
"filtering": {"behavior": "...", "tested_against": "..."}. Always present from this release onward;tested_againstomitted whenbehavior == "untested". - New warning value in
warnings[]:"filtering_skipped_no_change_request"(server response did not includeOTHER-ADDRESS, so the §4.4 sequence could not run). - The existing
"filtering_behavior_not_tested"warning is still emitted when filtering classification was not attempted at all (no server in the probe set advertisedOTHER-ADDRESS).
Consumers doing strict equality on the entire JSON blob need to expect the new filtering key. Field-level consumers (e.g., jq '.nat_type') are unaffected.
Known limitations (deferred)
- Hairpinning detection — planned for v0.1.3.
natcheck serversubcommand — planned for v0.1.4.WarnFilteringPartialwarning — currentFilteringResultshape can't distinguish "filter blocked" from "transport error", so the warning is not emitted. Will return when the probe-side gains the necessary granularity.
Install
brew tap 1mb-dev/tap
brew install natcheckor
go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.2v0.1.1
Patch release.
Install: go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.1
Fixed
natcheck --versionnow reports the correct tag when installed viago install github.com/1mb-dev/natcheck/cmd/natcheck@vX.Y.Z. Previously fell back todevbecause ldflags aren't applied bygo install; now resolves viaruntime/debug.ReadBuildInfowhen ldflags are absent.
Full changelog
v0.1.0
Initial release.
Install: go install github.com/1mb-dev/natcheck/cmd/natcheck@v0.1.0
See docs/design.md for scope and architecture.
Added
natcheckCLI: probes STUN servers and reports NAT mapping classification (EIM / ADM / APDM per RFC 5780) plus a WebRTC direct-P2P forecast.- Default STUN servers:
stun.l.google.com:19302andstun.cloudflare.com:3478. - Flags:
--json,--verbose,--server host:port(repeatable),--timeout,--version,--help. - Forecast-first human output; schema-stable JSON via
--json. - Exit codes:
0P2P-friendly,1P2P-hostile,2probe or flag error. - CGNAT detection (RFC 6598
100.64.0.0/10) with forecastunknown. - IPv4 + IPv6 operation via
pion/stunand Go's net package.
Public contracts (stable from v0.1.0)
--jsonschema: additive changes only.- Exit-code mapping (0 / 1 / 2).
Dependencies
- Go 1.25+
github.com/pion/stun/v3