Skip to content

Use packet capture timestamps for RTT accounting#136

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 4 commits into
mainfrom
augustin/kernel-timestamp
Jul 13, 2026
Merged

Use packet capture timestamps for RTT accounting#136
gh-worker-dd-mergequeue-cf854d[bot] merged 4 commits into
mainfrom
augustin/kernel-timestamp

Conversation

@qsl73

@qsl73 qsl73 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Compute probe RTT from the kernel/BPF packet capture timestamp when the packet source can provide one, instead of the userspace time at which the packet happens to be read/processed. This avoids RTT inflation from capture delivery batching or scheduler delay.
  • Add a TimestampedSource interface; ReadAndParse now returns the received-at timestamp, preferring the capture timestamp and falling back to time.Now() for sources that don't support it (Linux, Windows unaffected).
  • macOS's PcapSource implements TimestampedSource via libpcap's CaptureInfo.Timestamp.
  • Updated all four drivers (ICMP, UDP, TCP-SYN, SACK) to compute RTT from this timestamp instead of time.Since(sendTime).

Closes #135. This is separate from #134 (macOS immediate-mode fix for the ~100ms RTT floor), per the issue's own scoping.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./... (full suite)
  • New regression tests in packets/packet_source_test.go: simulate delayed packet delivery with a valid capture timestamp and assert RTT uses the capture timestamp, not delivery time; and assert fallback to time.Now() when no capture timestamp is available.
  • Manually verified real-world impact on macOS against loopback, 8.8.8.8, and www.sakura.ad.jp for all four drivers using instrumented before/after RTT comparisons — kernel timestamps eliminate userspace scheduling jitter from RTT (up to tens of ms under load).

🤖 Generated with Claude Code

Compute probe RTT from the kernel/BPF packet capture timestamp when
the packet source can provide one, instead of the userspace time at
which the packet happens to be read and processed. This avoids RTT
inflation from capture delivery batching or scheduler delay.

ReadAndParse now returns the timestamp of the received packet,
preferring a TimestampedSource-provided capture timestamp and falling
back to time.Now() for sources that don't support it (Linux, Windows).
macOS's PcapSource implements TimestampedSource using libpcap's
CaptureInfo.Timestamp. All four drivers (ICMP, UDP, TCP-SYN, SACK) now
compute RTT from this timestamp.

Closes #135

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 10, 2026 12:59
@qsl73
qsl73 requested review from a team and AlexandreYang as code owners July 10, 2026 12:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves traceroute RTT accuracy by computing RTT using packet-capture/kernel timestamps when available (notably on macOS via libpcap), falling back to userspace time.Now() when capture timestamps aren’t provided.

Changes:

  • Introduces packets.TimestampedSource and updates packets.ReadAndParse to return a receivedAt timestamp.
  • Updates ICMP/UDP/TCP-SYN/SACK drivers to compute RTT via receivedAt.Sub(sendTime) instead of time.Since(sendTime).
  • Implements timestamp reporting in the macOS PcapSource and adds unit tests for timestamp selection behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
udp/udp_driver.go Uses receivedAt from ReadAndParse for RTT calculation.
tcp/tcp_driver.go Uses receivedAt from ReadAndParse for RTT calculation.
icmp/icmp_driver.go Threads receivedAt through ICMP RTT computation helpers.
sack/sack_driver.go Threads receivedAt through SACK RTT computation helpers.
packets/packet_source.go Adds TimestampedSource and returns a receive timestamp from ReadAndParse.
packets/bpfdev_darwin.go Stores libpcap CaptureInfo.Timestamp and exposes it via LastPacketTimestamp.
packets/packet_source_test.go New unit tests asserting kernel timestamp preference and fallback behavior.
packets/bpfdev_darwin_test.go Updates call sites for new ReadAndParse return signature.
packets/tcp_filter_test.go Updates call sites for new ReadAndParse return signature.
packets/pcap_filter_test.go Updates call sites for new ReadAndParse return signature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread udp/udp_driver.go Outdated
Comment thread tcp/tcp_driver.go Outdated
Comment thread icmp/icmp_driver.go Outdated
Comment thread sack/sack_driver.go Outdated
Comment thread packets/packet_source.go Outdated
Comment thread packets/packet_source_test.go Outdated
qsl73 added 2 commits July 10, 2026 15:19
Kernel/BPF capture timestamps carry no monotonic clock reading, so
receivedAt.Sub(sendTime) falls back to wall-clock-only subtraction
(unlike the previous time.Since(sendTime), which was immune to clock
steps via the monotonic reading). If the system wall clock steps
backward between sendTime and the packet being captured, this could
previously yield a negative RTT.

Add packets.RTT(receivedAt, sendTime), which falls back to
time.Since(sendTime) whenever the capture timestamp predates the send
time, and use it at all four RTT computation sites.
- ReadAndParse's error messages referred to "ConnHandle", a stale name
  from before it operated on the generic Source interface.
- Drop the flaky elapsed-time bounds in
  TestReadAndParseUsesKernelTimestampOverDelayedDelivery; the exact
  timestamp equality check already proves the kernel timestamp is used.
@qsl73

qsl73 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Local experiment results (macOS)

Measured the accuracy impact of preferring the kernel/BPF capture timestamp over time.Since(sendTime) on this machine, across all four drivers.

Method — same-run dual RTT computation (avoids run-to-run jitter confounds): for every received packet, computed both time.Now().Sub(sendTime) (old) and kernelTimestamp.Sub(sendTime) (new) from the same packet, and diffed them.

Against 8.8.8.8:

  • ICMP: old RTT overstated the true RTT by ~31% on average, with tail deltas of 25–42ms under scheduler/system load.
  • Overstatement was consistently present across UDP, TCP-SYN, and SACK as well, though ICMP showed the largest effect.

Against www.sakura.ad.jp (lower system load at the time):

Driver n mean delta p50 p90 p99 max
ICMP 862 0.14ms 0.08ms 0.28ms 1.29ms 3.36ms
UDP 301 0.15ms 0.07ms 0.37ms 0.89ms 1.00ms
TCP-SYN 235 0.69ms 0.04ms 0.14ms 47.3ms 47.5ms
TCP-SACK 99 0.06ms 0.05ms 0.09ms 0.47ms 0.47ms

Takeaway: the delta between kernel-timestamp RTT and userspace-timestamp RTT is a direct measurement of scheduler/delivery jitter, not a network property — it's small (sub-ms p50/p90) when the system is idle, but grows to tens of milliseconds under load (background CPU contention, many concurrent probes, etc.). This is the same class of jitter #133/#134 exposed for macOS pcap's old 100ms-cadence delivery; capture timestamps remove this source of RTT inflation entirely rather than just reducing its likelihood.

@AlexandreYang

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0229c0a49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packets/packet_source.go

@AlexandreYang AlexandreYang left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Reviewed the kernel/BPF timestamp propagation through PcapSource, ReadAndParse, and all four traceroute drivers. The implementation is structurally sound, but the production macOS timestamp path is not asserted by a test.

Overall assessment: needs fixes (test coverage gap only; no security or correctness defect found).

# Priority File Finding
1 P2 Badge packets/bpfdev_darwin_test.go:64 The real PcapSource kernel timestamp path is untested

Checks run successfully: go test ./..., go vet ./..., go build ./..., and go test -race ./packets ./icmp ./tcp ./udp.

Comment thread packets/bpfdev_darwin_test.go Outdated
… test

TestPcapSourceDeliversPacketsWithoutReadTimeoutDelay discarded
ReadAndParse's returned timestamp, so it wouldn't catch a regression
where PcapSource stopped implementing TimestampedSource or Read()
stopped copying CaptureInfo.Timestamp -- exactly the class of
regression this PR guards against. Now it retains and asserts on the
returned timestamp, and bpfdev_darwin.go asserts PcapSource satisfies
TimestampedSource at compile time.

Also add TestTCPDriverUsesKernelTimestampForRTT, which wraps MockSource
with a TimestampedSource implementation and a deliberately delayed
Read() to prove the tcp driver computes RTT from the reported capture
timestamp rather than delivery time.
@qsl73

qsl73 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use packet capture timestamps for RTT accounting

3 participants