Repository navigation
Releases: korya/netquality
Release list
v1.0.0
The API is now stable. Everything a caller can reach is declared in this
module's own packages and documented on pkg.go.dev, and from here on a change
that breaks a compiling program needs a /v2 import path rather than a
version bump. Additions stay additions. The measurement algorithm, the wire
behaviour and schema_version are unchanged from 0.5.0: this release is about
what the API promises, not what the test does.
Upgrading from 0.5.0 touches three things, all of them mechanical, and the
compiler finds every one.
Changed
- The public API no longer aliases internal types.
LatencyStats,
StageMedians,ConfidenceandStabilityParamswere aliases of
internal/enginetypes, so pkg.go.dev showed callers a bare
type StabilityParams = engine.StabilityParamsand none of the fields, and a
rename inside the engine broke importers with nothing to warn us. They are
declared in the library package now, with the documentation the aliases hid.
Field names, types and JSON tags are unchanged, so existing code compiles and
schema_versionstays 1; only code that namedengine.*types through an
alias is affected. Runis the only entry point.RunWithEventsis gone; its sink is
Options.Events, which carries the same concurrency contract (RES-8).
ReplaceRunWithEvents(ctx, t, o, sink)withRun(ctx, t, o)after setting
o.Events = sink.ParseServerConfig,DefaultStabilityParamsandLatencySampleare no
longer exported. Discovery happens insideRun, a zeroStabilityParams
already selects the defaults, and noResultfield ever reached a
LatencySample.ErrInvalidConfigstays exported forerrors.Is.
Added
scripts/api-compat.shand a CI job report incompatible public API changes
since the newest release tag. The job is advisory in this release and becomes
blocking immediately after it, once there is a v1 tag to compare against.
v0.5.0
This release puts a bound on the parts of a run, and of the reference
server, that had none. A peer that stopped responding could hang the idle
phase for as long as it liked, return a probe body of any size, or hold a
server's byte budget open for ever. Each now has a limit, a warning that
names the limit it hit, and partial results that survive it.
schema_version stays 1.
Added
Options.IdleTimeoutandnq --idle-timeoutbound the entire idle probing
phase (default 10 s). A stalled idle response now times out, preserves any
completed samples, warns, and allows the selected load phases to run (#39).
This flat cap also applies to healthy slow paths and existing callers with
largeIdleProbesvalues; raiseIdleTimeoutto collect more samples and
reach higher percentile thresholds.
Changed
- Server byte budgets now limit active downloads and uploads per client IP or
signed subject to 32 by default (--client-concurrency/
server.Options.MaxClientConcurrency). Nonpositive values select 32;
disabling the byte budget also disables these slots. Higher concurrency
gets 429; admitted transfers remain unthrottled. Default request caps allow
up to 512 GiB of outstanding payload, so this is not a strict byte quota (#41).
Slots have no expiry: stalled requests can indefinitely deny large/download
and upload endpoints to clients sharing an identity. Separately issued
signed subjects isolate clients behind shared IPs. - Small probe responses must now be complete, nonempty bodies of 1–10 bytes.
Previously accepted empty or larger custom-server responses are invalid.
The ten-byte ceiling is fixed, with no caller override; it preserves the
observed Apple and Cloudflare responses (#40).
Fixed
nqserverwarns when an explicit--client-concurrencyis ignored because
byte budgeting is disabled, including self-signed mode's default; a large
positive byte budget can retain concurrency-only admission (#41).- Server budget admission and completion are accounted atomically, preventing
unlimited concurrent overshoot. Cancellation and I/O errors settle actual
bytes; panic unwinding releases the slot and charges the request cap.
Cleanup preserves active admissions and unpaid debt; extreme byte-debt
retry hints saturate portably. Authenticated unsupported methods return
405 before admission checks (#41). - Declared oversized probe bodies are rejected before reading, and streamed
bodies after at most 11 bytes. Invalid sizes no longer become latency
samples; bounded warnings explain rejection while valid samples and load
measurements survive (#40).
Probe cost remains estimated; transport buffering can exceed body-read limits. - Idle-timeout warnings retain the preceding probe error and are omitted
when all requested idle samples have already completed (#39). - Caller cancellation retains completed idle samples in partial results;
caller deadlines during load reportcancelledrather than the phase's
duration_cap. Discovery, idle, and load phase budgets are documented
together with their teardown and caller-code limitations (#39). - Cancellation closes an upload's response body and joins its cleanup, so
an HTTP/2 upload peer that responds early and then stops reading cannot
leave the client waiting on flow control after its deadline (#39). - Connections opened through a caller-supplied
DialTLSContext(orDialTLS)
were not tracked, so an HTTP/2 connection still winding down whenRun
returned outlived it — for ever, on a transport withoutIdleConnTimeout.
They are now torn down with the run (INV-4).test_endpointcannot be
honoured through a custom TLS dialer; the run now warns instead of silently
ignoring it (DISC-9).
v0.4.0
The measurement algorithm reaches a confident answer on links the previous
release could not finish measuring, and every figure now says which part of
the run it came from. schema_version stays 1: all result fields are additions.
Added
- A throughput lower bound. Each direction reports
throughput_lower_bound_bps— the lowest goodput of the latest four
consecutive measured intervals that agree within tolerance — with
lower_bound_windowandrpm_upper_bound(the responsiveness over that
window). It is present whenever such a window formed, converged or not, so
a caller that gets a medium-confidence estimate still has a figure that
holds. The CLI prints it after the estimate; interval events carryhold
(#20). loaded_windowreports the intervalsrpmandloadedwere computed
over, andnqserver --idle-timeout(default 2 min) bounds how long a
connection with no request in flight is kept (#24).
Changed
- Ramp-and-hold algorithm (#20). Flows are added in doubling steps and
the ramp stops when a step gains less than 10 % goodput; responsiveness is
tracked from the end of the ramp and judged on its windowed values; a
goodput drop of more than 25 % restarts goodput tracking; upload intervals
inflated by the HTTP/2 send window of new flows are excluded. Every
simulated scenario now converges with high confidence inside the 12 s
budget — 10 Gbps at 150 ms RTT, CDN per-flow caps, shaper bursts, a
capacity change mid-run — links ≤ 100 Mbps finish 1–3 s sooner, and a
20 Mbps upload reports 20 Mbps instead of 53. NewStabilityParams
fields:SendBufferBytes,RampGainTolerance,ChangeTolerance;
DefaultUploadSendBuffer.FlowIncrementis now the floor of a step. rpmandloaded.*are computed over every probe sample since throughput
became stable, never fewer than the draft's trailing four intervals, rather
than over the trailing intervals alone. Foreign probes are sparse — a TLS
handshake each — so the old window could hold self samples and no foreign
one, andloaded.foreignwas then omitted as if no fresh-connection probe
had ever succeeded. A series with samples in the phase but none in the
window is now named in a warning (#24).
Fixed
- Windows latency figures are no longer quantised to the system timer
tick. Go'stime.Nowtakes its monotonic reading from_INTERRUPT_TIME,
which advances only every 0.5–15.625 ms depending on what else is running
on the machine, so probe durations — tens of microseconds on a fast path —
measured either 0 or a whole tick. Means survived that, which is whyrpm
looked plausible, but jitter was overstated by roughly 7×, the median
snapped to a tick boundary andp95was tens of percent high; a probe
shorter than a tick could collapseself_rpmto 0 entirely. Probe timings
now readQueryPerformanceCounter, as Go's owntestingpackage does for
benchmarks (LAT-10). Other platforms are unchanged. If the high-resolution
counter cannot be reached the run says so inwarningsrather than
presenting coarse numbers as exact. - Throughput no longer measures the test's own probe traffic. The fixed
per-probe byte estimate (5000 B foreign, 1000 B self) was added to the same
counter the engine reads as goodput, so a stalled or very slow path
reported hundreds of kbps that were purely probes — and, because probe
spacing is derived from that estimate, the client throttled itself to about
one probe per second on exactly the paths that most need probing. Probe
cost still counts againstMaxBytesandbytes(LIM-2);throughput_bps,
peak_throughput_bpsandmean_throughput_bpsnow count load-flow bytes
only (LOAD-4, LOAD-11). nqservercloses idle connections and pings silent HTTP/2 peers (30 s +
15 s); previously a connection whose client had gone quiet was held until
the process exited.server.LimitListener:Closenow releases an
Acceptwaiting at the connection cap, sohttp.Server.Servereturns and
Shutdownno longer waits out its timeout (#24).- Data race in the load phase: the reason a phase stopped was read on the
phase goroutine before the flow and probe goroutines were joined, while any
of them could still be writing it.
v0.3.0
Changed
- No byte cap by default.
MaxBytesdefaults to 0 (unlimited) and
nq --max-bytesis omitted unless the link is metered;MaxDuration
(12 s per direction) is the budget, so cost is at most link rate × 12 s and
a confident result is reachable at any speed. Previously a 250 MiB cap
truncated every run above ~250 Mbps with low confidence (#3). Slow links
are unaffected. SetMaxByteson metered links for the old behaviour. - Latency percentiles are honest about sample size:
p95_nsis present only
from 20 samples, andp80_ns(from 5),p90_ns(from 10) andp99_ns
(from 100) are added. Previouslyp95_nsof the five idle probes was
always the maximum (#4). The CLI prints the highest percentile available. - A load phase that fails before completing an interval no longer discards
the whole result:Runreturns the partialResult(that direction
flaggedflow_error, earlier phases intact) together with the error. Only
discovery failures return no result. - Signed URLs:
sigaccepted in raw or padded, URL-safe or standard base64. nqserverrefuses to serve anonymously with a real certificate unless
--allow-anonymous;--self-signedimplies it.- Internal: the per-interval measurement logic now lives in a pure,
clock-freeinternal/enginepackage driven by observations, so it can be
tested against recorded and simulated links. No behaviour or JSON change;
public latency types are aliases of the engine's.
Added
schema_version(1) as the first field ofResult, bumped only when a
field is renamed, removed, retyped, or changes meaning (#2).- Regression guards: leak check across mixed runs, wire-contract test,
repeated-run isolation, JSON schema golden and v0.2.1 document fixture,
fuzz targets for the four parsers (with a CI job), and a per-scenario cost
ledger for the algorithm matrix. - End-to-end tests for real-world failure modes (upload rejected mid-stream,
stalled body, redirects, dead hosts, upload cancellation/HTTP/1.1), IPv6
loopback, full ramp to 16 flows, JSON round-trip, CLI edge flags, and the
signed-URL encoding/canonicalisation edge cases. nqserver --signing-key: the test endpoints accept HMAC-SHA256-signed,
expiring URLs (exp, optionalsub,sig) minted by a backend, so
clients hold no credential;subkeys the per-client budget.nqserver signmints keys and URLs;server.SignURLfor Go issuers (#1, plan B).nqserverbearer-token authentication (--auth-token/NQSERVER_AUTH_TOKEN)
on every endpoint, and load limits that gate requests without shaping
traffic: per-client byte budget (--client-bytes/--client-window, default 8 GiB
per 10 min, unlimited under--self-signed; 429 +Retry-After),
--upload-size,--max-connections(#1).Options.Headerapplied to every request;nq --auth-token/
NQ_AUTH_TOKEN.- Internal:
internal/linksim, a fluid link model, and an algorithm
scenario matrix with honesty/budget/convergence/accuracy oracles and a
knownFailingledger of what the current algorithm cannot yet do.
Fixed
- Every load phase leaked one HTTP/2 connection and its read/write
goroutines when a stream was still winding down at teardown (present since
v0.1.0); the config fetch also left a keep-alive connection in the caller's
transport pool. All library-dialled connections are now tracked and closed
whenRunreturns (INV-4). nq --max-bytesrejected 0 but accepted sub-byte values that rounded to 0
and silently meant the default.
v0.2.1
Added
target.local_ips: the local source addresses the test's connections used,
for correlating a stored result with the network it was taken on (#5).
Changed
target.resolved_ips(andlocal_ips) are now populated on cancelled
partial results as well.
v0.2.0
Changed
- Minimum Go version is 1.26. Go 1.24 is out of support and its
crypto/tls,
net/httpandencoding/asn1carry unfixed vulnerabilities
(GO-2026-6090, GO-2026-6089, GO-2026-5972, GO-2026-5856); binaries from
v0.1.x were built with it. CI and release builds pin the toolchain
(GOTOOLCHAIN=local) and rungovulncheck.
Added
-
Proxy detection:
target.proxyreports explicit proxies (from the
transport's proxy function) and TLS interception (verified chain without
Certificate Transparency SCTs), each with a warning. CLI prints aProxy
line. -
Test matrix (
docs/test-matrix.md) enforced byTestMatrix; end-to-end
tests for HTTP/1.1 fallback, flow errors,test_endpoint, TLS 1.2
normalisation, upload byte cap, custom RoundTrippers, config timeouts,
the CLI, and thenqserverbinary. NightlyLiveworkflow.
Fixed
test_endpointdial override no longer rewrites dials to a proxy that
shares the origin's host.- Foreign probes over a custom RoundTripper that reuses connections now yield
unstaged samples instead of failing outright. server.SelfSignedCertreturns the parsed leaf certificate, so it can be
added to a cert pool.
v0.1.1
Added
- Release workflow: tags
vX.Y.Zbuildnq/nqserverfor all six
platforms, publish a GitHub release with the matching changelog section,
and trigger pkg.go.dev indexing. - CI and Go Reference badges; "Releasing" section in the README.
Fixed
- Data race in the loopback integration test's event sink (library unchanged).
v0.1.0
Added
netqualitylibrary implementing draft-ietf-ippm-responsiveness-09:
config discovery (/.well-known/nq, Apple and Cloudflare documents,
test_endpoint), idle latency with per-stage timings, sequential
download/upload load phases with the draft's moving-average stability
algorithm, foreign and self loaded-latency probes, RPM, jitter, and
per-phase confidence.- Hard safety limits:
MaxDuration,MaxBytes,MaxFlows, context
cancellation; results flag truncation and its reason. cmd/nqCLI with human and--jsonoutput,--eventsprogress stream,
exit codes 0/1/2.cmd/nqserverreference server (HTTP/2,--self-signed).- Unit, loopback integration and opt-in live (
NQ_LIVE=1) tests; CI on
Linux/macOS/Windows plus a six-target cross-compile matrix.