v1.6.0

domcyrus/rustnetv1.6.0Aug 20, 2026by github-actions[bot]

AI Summary

v1.6.0 introduces significant enhancements to visual customization, detailed network metrics, and cross-platform support. Key additions include automatic light/dark theme detection, detailed round-trip time (RTT) tracking for protocols like Ping, DNS, and NTP, and passive DNS attribution. The release also adds configuration file support and expands PPA availability for Ubuntu 22.04 and 24.04 LTS.

Key Highlights

  • Enhanced visual customization with automatic light/dark mode detection, contrast warnings, and new truecolor presets (Catppuccin, Tokyo Night, Gruvbox, Nord).
  • Comprehensive network metrics including detailed RTT tracking for Ping, DNS, NTP, STUN, LLMNR, and NetBIOS protocols.
  • Improved device identification via passive DNS attribution and LAN device identification with MAC vendor lookups.
  • Expanded OS support with new PPAs for Ubuntu 22.04 and 24.04 LTS.
  • Added configuration file support for theme and color overrides.

Breaking Changes

  • Added `nodata` field to `DnsInfo` struct in `rustnet-core`, breaking code that constructs this struct with a literal.
  • Added `ndp_neighbor` field to `ProtocolState::Icmp` variant in `rustnet-core`, breaking code that constructs or exhaustively matches this variant.

New Features

  • Light Background Detection: Automatically detects terminal background color and adjusts text visibility.
  • Theme Contrast Warning: Warns users if color overrides result in low contrast (below 3:1).
  • Truecolor Detection on Direct-Color Terminals: Properly supports 24-bit color for terminals with `TERM=*-direct`.
  • Config File Under sudo: Reads the invoking user's config file rather than root's when running with sudo.
  • New Theme Presets: Adds Catppuccin Mocha, Tokyo Night, Gruvbox, and Nord themes with ANSI fallback.
  • Config File Support: Optional `~/.config/rustnet/config.toml` for theme and color overrides.
  • Passive DNS Attribution: Tags connections without SNI/Host headers with inferred hostnames from DNS responses.
  • LLMNR Response Time: Displays query-to-first-response time for UDP LLMNR lookups.
  • Headless Example: New `examples/headless.rs` demonstrates library usage without the TUI.
  • NetBIOS Response Time: Shows response time and status for UDP Name/Datagram Service requests.
  • Ubuntu 22.04 and 24.04 LTS PPA Support: Expanded package availability for these distributions.
  • DNS Query Name in Details: Displays queried domain names and flags NODATA answers in the DNS card.
  • LAN Device Identification: Displays MAC addresses with OUI vendor information in the Network Context card.
  • Default Gateway Marker: Annotates connections to the default gateway in the UI and logs.
  • NTP, STUN, and Ping RTT: Shows latest request-to-response round trip times for these protocols.
  • DNS Response Time: Displays transport metrics for unicast UDP DNS connections.

Full Release Notes


### Added
- **Light Background Detection**: at startup rustnet asks the terminal for its
  background color (OSC 11, Unix only) and, on a light background, darkens the
  ANSI Gray muted/label text tiers to DarkGray, which were nearly unreadable on
  white; the per-process identity tints darken likewise. Terminals that stay
  silent past a 150 ms timeout keep the theme unchanged, and explicit
  `[theme.overrides]` values are never touched (#563)
- **Theme Contrast Warning**: config file color overrides that leave a
  foreground/background pair below 3:1 contrast now print a startup warning.
  Only pairs the override touches are judged, so the built-in palettes are
  never second-guessed, and the colors are never altered. Body text is the
  terminal's own foreground, so pairs involving it cannot be measured (#563)
- **Truecolor Detection on Direct-Color Terminals**: `TERM=*-direct` entries
  advertise 24-bit color without setting `COLORTERM`, and are no longer
  downgraded to ANSI-16 (#563)
- **Config File Under sudo**: `sudo rustnet` now reads the invoking user's
  config rather than root's, resolving their home from the passwd database
  the same way the privilege drop resolves `SUDO_UID`. A config not owned by
  that user is refused, since the read happens with root privileges (#563)
- **New Theme Presets**: `--theme` gains `catppuccin-mocha`, `tokyo-night`,
  `gruvbox`, and `nord` truecolor themes with ANSI fallback (#563)
- **Config File**: optional `~/.config/rustnet/config.toml` sets the theme and
  per-color overrides; `--theme` takes precedence (#563)
- **Passive DNS Attribution**: connections without an SNI or HTTP Host header
  (encrypted QUIC, plain TCP/UDP) are now tagged with a hostname inferred from
  DNS responses observed on the wire within the last 10 seconds, shown as a
  dimmed `~name` in the Remote column and as an Attributed Name row with source
  and age in the Details tab; attributed names match the hostname filters and
  free-text search. Event-driven cache in `rustnet-core`, no per-packet
  lookups; works without reverse DNS (#553)
- **LLMNR Response Time**: UDP LLMNR lookups now show their query-to-first-
  response time in the Details Transport Health card. Pairing reuses the DNS
  transaction tracker, scoped to the local socket so multicast queries match
  unicast replies, and stamps the timing on both connection rows. Pending
  queries share the existing 10s expiry and hard cap. `rustnet-core` API note:
  `LlmnrInfo` gained `txid`, while `Connection` and `IngestOutcome` gained
  `llmnr_response_time` (#552)
- **Headless Example**: `examples/headless.rs` shows the library-crate
  pairing without the TUI: capture, parsing, connection tracking, process
  attribution, interface stats, and sandboxing, printing connection
  summaries to stdout. ROADMAP.md documents the remaining headless
  workstream (#550)
- **NetBIOS Response Time**: UDP Name Service and Datagram Service requests now
  show response time and the latest response status in Details. Pairing uses the
  16-bit transaction ID plus the local socket and service, so broadcast requests
  match replies from individual hosts, and the round trip is shown on both the
  broadcast request row and the responder's connection. WACK packets keep the
  request pending until its final response, and pending requests have a 10s
  expiry and hard cap (#538)
- **Ubuntu 22.04 LTS (Jammy) PPA**: The PPA now also builds for Ubuntu 22.04
  LTS using its backported `rustc-1.89` toolchain, covering Linux Mint 21.x
  and Pop!_OS 22.04. Install docs now list the supported derivatives and the
  Pop!_OS `apt-manage` command (#534)
- **Ubuntu 24.04 LTS (Noble) PPA**: The PPA now also builds for Ubuntu 24.04
  LTS using its backported `rustc-1.89` toolchain, which makes the documented
  `add-apt-repository` install work on Ubuntu 24.04 and Linux Mint 22.x.
  Install docs updated accordingly (#533)
- **DNS Query Name in Details**: The Details tab's DNS card now shows the
  queried domain as `DNS Query` alongside the query type and response IPs.
  The name was already parsed from query and response packets but only used
  for filtering and the Overview protocol column. The card also flags NODATA
  answers: a NOERROR response whose answer section holds no record of the
  queried type (e.g. an HTTPS-type lookup for a name with only A/AAAA
  records) shows `DNS Answer: no data` instead of silently omitting the
  response rows. The claim follows RFC 2308: truncated (TC) responses,
  referrals (NS-without-SOA authority), and answer sections that do not
  parse completely leave the flag unset rather than reporting a false
  "no data". `rustnet-core` API note: `DnsInfo` gained a `nodata` field,
  a breaking change for code constructing that struct with a literal (#532)
- **LAN Device Identification**: The Details tab's Network Context card shows
  Local MAC and Remote MAC rows with the OUI vendor (e.g. "Apple, Inc.") for
  addresses the neighbor cache has resolved, learned passively from observed
  ARP (IPv4) and NDP (IPv6) traffic. NDP messages are trusted only at hop
  limit 255 (RFC 4861), fragmented NDP is ignored (RFC 6980), and messages
  carrying any malformed option are discarded whole (RFC 4861 §7.1.1). Since
  neither protocol crosses routers, normally only on-link addresses (LAN
  devices and the gateway) populate; randomized MACs are labeled "locally
  administered". The cache holds up to 4096 neighbors — when full, entries
  idle for 30+ minutes are swept out to make room — and the clear-connections
  action resets it. `rustnet-core` API note: `ProtocolState::Icmp` gained an
  `ndp_neighbor` field, a breaking change for code constructing or
  exhaustively matching that variant (#530, #531)
- **Default Gateway Marker**: Connections whose remote endpoint is the host's
  default gateway (the local router) are now marked. The Overview Remote
  column appends `(gw)` when it fits, the Details tab annotates the remote
  address with `(gateway)`, and JSONL logs gain `remote_is_gateway` (sidecar)
  and `destination_is_gateway` (event log) keys, emitted only when true.
  Gateways are read from the OS routing table (`/proc/net/route` and
  `/proc/net/ipv6_route` on Linux, a `PF_ROUTE` sysctl dump on macOS/FreeBSD,
  `GetIpForwardTable2` on Windows) and refreshed with the local-address
  snapshot, so VPN or network changes are picked up (#529)
- **NTP RTT**: NTP client connections now show the latest request→response
  round trip in the Details Transport Health card, along with the server
  stratum. Polls pair with responses through the originate timestamp echo
  (RFC 5905), so daemons polling several servers stay distinct. Pending
  requests are bounded (hard cap plus 10s expiry) like DNS queries (#527)
- **STUN RTT**: STUN connections now show the latest request→response round
  trip in the Details Transport Health card, paired by the 96-bit transaction
  ID that retransmits reuse. Pending requests are bounded (hard cap plus 10s
  expiry) like DNS queries (#526)
- **Ping RTT**: ICMPv4 and ICMPv6 echo connections now show their latest RTT
  in the Overview RTT column and the Details Transport Health card. Requests
  and replies are paired by identifier and sequence number using per-packet
  capture timestamps, so overlapping or reordered exchanges from commands such
  as `ping 8.8.8.8 -i .2` remain distinct. Loopback pings are timed too, and
  inbound echo flows skip the RTT row since only the remote sender can measure
  it. Pending requests expire after 10 seconds and have a hard cap (#525)
- **DNS Response Time**: Unicast UDP DNS connections now show a transport
  metric in the Details Transport Health card instead of "No transport metrics
  for this protocol". Queries and responses are paired by their 16-bit
  transaction ID using capture timestamps, the latest completed round trip is
  shown as `DNS Response Time`, and the card also surfaces the last response
  code (NOERROR, NXDOMAIN, SERVFAIL, ...). Pending queries are bounded (hard
  cap plus 10s expiry), so floods cost samples, not memory. mDNS/LLMNR
  first-response timing is a possible follow-up (#523)
- **Cross-Platform Process Lineage**: The Details tab shows up to four parent
  processes for each attributed connection on Linux, macOS, Windows, and
  FreeBSD. JSONL exports include each ancestor's PID, name, executable path,
  start time, and whether the chain was truncated (#520)
- **Per-Connection RTT**: Every TCP connection now carries a live round-trip
  estimate for its whole lifetime, not just a one-shot handshake RTT. An
  outbound data segment is timed until the ACK that covers it, with Karn's
  algorithm discarding samples around retransmissions, and the samples feed an
  RFC 6298 style smoothed value. The Overview table gains a sortable RTT
  column (descending by default, so the slowest connections surface first),
  the Details Transport Health card shows `Live RTT` next to `Initial RTT`,
  and JSON event logs plus the PCAP sidecar gain `rtt_ms`. QUIC connections
  keep their handshake RTT, since QUIC ACKs are encrypted on the wire. Data
  round trips also feed the Graph tab's aggregate RTT, which previously only
  saw new-connection handshakes (#515)
- **Capture Failure Reporting**: Capture startup and runtime failures are retained in
  application state and shown as a persistent status-bar error with restart and quit
  guidance, instead of the TUI running on silently after capture stopped. The status
  bar grows a second row when the message and hint do not fit on one (#497)
- **Rich Process Attribution**: `ProcessLookup::get_process_attribution()` returns a
  `ProcessAttribution` carrying TGID, PPID, effective UID/GID, executable path,
  process lineage, and match quality instead of only `(pid, name)`. `MatchQuality`
  records how a connection was matched, so a relaxed wildcard or listener guess is
  no longer indistinguishable from a proven 4-tuple hit. On Linux the eBPF backends
  carry the kernel-recorded identity through unchanged and resolve process metadata
  in user space; the enhanced cache stores the full result, so metadata and match
  quality survive the first lookup. Every platform implements the rich lookup
  directly (#505, #513)
- **Process Identity, User, and Match Quality in the UI and Exports**: Connections now
  carry the owning process's PPID, executable path, effective UID/GID, and how
  confidently the attribution matched. The Attribution card in the Details pane
  repeats PID, shows PPID, and resolves user/group names with numeric fallbacks. It
  appears only when a backend actually resolved something, so platforms that cannot
  supply a field show nothing rather than a permanent placeholder. A relaxed
  wildcard or listener match renders in the warning color instead of passing for a
  proven one. The JSONL sidecar gains `process_ppid`, `process_executable`,
  `process_uid`, `process_gid`, and `attribution_match`; PCAPNG packet comments gain
  `ppid=`, `uid=`, and `attr=` but deliberately omit the executable path, which would
  repeat in every packet block. Executable paths are interned behind an `Arc`, so
  bulk connection snapshots stay allocation-free. Long paths shorten from the middle at render time
  (`/nix/store/…/bin/hello`), keeping the location prefix and the basename visible,
  and a leading `$HOME` renders as `~`; click-to-copy still yields the full
  path (#506, #511, #512, #513)
- **Rich macOS Process Attribution**: PKTAP keeps its kernel-provided per-packet PID
  and process name, marks the result as an exact PKTAP attribution, and uses libproc
  to add the PPID, executable path, and effective UID/GID. The lsof fallback
  requests and parses numeric UIDs, resolves executable paths through libproc, and
  reports exact, wildcard-address, or listener match quality. Packet-seeded PKTAP connections now
  receive one rich-enrichment pass without making permanently unavailable optional
  fields hot-loop retries (#510, #513)
- **Rich FreeBSD Process Attribution**: `sockstat` ownership is enriched through
  native `KERN_PROC_PID` and `KERN_PROC_PATHNAME` sysctl queries with PPID,
  effective UID/GID, and executable path. Process details are cached by PID for
  each socket-table refresh (#513)

### Changed
- **Library Crates 0.5.0**: the workspace library crates are released as
  0.5.0: `rustnet-core`, `rustnet-capture`, `rustnet-host`, and the new
  `rustnet-sandbox`, carrying the breaking API changes noted in this
  section's entries
- **Scrollbar**: the thumb is now a thin accent-colored bar on the outer edge
  of its column instead of a full block in the terminal foreground, and the
  track rule is gone, so the bar reads as a cue rather than a second vertical
  line beside the data (#563)
- **`--theme classic` Renamed to `vivid`**: the name now describes the colors
  rather than the provenance. It is the same ANSI-16 palette as `muted` with
  the chrome colored: yellow headings and keys, magenta borders. `classic` is
  no longer accepted (#563)
- **Status Bar Rebuilt Around Priority**: the footer now shows the active
  tab's context actions on the left and a fixed `h help  q quit` cluster
  pinned right. The cluster is reserved before any context action is placed,
  so quit never falls off the edge; when the terminal is too narrow to spell
  the actions out, the labels go first and the keys stand alone. Tab
  navigation hints are gone, since the numbered tab bar already advertises
  them, and the exhaustive keymap lives on the Help tab. Trailing actions
  are dropped one at a time to keep the remaining labels readable, and only
  a very narrow terminal falls back to bare keys. Keys are bright text on
  the terminal background rather than a reverse-video band, and Details
  only offers `ctrl-d/u` when the record outgrows its pane (#563)
- **Status Bar Alerts Match the Chrome**: the quit prompt, copy feedback,
  and capture errors now carry their meaning in a bold signal color instead
  of filling the row with a solid yellow, green, or red band. `NO_COLOR`
  keeps the reverse-video band, where it is the only cue available (#563)
- **Copy Hints Follow the Sandbox**: on Linux under the default sandbox the
  clipboard cannot be reached, so the `c copy` hints and the Details
  "click a field to copy" affordance are hidden rather than offering a key
  that can only report an error. `--no-sandbox` restores them (#563)
- **Filtering Is a Mode, Not Four Notices**: the filter input row now shows
  only while a query is being typed. Once confirmed it collapses, leaving the
  query chip in the Connections title and the activity dot on the Overview
  tab, and the status bar returns to actions with `esc clear filter` first.
  While typing, the footer offers only what the filter editor handles, since
  every other key types a character into the query (#563)
- **Connection Table Cues**: the selected row now leads with an accent bar,
  and process names get a stable per-name tint (#563)
- **Tab Bar Status Cues**: the tab row right-aligns the capture interface and
  its link type, with a dot that turns red while capture is failing, and marks
  Overview with a `•` while a filter is active. The active filter query also
  shows next to the connection count in the Connections title. The capture
  cluster drops its link type, then itself, when the tab row gets tight (#563)
- **Details Header Badges**: the Details header shows the connection state as a
  colored pill plus chips for the current rates and RTT, dropping chips
  right to left as the terminal narrows. Scrolled Details and Help panes dim the
  line where the content continues, and long process paths now truncate from the
  left so the binary name stays visible (#563)
- **Loading Shimmer**: the loading screen text shimmers across the accent color
  on truecolor terminals and stays static everywhere else (#563)
- **TUI Restyle**: keycap-style status bar, realigned Help tab, and refined
  bar, scrollbar, and selection styling (#563)
- **Details Tab Application Card Alignment**: every protocol's Application
  card now renders a fixed row set with `-` placeholders instead of rows that
  appear and disappear with data availability; HTTPS shows its four rows even
  before the TLS handshake is parsed, and QUIC's SNI/ALPN rows are no longer
  hidden behind it. ICMP/ICMPv6 and IGMP gain their own cards (message name,
  echo ID/sequence, NDP neighbor, group address), HTTP gains Version, Host,
  and User-Agent rows, and ARP gains an Operation row plus the same
  protocol-colored heading as DPI protocols. SSH version/state and DNS, mDNS,
  and LLMNR response IPs render human-readable instead of Rust debug output,
  and FTP's response code and message merge into one row. DNS, LLMNR, and
  NetBIOS expose their transaction IDs like STUN already did. Transport Health
  drops its duplicate NTP Stratum and STUN Last Message rows; those now live
  only in the Application card (#557)
- **Library Internals Deduplicated and Narrowed**: Removed remaining dead code
  and test-only public API from the workspace crates, narrowed public items
  with no external consumers to crate or module visibility (including
  cfg-gated macOS and Windows internals the Linux lint pass could not see),
  and deduplicated repeated logic: the four Windows socket-table refreshes,
  the macOS/FreeBSD BPF privilege probe, PKTAP and lsof process-name
  normalization, `ParsedPacket` construction, and the Details tab field
  rendering (#556)
- **Dead Library-Crate API Removed**: The workspace crates dropped public API
  nothing in the workspace uses: a never-compiling procfs-only module and
  write-only lookup statistics in `rustnet-host`, the unused thread-id and
  monotonic-timestamp attribution fields, and assorted unused methods and
  write-only fields in `rustnet-core` (including the crate-root flat
  re-exports and the `psh`/`urg` TCP flags) (#551)
- **Interface Stats Moved to rustnet-core**: The per-platform interface
  statistics providers (sysfs on Linux, getifaddrs on macOS/FreeBSD, IP
  Helper on Windows) moved from the binary into `rustnet-core` behind a new
  `interface_stats::create_stats_provider()` composition point, and the
  macOS/FreeBSD getifaddrs walkers now share one implementation (#549)
- **Sandboxing Moved to rustnet-sandbox**: Sandboxing and the root uid drop
  now live in the new dependency-free `rustnet-sandbox` crate with one
  `apply_sandbox` entry point per platform. `--no-sandbox` and
  `--sandbox-strict` now exist on all platforms: FreeBSD honors them for the
  uid drop (previously ignored), macOS builds without Seatbelt honor them
  too, and macOS now reports partial enforcement (e.g. Seatbelt applied but
  uid drop failed) instead of only fully-enforced/not-applied (#548)
- **Unified TLS Handshake Parser**: The HTTPS (TCP) and QUIC DPI paths now
  share one TLS handshake parser. TCP SNI values are validated with the same
  strict hostname rules as QUIC (values containing characters like `/ @ : ?`
  or single-label names are no longer shown), ClientHellos larger than 16 KB
  (e.g. post-quantum key shares) are now parsed on TCP, and the 100-extension
  parsing cap now also applies to QUIC. Invalid older supported-version values
  cannot cause QUIC handshakes to be reported below TLS 1.3 (#546)
- **Shared OUI Database**: The OUI vendor table is now shared between
  packet-processor threads via `Arc` instead of being cloned per thread,
  saving roughly 10 MB of memory (#542)
- **Modern Linux eBPF Attribution Backend**: Process attribution now prefers BPF
  trampoline programs (fentry/fexit) and falls back to legacy kprobes and then procfs,
  choosing the backend from actual BTF, load, and attach results rather than the
  reported kernel version. The preferred backend avoids `perf_event_open(2)`, so
  Debian's `kernel.perf_event_paranoid=3` default no longer blocks eBPF attribution.
  Both BPF backends use CO-RE socket reads and fall through to procfs when target BTF
  is unavailable rather than risking misattribution with fixed offsets. The Statistics
  panel reports the selected backend, a missing optional ICMP hook now degrades only
  ICMP coverage instead of disabling TCP/UDP attribution, and BPF capabilities are
  dropped on the capture workers after privileged initialization (#498)

### Fixed
- **Linux Aggregate Capture Recovery**: Capturing with `-i any` now retries
  transient libpcap "interface disappeared" errors during VPN or other
  interface removal instead of stopping immediately. Named-interface failures
  and persistent errors remain fatal (#559)
- **Details Tab ARP Layout**: ARP connections were exempt from the fixed
  Details layout and dropped the Network Context MAC/attribution rows and the
  whole Attribution card, so Traffic Statistics and the cards below jumped
  when moving between ARP and non-ARP entries. ARP now renders the same
  left-column row set with `-` placeholders, and its MAC rows resolve from
  the neighbor cache like any other on-link connection. The Status and
  Attributed Via ages now share one formatter, so connections closed or idle
  for over an hour show `2h ago` instead of a large minute count (#555)
- **Details Tab Layout Stability**: rows in the Details tab no longer appear
  or disappear with data availability. MAC, Attributed Name/Via, the
  Attribution card's process fields, the Kubernetes card's fields, and the
  inbound Ping RTT row now always render for their connection class, showing
  `-` when unresolved, so labels and the cards below keep fixed positions
  while navigating between connections. Placeholder rows are not
  click-to-copy targets (#554)
- **macOS lsof Attribution UID**: When libproc details resolve, lsof-based
  attribution now reports the process's live effective UID from libproc
  instead of the UID captured in the earlier lsof scan, matching the other
  platforms (#545)
- **Broadcast/Multicast Endpoint Display**: A broadcast or multicast datagram
  sent by a peer (e.g. NetBIOS to 192.168.0.255) used to render its
  destination as a normal-looking Local address. Such endpoints now render as
  `bcast:PORT` / `mcast:PORT` in the Overview table, the Details tab annotates
  the full address with `(broadcast)` / `(multicast)`, and the Scope field
  reports BROADCAST for subnet-directed broadcasts instead of PRIVATE.
  Interface prefixes are now collected alongside local addresses to recognize
  each subnet's broadcast address; recognized broadcasts no longer trigger
  ambiguous-endpoint interface re-enumeration. JSONL logs gain
  `local_addr_kind`/`remote_addr_kind` (sidecar) and
  `source_addr_kind`/`destination_addr_kind` (event log) keys, emitted only
  for non-unicast endpoints (#528)
- **FreeBSD User Names in Connection Details**: Numeric socket-owner UIDs from
  `sockstat` now survive process attribution, so Details can resolve user names
  even when live process metadata is unavailable (#522)
- **Linux User Names in Connection Details**: The Landlock profile now permits
  read-only access to the public NSS account files, so the Details tab resolves
  process UID/GID values to user/group names instead of showing numeric values
  such as `1000:1000` (#519)
- **Comm-Truncated Linux Process Names**: The kernel `comm` field holds at most
  15 bytes, so both the eBPF and procfs backends showed names like
  "chromium-browse". When a name sits at that limit and the resolved
  executable's file name strictly extends it, the executable name now wins
  ("chromium-browse" becomes "chromium-browser"). Shorter names and interpreter
  cases (comm "myscript", exe "python3") are left untouched, so deliberately
  renamed comms keep working (#514)
- **Initial RTT Measured Against The Wrong Clock**: `Initial RTT` in the Details
  pane reported round trips as `0.0ms`. RTT was timed with a clock read taken while
  processing a packet rather than from the packet's capture timestamp, and capture
  hands packets to processing in batches spanning up to 100 packets or 100ms — wide
  enough that a whole handshake usually lands in one batch and gets timed as the
  batch loop's own microseconds. Connections are now stamped with each packet's
  libpcap timestamp, which costs no extra clock reads and also makes RTTs correct
  when a saved pcap is replayed. Separately, RTT could be measured from an inbound
  packet to this host's own reply, which spans no network at all; the clock now
  starts only on a packet leaving this host. This affected TCP as well — any
  connection to a local listener reported a handshake RTT of roughly 60µs (#507)
- **Transport Health On QUIC Connections**: The Details pane labelled every
  connection's Transport Health card with TCP loss counters, so a QUIC flow showed
  `TCP Retransmits`, `Duplicate ACKs`, and `Window Size` sitting empty as if the
  measurement had failed. QUIC encrypts its ACK frames and protects its packet
  numbers, so those counters are unobservable on the wire rather than merely
  unmeasured. QUIC connections now get their own rows — `Idle Timeout` and
  `Connection Close` from the transport parameters and CONNECTION\_CLOSE frame,
  plus a note about the encrypted counters — and `Initial RTT` is filled in from
  the long-header handshake exchange, the QUIC analogue of SYN/SYN-ACK timing.
  Other UDP flows say so instead of showing six blank TCP fields. All variants keep
  the card's height so the dashboard doesn't resize between connections (#507)
- **TCP Transport Health Counters**: `TCP Retransmits` in the Details pane stayed
  at 0 for the life of a connection while `Fast Retransmits` climbed into the
  dozens. Outbound sequence tracking desynchronized permanently the first time a
  segment was missed, so no later retransmission was counted; it now tracks a
  high-water mark that resyncs across gaps. Duplicate-ACK detection also counted
  inbound data segments, which repeat the same ack number throughout any download
  and inflated `Fast Retransmits` on healthy connections. `Duplicate ACKs` now
  reports a lifetime total rather than the length of the run in progress, and
  sequence comparisons use RFC 1982 serial arithmetic so they survive the 32-bit
  wraparound (#501)
- **JSON Outputs After Privilege Drop**: JSON event logs and PCAP sidecars are opened
  before sandboxing and the UID drop and written through retained descriptors, so
  logging no longer stops silently when the target path lives under a directory the
  unprivileged user cannot traverse (for example `/root`). Unopenable outputs now fail
  before terminal setup instead of failing quietly at runtime (#486)
- **eBPF Attribution Correctness**: Fixed TCP accept attribution and UID/GID
  extraction, stored dual-stack `AF_INET6` sockets with IPv4-mapped peers under the
  `AF_INET` key that matches the wire packets, removed a per-thread handoff map that
  leaked on missed kretprobes, and made socket-map cleanup skip unreadable entries
  instead of aborting the sweep and letting the map fill (#498)

### Documentation
- **Roadmap Audit**: Synced completed capabilities and clarified remaining DPI,
  platform, and analysis work (#547)
- **eBPF Install and Troubleshooting**: Documented the fentry/kprobe/procfs backend
  order, BTF and `RLIMIT_MEMLOCK` requirements, and reworked the BPF-denied
  troubleshooting steps now that `perf_event_paranoid` affects only the legacy
  backend (#498)
- **Localized Docs**: Added a Japanese README and synchronized the Simplified Chinese
  README, install, and usage docs (#484)