Skip to content

Releases: korya/netquality

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 05:09
adce543

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, Confidence and StabilityParams were aliases of
    internal/engine types, so pkg.go.dev showed callers a bare
    type StabilityParams = engine.StabilityParams and 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_version stays 1; only code that named engine.* types through an
    alias is affected.
  • Run is the only entry point. RunWithEvents is gone; its sink is
    Options.Events, which carries the same concurrency contract (RES-8).
    Replace RunWithEvents(ctx, t, o, sink) with Run(ctx, t, o) after setting
    o.Events = sink.
  • ParseServerConfig, DefaultStabilityParams and LatencySample are no
    longer exported. Discovery happens inside Run, a zero StabilityParams
    already selects the defaults, and no Result field ever reached a
    LatencySample. ErrInvalidConfig stays exported for errors.Is.

Added

  • scripts/api-compat.sh and 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

Choose a tag to compare

@github-actions github-actions released this 11 Sep 03:01
05a7238

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.IdleTimeout and nq --idle-timeout bound 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
    large IdleProbes values; raise IdleTimeout to 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

  • nqserver warns when an explicit --client-concurrency is 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 report cancelled rather 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 (or DialTLS)
    were not tracked, so an HTTP/2 connection still winding down when Run
    returned outlived it — for ever, on a transport without IdleConnTimeout.
    They are now torn down with the run (INV-4). test_endpoint cannot be
    honoured through a custom TLS dialer; the run now warns instead of silently
    ignoring it (DISC-9).

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 29 Aug 07:40
e531d7b

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_window and rpm_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 carry hold
    (#20).
  • loaded_window reports the intervals rpm and loaded were computed
    over, and nqserver --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. New StabilityParams
    fields: SendBufferBytes, RampGainTolerance, ChangeTolerance;
    DefaultUploadSendBuffer. FlowIncrement is now the floor of a step.
  • rpm and loaded.* 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, and loaded.foreign was 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's time.Now takes 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 why rpm
    looked plausible, but jitter was overstated by roughly 7×, the median
    snapped to a tick boundary and p95 was tens of percent high; a probe
    shorter than a tick could collapse self_rpm to 0 entirely. Probe timings
    now read QueryPerformanceCounter, as Go's own testing package does for
    benchmarks (LAT-10). Other platforms are unchanged. If the high-resolution
    counter cannot be reached the run says so in warnings rather 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 against MaxBytes and bytes (LIM-2); throughput_bps,
    peak_throughput_bps and mean_throughput_bps now count load-flow bytes
    only (LOAD-4, LOAD-11).
  • nqserver closes 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: Close now releases an
    Accept waiting at the connection cap, so http.Server.Serve returns and
    Shutdown no 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

Choose a tag to compare

@github-actions github-actions released this 28 Aug 23:07
f58de4a

Changed

  • No byte cap by default. MaxBytes defaults to 0 (unlimited) and
    nq --max-bytes is 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. Set MaxBytes on metered links for the old behaviour.
  • Latency percentiles are honest about sample size: p95_ns is present only
    from 20 samples, and p80_ns (from 5), p90_ns (from 10) and p99_ns
    (from 100) are added. Previously p95_ns of 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: Run returns the partial Result (that direction
    flagged flow_error, earlier phases intact) together with the error. Only
    discovery failures return no result.
  • Signed URLs: sig accepted in raw or padded, URL-safe or standard base64.
  • nqserver refuses to serve anonymously with a real certificate unless
    --allow-anonymous; --self-signed implies it.
  • Internal: the per-interval measurement logic now lives in a pure,
    clock-free internal/engine package 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 of Result, 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, optional sub, sig) minted by a backend, so
    clients hold no credential; sub keys the per-client budget. nqserver sign mints keys and URLs; server.SignURL for Go issuers (#1, plan B).
  • nqserver bearer-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.Header applied 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
    knownFailing ledger 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
    when Run returns (INV-4).
  • nq --max-bytes rejected 0 but accepted sub-byte values that rounded to 0
    and silently meant the default.

v0.2.1

Choose a tag to compare

@github-actions github-actions released this 28 Aug 13:26

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 (and local_ips) are now populated on cancelled
    partial results as well.

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 13:16

Changed

  • Minimum Go version is 1.26. Go 1.24 is out of support and its crypto/tls,
    net/http and encoding/asn1 carry 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 run govulncheck.

Added

  • Proxy detection: target.proxy reports explicit proxies (from the
    transport's proxy function) and TLS interception (verified chain without
    Certificate Transparency SCTs), each with a warning. CLI prints a Proxy
    line.

  • Test matrix (docs/test-matrix.md) enforced by TestMatrix; 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 the nqserver binary. Nightly Live workflow.

Fixed

  • test_endpoint dial 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.SelfSignedCert returns the parsed leaf certificate, so it can be
    added to a cert pool.

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 28 Aug 04:09

Added

  • Release workflow: tags vX.Y.Z build nq/nqserver for 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

Choose a tag to compare

@korya korya released this 28 Aug 03:47

Added

  • netquality library 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/nq CLI with human and --json output, --events progress stream,
    exit codes 0/1/2.
  • cmd/nqserver reference 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.