Files
irtt-rs/docs/validation/UPSTREAM_CLIENT_INTEROP.md
Nils Andreas Svee 9d7566955d docs(server): drop the unobserved continuous-mode wire claim
The continuous-request row said a -d 0 request never reaches the wire,
which no observation in this validation supports: the run took no packet
capture and the server was not instrumented for datagram arrival, so
what the client emitted before exiting was never seen.

Record what was actually observed — the argument was refused, the client
exited non-zero, and it printed none of the connection line it prints in
every other scenario — and state plainly that the emission was not
observed. The conclusion that matters is unchanged: continuous mode was
not exercisable through this CLI.

No implementation, test or protocol evidence changes.
2026-08-13 17:39:28 +07:00

8.5 KiB

Upstream IRTT 0.9.1 Client Interoperability

Scope

The released upstream IRTT 0.9.1 client was run as a black box against the Tokio irtt_server::Server runtime. Only the executable's reported version, its command-line help, its exit status and its normal stdout/stderr were observed. Upstream implementation source, tests and repository history were not inspected, and no upstream behavior was inferred beyond what the running program printed.

  • Tested against irtt-rs base commit ace3defe22e71980f18ccdce1ef783d630e070cf. No implementation change was required, so the validated behavior is that of the unmodified baseline.
  • Listeners were explicit loopback binds only (127.0.0.1:0, [::1]:0). Wildcard binds were not exercised.
  • DSCP marking, the full ServerFill policy and wildcard/multi-homed source-address (pktinfo) handling are unimplemented and out of scope here.
  • This document records implementation validation. It creates no protocol requirements and does not amend the airlocked protocol evidence.

The server was exposed through a small throwaway Cargo harness kept outside this repository. It only built a ServerConfig, called Server::bind, printed the bound SocketAddr and called Server::run; it contains no protocol handling and is deliberately not committed.

Environment

Item Value
Date 2026-08-13
OS macOS 26.5.2 (Darwin 25.5.0)
Architecture arm64 (Apple silicon)
Rust toolchain rustc 1.97.1, cargo 1.97.1
Upstream reported version irtt version: 0.9.1, protocol version 1, JSON format version 1
Upstream executable SHA-256 35df8b2b2c10fe81a222eaac2fa7461891fa2354f74c1e9b379c3c31f43c61cc
How obtained Pre-existing installation already on PATH; version confirmed with irtt version. No isolated build was needed and no upstream source was fetched.
irtt-rs base SHA ace3defe22e71980f18ccdce1ef783d630e070cf

Flags used were taken from the executable's own irtt help client output: -d, -i, -l, -n, -4, -6, --hmac, --loose, --timeouts, --tstamp, --clock.

--loose is upstream's documented client-side switch for accepting server-restricted test parameters instead of exiting non-zero. The packet-length restriction was run both ways: without it, to confirm the client detects and reports the restriction, and with it, to confirm the restricted session actually runs. The interval and maximum-duration restrictions were run with --loose only, so strict-mode refusal was observed for packet length alone and is not claimed for the other two.

Matrix

Server policy is ServerConfig::default() unless stated. All runs used numeric loopback addresses and no DNS.

Scenario Server policy Client request Result Notes
IPv4 ordinary default -4 -d 1s -i 100ms -l 128 PASS 10/10 packets, 0.00% loss, exit 0
HMAC with_hmac_key("interop-secret") --hmac=interop-secret -d 500ms -i 100ms -l 128 PASS 5/5 packets, 0.00% loss, exit 0
HMAC, wrong key (negative control) with_hmac_key("interop-secret") --hmac=wrong-secret --timeouts=200ms,200ms PASS Silently refused; client reports [OpenTimeout] no reply from server, exit 1; server unaffected
IPv6 default, bound [::1]:0 -6 -d 1s -i 100ms -l 128 PASS 10/10 packets, 0.00% loss, exit 0
Packet-length restriction with_max_packet_length(96) -l 128 (strict) PASS [ServerRestriction] server reduced length from 128 to 96, exit 1 — upstream's documented strict-mode policy
Packet-length restriction with_max_packet_length(96) -l 128 --loose PASS Runs at 96 bytes, 10/10 packets, 0.00% loss, exit 0
Interval restriction default (10 ms floor) -i 1ms -d 500ms --loose PASS server increased interval from 1ms to 10ms; 50/50 packets, 0.00% loss — the rate limiter never penalised a client sending at the interval it was given
Maximum-duration Close with_max_test_duration(500ms) -d 5s -i 100ms --loose PASS (restriction) server reduced duration from 5s to 500ms; 5/5 packets, exit 0. Close reply not reached on loopback; see Findings.
Continuous request with_max_test_duration(500ms) -d 0 SKIPPED Upstream refuses the argument ([DurationNonPositive] duration (0s) must be > 0), exiting non-zero without printing the connection line it prints in every other scenario. No packet capture was taken, so what the client emitted before exiting was not observed; continuous mode was simply not exercisable through this CLI
No-test (optional) default -n -l 128 PASS [NoTest] skipping test at user request, exit 0, no measurement session
Midpoint, single clock (optional) default --tstamp=midpoint --clock=monotonic PASS 5/5 packets, exit 0; RTT and IPDV computed from the single midpoint field
Midpoint, wall only (optional) default --tstamp=midpoint --clock=wall PASS 3/3 packets, exit 0
Midpoint, both clocks (optional) default --tstamp=midpoint --clock=both PASS 3/3 packets, exit 0

In every scenario the harness process was still alive after the client exited and shut down cleanly on SIGTERM (Server::run returned Ok).

Findings

No implementation incompatibility was found in the tested matrix, and no code change was made.

Two observations are worth recording.

The single-field midpoint reply is accepted. irtt-rs emits one timestamp field per negotiated clock for StampAt::Midpoint, deliberately not reproducing upstream 0.9.1's dual-field midpoint form (see crates/irtt-server/AGENTS.md). The released upstream client parsed that reply without complaint and derived RTT and send/receive IPDV from it, in the monotonic-only, wall-only and both-clocks cases. This was the tested path most likely to expose a reply-shape incompatibility, and it did not.

The maximum-duration Close was not reached in the tested loopback run. The deadline is first served echo + configured maximum + 2 s, and negotiation reduced this session's Duration to the configured 500 ms maximum. On loopback the upstream client honoured that restriction, stopped transmitting after ~400 ms and exited 0; no echo arrived anywhere near the server's +2 s deadline, so the Close-flagged reply was never due and this black-box scenario did not exercise it.

That result is scoped to the tested path. Under ordinary low-delay conditions a client honouring its negotiated Duration will normally finish sending well before the +2 s boundary. It does not follow that the Close is unreachable in general: the deadline is evaluated on arrival, not on transmission, so an echo sent legitimately before the negotiated Duration elapsed could still be queued or delayed for more than two seconds on a real network path and arrive after first served echo + configured maximum + 2 s, carrying Close while the client is still draining replies. The loopback run establishes only that this did not occur under negligible delay. The Close therefore primarily provides a termination path for traffic continuing or arriving beyond the server's maximum-duration grace — an overrunning client, or sufficiently delayed in-flight echo traffic.

This is not a defect and no fix is authorised for it. The two-second grace is recorded in the airlocked evidence as the measured upstream margin and is a fixed internal constant; the implementation matches that evidence rather than violating it. The Close path itself remains covered deterministically in-repo by crates/irtt-server/src/tests/lifecycle.rs, notably the_first_echo_past_the_maximum_duration_carries_close_and_ends_the_session.

Scope remaining

This validation does not establish:

  • DSCP response marking;
  • full ServerFill behavior (echo payloads remain zero-filled);
  • wildcard or multi-homed source-address handling (pktinfo);
  • behavior under pathological or exhaustive parameter combinations;
  • behavior on operating systems other than the macOS/arm64 host recorded above;
  • behavior across a real network path, MTU limits or fragmentation.

No claim of universal compatibility is made.

This document is a record of the manual validation that was performed, not a reproduction harness. The server policy and client arguments used in each scenario are summarized in the matrix above, but the throwaway harness was deliberately discarded afterwards and is not committed, so the run is not reproducible from this artifact alone. No permanent test in this repository depends on the upstream executable, the Go toolchain or network access.