Linux server replies now prefer the captured SCM_TIMESTAMPNS receive wall
time for the Receive/Both wall timestamp field when it is plausible for the
datagram it describes;
the userspace receive wall remains the fallback, and is what every other case
emits;
monotonic receive timing remains userspace and continues to own lifecycle and
same-host duration semantics;
midpoint deliberately remains based on the paired userspace samples.
Core boundary
ServerCore::handle_datagram(peer, packet) is unchanged — same signature, same
behavior, no transport metadata. It samples every instant it reports, exactly as
before.
The Tokio runtime uses a crate-private handle_received_datagram(peer, packet, kernel_rx_timestamp), which passes the observation explicitly with the datagram
it describes. Both entry points run one private implementation, so there is no
duplicated request-processing logic and no second public API.
No mutable "next receive timestamp" side channel, no pending-metadata field, no
thread-local or global state, and no ClockSource wrapper was introduced. ReplySource stays transport-side and never enters the core.
ClockSample is unchanged: still one paired userspace wall+monotonic instant.
The preferred receive wall value is carried beside it on the classified echo
(EchoRequest::receive_wall_ns), never inside it.
Measurement semantics
Upstream OWD: improved server receive endpoint on Linux — the reported
arrival becomes server_kernel_receive_wall instead of server_userspace_receive_wall, removing socket-queue and Tokio wakeup latency
from the apparent upstream path. It does not remove client send syscall or qdisc
latency, network-stack TX delay, or clock synchronization error.
Monotonic server processing: unchanged (userspace_send_mono - userspace_receive_mono).
Lifecycle, rate and deadlines: unchanged — always received_at.mono_ns.
Midpoint: unchanged — the per-domain mean of the paired userspace receive
and send samples.
Downstream OWD server endpoint: unchanged userspace send wall.
Clock::Wall + StampAt::Both: the wall processing interval now includes
the kernel-to-userspace receive delay when the kernel reading is selected, so a
client may see a larger server_processing and a correspondingly smaller
adjusted RTT. This is intended, and arguably the more complete server residency
interval. Clock::Both still yields the unchanged monotonic interval; no attempt
is made to force the two to agree.
Under Clock::Both with StampAt::Both, recv_wall and recv_mono may name
different physical instants. That is a deliberate wire policy — each field is
honest about its own domain and nothing computes across the two — and it is
documented as such rather than modelled as one sample.
Validation
A kernel reading is used only when it:
converts into the wire's signed-nanosecond vocabulary (checked, not saturating,
so a clamped value cannot masquerade as a real instant);
is not later than the core's own paired receive wall sample;
lags that sample by at most MAX_KERNEL_RX_LAG (1 s, inclusive) — a sanity
guard against realtime clock discontinuities, not an expected wakeup latency.
Anything else falls back to the userspace receive wall. A failed check never
rejects the datagram, never emits an error reply, and never moves session state.
The reported arrival wall value is held back to the send reading in its own
right, so recv_wall <= send_wall still holds when the wall clock steps backwards
after receive — the kernel reading and the send sample are no longer from one
paired source, so ClockSample::not_after alone is not sufficient.
Compatibility
non-Linux: kernel_rx_timestamp is always None, so behavior is exactly as
before;
Linux without a usable kernel timestamp: exactly as before;
public ServerCore callers: exactly as before;
no public API break, no protocol negotiation change, no wire layout change.
Scope
Explicitly not in this PR:
kernel monotonic estimation or any reconstructed ClockSample;
any irtt-client, irtt-stats or irtt-cli production change;
any Cargo.toml/Cargo.lock change.
No socket capture work was revisited: #101's transport tests already prove the
timestamp is captured, and nothing here recertifies SCM_TIMESTAMPNS.
Tests
New deterministic core tests in crates/irtt-server/src/tests/kernel_rx.rs, all
with fabricated values and no sleeps or tolerances:
the acceptance rule: no observation, plausible earlier reading, equal to the
userspace sample, later than it, lag exactly at the bound, lag past the bound,
and a pre-epoch reading judged by the same rule against a realistic host clock;
unrepresentable readings in both directions, judged against userspace samples at 0, i64::MAX and i64::MIN — fallback, no panic;
the full StampAt x Clock matrix with a plausible reading, asserted as whole TimestampFields structs so a field appearing where it should not is as loud as
a wrong instant: Receive+Wall, Receive+Both, Both+Both, Both+Wall, Both+Monotonic, Receive+Monotonic, Send+Wall, Send+Both, None,
and all three Midpoint clocks;
the same matrix without metadata, proving the pre-existing emission;
an implausible reading (both step directions) falling back while the reply is
otherwise unchanged;
a backward wall step after receive: accepted kernel reading 900_000, send
sample stepped back to 850_000, asserting recv_wall <= send_wall and that the
monotonic pair is untouched;
the public handle_datagram path emitting exactly the userspace receive wall;
a lifecycle non-regression running one arrival schedule twice — with and
without a kernel reading on every echo — across a rate-limit burst, a refill, an
out-of-order window transition, an idle refresh only a rate-limited request can
have produced, and the maximum-duration close, asserting identical answered/
closing/count/window/session-count outcomes and that the only difference is the
reported recv_wall;
open and close ignoring the observation, pinned by the clock script.
Also added: a wall_ns_of conversion test in clock.rs covering epoch, positive,
pre-epoch and out-of-field values.
Verification
All run and passing:
cargo test -p irtt-server --all-features --locked (148 core tests)
cross-target cargo check -p irtt-server --all-features --all-targets for x86_64-unknown-linux-gnu, aarch64-unknown-linux-musl, x86_64-unknown-freebsd
and x86_64-pc-windows-gnu
Documentation
crates/irtt-server/AGENTS.md replaces the "Nothing consumes it yet" rule with
the two-entry-point boundary, the acceptance policy, what the observation may and
may not change, and the intended Clock::Wall processing-interval consequence. clock.rs's module doc no longer claims callers never pass timestamps into the
core, and states that ClockSample remains a paired userspace instant.
## Summary
- Linux server replies now prefer the captured `SCM_TIMESTAMPNS` receive wall
time for the `Receive`/`Both` wall timestamp field when it is plausible for the
datagram it describes;
- the userspace receive wall remains the fallback, and is what every other case
emits;
- monotonic receive timing remains userspace and continues to own lifecycle and
same-host duration semantics;
- midpoint deliberately remains based on the paired userspace samples.
## Core boundary
`ServerCore::handle_datagram(peer, packet)` is unchanged — same signature, same
behavior, no transport metadata. It samples every instant it reports, exactly as
before.
The Tokio runtime uses a crate-private `handle_received_datagram(peer, packet,
kernel_rx_timestamp)`, which passes the observation explicitly with the datagram
it describes. Both entry points run one private implementation, so there is no
duplicated request-processing logic and no second public API.
No mutable "next receive timestamp" side channel, no pending-metadata field, no
thread-local or global state, and no `ClockSource` wrapper was introduced.
`ReplySource` stays transport-side and never enters the core.
`ClockSample` is unchanged: still one paired userspace wall+monotonic instant.
The preferred receive wall value is carried beside it on the classified echo
(`EchoRequest::receive_wall_ns`), never inside it.
## Measurement semantics
- **Upstream OWD**: improved server receive endpoint on Linux — the reported
arrival becomes `server_kernel_receive_wall` instead of
`server_userspace_receive_wall`, removing socket-queue and Tokio wakeup latency
from the apparent upstream path. It does not remove client send syscall or qdisc
latency, network-stack TX delay, or clock synchronization error.
- **Monotonic server processing**: unchanged (`userspace_send_mono -
userspace_receive_mono`).
- **Lifecycle, rate and deadlines**: unchanged — always `received_at.mono_ns`.
- **Midpoint**: unchanged — the per-domain mean of the paired userspace receive
and send samples.
- **Downstream OWD server endpoint**: unchanged userspace send wall.
- **`Clock::Wall` + `StampAt::Both`**: the wall processing interval now includes
the kernel-to-userspace receive delay when the kernel reading is selected, so a
client may see a larger `server_processing` and a correspondingly smaller
adjusted RTT. This is intended, and arguably the more complete server residency
interval. `Clock::Both` still yields the unchanged monotonic interval; no attempt
is made to force the two to agree.
Under `Clock::Both` with `StampAt::Both`, `recv_wall` and `recv_mono` may name
different physical instants. That is a deliberate wire policy — each field is
honest about its own domain and nothing computes across the two — and it is
documented as such rather than modelled as one sample.
## Validation
A kernel reading is used only when it:
- converts into the wire's signed-nanosecond vocabulary (checked, not saturating,
so a clamped value cannot masquerade as a real instant);
- is not later than the core's own paired receive wall sample;
- lags that sample by at most `MAX_KERNEL_RX_LAG` (1 s, inclusive) — a sanity
guard against realtime clock discontinuities, not an expected wakeup latency.
Anything else falls back to the userspace receive wall. A failed check never
rejects the datagram, never emits an error reply, and never moves session state.
The reported arrival wall value is held back to the send reading in its own
right, so `recv_wall <= send_wall` still holds when the wall clock steps backwards
after receive — the kernel reading and the send sample are no longer from one
paired source, so `ClockSample::not_after` alone is not sufficient.
## Compatibility
- non-Linux: `kernel_rx_timestamp` is always `None`, so behavior is exactly as
before;
- Linux without a usable kernel timestamp: exactly as before;
- public `ServerCore` callers: exactly as before;
- no public API break, no protocol negotiation change, no wire layout change.
## Scope
Explicitly not in this PR:
- kernel monotonic estimation or any reconstructed `ClockSample`;
- server TX timestamping;
- client TX timestamping (`SO_TIMESTAMPING`, `MSG_ERRQUEUE`, send correlation);
- midpoint kernel substitution;
- protocol negotiation changes;
- any `irtt-client`, `irtt-stats` or `irtt-cli` production change;
- any `Cargo.toml`/`Cargo.lock` change.
No socket capture work was revisited: #101's transport tests already prove the
timestamp is captured, and nothing here recertifies `SCM_TIMESTAMPNS`.
## Tests
New deterministic core tests in `crates/irtt-server/src/tests/kernel_rx.rs`, all
with fabricated values and no sleeps or tolerances:
- the acceptance rule: no observation, plausible earlier reading, equal to the
userspace sample, later than it, lag exactly at the bound, lag past the bound,
and a pre-epoch reading judged by the same rule against a realistic host clock;
- unrepresentable readings in both directions, judged against userspace samples at
`0`, `i64::MAX` and `i64::MIN` — fallback, no panic;
- the full `StampAt` x `Clock` matrix with a plausible reading, asserted as whole
`TimestampFields` structs so a field appearing where it should not is as loud as
a wrong instant: `Receive`+`Wall`, `Receive`+`Both`, `Both`+`Both`, `Both`+`Wall`,
`Both`+`Monotonic`, `Receive`+`Monotonic`, `Send`+`Wall`, `Send`+`Both`, `None`,
and all three `Midpoint` clocks;
- the same matrix without metadata, proving the pre-existing emission;
- an implausible reading (both step directions) falling back while the reply is
otherwise unchanged;
- a backward wall step after receive: accepted kernel reading 900_000, send
sample stepped back to 850_000, asserting `recv_wall <= send_wall` and that the
monotonic pair is untouched;
- the public `handle_datagram` path emitting exactly the userspace receive wall;
- a lifecycle non-regression running one arrival schedule twice — with and
without a kernel reading on every echo — across a rate-limit burst, a refill, an
out-of-order window transition, an idle refresh only a rate-limited request can
have produced, and the maximum-duration close, asserting identical answered/
closing/count/window/session-count outcomes and that the only difference is the
reported `recv_wall`;
- open and close ignoring the observation, pinned by the clock script.
Also added: a `wall_ns_of` conversion test in `clock.rs` covering epoch, positive,
pre-epoch and out-of-field values.
## Verification
All run and passing:
- `cargo test -p irtt-server --all-features --locked` (148 core tests)
- `cargo test -p irtt-client --all-features --locked`
- `cargo fmt --all -- --check`
- `cargo check --workspace --all-targets --all-features --locked`
- `cargo test --workspace --all-targets --all-features --locked`
- `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings`
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked`
- `cargo check -p irtt-cli --no-default-features --features {server,client,tui} --all-targets --locked`
- `git diff --check`
- cross-target `cargo check -p irtt-server --all-features --all-targets` for
`x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-musl`, `x86_64-unknown-freebsd`
and `x86_64-pc-windows-gnu`
## Documentation
`crates/irtt-server/AGENTS.md` replaces the "Nothing consumes it yet" rule with
the two-entry-point boundary, the acceptance policy, what the observation may and
may not change, and the intended `Clock::Wall` processing-interval consequence.
`clock.rs`'s module doc no longer claims callers never pass timestamps into the
core, and states that `ClockSample` remains a paired userspace instant.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
SCM_TIMESTAMPNSreceive walltime for the
Receive/Bothwall timestamp field when it is plausible for thedatagram it describes;
emits;
same-host duration semantics;
Core boundary
ServerCore::handle_datagram(peer, packet)is unchanged — same signature, samebehavior, no transport metadata. It samples every instant it reports, exactly as
before.
The Tokio runtime uses a crate-private
handle_received_datagram(peer, packet, kernel_rx_timestamp), which passes the observation explicitly with the datagramit describes. Both entry points run one private implementation, so there is no
duplicated request-processing logic and no second public API.
No mutable "next receive timestamp" side channel, no pending-metadata field, no
thread-local or global state, and no
ClockSourcewrapper was introduced.ReplySourcestays transport-side and never enters the core.ClockSampleis unchanged: still one paired userspace wall+monotonic instant.The preferred receive wall value is carried beside it on the classified echo
(
EchoRequest::receive_wall_ns), never inside it.Measurement semantics
arrival becomes
server_kernel_receive_wallinstead ofserver_userspace_receive_wall, removing socket-queue and Tokio wakeup latencyfrom the apparent upstream path. It does not remove client send syscall or qdisc
latency, network-stack TX delay, or clock synchronization error.
userspace_send_mono - userspace_receive_mono).received_at.mono_ns.and send samples.
Clock::Wall+StampAt::Both: the wall processing interval now includesthe kernel-to-userspace receive delay when the kernel reading is selected, so a
client may see a larger
server_processingand a correspondingly smalleradjusted RTT. This is intended, and arguably the more complete server residency
interval.
Clock::Bothstill yields the unchanged monotonic interval; no attemptis made to force the two to agree.
Under
Clock::BothwithStampAt::Both,recv_wallandrecv_monomay namedifferent physical instants. That is a deliberate wire policy — each field is
honest about its own domain and nothing computes across the two — and it is
documented as such rather than modelled as one sample.
Validation
A kernel reading is used only when it:
so a clamped value cannot masquerade as a real instant);
MAX_KERNEL_RX_LAG(1 s, inclusive) — a sanityguard against realtime clock discontinuities, not an expected wakeup latency.
Anything else falls back to the userspace receive wall. A failed check never
rejects the datagram, never emits an error reply, and never moves session state.
The reported arrival wall value is held back to the send reading in its own
right, so
recv_wall <= send_wallstill holds when the wall clock steps backwardsafter receive — the kernel reading and the send sample are no longer from one
paired source, so
ClockSample::not_afteralone is not sufficient.Compatibility
kernel_rx_timestampis alwaysNone, so behavior is exactly asbefore;
ServerCorecallers: exactly as before;Scope
Explicitly not in this PR:
ClockSample;SO_TIMESTAMPING,MSG_ERRQUEUE, send correlation);irtt-client,irtt-statsorirtt-cliproduction change;Cargo.toml/Cargo.lockchange.No socket capture work was revisited: #101's transport tests already prove the
timestamp is captured, and nothing here recertifies
SCM_TIMESTAMPNS.Tests
New deterministic core tests in
crates/irtt-server/src/tests/kernel_rx.rs, allwith fabricated values and no sleeps or tolerances:
userspace sample, later than it, lag exactly at the bound, lag past the bound,
and a pre-epoch reading judged by the same rule against a realistic host clock;
0,i64::MAXandi64::MIN— fallback, no panic;StampAtxClockmatrix with a plausible reading, asserted as wholeTimestampFieldsstructs so a field appearing where it should not is as loud asa wrong instant:
Receive+Wall,Receive+Both,Both+Both,Both+Wall,Both+Monotonic,Receive+Monotonic,Send+Wall,Send+Both,None,and all three
Midpointclocks;otherwise unchanged;
sample stepped back to 850_000, asserting
recv_wall <= send_walland that themonotonic pair is untouched;
handle_datagrampath emitting exactly the userspace receive wall;without a kernel reading on every echo — across a rate-limit burst, a refill, an
out-of-order window transition, an idle refresh only a rate-limited request can
have produced, and the maximum-duration close, asserting identical answered/
closing/count/window/session-count outcomes and that the only difference is the
reported
recv_wall;Also added: a
wall_ns_ofconversion test inclock.rscovering epoch, positive,pre-epoch and out-of-field values.
Verification
All run and passing:
cargo test -p irtt-server --all-features --locked(148 core tests)cargo test -p irtt-client --all-features --lockedcargo fmt --all -- --checkcargo check --workspace --all-targets --all-features --lockedcargo test --workspace --all-targets --all-features --lockedcargo clippy --workspace --all-targets --all-features --locked -- -D warningsRUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --lockedcargo check -p irtt-cli --no-default-features --features {server,client,tui} --all-targets --lockedgit diff --checkcargo check -p irtt-server --all-features --all-targetsforx86_64-unknown-linux-gnu,aarch64-unknown-linux-musl,x86_64-unknown-freebsdand
x86_64-pc-windows-gnuDocumentation
crates/irtt-server/AGENTS.mdreplaces the "Nothing consumes it yet" rule withthe two-entry-point boundary, the acceptance policy, what the observation may and
may not change, and the intended
Clock::Wallprocessing-interval consequence.clock.rs's module doc no longer claims callers never pass timestamps into thecore, and states that
ClockSampleremains a paired userspace instant.