Commit Graph
42 Commits
Author SHA1 Message Date
Nils Andreas Svee 596dd20bcf fix(client): validate blocking receive lifecycle before socket I/O
Rust CI / cargo fmt (push) Successful in 15s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 28s
Rust CI / cargo clippy (push) Successful in 31s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo doc (push) Successful in 23s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m10s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / cargo-deny bans licenses sources (push) Successful in 5s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 59s
Blocking receives previously checked lifecycle only after a datagram arrived, allowing invalid operations to block or return empty success after timeout. Reuse session validation before socket access and cover connected, closed, and no-test-completed states through public APIs.
2026-10-04 13:35:37 +07:00
Nils Andreas Svee 48a50d7f3a tests: rework the actually useful tests to live in the integration crate
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 34s
Rust CI / cargo clippy (push) Successful in 35s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m14s
Rust CI / cargo doc (push) Successful in 23s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny advisories (push) Successful in 9s
Supply chain / cargo-deny bans licenses sources (push) Successful in 5s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m11s
2026-10-03 13:59:38 +07:00
Nils Andreas Svee 1753e91d5f fix(tests): break the irtt-client/irtt-server dev-dependency cycle
irtt-client and irtt-server mutually dev-depended on each other for
cross-crate integration tests. That cycle blocks `cargo publish` for
either crate whenever the other doesn't already exist on the registry
at the required version, which is exactly the situation for a crate's
first-ever release (irtt-server has never been published before).

Introduce a new `irtt-integration-tests` crate (publish = false, no
library target) to own tests whose actual subject is the interaction
between two or more irtt-* production crates:

  crate-local behavior          -> crate's own tests/
  cross-crate/system behavior   -> irtt-integration-tests

Move every test that imports both irtt_client and irtt_server there:
the client-side support harness (hmac, large_packets, protocol_options,
fill_negotiation) and the server-side real-socket tests that drive a
real AsyncClient against a real Server (async_client, negotiation_policy,
fill_wire_bytes, traffic_class, wildcard_source). server_set.rs stays in
irtt-server/tests since it only uses irtt_proto as test machinery to
drive irtt-server directly, which is a normal one-directional test
dependency, not a peer relationship.

irtt-server's dev-dependency on irtt-client is dropped entirely now
that nothing under crates/irtt-server/tests needs it, which fully
breaks the cycle from that side.

irtt-client keeps a one-directional dev-dependency on irtt-server: a
handful of white-box unit tests in src/async_client/tests.rs assert on
private AsyncClient internals (socket, remote, test_hooks) while also
needing a real in-tree server for blocking/async equivalence checks, so
they can't move to an external crate without losing that access. Their
InTreeServer helper is duplicated locally rather than shared, matching
the existing precedent of irtt-cli's own tests/support/in_tree_server.rs.
2026-08-17 09:49:32 +07:00
Nils Andreas Svee a598dbdc5c fix(io): address review feedback on interrupted-receive retries
- Error-queue drain: an interrupted MSG_ERRQUEUE read no longer ends
  the bounded drain early as if the queue were empty (which could
  strand a still-queued TX timestamp past the point its probe is
  looked up and removed). It now reports ErrorQueueRecord::Ignored,
  consuming one attempt of the drain's existing per-call work budget
  and retrying the same slot instead.
- Fake-server recv_from test helpers (client_multi_target.rs and
  tests/support/mod.rs) now keep one absolute deadline across
  interruptions, re-applying only the remaining time on each retry
  and restoring the configured timeout afterward, mirroring the
  production blocking-receive fix these tests exist to guard rather
  than restarting the full timeout on every interruption.
2026-08-17 00:32:37 +07:00
Nils Andreas Svee ca2389174f fix(io): retry interrupted receives
Real signal delivery on Linux (observed via x86_64-musl workspace
testing) can interrupt blocking and nonblocking recv/recvmsg calls
with EINTR. Several production and test receive paths treated that as
a fatal error rather than a transient interruption:

- Client::open's blocking receive loop and Client::recv_once/
  recv_available's blocking receive returned ClientError::Socket on
  Interrupted instead of retrying within their existing timeout
  budget.
- AsyncClient::poll_open and poll_recv's nonblocking recvmsg path
  (Linux ancillary) failed the same way instead of retrying through
  the existing readiness/work-budget machinery.
- The server's ancillary receive loop could terminate the listener on
  an interrupted nonblocking recvmsg.
- The client's MSG_ERRQUEUE TX-timestamp drain turned an interrupted
  read of optional metadata into a fatal ClientError::Socket.
- Fake-server test helpers panicked or misclassified EINTR as a
  timeout instead of retrying it.

Interrupted is now retried transparently everywhere a receive can
observe it, while preserving each path's existing timing contract:

- Blocking Open reuses its already-absolute per-attempt deadline, so
  interruptions resume within the same attempt without retransmitting
  or extending the deadline.
- recv_once/recv_available compute one absolute deadline per logical
  receive (when a timeout is configured) so repeated interruptions
  cannot extend it past the configured timeout, and restore the
  socket's configured read timeout afterward so a later call in the
  same recv_available budget is unaffected.
- AsyncClient's poll_open already bounds its retry loop with
  OPEN_POLL_WORK_BUDGET; poll_recv gains an equivalent
  RECV_POLL_WORK_BUDGET, since unlike WouldBlock, Interrupted does not
  clear Tokio's readiness state, so an unbounded retry loop was
  otherwise possible under sustained signal delivery.
- The server's receive loop retries Interrupted the same way it
  already retries WouldBlock, yielding to the runtime after a bounded
  number of consecutive interruptions for the same reason.
- The error-queue drain treats an interrupted read as no record
  obtained this drain (deferring to the next natural drain point)
  rather than a socket error, matching its existing EWOULDBLOCK
  handling.

Deterministic regression tests use a small test-only injection hook
(mirroring existing send-injection test hooks) rather than real signal
timing, covering: Open completing despite interruptions with no
retransmit, Open's deadline not extending under repeated
interruptions, recv_once classifying a reply normally despite
interruptions, recv_once's timeout not extending under repeated
interruptions, and the AsyncClient equivalents for open and recv.

No protocol, session, or timeout-classification behavior changes; no
new dependencies; send-side EINTR handling is unchanged and out of
scope.
2026-08-17 00:23:24 +07:00
Nils Andreas Svee 446028671b test(cli): use real server for compliant peers 2026-08-14 17:21:22 +07:00
Nils Andreas Svee fca7a2fcc6 test(client): use real server for compliant peers 2026-08-14 17:17:41 +07:00
Nils Andreas Svee 3845142d30 fix(proto): reject packet lengths usize cannot represent 2026-08-12 20:05:01 +07:00
Nils Andreas Svee 68ed5c6e2e fix(proto): floor negative echo lengths 2026-08-12 17:16:03 +07:00
Nils Andreas Svee 93491e7762 refactor(proto): preserve open parameter presence
`Params::decode` seeded `Params::default()` and overwrote only the tags it
saw, so an omitted tag and one explicitly encoded as zero were
indistinguishable afterwards. The protocol gives those the same value but not
the same meaning, and a server cannot apply its open admission rules without
telling them apart: an absent Duration or Interval is accepted as the wire
default zero, while one explicitly encoded as zero is invalid.

Add `Params::decode_with_presence`, returning the decoded values alongside a
`ParamPresence` recording which known tags appeared. `Params::decode` now
delegates to it, so there is still exactly one parser and the two cannot
diverge. Unknown tags stay ignored and untracked, and repeated known tags keep
last-value-wins.

Add `Clock::Unspecified = 0` as the default, which is the wire default for an
absent Clock tag and what an empty parameter payload decodes to. An explicit
Clock tag encoding zero remains an invalid enum value, so `Unspecified` is
only ever reached by omission; `encode` omits the tag for it rather than
emitting an explicit zero. It selects no clock, so `has_wall`/`has_mono` are
false and no timestamp field is laid out.

`ClientConfig::clock` is public and therefore now constructible as
`Unspecified`, which is a wire default rather than a client mode. Reject it
during config validation. The client default stays `Clock::Both`, so normal
client-generated params and wire vectors are unchanged.
2026-08-12 00:23:14 +07:00
Nils Andreas Svee 5a4272e7fd refactor(proto): unify request codecs
Replace the three per-kind request encoders and decoders with one
directional pair, ahead of introducing irtt-server.

Sender side: encode_request(RequestToEncode, hmac_key) constructs OPEN,
CLOSE and ECHO requests from borrowed values. Params and the ECHO payload
are borrowed rather than owned, removing a Params clone and a payload Vec
from the client's hot paths. Wire output is byte-identical; all captured
vectors still pass unchanged.

Receiver side: decode_request(packet) structurally classifies an inbound
request with no HMAC key, no negotiated Params, no session state and no
allocation. A server must extract a token before it knows which key
applies, so authentication is a separate step. Classification follows the
verified server spec: reserved bits and FLAG_REPLY are rejected, OPEN
takes precedence over CLOSE and over an echo-shaped body, and only the
structural minimums (4/12/16, plus 16 with FLAG_HMAC) are enforced.
Trailing bytes are tolerated, no negotiated length ceiling is applied, and
a zero token is structurally valid. OPEN parameters stay encoded bytes so
an authenticating receiver can verify before decoding untrusted data, and
everything after an ECHO sequence number is exposed as an opaque tail.

Add verify_packet_hmac(key, packet), which locates the standard field
itself and rejects a packet lacking FLAG_HMAC. hmac_present reports packet
layout, never authentication. The offset-based verify_hmac stays public as
the low-level primitive for hand-built and captured wire vectors, matching
the existing compute_hmac pair.

Refactor the private envelope into structural header decoding, codec flag
rules, HMAC presence policy and verification as separate steps. The reply
codecs compose the same steps in the same order, so decode_open_reply and
decode_echo_reply keep their exact error ordering and the client's
fatal-versus-ignore classification is unchanged. Reply decoding stays
state-specific: the client already knows which reply to expect, and the
two need different semantic context.

Migrate the production client and every fake peer to the new API; the
fake peers now admit requests the way a server does, instead of parsing
packets by hand.
2026-08-11 23:22:20 +07:00
Nils Andreas Svee 3d5a8fae14 fix(client): align DSCP wire semantics 2026-08-11 20:29:57 +07:00
Nils Andreas Svee 8924cc531c test(client): use protocol encoders in fake peers 2026-08-10 22:53:36 +07:00
Nils Andreas Svee f0f9277469 test: trim redundant coverage 2026-08-10 21:06:49 +07:00
Nils Andreas Svee ff27ec24c6 refactor(client): transact blocking session opening 2026-08-04 12:11:32 +07:00
Nils Andreas Svee 2d6581f31f refactor(client): encapsulate SignedDuration and enhance its API
Hide internal fields to improve type safety and provide helper methods for unit conversions and negative value checks. Fix several feature-gated test compilation issues.
2026-05-17 22:47:16 +07:00
Nils Andreas Svee bec56d4114 refactor(client): use SignedDuration for effective RTT to support negative values
Simplify RttSample by merging signed and unsigned fields into a single SignedDuration for adjusted and effective RTT. This ensures accurate reporting and statistics when server processing time exceeds raw RTT.
2026-05-17 21:30:51 +07:00
Nils Andreas Svee ddd420cc21 test: consolidate and prune implementation-coupled tests 2026-05-17 00:28:05 +07:00
Nils Andreas Svee a7288cc612 test(client): extract backend smoke tests into a dedicated module 2026-05-17 00:17:10 +07:00
Nils Andreas Svee 72470bd196 test(client): update and expand DSCP negotiation test coverage 2026-05-13 00:37:54 +07:00
Nils Andreas Svee a7e10358fd test(client): use fallible packet length helpers in integration tests 2026-05-12 13:05:45 +07:00
Nils Andreas Svee 4549d95d0a test(client): update test helpers to handle fallible packet length calculations 2026-05-12 13:05:45 +07:00
Nils Andreas Svee 7ad83ca1f6 refactor: remove logical_seq from client events, session tracking and stats usage
It was an attempt at handling integer wraparound more cleanly keeping in mind that we have a continuous mode,
and so we can more easily reach seq > u32::MAX

However after running the numbers, if we run with a interval of 10ms, it's still gonna take ~497 days before we hit integer overflow, so for now it doesn't seem worth to have this extra logic to handle something that'll in 99.9% of cases be purely an edge case

Keeping in mind autorate usage, in sqm-autorate-rust each send loop is 500 ms by default, and the sleep is then 500 ms / 5 reflectors, so our interval is 100 ms. that means it'll take 4970 days or ~13.6 years to reach the limit. Not worth dealing with IMO
2026-05-11 14:56:07 +07:00
Nils Andreas Svee 8d1d0f50de style(client): reformat imports in test files
Collapse multi-line import statements into single lines to improve code legibility and maintain consistent formatting across test suites.
2026-05-11 10:13:06 +07:00
Nils Andreas Svee 1a04f44bb5 refactor(client): remove redundant timestamp parameter from poll_timeouts
Use internal monotonic time by default and introduce poll_timeouts_at for callers requiring a specific sampled Instant. This simplifies the API for standard usage while maintaining flexibility for external event loops.
2026-05-11 09:33:55 +07:00
Nils Andreas Svee d8b580a19c refactor(client): remove redundant timestamp parameter from close method 2026-05-10 23:47:46 +07:00
Nils Andreas Svee 023e85741a refactor(client): remove redundant timestamp parameter from open method 2026-05-10 23:37:35 +07:00
Nils Andreas Svee c3f2a524a3 fix(client): avoid lossy timing casts 2026-05-10 20:56:43 +07:00
Nils Andreas Svee afb20b6625 fix(client): retry real backend startup on port races 2026-05-10 20:56:43 +07:00
Nils Andreas Svee ed62a5c80c test: refactor real backend support for reliable output capture 2026-05-10 20:56:43 +07:00
Nils Andreas Svee d93e10ba45 feat(cli): add support for configuring outgoing TTL and hop limit
Introduce a `--ttl` flag to allow users to set the IPv4 TTL or IPv6 hop limit, providing control over packet lifespan and helping diagnose network topology issues.
2026-05-10 20:56:43 +07:00
Nils Andreas Svee a80a5e2b7b test(client): rename DSCP smoke test to reflect specific behavior
Update the test name to clarify that it verifies DSCP configuration during the session open and close lifecycle.
2026-05-10 20:56:43 +07:00
Nils Andreas Svee 063214b83b feat(client): implement DSCP negotiation and socket traffic class application
Apply the negotiated DSCP value to the UDP socket after a successful session open and clear it before closing to support network-level quality of service. This change adds platform-specific socket option handling for IPv4 TOS and IPv6 Traffic Class.
2026-05-10 20:56:43 +07:00
Nils Andreas Svee ab95489f20 test(client): add integration tests for packet length and server fill
Verify that the client correctly handles custom packet lengths and server fill negotiation to ensure protocol compliance and support for larger MTUs. Add validation to reject empty server fill strings during configuration.
2026-05-10 20:56:43 +07:00
Nils Andreas Svee a4e2f3aef4 test(client): unify fake and real backend testing via BackendPeer
Introduce a BackendPeer abstraction to transparently switch between fake and real IRTT backends. This simplifies test logic by removing manual skip checks and providing a consistent interface for server lifecycle management.
2026-05-10 20:56:43 +07:00
Nils Andreas Svee 3d49f725ae test(client): add integration tests for compatibility with real IRTT backends
Introduce a mechanism to run tests against a live irtt binary to verify real-world protocol interoperability, including HMAC authentication and various timestamp modes.
2026-05-10 20:56:43 +07:00
Nils Andreas Svee 797120056e test(client): add comprehensive integration tests for HMAC authentication
Verify HMAC negotiation, session closure, and error handling for authenticated probes to ensure protocol security and reliability.
2026-05-10 20:56:43 +07:00
Nils Andreas Svee 8bd52f35fa refactor(cli): rename m8 compatibility terms to advanced protocol options 2026-05-10 20:56:43 +07:00
Nils Andreas Svee e49e91c528 test: verify RTT equality when no timestamping is present 2026-05-10 20:56:43 +07:00
Nils Andreas Svee b5b6ecc69e refactor(test): extract RTT processing verification to helper function 2026-05-10 20:56:43 +07:00
Nils Andreas Svee 2052417b6b test: update mock timestamp simulation in test support 2026-05-10 20:56:43 +07:00
Nils Andreas Svee 3de92749e9 test(client): add integration tests for m8 compatibility 2026-05-10 20:56:43 +07:00