From 5a943c4d2889f722d52e1bb5deb0366623222839 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:00:33 -0700 Subject: [PATCH 01/15] docs(spec): Add timer wheel subsystem design Protocol-agnostic hierarchical timer wheel to replace O(n) TCP timer scanning. Designed for 10k+ connections with extensibility for DNS and QUIC timer consumers. --- .gitignore | 1 + .../plans/2026-03-17-timer-wheel.md | 1194 +++++++++++++++++ .../specs/2026-03-17-timer-wheel-design.md | 352 +++++ 3 files changed, 1547 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-17-timer-wheel.md create mode 100644 docs/superpowers/specs/2026-03-17-timer-wheel-design.md diff --git a/.gitignore b/.gitignore index 704ed96..5404ef7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ *.perf *.pub local.tfvars +.worktrees/ diff --git a/docs/superpowers/plans/2026-03-17-timer-wheel.md b/docs/superpowers/plans/2026-03-17-timer-wheel.md new file mode 100644 index 0000000..65385f0 --- /dev/null +++ b/docs/superpowers/plans/2026-03-17-timer-wheel.md @@ -0,0 +1,1194 @@ +# Timer Wheel Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace O(n) TCP timer scanning with a protocol-agnostic hierarchical timer wheel that supports 10k+ connections with O(1) arm/cancel and O(fired) advance. + +**Architecture:** A 3-tier hierarchical timer wheel (1ms/256ms/65.5s resolution) with entries in a slab-allocated doubly-linked list. The wheel lives at `src/net/timer_wheel.rs`, protocol-agnostic. TCP-specific timer kinds and handle storage live in `src/net/handler/tcp/timer_kinds.rs`. The event loop owns the wheel and dispatches fired `TimerId` values to protocol handlers. + +**Tech Stack:** Rust, `slab` crate (already a dependency), `coarsetime` (already a dependency), `smallvec` (already a dependency). + +**Spec:** `docs/superpowers/specs/2026-03-17-timer-wheel-design.md` + +**Test runner:** `cargo test` (no feature flags, runs as root via `.cargo/config.toml`) + +--- + +## File Structure + +| File | Responsibility | +|------|---------------| +| `src/net/timer_wheel.rs` | **New.** Protocol-agnostic wheel core: `TimerWheel`, `TimerEntry`, `TimerHandle`, `TimerId`, `Slot`, `Tier`. All wheel operations: `arm`, `cancel`, `advance` (with cascade). Owns a `Slab`. | +| `src/net/handler/tcp/timer_kinds.rs` | **New.** `TcpTimerKind` enum, `TcpTimerHandles` struct (6 × `Option`), `TimerId` packing/unpacking helpers for TCP. | +| `src/net/handler/tcp/timers.rs` | **Rewrite.** Delete `poll_timers()` and `evict_stale()`. Add `handle_timer()` → `fire_delayed_ack()`, `fire_retransmit()`, `fire_keep_alive()`, `fire_persist()`, `fire_linger()`, `fire_time_wait()`. Each is `#[inline]`, independently testable. | +| `src/net/handler/tcp/transmit.rs` | **Modify.** Remove persist deadline check and linger deadline check from `poll_send()`. Convert retransmit/delayed-ack/persist arming from field writes to wheel arm calls. | +| `src/net/handler/tcp/tcb.rs` | **Modify.** Remove 5 `Option` deadline fields. | +| `src/net/handler/tcp/handler.rs` | **Modify.** Add `timer_handles: Slab` field. Update `insert_connection`/`remove_connection_by_key` to manage handle lifecycle. | +| `src/net/handler/tcp/connection.rs` | **Modify.** Remove deadline field initialization from active-open TCB construction. Arm initial retransmit via wheel. | +| `src/net/handler/tcp/inbound/listen.rs` | **Modify.** Remove deadline field initialization from passive-open TCB construction. Arm initial retransmit via wheel. | +| `src/net/handler/tcp/inbound/established.rs` | **Modify.** Replace ~20 deadline field read/writes with wheel arm/cancel calls. | +| `src/net/handler/tcp/inbound/syn_received.rs` | **Modify.** Replace retransmit_deadline cancel with wheel cancel. | +| `src/net/handler/tcp/inbound/syn_sent.rs` | **Modify.** Replace retransmit_deadline writes with wheel arm/cancel. | +| `src/net/handler/tcp/inbound/teardown.rs` | **Modify.** Replace time_wait_deadline and retransmit_deadline writes with wheel arm/cancel. | +| `src/net/handler/tcp/inbound/segment.rs` | **Modify.** Replace `persist_deadline.is_some()` / `retransmit_deadline.is_some()` checks with handle-based checks. | +| `src/rt/local.rs` | **Modify.** Own the `TimerWheel`. Increase clock refresh frequency. Replace `tcp.poll_timers()` + `tcp.evict_stale()` with `wheel.advance()` + protocol dispatch loop. | +| `src/net/mod.rs` | **Modify.** Add `pub mod timer_wheel;` | +| `src/net/handler/tcp/mod.rs` | **Modify.** Add `pub(crate) mod timer_kinds;` | + +--- + +### Task 1: Timer wheel core — types and arm + +**Files:** +- Create: `src/net/timer_wheel.rs` +- Modify: `src/net/mod.rs` + +- [ ] **Step 1: Write failing test — arm returns handle and stores entry** + +In `src/net/timer_wheel.rs`, add a `#[cfg(test)] mod tests` block: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn arm_returns_handle() { + let mut wheel = TimerWheel::new(0); + let id = TimerId(42); + let handle = wheel.arm(id, 100); + assert!(wheel.is_armed(handle)); + } + + #[test] + fn arm_places_in_correct_inner_slot() { + let mut wheel = TimerWheel::new(0); + let id = TimerId(1); + // Deadline 50ms from now → inner tier, slot 50 + let _handle = wheel.arm(id, 50); + assert_eq!(wheel.tiers[0].slots[50].head.is_some(), true); + } + + #[test] + fn arm_places_in_middle_tier_for_large_delta() { + let mut wheel = TimerWheel::new(0); + let id = TimerId(1); + // Deadline 1000ms → beyond inner (256ms), goes to middle tier + let _handle = wheel.arm(id, 1000); + // Middle tier slot = (1000 >> 8) & 0xFF = 3 + assert_eq!(wheel.tiers[1].slots[3].head.is_some(), true); + } + + #[test] + fn arm_places_in_outer_tier_for_huge_delta() { + let mut wheel = TimerWheel::new(0); + let id = TimerId(1); + // Deadline 100_000ms → beyond middle (65536ms), goes to outer tier + let _handle = wheel.arm(id, 100_000); + // Outer tier slot = (100_000 >> 16) & 0xFF = 1 + assert_eq!(wheel.tiers[2].slots[1].head.is_some(), true); + } + + #[test] + fn arm_multiple_same_slot_chains_correctly() { + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + let h2 = wheel.arm(TimerId(2), 50); + // Both in slot 50, h2 is head (prepend) + assert!(wheel.is_armed(h1)); + assert!(wheel.is_armed(h2)); + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cargo test timer_wheel` +Expected: FAIL — `TimerWheel` not defined. + +- [ ] **Step 3: Implement types and `arm()`** + +In `src/net/timer_wheel.rs`, implement: + +```rust +use slab::Slab; +use smallvec::SmallVec; + +/// Opaque identifier stored in the wheel and returned on expiry. +/// The wheel never inspects this — protocol handlers pack/unpack their own meaning. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimerId(pub u64); + +/// Handle returned by `arm()`. Used to cancel or check if a timer is still armed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimerHandle(usize); + +struct TimerEntry { + id: TimerId, + next: Option, + prev: Option, + slot: u16, // tier (2 bits high) | slot_index (8 bits low) +} + +struct Slot { + head: Option, +} + +struct Tier { + slots: [Slot; 256], + shift: u8, +} + +pub struct TimerWheel { + tiers: [Tier; 3], + entries: Slab, + current_tick_ms: u64, +} + +impl Slot { + const fn new() -> Self { + Self { head: None } + } +} + +impl Tier { + fn new(shift: u8) -> Self { + Self { + slots: [const { Slot::new() }; 256], + shift, + } + } +} + +impl TimerWheel { + pub fn new(start_ms: u64) -> Self { + Self { + tiers: [Tier::new(0), Tier::new(8), Tier::new(16)], + entries: Slab::new(), + current_tick_ms: start_ms, + } + } + + pub fn arm(&mut self, id: TimerId, deadline_ms: u64) -> TimerHandle { + let delta = deadline_ms.saturating_sub(self.current_tick_ms); + let (tier_idx, slot_idx) = if delta < 256 { + (0usize, (deadline_ms & 0xFF) as usize) + } else if delta < 65536 { + (1, ((deadline_ms >> 8) & 0xFF) as usize) + } else { + (2, ((deadline_ms >> 16) & 0xFF) as usize) + }; + + let packed_slot = (tier_idx as u16) << 8 | slot_idx as u16; + let entry_key = self.entries.insert(TimerEntry { + id, + next: None, + prev: None, + slot: packed_slot, + }); + + // Prepend to slot's linked list. + let slot = &mut self.tiers[tier_idx].slots[slot_idx]; + if let Some(old_head) = slot.head { + self.entries[old_head].prev = Some(entry_key); + } + self.entries[entry_key].next = slot.head; + slot.head = Some(entry_key); + + TimerHandle(entry_key) + } + + pub fn is_armed(&self, handle: TimerHandle) -> bool { + self.entries.contains(handle.0) + } +} +``` + +- [ ] **Step 4: Add `pub mod timer_wheel;` to `src/net/mod.rs`** + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `cargo test timer_wheel` +Expected: All 5 tests PASS. + +- [ ] **Step 6: Commit** + +```bash +git add src/net/timer_wheel.rs src/net/mod.rs +git commit -m "feat(timer_wheel): Add core types and arm operation" +``` + +--- + +### Task 2: Timer wheel core — cancel + +**Files:** +- Modify: `src/net/timer_wheel.rs` + +- [ ] **Step 1: Write failing tests for cancel** + +```rust +#[test] +fn cancel_removes_entry() { + let mut wheel = TimerWheel::new(0); + let handle = wheel.arm(TimerId(1), 50); + assert!(wheel.is_armed(handle)); + wheel.cancel(handle); + assert!(!wheel.is_armed(handle)); +} + +#[test] +fn cancel_unlinks_head() { + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + let h2 = wheel.arm(TimerId(2), 50); + // h2 is head. Cancel h2, h1 becomes head. + wheel.cancel(h2); + assert!(!wheel.is_armed(h2)); + assert!(wheel.is_armed(h1)); + assert_eq!(wheel.tiers[0].slots[50].head, Some(h1.0)); +} + +#[test] +fn cancel_unlinks_middle() { + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + let h2 = wheel.arm(TimerId(2), 50); + let h3 = wheel.arm(TimerId(3), 50); + // Chain: h3 → h2 → h1. Cancel h2. + wheel.cancel(h2); + assert!(wheel.is_armed(h1)); + assert!(!wheel.is_armed(h2)); + assert!(wheel.is_armed(h3)); +} + +#[test] +fn cancel_unlinks_tail() { + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + let _h2 = wheel.arm(TimerId(2), 50); + // Chain: h2 → h1. Cancel h1 (tail). + wheel.cancel(h1); + assert!(!wheel.is_armed(h1)); +} + +#[test] +fn cancel_invalid_handle_is_noop() { + let mut wheel = TimerWheel::new(0); + let handle = wheel.arm(TimerId(1), 50); + wheel.cancel(handle); + // Double cancel — should not panic. + wheel.cancel(handle); +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cargo test timer_wheel` +Expected: FAIL — `cancel` not defined. + +- [ ] **Step 3: Implement `cancel()`** + +```rust +impl TimerWheel { + pub fn cancel(&mut self, handle: TimerHandle) { + let Some(entry) = self.entries.try_remove(handle.0) else { + return; + }; + let tier_idx = (entry.slot >> 8) as usize; + let slot_idx = (entry.slot & 0xFF) as usize; + + // Unlink from doubly-linked list. + if let Some(prev) = entry.prev { + self.entries[prev].next = entry.next; + } else { + // Was head of slot. + self.tiers[tier_idx].slots[slot_idx].head = entry.next; + } + if let Some(next) = entry.next { + self.entries[next].prev = entry.prev; + } + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cargo test timer_wheel` +Expected: All tests PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/net/timer_wheel.rs +git commit -m "feat(timer_wheel): Add cancel operation" +``` + +--- + +### Task 3: Timer wheel core — advance (inner tier, no cascade) + +**Files:** +- Modify: `src/net/timer_wheel.rs` + +- [ ] **Step 1: Write failing tests for advance** + +```rust +#[test] +fn advance_fires_expired_timer() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(42), 10); + let fired = wheel.advance(10); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(42)); +} + +#[test] +fn advance_fires_nothing_when_no_timers_due() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 100); + let fired = wheel.advance(50); + assert!(fired.is_empty()); +} + +#[test] +fn advance_fires_multiple_in_same_slot() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 10); + wheel.arm(TimerId(2), 10); + let fired = wheel.advance(10); + assert_eq!(fired.len(), 2); + assert!(fired.contains(&TimerId(1))); + assert!(fired.contains(&TimerId(2))); +} + +#[test] +fn advance_fires_across_multiple_slots() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 5); + wheel.arm(TimerId(2), 10); + let fired = wheel.advance(10); + assert_eq!(fired.len(), 2); +} + +#[test] +fn advance_removes_fired_entries() { + let mut wheel = TimerWheel::new(0); + let handle = wheel.arm(TimerId(1), 10); + wheel.advance(10); + assert!(!wheel.is_armed(handle)); +} + +#[test] +fn advance_noop_when_time_unchanged() { + let mut wheel = TimerWheel::new(100); + wheel.arm(TimerId(1), 100); + let fired = wheel.advance(100); + // current_tick_ms == now_ms, no slots to drain + assert!(fired.is_empty()); +} + +#[test] +fn advance_catches_up_burst() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 5); + wheel.arm(TimerId(2), 50); + wheel.arm(TimerId(3), 200); + // Jump from 0 to 201 in one call. + let fired = wheel.advance(201); + assert_eq!(fired.len(), 3); +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cargo test timer_wheel` +Expected: FAIL — `advance` not defined. + +- [ ] **Step 3: Implement `advance()` (inner tier only, cascade placeholder)** + +```rust +impl TimerWheel { + pub fn advance(&mut self, now_ms: u64) -> SmallVec<[TimerId; 16]> { + let mut fired = SmallVec::new(); + + while self.current_tick_ms < now_ms { + let slot_idx = (self.current_tick_ms & 0xFF) as usize; + + // Drain inner wheel slot. + let mut cursor = self.tiers[0].slots[slot_idx].head; + while let Some(key) = cursor { + let entry = self.entries.remove(key); + cursor = entry.next; + fired.push(entry.id); + } + self.tiers[0].slots[slot_idx].head = None; + + // Cascade on inner wheel wrap. + if slot_idx == 0 && self.current_tick_ms > 0 { + self.cascade(1); + let mid_slot = ((self.current_tick_ms >> 8) & 0xFF) as usize; + if mid_slot == 0 { + self.cascade(2); + } + } + + self.current_tick_ms += 1; + } + + fired + } + + fn cascade(&mut self, tier_idx: usize) { + let slot_idx = ((self.current_tick_ms >> self.tiers[tier_idx].shift) & 0xFF) as usize; + let mut cursor = self.tiers[tier_idx].slots[slot_idx].head; + self.tiers[tier_idx].slots[slot_idx].head = None; + + while let Some(key) = cursor { + let entry = self.entries.remove(key); + cursor = entry.next; + // Re-arm into a lower tier with the original deadline. + // We need the deadline to recompute the slot — but we don't store it. + // Solution: re-arm using the TimerId. But we don't know the deadline. + // + // This is the cascade problem: we need to store the deadline_ms in the entry. + // TODO: Add deadline_ms to TimerEntry in the next step. + // For now, this is a stub — tests for cascade come in Task 4. + } + } +} +``` + +Wait — cascade needs the deadline. Let me fix the entry struct now. + +- [ ] **Step 4: Add `deadline_ms` to `TimerEntry` and update `arm()`/`advance()`** + +Update `TimerEntry`: +```rust +struct TimerEntry { + id: TimerId, + deadline_ms: u64, + next: Option, + prev: Option, + slot: u16, +} +``` + +Update `arm()` to store `deadline_ms`. Update `cascade()` to re-insert entries: +```rust +fn cascade(&mut self, tier_idx: usize) { + let slot_idx = ((self.current_tick_ms >> self.tiers[tier_idx].shift) & 0xFF) as usize; + let mut cursor = self.tiers[tier_idx].slots[slot_idx].head; + self.tiers[tier_idx].slots[slot_idx].head = None; + + while let Some(key) = cursor { + let entry = self.entries.remove(key); + cursor = entry.next; + // Re-arm into the correct lower-tier slot. + self.arm(entry.id, entry.deadline_ms); + } +} +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `cargo test timer_wheel` +Expected: All tests PASS. + +- [ ] **Step 6: Commit** + +```bash +git add src/net/timer_wheel.rs +git commit -m "feat(timer_wheel): Add advance with inner tier draining and cascade stub" +``` + +--- + +### Task 4: Timer wheel core — cascade + +**Files:** +- Modify: `src/net/timer_wheel.rs` + +- [ ] **Step 1: Write failing tests for cascade behavior** + +```rust +#[test] +fn cascade_middle_to_inner() { + let mut wheel = TimerWheel::new(0); + // Deadline 300ms — goes to middle tier (delta >= 256). + wheel.arm(TimerId(1), 300); + assert!(wheel.tiers[1].slots[(300 >> 8) & 0xFF].head.is_some()); + + // Advance to 256 — triggers cascade at inner wrap. + let fired = wheel.advance(256); + // Timer not due yet (deadline=300), but should have cascaded to inner tier. + assert!(fired.is_empty()); + // After cascade, entry should be in inner tier slot 300 & 0xFF = 44. + assert!(wheel.tiers[0].slots[44].head.is_some()); +} + +#[test] +fn cascade_fires_on_correct_tick() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 300); + // Advance past deadline. + let fired = wheel.advance(301); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(1)); +} + +#[test] +fn cascade_outer_to_middle() { + let mut wheel = TimerWheel::new(0); + // Deadline 70_000ms — goes to outer tier (delta >= 65536). + wheel.arm(TimerId(1), 70_000); + assert!(wheel.tiers[2].slots[(70_000 >> 16) & 0xFF].head.is_some()); + + // Advance to 65536 — triggers outer cascade. + let fired = wheel.advance(65536); + assert!(fired.is_empty()); + // Should have cascaded to middle tier. + let mid_slot = (70_000 >> 8) & 0xFF; + assert!(wheel.tiers[1].slots[mid_slot].head.is_some()); +} + +#[test] +fn cascade_multiple_entries() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 300); + wheel.arm(TimerId(2), 310); + wheel.arm(TimerId(3), 500); + // All in middle tier. Advance to 256 — cascade middle slot 1. + let fired = wheel.advance(311); + assert_eq!(fired.len(), 2); // TimerId(1) at 300 and TimerId(2) at 310 +} +``` + +- [ ] **Step 2: Run tests to verify current cascade behavior** + +Run: `cargo test timer_wheel` +Expected: Some tests may already pass if cascade was implemented in Task 3 step 4. Verify which pass, which fail. + +- [ ] **Step 3: Fix any cascade issues found** + +The `cascade()` method from Task 3 calls `self.arm()` which does full tier selection based on `deadline_ms - current_tick_ms`. At cascade time, `current_tick_ms` has advanced so the delta is smaller — the entry naturally falls into a lower tier. This should work correctly. Fix any edge cases discovered by the tests. + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cargo test timer_wheel` +Expected: All tests PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/net/timer_wheel.rs +git commit -m "feat(timer_wheel): Verify cascade from outer/middle tiers to inner" +``` + +--- + +### Task 5: Timer wheel core — re-arm and edge cases + +**Files:** +- Modify: `src/net/timer_wheel.rs` + +- [ ] **Step 1: Write tests for re-arm and edge cases** + +```rust +#[test] +fn rearm_cancel_then_arm() { + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + wheel.cancel(h1); + let h2 = wheel.arm(TimerId(1), 100); + assert!(!wheel.is_armed(h1)); + assert!(wheel.is_armed(h2)); + let fired = wheel.advance(101); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(1)); +} + +#[test] +fn arm_at_current_tick_fires_immediately() { + let mut wheel = TimerWheel::new(100); + wheel.arm(TimerId(1), 100); + // Deadline == current_tick — should be in slot 100 & 0xFF = 100. + // Advance by 1 tick to drain slot 100. + let fired = wheel.advance(101); + assert_eq!(fired.len(), 1); +} + +#[test] +fn arm_in_past_fires_on_next_advance() { + let mut wheel = TimerWheel::new(100); + // Deadline in the past. + wheel.arm(TimerId(1), 50); + // delta saturates to 0, goes to inner slot (50 & 0xFF) = 50. + // Since current_tick is 100, slot 50 was already passed. + // It will fire when the wheel wraps back to slot 50 (at tick 256+50=306). + // This is acceptable — past deadlines fire on the next wrap. + let fired = wheel.advance(307); + assert_eq!(fired.len(), 1); +} + +#[test] +fn entry_count_returns_armed_count() { + let mut wheel = TimerWheel::new(0); + assert_eq!(wheel.entry_count(), 0); + let h1 = wheel.arm(TimerId(1), 50); + let _h2 = wheel.arm(TimerId(2), 100); + assert_eq!(wheel.entry_count(), 2); + wheel.cancel(h1); + assert_eq!(wheel.entry_count(), 1); +} +``` + +- [ ] **Step 2: Run tests to verify failures** + +Run: `cargo test timer_wheel` +Expected: `entry_count` test fails (not defined). Other tests may pass or reveal edge cases. + +- [ ] **Step 3: Add `entry_count()` and fix any edge cases** + +```rust +impl TimerWheel { + pub fn entry_count(&self) -> usize { + self.entries.len() + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cargo test timer_wheel` +Expected: All tests PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/net/timer_wheel.rs +git commit -m "feat(timer_wheel): Add re-arm, entry_count, and edge case handling" +``` + +--- + +### Task 6: TCP timer kinds and handle storage + +**Files:** +- Create: `src/net/handler/tcp/timer_kinds.rs` +- Modify: `src/net/handler/tcp/mod.rs` + +- [ ] **Step 1: Write failing tests for TimerId packing/unpacking** + +In `src/net/handler/tcp/timer_kinds.rs`: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pack_unpack_roundtrip() { + let key = 12345usize; + let kind = TcpTimerKind::Retransmit; + let id = TimerId::tcp(key, kind); + let (k, t) = id.unpack_tcp(); + assert_eq!(k, key); + assert_eq!(t, kind); + } + + #[test] + fn pack_unpack_all_kinds() { + for kind in [ + TcpTimerKind::Retransmit, + TcpTimerKind::DelayedAck, + TcpTimerKind::Persist, + TcpTimerKind::KeepAlive, + TcpTimerKind::TimeWait, + TcpTimerKind::Linger, + ] { + let id = TimerId::tcp(999, kind); + let (k, t) = id.unpack_tcp(); + assert_eq!(k, 999); + assert_eq!(t, kind); + } + } + + #[test] + fn tcp_timer_handles_default_all_none() { + let handles = TcpTimerHandles::new(); + for i in 0..6 { + assert!(handles.handles[i].is_none()); + } + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cargo test timer_kinds` +Expected: FAIL — module doesn't exist. + +- [ ] **Step 3: Implement `timer_kinds.rs`** + +```rust +use crate::net::timer_wheel::{TimerHandle, TimerId}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum TcpTimerKind { + Retransmit = 0, + DelayedAck = 1, + Persist = 2, + KeepAlive = 3, + TimeWait = 4, + Linger = 5, +} + +impl From for TcpTimerKind { + fn from(v: u8) -> Self { + match v { + 0 => Self::Retransmit, + 1 => Self::DelayedAck, + 2 => Self::Persist, + 3 => Self::KeepAlive, + 4 => Self::TimeWait, + 5 => Self::Linger, + _ => panic!("invalid TcpTimerKind: {v}"), + } + } +} + +impl TimerId { + pub fn tcp(key: usize, kind: TcpTimerKind) -> Self { + TimerId((key as u64) << 8 | kind as u64) + } + + pub fn unpack_tcp(self) -> (usize, TcpTimerKind) { + let kind = TcpTimerKind::from(self.0 as u8); + let key = (self.0 >> 8) as usize; + (key, kind) + } +} + +pub struct TcpTimerHandles { + pub handles: [Option; 6], +} + +impl TcpTimerHandles { + pub fn new() -> Self { + Self { + handles: [None; 6], + } + } + + #[inline] + pub fn get(&self, kind: TcpTimerKind) -> Option { + self.handles[kind as usize] + } + + #[inline] + pub fn set(&mut self, kind: TcpTimerKind, handle: TimerHandle) { + self.handles[kind as usize] = Some(handle); + } + + #[inline] + pub fn clear(&mut self, kind: TcpTimerKind) { + self.handles[kind as usize] = None; + } + + #[inline] + pub fn is_armed(&self, kind: TcpTimerKind) -> bool { + self.handles[kind as usize].is_some() + } +} +``` + +- [ ] **Step 4: Add `pub(crate) mod timer_kinds;` to `src/net/handler/tcp/mod.rs`** + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `cargo test timer_kinds` +Expected: All tests PASS. + +- [ ] **Step 6: Commit** + +```bash +git add src/net/handler/tcp/timer_kinds.rs src/net/handler/tcp/mod.rs +git commit -m "feat(tcp): Add TcpTimerKind, TimerId packing, and TcpTimerHandles" +``` + +--- + +### Task 7: Add timer handle storage to TcpHandler + +**Files:** +- Modify: `src/net/handler/tcp/handler.rs` + +- [ ] **Step 1: Add `timer_handles: Slab` to `TcpHandler`** + +Add field to `TcpHandler` struct and initialize in `new()`. Update `insert_connection` to also insert a `TcpTimerHandles` entry with the same key. Update `remove_connection_by_key` to also remove the handles entry. + +The slab key synchronization requires inserting into both slabs and asserting the keys match. Since `slab::Slab` assigns keys sequentially and both start empty, the keys will stay in sync as long as inserts and removes are paired. + +```rust +// In TcpHandler struct: +pub(crate) timer_handles: Slab, + +// In new(): +timer_handles: Slab::new(), + +// In insert_connection(): +let handle_key = self.timer_handles.insert(TcpTimerHandles::new()); +debug_assert_eq!(key, handle_key, "timer_handles slab key mismatch"); + +// In remove_connection_by_key(): +if self.timer_handles.contains(key) { + // TODO: cancel all armed timers via wheel before removing. + self.timer_handles.remove(key); +} +``` + +- [ ] **Step 2: Run full test suite to verify nothing breaks** + +Run: `cargo test` +Expected: All existing tests PASS. The timer_handles slab is added but not yet used for scheduling. + +- [ ] **Step 3: Commit** + +```bash +git add src/net/handler/tcp/handler.rs +git commit -m "feat(tcp): Add TcpTimerHandles parallel slab to TcpHandler" +``` + +--- + +### Task 8: Extract `fire_*` methods from `poll_timers()` and `poll_send()` + +This is the largest task. Extract the per-connection timer logic from the current O(n) scan loops into individual `fire_*` methods. Do NOT wire them to the wheel yet — that comes in Task 10. The existing `poll_timers()` and `evict_stale()` continue to work as before during this task. + +**Files:** +- Modify: `src/net/handler/tcp/timers.rs` + +- [ ] **Step 1: Write tests for `fire_delayed_ack`** + +Tests should set up a TCB with `ack_pending = true` in Established state, call `fire_delayed_ack`, and assert that an ACK segment was produced in `tx_return` and `ack_pending` was cleared. + +Use the existing test harness patterns from `src/net/handler/tcp/tests/timers.rs` — look at how it constructs a `TcpHandler` with test connections and checks frame output. + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cargo test fire_delayed_ack` +Expected: FAIL — method not defined. + +- [ ] **Step 3: Implement `fire_delayed_ack`** + +Extract lines 35-86 of `timers.rs` (the per-connection body of the delayed ACK pass) into: +```rust +#[inline] +pub fn fire_delayed_ack<'umem>( + &mut self, + key: usize, + now: Instant, + src_mac: MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, +) -> Option { ... } +``` + +- [ ] **Step 4: Run test to verify it passes** + +- [ ] **Step 5: Repeat for `fire_retransmit`** + +Extract lines 258-429 of `timers.rs`. This handles SYN/SYN-ACK/Established/FIN retransmit with R2 threshold check and exponential backoff. + +- [ ] **Step 6: Repeat for `fire_keep_alive`** + +Extract lines 91-155 of `timers.rs`. Sends keep-alive probe or aborts connection. + +- [ ] **Step 7: Repeat for `fire_persist`** + +Extract lines 231-276 of `transmit.rs`. Sends zero-window probe with backoff. + +- [ ] **Step 8: Repeat for `fire_linger`** + +Extract lines 278-290 of `transmit.rs`. Sends RST and marks for removal. + +- [ ] **Step 9: Repeat for `fire_time_wait`** + +Extract lines 448-466 of `timers.rs`. Removes the connection. + +- [ ] **Step 10: Add `handle_timer` dispatch method** + +```rust +pub fn handle_timer<'umem>( + &mut self, + key: usize, + kind: TcpTimerKind, + now: Instant, + src_mac: MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, +) { + match kind { + TcpTimerKind::DelayedAck => { self.fire_delayed_ack(key, now, src_mac, neighbor_handler, free_frames, rx_return, tx_return); } + TcpTimerKind::Retransmit => { self.fire_retransmit(key, now, src_mac, neighbor_handler, free_frames, rx_return, tx_return); } + TcpTimerKind::KeepAlive => { self.fire_keep_alive(key, now, src_mac, neighbor_handler, free_frames, rx_return, tx_return); } + TcpTimerKind::Persist => { self.fire_persist(key, now, src_mac, neighbor_handler, free_frames, rx_return, tx_return); } + TcpTimerKind::Linger => { self.fire_linger(key, now, src_mac, neighbor_handler, free_frames, rx_return, tx_return); } + TcpTimerKind::TimeWait => { self.fire_time_wait(key); } + } +} +``` + +- [ ] **Step 11: Run full test suite** + +Run: `cargo test` +Expected: All tests PASS. The `fire_*` methods exist alongside the old `poll_timers()` — nothing is removed yet. + +- [ ] **Step 12: Commit** + +```bash +git add src/net/handler/tcp/timers.rs +git commit -m "feat(tcp): Extract fire_* timer methods for wheel dispatch" +``` + +--- + +### Task 9: Remove deadline fields from TCB + +**Files:** +- Modify: `src/net/handler/tcp/tcb.rs` +- Modify: `src/net/handler/tcp/connection.rs` +- Modify: `src/net/handler/tcp/inbound/listen.rs` +- Modify: `src/net/handler/tcp/inbound/established.rs` +- Modify: `src/net/handler/tcp/inbound/syn_received.rs` +- Modify: `src/net/handler/tcp/inbound/syn_sent.rs` +- Modify: `src/net/handler/tcp/inbound/teardown.rs` +- Modify: `src/net/handler/tcp/inbound/segment.rs` +- Modify: `src/net/handler/tcp/transmit.rs` +- Modify: `src/net/handler/tcp/timers.rs` +- Modify: `src/net/handler/tcp/tests/timers.rs` (and other test files that reference deadline fields) + +This is a large mechanical refactor. Every read/write of the 5 `Option` deadline fields must be replaced with wheel operations via `TcpTimerHandles`. + +- [ ] **Step 1: Remove fields from TCB struct** + +In `src/net/handler/tcp/tcb.rs`, remove: +- `delayed_ack_deadline: Option` +- `retransmit_deadline: Option` +- `time_wait_deadline: Option` +- `persist_deadline: Option` +- `linger_deadline: Option` + +- [ ] **Step 2: Fix all compilation errors** + +The compiler will report every site that reads or writes these fields. For each site: + +**Writes (arming):** `tcb.retransmit_deadline = Some(now + Duration::from_millis(rto))` becomes a wheel arm call. The `fire_*` methods and `handle_timer` need access to the wheel, so the wheel must be passed through or accessible. The cleanest path: `TcpHandler` gets a `&mut TimerWheel` parameter on methods that arm timers, or the event loop passes it when calling into TCP. + +**Writes (cancelling):** `tcb.retransmit_deadline = None` becomes a wheel cancel via the handle. + +**Reads (checking if armed):** `tcb.retransmit_deadline.is_some()` becomes `timer_handles[key].is_armed(TcpTimerKind::Retransmit)`. + +**Reads (checking deadline value):** `tcb.persist_deadline` in `transmit.rs:231` checks `now >= deadline`. This no longer exists — the wheel fires when the deadline is reached, so these checks move into the `fire_*` methods (which are only called when the timer is actually due). + +Key files and approximate change counts: +- `connection.rs`: ~5 field inits → remove, arm retransmit via wheel +- `inbound/listen.rs`: ~5 field inits → remove, arm retransmit via wheel +- `inbound/established.rs`: ~20 reads/writes → wheel arm/cancel +- `inbound/syn_received.rs`: ~1 cancel +- `inbound/syn_sent.rs`: ~2 writes +- `inbound/teardown.rs`: ~7 writes (time_wait + retransmit) +- `inbound/segment.rs`: ~2 `.is_some()` checks +- `transmit.rs`: ~10 reads/writes (retransmit, delayed_ack, persist, linger) +- `timers.rs`: remaining deadline references in old `poll_timers()` + +- [ ] **Step 3: Decide how the wheel is threaded through TCP methods** + +The wheel is owned by the event loop (`LocalRuntime`). TCP inbound handlers need to arm/cancel timers. Options: +- Pass `&mut TimerWheel` to every TCP method that might touch timers (verbose but explicit) +- Store a reference/pointer on `TcpHandler` (requires the wheel to be in an `UnsafeCell` like the other shared state in `local.rs`) + +Look at how `local.rs` already handles `tcp_handler`, `udp_handler`, etc. via `UnsafeCell` — the wheel should follow the same pattern: `UnsafeCell` owned by `LocalRuntime`, passed as `&mut` into TCP methods. + +- [ ] **Step 4: Run `cargo check` to verify compilation** + +Run: `cargo check` +Expected: Compiles with no errors. + +- [ ] **Step 5: Run full test suite** + +Run: `cargo test` +Expected: Some tests in `tests/timers.rs` will fail because they manipulate deadline fields directly. These need to be updated to use wheel arm/cancel instead. + +- [ ] **Step 6: Update tests to use wheel-based timer manipulation** + +Tests that did `tcb.retransmit_deadline = Some(now - Duration::from_millis(1))` to simulate an expired timer now need to: +1. Arm a timer via the wheel at a deadline in the past +2. Call `wheel.advance()` to fire it +3. Assert the `fire_*` method produced the expected output + +- [ ] **Step 7: Run full test suite** + +Run: `cargo test` +Expected: All tests PASS. + +- [ ] **Step 8: Commit** + +```bash +git add -A +git commit -m "refactor(tcp): Replace deadline fields with timer wheel arm/cancel" +``` + +--- + +### Task 10: Wire event loop to timer wheel + +**Files:** +- Modify: `src/rt/local.rs` + +- [ ] **Step 1: Add `TimerWheel` to `LocalRuntime`** + +Add `wheel: UnsafeCell` to the `LocalRuntime` struct. Initialize it in the builder with `TimerWheel::new(now_ms)` where `now_ms` is from the initial `coarsetime::Instant::now()`. + +- [ ] **Step 2: Increase clock refresh frequency** + +Move `now = coarsetime::Instant::now()` out of the `evict_counter & 65535` block. Place it before the wheel advance call. The exact check frequency can be tuned later — start with every iteration and measure. + +- [ ] **Step 3: Add wheel advance + dispatch loop** + +Replace the `tcp.poll_timers()` and `tcp.evict_stale()` calls with: + +```rust +let wheel = unsafe { &mut *self.wheel.get() }; +let now_ms = now.as_ticks() / /* ticks per ms — check coarsetime's tick rate */; +let fired = wheel.advance(now_ms); +let tcp_handler = unsafe { &mut *self.tcp_handler.get() }; +for timer_id in &fired { + let (key, kind) = timer_id.unpack_tcp(); + tcp_handler.handle_timer( + key, kind, now, + self.neighbor_handler.local_mac(), + &self.neighbor_handler, + &mut self.free_frames, + &mut self.rx_return, + &mut self.tx_return, + ); +} +``` + +**Important:** Check `coarsetime`'s tick-to-millisecond conversion. Look at `Duration::as_millis()` and `Instant::as_ticks()` to determine the conversion factor. The wheel works in milliseconds; coarsetime ticks may be a different unit. + +- [ ] **Step 4: Remove old `tcp.poll_timers()` and `tcp.evict_stale()` calls** + +Delete these lines from the `evict_counter & 65535` block: +```rust +tcp_handler.evict_stale(now, &mut self.rx_return); +tcp_handler.poll_timers(now, ...); +``` + +- [ ] **Step 5: Delete `poll_timers()` and `evict_stale()` from `timers.rs`** + +These methods are now dead code. Remove them. + +- [ ] **Step 6: Remove persist/linger deadline checks from `poll_send()`** + +In `transmit.rs`, the persist deadline check (lines 225-276) and linger deadline check (lines 278-290) are now handled by the wheel. Remove these blocks from `poll_send()`. The `fire_persist` and `fire_linger` methods handle this logic when the wheel fires. + +- [ ] **Step 7: Run full test suite** + +Run: `cargo test` +Expected: All tests PASS. + +- [ ] **Step 8: Commit** + +```bash +git add -A +git commit -m "feat(rt): Wire timer wheel into event loop, remove O(n) timer scanning" +``` + +--- + +### Task 11: Keep-alive semantic change + +**Files:** +- Modify: `src/net/handler/tcp/timers.rs` (fire_keep_alive) +- Modify: `src/net/handler/tcp/inbound/established.rs` (arm on packet activity) +- Modify: `src/net/handler/tcp/connection.rs` (arm on connect) +- Modify: `src/net/handler/tcp/inbound/listen.rs` (arm on passive open) + +- [ ] **Step 1: Write test for keep-alive timer arm on connection** + +Test that when a connection is established with keep-alive enabled, a KeepAlive timer is armed in the wheel. + +- [ ] **Step 2: Implement keep-alive arm on connection establishment** + +In the TCB construction paths (active open in `connection.rs`, passive open transition to Established in `inbound/syn_received.rs`), if `keep_alive_enabled`, arm a KeepAlive timer at `now + keep_alive_idle_ms`. + +- [ ] **Step 3: Write test for keep-alive re-arm on activity** + +Test that receiving a data segment cancels and re-arms the keep-alive timer. + +- [ ] **Step 4: Implement keep-alive re-arm on packet activity** + +In `inbound/established.rs`, where `tcb.last_activity = now` is set (on receiving data or ACK), also cancel and re-arm the KeepAlive timer. Reset `keep_alive_probes_sent`. + +- [ ] **Step 5: Write test for keep-alive probe firing** + +Test that when the keep-alive timer fires, a probe segment is sent and the timer is re-armed at `now + keep_alive_interval_ms`. + +- [ ] **Step 6: Implement in `fire_keep_alive`** + +Update `fire_keep_alive` to: +- Send probe (existing logic) +- Re-arm KeepAlive timer at `now + keep_alive_interval_ms` +- Increment `keep_alive_probes_sent` +- If `keep_alive_probes_sent >= keep_alive_count`, abort connection + +- [ ] **Step 7: Run full test suite** + +Run: `cargo test` +Expected: All tests PASS including existing keep-alive tests (updated as needed). + +- [ ] **Step 8: Commit** + +```bash +git add -A +git commit -m "feat(tcp): Convert keep-alive to wheel-driven deadline timer" +``` + +--- + +### Task 12: Cleanup and final verification + +**Files:** +- All modified files + +- [ ] **Step 1: Run full test suite** + +Run: `cargo test` +Expected: All tests PASS. + +- [ ] **Step 2: Check for dead code** + +Run: `cargo check 2>&1 | grep "warning.*dead_code\|warning.*unused"` +Remove any dead code warnings related to the old timer infrastructure. + +- [ ] **Step 3: Fix any warnings** + +- [ ] **Step 4: Run full test suite again** + +Run: `cargo test` +Expected: All tests PASS, no warnings. + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "chore(tcp): Remove dead timer code and fix warnings" +``` diff --git a/docs/superpowers/specs/2026-03-17-timer-wheel-design.md b/docs/superpowers/specs/2026-03-17-timer-wheel-design.md new file mode 100644 index 0000000..29a799c --- /dev/null +++ b/docs/superpowers/specs/2026-03-17-timer-wheel-design.md @@ -0,0 +1,352 @@ +# Timer Wheel Subsystem + +## Problem + +The event loop in `rt/local.rs` checks TCP timers by scanning all connections +every 65,536 iterations. At 10k+ connections this means ~50k TCB touches per +check cycle (5 full passes: delayed ACK, keep-alive, SACK recovery, RTO +retransmit, TIME-WAIT eviction). Most entries have no expired timer — the work +is wasted. + +Timer precision is also poor: the check frequency is iteration-based, not +time-based. The loop is a busy loop whose iteration speed depends on workload +(user futures, packet volume, TX/RX ring ops). A 40ms delayed ACK target can +be missed by an arbitrary margin. + +## Scope + +**Initial implementation — TCP timers:** +- Retransmit (RTO) +- Delayed ACK +- Persist (zero-window probe) +- Keep-alive +- TIME-WAIT +- Linger + +**Out of scope for now — stays as-is:** +- Neighbor cache eviction (small table, O(n) is fine) +- PMTU cache eviction (tiny table) +- Fragment reassembly timeout (bounded entry count) +- SACK recovery pass (ACK-feedback-driven, not timer-driven) + +**Future consumers (the wheel must support these without redesign):** +- **DNS resolver** — query retransmission, response timeout, cache TTL expiry +- **QUIC** — loss detection, idle timeout, ACK delay, path validation, + handshake timeout, key update, connection migration probes. QUIC has + per-stream and per-connection timers, potentially 100k+ at scale. + +## Design + +### Protocol-agnostic wheel core + +The wheel itself knows nothing about TCP, DNS, or QUIC. It stores opaque +`TimerId` values and returns them on expiry. Protocol handlers interpret the +IDs. + +```rust +/// Opaque identifier returned by the wheel on expiry. +/// Encodes enough information for the caller to dispatch to the right +/// protocol handler, connection, and timer kind. +/// +/// Layout: protocol-defined. TCP uses (slab_key, TimerKind). DNS and QUIC +/// will define their own packing. The wheel treats it as an opaque u64. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimerId(pub u64); +``` + +Each protocol defines its own packing/unpacking. TCP example: + +```rust +impl TimerId { + fn tcp(key: usize, kind: TcpTimerKind) -> Self { + TimerId((key as u64) << 8 | kind as u64) + } + fn unpack_tcp(self) -> (usize, TcpTimerKind) { + let kind = TcpTimerKind::from(self.0 as u8); + let key = (self.0 >> 8) as usize; + (key, kind) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum TcpTimerKind { + Retransmit = 0, + DelayedAck = 1, + Persist = 2, + KeepAlive = 3, + TimeWait = 4, + Linger = 5, +} +``` + +Future protocols pack their own IDs into the same `TimerId(u64)`: +- DNS: `(query_id, DnsTimerKind)` — retransmit, response timeout, cache TTL +- QUIC: `(connection_key, QuicTimerKind)` — loss detection, idle, ACK delay, + path validation, etc. + +The wheel doesn't care — it stores and returns `TimerId` values without +inspecting them. + +### Hierarchical wheel structure + +Three tiers, 256 slots each. Slot index is `(deadline_ms >> shift) & 0xFF`. + +| Tier | Shift | Resolution | Range | Covers | +|--------|-------|-----------|-----------|-------------------------------------| +| Inner | 0 | 1ms | 256ms | Delayed ACK (40ms), short RTOs | +| Middle | 8 | 256ms | 65.5s | Retransmit, TIME-WAIT, persist | +| Outer | 16 | 65.536s | ~4.66 hrs | Keep-alive (2h), long linger | + +```rust +pub struct TimerWheel { + tiers: [Tier; 3], + current_tick_ms: u64, +} + +struct Tier { + slots: [Slot; 256], + shift: u8, +} + +struct Slot { + head: Option, // index into TimerWheel::entries +} +``` + +Total fixed memory: ~6KB (3 x 256 slots x ~8 bytes). + +### Timer entries and linked list + +Timer entries live in a `Slab` owned by the wheel. Each entry +holds the opaque `TimerId` plus doubly-linked list pointers for its slot chain. + +```rust +pub struct TimerEntry { + id: TimerId, + next: Option, // entry index of next in slot chain + prev: Option, // entry index of prev in slot chain + slot: u16, // packed: tier (2 bits high) | slot_index (8 bits low) +} +``` + +**Entry handle**: `arm()` returns a `TimerHandle(usize)` (the entry's slab +key). The caller stores this handle to cancel or re-arm later. For TCP, the +handles live in a parallel `Slab` indexed by the same keys +as `Slab`: + +```rust +pub struct TcpTimerHandles { + pub handles: [Option; 6], // one per TcpTimerKind +} +``` + +This replaces the intrusive-list-through-TCB approach with a cleaner +separation: the wheel owns its entries, protocols own their handles. + +**Extensibility**: DNS and QUIC add their own handle storage without touching +the wheel. DNS might store handles in a `HashMap`. QUIC +might use a parallel slab like TCP. The wheel is unaware. + +Mixed slots: a single slot chain can contain entries from TCP, DNS, and QUIC +interleaved. The chain is a standard doubly-linked list through `TimerEntry` +nodes in the wheel's entry slab — no protocol-specific traversal logic. + +### Operations + +**Arm** — called when setting a timer deadline. O(1). + +1. Allocate a `TimerEntry` in the wheel's entry slab. Store `TimerId`. +2. Compute tier and slot index from `deadline_ms - current_tick_ms`. +3. Prepend to slot's linked list. +4. Return `TimerHandle` to caller. + +**Cancel** — called with a `TimerHandle`. O(1). + +1. Look up entry by handle. If not present, no-op. +2. Unlink from slot's linked list via prev/next. +3. Remove entry from slab, freeing the slot. + +**Re-arm** — cancel + arm. Callers can also just arm with a new ID and let +the old handle go stale if they track validity externally (but explicit +cancel + arm is preferred to avoid entry slab bloat). + +**Advance** — called from event loop after clock refresh. O(fired + cascade). + +1. While `current_tick_ms < now_ms`: + - Drain inner wheel slot at `current_tick_ms & 0xFF`. + - Collect all `TimerId` values from drained entries as fired timers. + - Remove drained entries from the entry slab. + - On inner wheel wrap (every 256 ticks): cascade one middle-tier slot into + inner slots (redistribute entries at finer granularity). + - On middle wheel wrap (every 65,536 ticks): cascade one outer-tier slot + into middle slots. + - Increment `current_tick_ms`. +2. Return fired list. + +When no timers are due, advance is a single integer comparison. + +### Event loop integration + +Changes to `rt/local.rs`: + +1. **Clock refresh frequency increases.** Move `now = coarsetime::Instant::now()` + out of the 65k-iteration block. The exact frequency (every iteration vs every + N iterations) is a tuning decision. `coarsetime::Instant::now()` calls + `clock_gettime(CLOCK_MONOTONIC_COARSE)` which is a vDSO call (tens of + nanoseconds), not a bare atomic load. The wheel tolerates stale `now` + gracefully — it just advances in a burst when `now` catches up. + +2. **Wheel advance replaces `poll_timers()` and TCP `evict_stale()`.** + The event loop dispatches fired timers by inspecting the `TimerId`: + ```rust + let fired = wheel.advance(now_ms); + for timer_id in fired { + // Top bits of TimerId encode protocol (0 = TCP, 1 = DNS, 2 = QUIC, etc.) + // Each protocol handler unpacks its own IDs. + let protocol = timer_id.protocol(); + match protocol { + Protocol::Tcp => { + let (key, kind) = timer_id.unpack_tcp(); + tcp_handler.handle_timer(key, kind, now, ...); + } + // Future: + // Protocol::Dns => dns_handler.handle_timer(timer_id, now, ...), + // Protocol::Quic => quic_handler.handle_timer(timer_id, now, ...), + } + } + ``` + +3. **Non-TCP eviction stays on the 65k counter** (neighbor, PMTU, UDP fragments). + +4. **`poll_send()` is unchanged.** Timer fire methods that produce outbound work + call `send_tracker.mark()` so `poll_send()` picks it up on the next iteration. + +### TCP timer dispatch + +`handle_timer` dispatches to small, inline, independently testable methods: + +```rust +pub fn handle_timer(...) { + match kind { + TcpTimerKind::DelayedAck => self.fire_delayed_ack(key, now, ...), + TcpTimerKind::Retransmit => self.fire_retransmit(key, now, ...), + TcpTimerKind::KeepAlive => self.fire_keep_alive(key, now, ...), + TcpTimerKind::Persist => self.fire_persist(key, now, ...), + TcpTimerKind::Linger => self.fire_linger(key, now, ...), + TcpTimerKind::TimeWait => self.fire_time_wait(key), + } +} +``` + +Each `fire_*` method is `#[inline]`, takes a slab key, does exactly one thing, +and is independently testable. + +### TCB changes + +**Removed fields** (scheduling moves to wheel): +- `delayed_ack_deadline: Option` +- `retransmit_deadline: Option` +- `time_wait_deadline: Option` +- `persist_deadline: Option` +- `linger_deadline: Option` + +Saves ~80 bytes per TCB (5 x 16-byte `Option`). + +**Retained fields** (configuration and state needed for re-arm logic): +- `rto`, `rto_backoff` — compute retransmit deadline +- `delayed_ack_ms` — compute delayed ACK deadline +- `time_wait_duration` — compute TIME-WAIT deadline +- `persist_backoff` — compute persist deadline with exponential backoff +- `keep_alive_enabled`, `keep_alive_idle_ms`, `keep_alive_interval_ms`, + `keep_alive_count`, `keep_alive_probes_sent`, `last_activity` — keep-alive + configuration and state +- `linger: Option` — configuration + +### Connection lifecycle + +- `insert_connection` also inserts a `TcpTimerHandles` entry in the parallel + slab (same key). All handles start as `None`. +- `remove_connection_by_key` cancels all armed timers via their handles + (which removes entries from the wheel's entry slab), then removes the + `TcpTimerHandles` entry. + +### Keep-alive semantic change + +Currently keep-alive is a computed threshold (`last_activity + idle_ms`) +checked during the O(n) scan. With the wheel, it becomes a real armed timer: + +- On connection establishment (if keep-alive enabled): arm at + `now + keep_alive_idle_ms`. +- On any packet activity: cancel and re-arm at `now + keep_alive_idle_ms`. + Reset `keep_alive_probes_sent`. +- On fire: send probe, arm next at `now + keep_alive_interval_ms`. +- After `keep_alive_count` fires with no activity: abort connection. + +This is a behavioral change in mechanism but produces identical externally +observable behavior. The benefit is precision — probes fire at the configured +time instead of whenever the scan happens to run. + +## Performance analysis + +### Per timer-check cycle (10k connections) + +| Pass | Before (O(n) scan) | After (wheel) | +|-------------------|--------------------|--------------------------| +| Delayed ACK | 10k TCB touches | O(fired), typically <100 | +| Keep-alive | 10k TCB touches | O(fired), typically <10 | +| RTO retransmit | 10k TCB touches | O(fired), typically <200 | +| TIME-WAIT | 10k TCB touches | O(fired), varies | +| Persist | (in poll_send) | O(fired) | +| **Total** | **~50k** | **O(fired), typically hundreds** | + +### Per packet (hot path) + +| Operation | Before | After | +|--------------------|-----------------|------------------------------------------| +| Arm retransmit | 1 field write | Unlink old + link new (2 slab index ops) | +| Cancel delayed ACK | 1 field write | Unlink (1 slab index op) | +| Arm delayed ACK | 1 field write | Link into slot (1 slab index op) | +| **Total** | **~2 stores** | **~4 slab-indexed pointer ops** | + +### Memory + +| Component | Before | After | +|------------------------------|--------|---------| +| Per-TCB timer fields | ~80B | Removed | +| Per-connection TcpTimerHandles | — | ~48B (6 x `Option`) | +| Per-armed-timer entry (wheel slab) | — | ~40B (`TimerId` + next/prev/slot) | +| Wheel fixed | — | ~6KB | +| **Net per-connection** | **0** | **~+8B fixed + ~40B per armed timer** | + +At 10k connections with ~2 timers armed on average (retransmit + delayed ACK), +net overhead is ~96B per connection — comparable to the old deadline fields. + +### Where it wins + +- Timer check goes from O(connections) to O(fired) — 100x+ reduction at 10k + connections when most timers aren't due. +- Timer precision: fires within one wheel tick of the deadline, independent of + loop iteration speed. +- TCB shrinks by 80 bytes — better cache packing for hot fields. + +### Where it costs + +- Per-packet: ~2 extra slab index lookups + pointer writes for arm/cancel. +- Complexity: linked list in wheel entry slab, cascade logic, handle lifecycle. +- Memory: ~96B per connection (at 2 armed timers avg), +6KB fixed. + +## Files affected + +| File | Change | +|------|--------| +| `src/net/timer_wheel.rs` | **New** — `TimerWheel`, `TimerEntry`, `TimerHandle`, `TimerId`, `Slot`, `Tier`. Protocol-agnostic, lives outside `handler/tcp/` so DNS and QUIC can use it. | +| `src/net/handler/tcp/timer_kinds.rs` | **New** — `TcpTimerKind`, `TcpTimerHandles`, `TimerId` packing/unpacking for TCP. | +| `src/net/handler/tcp/timers.rs` | Rewrite — delete `poll_timers()` and `evict_stale()`, add `handle_timer()` dispatching to `fire_*` methods | +| `src/net/handler/tcp/transmit.rs` | Remove persist/linger timer checks from `poll_send()` (persist and linger fire via wheel now) | +| `src/net/handler/tcp/tcb.rs` | Remove 5 `Option` deadline fields | +| `src/net/handler/tcp/handler.rs` | Add `timer_handles: Slab` to `TcpHandler`; update `insert_connection` / `remove_connection_by_key` | +| `src/rt/local.rs` | Own the `TimerWheel`; increase clock refresh frequency; replace `tcp.poll_timers()` + `tcp.evict_stale()` with `wheel.advance()` + protocol dispatch loop | +| `src/net/handler/tcp/inbound/*.rs` | Arm/cancel calls go through wheel via handles instead of writing deadline fields directly | +| `src/net/handler/tcp/tests/timers.rs` | Rewrite to test `fire_*` methods in isolation + wheel integration tests | +| `src/net/timer_wheel/tests.rs` | **New** — unit tests for wheel core (arm, cancel, advance, cascade) independent of any protocol | From 6245bab42ffff0fa1fb515e9336275e08815b643 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:09:31 -0700 Subject: [PATCH 02/15] feat(timer_wheel): Add core types and arm operation Introduces TimerWheel with three-tier hierarchical layout (1ms/256ms/65.5s resolution), TimerId/TimerHandle types, and the arm()/is_armed() operations backed by a Slab and per-slot doubly-linked lists. --- src/net/mod.rs | 1 + src/net/timer_wheel.rs | 195 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 src/net/timer_wheel.rs diff --git a/src/net/mod.rs b/src/net/mod.rs index 88b682a..f314fc8 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -3,6 +3,7 @@ pub mod checksum; mod fragment; mod neighbor; mod pmtu; +pub mod timer_wheel; pub mod handler; pub mod http; diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs new file mode 100644 index 0000000..3293504 --- /dev/null +++ b/src/net/timer_wheel.rs @@ -0,0 +1,195 @@ +use slab::Slab; + +// --- Public types --- + +/// Opaque timer identity. Callers create these; the wheel stores and returns them on expiry. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimerId(pub u64); + +/// Handle returned by [`TimerWheel::arm`]. Pass to [`TimerWheel::cancel`] to disarm. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimerHandle(usize); + +// --- Internal types --- + +struct TimerEntry { + id: TimerId, + deadline_ms: u64, + /// Next entry key in the slot's doubly-linked list (slab key). + next: Option, + /// Previous entry key in the slot's doubly-linked list (slab key). + prev: Option, + /// Packed: bits [9:8] = tier index (0-2), bits [7:0] = slot index within tier. + slot: u16, +} + +struct Slot { + head: Option, +} + +impl Slot { + const fn new() -> Self { + Self { head: None } + } +} + +struct Tier { + slots: [Slot; 256], + /// Number of bits to right-shift a deadline_ms to get the slot index. + shift: u8, +} + +impl Tier { + fn new(shift: u8) -> Self { + // SAFETY: Slot is a struct with no padding issues; initializing an array of 256 items + // via a const fn is idiomatic. + Self { + slots: std::array::from_fn(|_| Slot::new()), + shift, + } + } +} + +/// Hierarchical timer wheel with three tiers. +/// +/// - Tier 0 (inner): shift=0, 1 ms resolution, 256 ms range +/// - Tier 1 (middle): shift=8, 256 ms resolution, ~65.5 s range +/// - Tier 2 (outer): shift=16, ~65.5 s resolution, ~4.66 hr range +pub struct TimerWheel { + tiers: [Tier; 3], + entries: Slab, + current_tick_ms: u64, +} + +impl TimerWheel { + /// Create a new wheel with `current_tick_ms` as the reference time. + pub fn new(current_tick_ms: u64) -> Self { + Self { + tiers: [Tier::new(0), Tier::new(8), Tier::new(16)], + entries: Slab::new(), + current_tick_ms, + } + } + + /// Arm a timer that fires at `deadline_ms` (absolute ms since epoch/start). + /// + /// Returns a [`TimerHandle`] that can be passed to [`cancel`](Self::cancel). + pub fn arm(&mut self, id: TimerId, deadline_ms: u64) -> TimerHandle { + let delta = deadline_ms.saturating_sub(self.current_tick_ms); + + // Select tier based on how far out the deadline is. + let tier_idx: usize = if delta < 256 { + 0 + } else if delta < 65536 { + 1 + } else { + 2 + }; + + let shift = self.tiers[tier_idx].shift; + let slot_idx = ((deadline_ms >> shift) & 0xFF) as usize; + + // Pack tier_idx (2 bits high) | slot_idx (8 bits low) into slot field. + let slot_packed = ((tier_idx as u16) << 8) | (slot_idx as u16); + + // Grab current head before borrowing entries. + let old_head = self.tiers[tier_idx].slots[slot_idx].head; + + let entry = TimerEntry { + id, + deadline_ms, + next: old_head, + prev: None, + slot: slot_packed, + }; + + let key = self.entries.insert(entry); + + // If there was an old head, update its prev pointer. + if let Some(old_head_key) = old_head { + self.entries[old_head_key].prev = Some(key); + } + + // The new entry becomes the head. + self.tiers[tier_idx].slots[slot_idx].head = Some(key); + + TimerHandle(key) + } + + /// Returns `true` if the timer identified by `handle` is still armed. + pub fn is_armed(&self, handle: TimerHandle) -> bool { + self.entries.contains(handle.0) + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn arm_returns_handle() { + let mut wheel = TimerWheel::new(0); + let handle = wheel.arm(TimerId(1), 50); + assert!(wheel.is_armed(handle)); + } + + #[test] + fn arm_places_in_correct_inner_slot() { + let mut wheel = TimerWheel::new(0); + // delta = 50 < 256 → tier 0, slot = (50 >> 0) & 0xFF = 50 + wheel.arm(TimerId(2), 50); + assert!(wheel.tiers[0].slots[50].head.is_some()); + } + + #[test] + fn arm_places_in_middle_tier_for_large_delta() { + let mut wheel = TimerWheel::new(0); + // delta = 1000; 256 ≤ 1000 < 65536 → tier 1 + // slot = (1000 >> 8) & 0xFF = 3 + let expected_slot = (1000usize >> 8) & 0xFF; + wheel.arm(TimerId(3), 1000); + assert!(wheel.tiers[1].slots[expected_slot].head.is_some()); + } + + #[test] + fn arm_places_in_outer_tier_for_huge_delta() { + let mut wheel = TimerWheel::new(0); + // delta = 100_000 ≥ 65536 → tier 2 + // slot = (100_000 >> 16) & 0xFF = 1 + let expected_slot = (100_000usize >> 16) & 0xFF; + wheel.arm(TimerId(4), 100_000); + assert!(wheel.tiers[2].slots[expected_slot].head.is_some()); + } + + #[test] + fn arm_multiple_same_slot_chains_correctly() { + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(10), 50); + let h2 = wheel.arm(TimerId(11), 50); + + // Both handles must be armed. + assert!(wheel.is_armed(h1)); + assert!(wheel.is_armed(h2)); + + // Both slab keys must differ. + assert_ne!(h1, h2); + + // The slot head must point to h2 (most recently armed, prepended). + let head_key = wheel.tiers[0].slots[50] + .head + .expect("slot should have a head"); + assert_eq!(head_key, h2.0); + + // h2.next must be h1. + let h2_entry = &wheel.entries[h2.0]; + assert_eq!(h2_entry.next, Some(h1.0)); + + // h1.prev must be h2. + let h1_entry = &wheel.entries[h1.0]; + assert_eq!(h1_entry.prev, Some(h2.0)); + } +} From 945cd666da00930d4a8d9d75bd08af9be191ebc0 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:11:30 -0700 Subject: [PATCH 03/15] feat(timer_wheel): Add cancel operation Implements cancel() on TimerWheel with full doubly-linked list unlinking (head, middle, tail cases) and silent no-op on already-cancelled handles. Five TDD tests cover all unlink positions and the idempotent cancel case. --- src/net/timer_wheel.rs | 115 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs index 3293504..b3b7542 100644 --- a/src/net/timer_wheel.rs +++ b/src/net/timer_wheel.rs @@ -120,6 +120,38 @@ impl TimerWheel { pub fn is_armed(&self, handle: TimerHandle) -> bool { self.entries.contains(handle.0) } + + /// Cancel a previously armed timer. + /// + /// If `handle` does not refer to an active timer (already fired or already + /// cancelled), this is a no-op. + pub fn cancel(&mut self, handle: TimerHandle) { + // Remove the entry from the slab; bail silently if not present. + let entry = match self.entries.try_remove(handle.0) { + Some(e) => e, + None => return, + }; + + // Decode the packed slot field. + let tier_idx = ((entry.slot >> 8) & 0x3) as usize; + let slot_idx = (entry.slot & 0xFF) as usize; + + // Unlink from the doubly-linked list. + match entry.prev { + Some(prev_key) => { + // There is a predecessor — point it past the removed entry. + self.entries[prev_key].next = entry.next; + } + None => { + // No predecessor: this entry was the slot head. + self.tiers[tier_idx].slots[slot_idx].head = entry.next; + } + } + + if let Some(next_key) = entry.next { + self.entries[next_key].prev = entry.prev; + } + } } // --------------------------------------------------------------------------- @@ -192,4 +224,87 @@ mod tests { let h1_entry = &wheel.entries[h1.0]; assert_eq!(h1_entry.prev, Some(h2.0)); } + + // --- cancel tests --- + + #[test] + fn cancel_removes_entry() { + let mut wheel = TimerWheel::new(0); + let h = wheel.arm(TimerId(1), 50); + assert!(wheel.is_armed(h)); + wheel.cancel(h); + assert!(!wheel.is_armed(h)); + } + + #[test] + fn cancel_unlinks_head() { + // arm h1, then h2 → chain: h2(head) → h1 + // cancel h2 (head) → h1 should become the new head + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + let h2 = wheel.arm(TimerId(2), 50); + + wheel.cancel(h2); + + assert!(!wheel.is_armed(h2)); + assert!(wheel.is_armed(h1)); + + // h1 should now be the slot head + let tier_idx = 0usize; // delta=50 < 256 + let slot_idx = 50usize; // (50 >> 0) & 0xFF + let head_key = wheel.tiers[tier_idx].slots[slot_idx] + .head + .expect("slot should still have a head after cancelling h2"); + assert_eq!(head_key, h1.0); + + // h1.prev should be None (it is now the head) + assert_eq!(wheel.entries[h1.0].prev, None); + } + + #[test] + fn cancel_unlinks_middle() { + // arm h1, h2, h3 → chain: h3(head) → h2 → h1 + // cancel h2 → chain should be: h3 → h1 + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + let h2 = wheel.arm(TimerId(2), 50); + let h3 = wheel.arm(TimerId(3), 50); + + wheel.cancel(h2); + + assert!(wheel.is_armed(h1)); + assert!(!wheel.is_armed(h2)); + assert!(wheel.is_armed(h3)); + + // h3.next should now point to h1 + assert_eq!(wheel.entries[h3.0].next, Some(h1.0)); + // h1.prev should now point to h3 + assert_eq!(wheel.entries[h1.0].prev, Some(h3.0)); + } + + #[test] + fn cancel_unlinks_tail() { + // arm h1, h2 → chain: h2(head) → h1(tail) + // cancel h1 (tail) → chain: h2 only + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + let h2 = wheel.arm(TimerId(2), 50); + + wheel.cancel(h1); + + assert!(!wheel.is_armed(h1)); + assert!(wheel.is_armed(h2)); + + // h2.next should now be None (h1 was removed) + assert_eq!(wheel.entries[h2.0].next, None); + } + + #[test] + fn cancel_invalid_handle_is_noop() { + let mut wheel = TimerWheel::new(0); + let h = wheel.arm(TimerId(1), 50); + wheel.cancel(h); + // Cancelling the same handle again should not panic. + wheel.cancel(h); + } } From 04fad58ae8c0209c48ca0251c9dbb6f8af2f6b42 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:18:54 -0700 Subject: [PATCH 04/15] feat(timer_wheel): Add advance with inner tier draining and cascade Implements advance(now_ms) which drains inner wheel slots tick by tick, cascades middle-tier entries to the inner wheel on inner-wrap, and cascades outer-tier entries to the middle wheel on middle-wrap. cascade() collects all entries before re-arming to avoid slab aliasing. --- src/net/timer_wheel.rs | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs index b3b7542..2616579 100644 --- a/src/net/timer_wheel.rs +++ b/src/net/timer_wheel.rs @@ -1,4 +1,5 @@ use slab::Slab; +use smallvec::SmallVec; // --- Public types --- @@ -121,6 +122,69 @@ impl TimerWheel { self.entries.contains(handle.0) } + /// Advance the wheel to `now_ms`, firing all timers whose deadline has + /// passed. Returns the [`TimerId`]s of every fired timer. + pub fn advance(&mut self, now_ms: u64) -> SmallVec<[TimerId; 16]> { + let mut fired: SmallVec<[TimerId; 16]> = SmallVec::new(); + + while self.current_tick_ms < now_ms { + let inner_slot_idx = (self.current_tick_ms & 0xFF) as usize; + + // Drain the inner wheel slot for this tick. + let mut cursor = self.tiers[0].slots[inner_slot_idx].head; + self.tiers[0].slots[inner_slot_idx].head = None; + while let Some(key) = cursor { + let entry = self.entries.remove(key); + cursor = entry.next; + fired.push(entry.id); + } + + self.current_tick_ms += 1; + + // After incrementing, check whether we need to cascade. + // Inner → middle cascade: inner slot just wrapped back to 0. + if (self.current_tick_ms & 0xFF) == 0 && self.current_tick_ms > 0 { + self.cascade(1); + + // Middle → outer cascade: middle slot also just wrapped. + let middle_slot_idx = + ((self.current_tick_ms >> self.tiers[1].shift) & 0xFF) as usize; + if middle_slot_idx == 0 { + self.cascade(2); + } + } + } + + fired + } + + /// Re-distribute entries from one tier's current slot into lower tiers. + /// + /// Tier `tier_idx` has just advanced such that its slot pointer has wrapped + /// to 0. We drain the slot that `current_tick_ms` now points to in that + /// tier and re-arm each entry using its stored `deadline_ms` so it lands in + /// the correct (lower) tier. + fn cascade(&mut self, tier_idx: usize) { + let shift = self.tiers[tier_idx].shift; + let slot_idx = ((self.current_tick_ms >> shift) & 0xFF) as usize; + + // Collect all entries from the slot BEFORE re-arming (arm() mutates + // the entries slab, so we cannot hold borrows across that call). + let mut to_rearm: SmallVec<[(TimerId, u64); 64]> = SmallVec::new(); + let mut cursor = self.tiers[tier_idx].slots[slot_idx].head; + self.tiers[tier_idx].slots[slot_idx].head = None; + while let Some(key) = cursor { + let entry = self.entries.remove(key); + cursor = entry.next; + to_rearm.push((entry.id, entry.deadline_ms)); + } + + // Re-arm each entry; the smaller delta will place it in a lower tier. + for (id, deadline_ms) in to_rearm { + self.arm(id, deadline_ms); + } + } + /// Cancel a previously armed timer. /// /// If `handle` does not refer to an active timer (already fired or already @@ -307,4 +371,34 @@ mod tests { // Cancelling the same handle again should not panic. wheel.cancel(h); } + + // ----------------------------------------------------------------------- + // Task 3: advance basic tests + // ----------------------------------------------------------------------- + + #[test] + fn advance_fires_inner_slot_timer() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 50); + let fired = wheel.advance(51); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(1)); + } + + #[test] + fn advance_fires_multiple_timers_same_slot() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 50); + wheel.arm(TimerId(2), 50); + let fired = wheel.advance(51); + assert_eq!(fired.len(), 2); + } + + #[test] + fn advance_does_not_fire_future_timer() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 100); + let fired = wheel.advance(50); + assert!(fired.is_empty()); + } } From cc96d59d6d39761a341ccbfeb85b9767522c79a8 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:20:02 -0700 Subject: [PATCH 05/15] test(timer_wheel): Verify cascade from outer/middle tiers to inner Adds four cascade tests: middle-to-inner placement after inner-wrap, correct firing tick after cascade, outer-to-middle cascade at the 65536 boundary, and simultaneous firing of multiple cascaded entries. --- src/net/timer_wheel.rs | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs index 2616579..954bf8d 100644 --- a/src/net/timer_wheel.rs +++ b/src/net/timer_wheel.rs @@ -401,4 +401,84 @@ mod tests { let fired = wheel.advance(50); assert!(fired.is_empty()); } + + // ----------------------------------------------------------------------- + // Task 4: Cascade verification tests + // ----------------------------------------------------------------------- + + /// Arm at 300 ms (goes to middle tier). Advance to 256 (triggers + /// inner→middle cascade). The timer should now live in inner tier slot 44 + /// (300 & 0xFF == 44) but must NOT yet have fired. + #[test] + fn cascade_middle_to_inner() { + let mut wheel = TimerWheel::new(0); + let handle = wheel.arm(TimerId(42), 300); + assert!(wheel.is_armed(handle)); + + // Advancing to 256 crosses the inner wrap boundary and cascades tier 1. + let fired = wheel.advance(256); + assert!(fired.is_empty(), "timer not due yet; should not fire"); + + // After cascade the entry should be in inner tier slot 44 (300 & 0xFF). + let expected_slot = 300usize & 0xFF; // 44 + assert!( + wheel.tiers[0].slots[expected_slot].head.is_some(), + "cascaded entry should now be in inner slot {expected_slot}" + ); + } + + /// Arm at 300 ms. Advance all the way to 301. The timer must fire. + #[test] + fn cascade_fires_on_correct_tick() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(7), 300); + let fired = wheel.advance(301); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(7)); + } + + /// Arm at 70_000 ms (outer tier). Advance to 65_536 (triggers + /// middle→outer cascade). The entry should now be in the middle tier. + #[test] + fn cascade_outer_to_middle() { + let mut wheel = TimerWheel::new(0); + let handle = wheel.arm(TimerId(99), 70_000); + assert!(wheel.is_armed(handle)); + + // 65_536 == 256 * 256; crossing this wraps the middle wheel and + // triggers a cascade from the outer tier. + let fired = wheel.advance(65_536); + assert!(fired.is_empty(), "timer not due yet"); + + // After cascade, entry should be in middle tier, not outer. + // Outer tier slot for 70_000: (70_000 >> 16) & 0xFF == 1 + assert!( + wheel.tiers[2].slots[1].head.is_none(), + "outer slot should be empty after cascade" + ); + // Middle tier: delta from 65_536 to 70_000 is 4_464 < 65_536, and + // slot = (70_000 >> 8) & 0xFF == 273 & 0xFF == 17. + let expected_slot = (70_000usize >> 8) & 0xFF; // 17 + assert!( + wheel.tiers[1].slots[expected_slot].head.is_some(), + "cascaded entry should now be in middle slot {expected_slot}" + ); + } + + /// Arm at 300, 310, 500. Advance to 311. Timers at 300 and 310 must fire; + /// the one at 500 must not. + #[test] + fn cascade_multiple_entries() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 300); + wheel.arm(TimerId(2), 310); + wheel.arm(TimerId(3), 500); + + let fired = wheel.advance(311); + assert_eq!(fired.len(), 2, "exactly two timers should fire"); + + let mut ids: Vec = fired.iter().map(|t| t.0).collect(); + ids.sort_unstable(); + assert_eq!(ids, vec![1, 2]); + } } From 364e26d5cd6dde2a063cc042fc3450bb707d899e Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:20:27 -0700 Subject: [PATCH 06/15] feat(timer_wheel): Add entry_count and edge case tests Adds entry_count() -> usize (delegates to slab len). Adds seven edge case tests: re-arm after cancel, arm at current tick, arm in past (slot wrap), entry_count tracking, advance noop when time unchanged, burst catch-up across multiple slots, and no-fire when no timers due. --- src/net/timer_wheel.rs | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs index 954bf8d..7cdbf6c 100644 --- a/src/net/timer_wheel.rs +++ b/src/net/timer_wheel.rs @@ -122,6 +122,11 @@ impl TimerWheel { self.entries.contains(handle.0) } + /// Returns the number of currently armed timers. + pub fn entry_count(&self) -> usize { + self.entries.len() + } + /// Advance the wheel to `now_ms`, firing all timers whose deadline has /// passed. Returns the [`TimerId`]s of every fired timer. pub fn advance(&mut self, now_ms: u64) -> SmallVec<[TimerId; 16]> { @@ -481,4 +486,107 @@ mod tests { ids.sort_unstable(); assert_eq!(ids, vec![1, 2]); } + + // ----------------------------------------------------------------------- + // Task 5: entry_count and edge cases + // ----------------------------------------------------------------------- + + #[test] + fn rearm_cancel_then_arm() { + let mut wheel = TimerWheel::new(0); + let h1 = wheel.arm(TimerId(1), 50); + wheel.cancel(h1); + // Timer should no longer be armed immediately after cancel. + assert!(!wheel.is_armed(h1)); + + // Re-arm the same logical timer with a new deadline. The slab may + // reuse the freed key, so h1 and h2 might be equal; what matters is + // that only one timer fires at the new deadline and not at the old one. + let _h2 = wheel.arm(TimerId(1), 150); + assert_eq!(wheel.entry_count(), 1, "exactly one timer should be armed"); + + // Nothing fires before the new deadline. + let not_fired = wheel.advance(100); + assert!(not_fired.is_empty(), "should not fire before new deadline"); + + // Fires at the new deadline. + let fired = wheel.advance(151); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(1)); + } + + /// Wheel at 100; arm at 100. Advancing to 101 should fire the timer. + #[test] + fn arm_at_current_tick_fires_on_next_advance() { + let mut wheel = TimerWheel::new(100); + // delta = 0, saturating_sub → goes to inner slot (100 & 0xFF == 100). + wheel.arm(TimerId(55), 100); + let fired = wheel.advance(101); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(55)); + } + + /// Wheel at 100; arm deadline in the past (50). Delta saturates to 0 → + /// goes to inner slot (50 & 0xFF == 50). The wheel must wrap around to + /// that slot to fire it. + #[test] + fn arm_in_past_fires_on_next_advance() { + let mut wheel = TimerWheel::new(100); + // delta = 0 (saturated), slot = (50 >> 0) & 0xFF = 50. + // current_tick_ms starts at 100 so slot 50 is behind us in this + // rotation. The inner wheel wraps at 256, so slot 50 is drained when + // current_tick_ms == 306 (306 & 0xFF == 50). advance(N) processes + // ticks up to but not including N, so we need advance(307) to include + // tick 306. + wheel.arm(TimerId(77), 50); + + let fired = wheel.advance(307); + assert_eq!(fired.len(), 1); + assert_eq!(fired[0], TimerId(77)); + } + + #[test] + fn entry_count_returns_armed_count() { + let mut wheel = TimerWheel::new(0); + assert_eq!(wheel.entry_count(), 0); + + let h1 = wheel.arm(TimerId(1), 100); + let _h2 = wheel.arm(TimerId(2), 200); + assert_eq!(wheel.entry_count(), 2); + + wheel.cancel(h1); + assert_eq!(wheel.entry_count(), 1); + } + + #[test] + fn advance_noop_when_time_unchanged() { + let mut wheel = TimerWheel::new(100); + wheel.arm(TimerId(1), 100); + // advance(100) — current_tick_ms is already 100, loop does not execute. + let fired = wheel.advance(100); + assert!(fired.is_empty()); + } + + #[test] + fn advance_catches_up_burst() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 5); + wheel.arm(TimerId(2), 50); + wheel.arm(TimerId(3), 200); + + let fired = wheel.advance(201); + assert_eq!(fired.len(), 3); + let mut ids: Vec = fired.iter().map(|t| t.0).collect(); + ids.sort_unstable(); + assert_eq!(ids, vec![1, 2, 3]); + } + + #[test] + fn advance_fires_nothing_when_no_timers_due() { + let mut wheel = TimerWheel::new(0); + wheel.arm(TimerId(1), 100); + let fired = wheel.advance(50); + assert!(fired.is_empty()); + assert_eq!(wheel.entry_count(), 1, "timer should still be armed"); + } } From b56a61018cfda0483b53b80a1013b7f00a1ed0d2 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:24:17 -0700 Subject: [PATCH 07/15] feat(tcp): Add TcpTimerKind, TimerId packing, and TcpTimerHandles Introduces timer_kinds.rs with the TcpTimerKind enum (6 variants, repr(u8)), free functions for packing/unpacking TimerId with a connection key and timer kind, and TcpTimerHandles for per-connection handle storage. Adds TimerHandle::from_raw(crate-visible) constructor to support tests. --- src/net/handler/tcp/mod.rs | 1 + src/net/handler/tcp/timer_kinds.rs | 159 +++++++++++++++++++++++++++++ src/net/timer_wheel.rs | 7 ++ 3 files changed, 167 insertions(+) create mode 100644 src/net/handler/tcp/timer_kinds.rs diff --git a/src/net/handler/tcp/mod.rs b/src/net/handler/tcp/mod.rs index f339228..bbead56 100644 --- a/src/net/handler/tcp/mod.rs +++ b/src/net/handler/tcp/mod.rs @@ -11,6 +11,7 @@ pub(crate) mod segment; pub(crate) mod send_tracker; pub(crate) mod state; pub(crate) mod tcb; +pub(crate) mod timer_kinds; mod timers; mod transmit; diff --git a/src/net/handler/tcp/timer_kinds.rs b/src/net/handler/tcp/timer_kinds.rs new file mode 100644 index 0000000..ef5d975 --- /dev/null +++ b/src/net/handler/tcp/timer_kinds.rs @@ -0,0 +1,159 @@ +use crate::net::timer_wheel::{TimerHandle, TimerId}; + +/// The six distinct timer kinds associated with a TCP connection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum TcpTimerKind { + Retransmit = 0, + DelayedAck = 1, + Persist = 2, + KeepAlive = 3, + TimeWait = 4, + Linger = 5, +} + +impl From for TcpTimerKind { + fn from(value: u8) -> Self { + match value { + 0 => TcpTimerKind::Retransmit, + 1 => TcpTimerKind::DelayedAck, + 2 => TcpTimerKind::Persist, + 3 => TcpTimerKind::KeepAlive, + 4 => TcpTimerKind::TimeWait, + 5 => TcpTimerKind::Linger, + v => panic!("invalid TcpTimerKind discriminant: {v}"), + } + } +} + +/// Pack a connection key and timer kind into a [`TimerId`]. +/// +/// The low 8 bits hold the `kind` discriminant; bits 8 and above hold `key`. +pub fn tcp_timer_id(key: usize, kind: TcpTimerKind) -> TimerId { + TimerId((key as u64) << 8 | kind as u64) +} + +/// Unpack a [`TimerId`] previously created by [`tcp_timer_id`]. +pub fn unpack_tcp_timer_id(id: TimerId) -> (usize, TcpTimerKind) { + let kind = TcpTimerKind::from(id.0 as u8); + let key = (id.0 >> 8) as usize; + (key, kind) +} + +/// Stores one optional [`TimerHandle`] per [`TcpTimerKind`]. +pub struct TcpTimerHandles { + pub handles: [Option; 6], +} + +impl TcpTimerHandles { + /// Create a new handle store with all slots empty. + pub fn new() -> Self { + Self { handles: [None; 6] } + } + + /// Return the handle for `kind`, if one is armed. + pub fn get(&self, kind: TcpTimerKind) -> Option { + self.handles[kind as usize] + } + + /// Store `handle` for `kind`. + pub fn set(&mut self, kind: TcpTimerKind, handle: TimerHandle) { + self.handles[kind as usize] = Some(handle); + } + + /// Clear the handle for `kind`. + pub fn clear(&mut self, kind: TcpTimerKind) { + self.handles[kind as usize] = None; + } + + /// Return `true` if a handle is currently stored for `kind`. + pub fn is_armed(&self, kind: TcpTimerKind) -> bool { + self.handles[kind as usize].is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pack_unpack_roundtrip() { + let key = 12345usize; + let kind = TcpTimerKind::Retransmit; + let id = tcp_timer_id(key, kind); + let (got_key, got_kind) = unpack_tcp_timer_id(id); + assert_eq!(got_key, key); + assert_eq!(got_kind, kind); + } + + #[test] + fn pack_unpack_all_kinds() { + let key = 999usize; + let all_kinds = [ + TcpTimerKind::Retransmit, + TcpTimerKind::DelayedAck, + TcpTimerKind::Persist, + TcpTimerKind::KeepAlive, + TcpTimerKind::TimeWait, + TcpTimerKind::Linger, + ]; + for kind in all_kinds { + let id = tcp_timer_id(key, kind); + let (got_key, got_kind) = unpack_tcp_timer_id(id); + assert_eq!(got_key, key, "key mismatch for {kind:?}"); + assert_eq!(got_kind, kind, "kind mismatch for {kind:?}"); + } + } + + #[test] + fn tcp_timer_handles_default_all_none() { + let handles = TcpTimerHandles::new(); + let all_kinds = [ + TcpTimerKind::Retransmit, + TcpTimerKind::DelayedAck, + TcpTimerKind::Persist, + TcpTimerKind::KeepAlive, + TcpTimerKind::TimeWait, + TcpTimerKind::Linger, + ]; + for kind in all_kinds { + assert!( + handles.get(kind).is_none(), + "{kind:?} should be None initially" + ); + } + } + + #[test] + fn set_and_get_handle() { + let mut handles = TcpTimerHandles::new(); + let handle = TimerHandle::from_raw(42); + handles.set(TcpTimerKind::KeepAlive, handle); + assert_eq!(handles.get(TcpTimerKind::KeepAlive), Some(handle)); + } + + #[test] + fn clear_handle() { + let mut handles = TcpTimerHandles::new(); + let handle = TimerHandle::from_raw(7); + handles.set(TcpTimerKind::TimeWait, handle); + handles.clear(TcpTimerKind::TimeWait); + assert!(!handles.is_armed(TcpTimerKind::TimeWait)); + } + + #[test] + fn is_armed_returns_false_when_not_set() { + let handles = TcpTimerHandles::new(); + let all_kinds = [ + TcpTimerKind::Retransmit, + TcpTimerKind::DelayedAck, + TcpTimerKind::Persist, + TcpTimerKind::KeepAlive, + TcpTimerKind::TimeWait, + TcpTimerKind::Linger, + ]; + for kind in all_kinds { + assert!(!handles.is_armed(kind), "{kind:?} should not be armed"); + } + } +} diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs index 7cdbf6c..5dc0be9 100644 --- a/src/net/timer_wheel.rs +++ b/src/net/timer_wheel.rs @@ -11,6 +11,13 @@ pub struct TimerId(pub u64); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TimerHandle(usize); +impl TimerHandle { + /// Construct a handle from a raw slab key. Intended for tests and internal use only. + pub(crate) fn from_raw(key: usize) -> Self { + Self(key) + } +} + // --- Internal types --- struct TimerEntry { From b0d53ff07955928b13cf7c101b0728948f2fc67b Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:26:46 -0700 Subject: [PATCH 08/15] feat(tcp): Add TcpTimerHandles parallel slab to TcpHandler --- src/net/handler/tcp/handler.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/net/handler/tcp/handler.rs b/src/net/handler/tcp/handler.rs index 36d5936..8471b4c 100644 --- a/src/net/handler/tcp/handler.rs +++ b/src/net/handler/tcp/handler.rs @@ -13,6 +13,7 @@ use super::{ send_tracker::SendTracker, state::TcpState, tcb::{ConnectionId, Tcb}, + timer_kinds::TcpTimerHandles, }; /// Initial RTO for SYN retransmission (1 second in coarsetime ticks). @@ -27,6 +28,7 @@ pub(super) const SYN_R2_THRESHOLD_MS: u64 = 180_000; /// incoming TCP segments through the appropriate state machine. pub struct TcpHandler { pub(super) connections: Slab, + pub(crate) timer_handles: Slab, pub(super) connection_map: FxHashMap, pub(super) listeners: Vec, pub(super) isn_generator: IsnGenerator, @@ -39,6 +41,7 @@ impl TcpHandler { pub fn new(rx_offload: bool, tx_offload: bool) -> Self { Self { connections: Slab::new(), + timer_handles: Slab::new(), connection_map: FxHashMap::default(), listeners: Vec::new(), isn_generator: IsnGenerator::new(), @@ -81,6 +84,8 @@ impl TcpHandler { pub fn insert_connection(&mut self, tcb: Tcb) -> usize { let id = tcb.id; let key = self.connections.insert(tcb); + let handle_key = self.timer_handles.insert(TcpTimerHandles::new()); + debug_assert_eq!(key, handle_key, "timer_handles slab key mismatch"); self.connection_map.insert(id, key); key } @@ -90,6 +95,9 @@ impl TcpHandler { if self.connections.contains(key) { let tcb = self.connections.remove(key); self.connection_map.remove(&tcb.id); + if self.timer_handles.contains(key) { + self.timer_handles.remove(key); + } Some(tcb) } else { None From cb84db5ec7aae683d4e4d6edeea4ecdd25d632a8 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 19:30:21 -0700 Subject: [PATCH 09/15] feat(tcp): Extract fire_* timer methods for wheel dispatch Add individual fire_delayed_ack, fire_retransmit, fire_keep_alive, fire_persist, fire_linger, fire_time_wait, and handle_timer methods alongside existing poll_timers()/evict_stale(). These methods operate on a single connection by slab key, enabling O(1) timer-wheel dispatch instead of O(n) scanning. --- src/net/handler/tcp/timers.rs | 582 ++++++++++++++++++++++++++++++++++ 1 file changed, 582 insertions(+) diff --git a/src/net/handler/tcp/timers.rs b/src/net/handler/tcp/timers.rs index c01b9bd..1c5964c 100644 --- a/src/net/handler/tcp/timers.rs +++ b/src/net/handler/tcp/timers.rs @@ -13,6 +13,7 @@ use super::{ send_tracker::SendReady, state::TcpState, tcb::{DEFAULT_RCV_MSS, DEFAULT_RCV_WND, DEFAULT_RCV_WSCALE, TcpEvent}, + timer_kinds::TcpTimerKind, }; impl TcpHandler { @@ -465,4 +466,585 @@ impl TcpHandler { self.remove_connection_by_key(key); } } + + // --- Individual fire_* methods for timer-wheel dispatch --- + + /// Fire a delayed ACK for a single connection identified by slab key. + /// + /// Sends a pure ACK if the connection has a pending ACK, then clears + /// the delayed-ACK state. If the connection doesn't exist or has no + /// pending ACK, this is a no-op. + #[inline] + pub fn fire_delayed_ack<'umem>( + &mut self, + key: usize, + now: Instant, + src_mac: crate::net::wire::ethernet::MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, + ) { + let Some(tcb) = self.connections.get_mut(key) else { + return; + }; + if !tcb.ack_pending { + return; + } + + let tsval = if tcb.ts_enabled { + now.duration_since(tcb.ts_offset).as_millis() as u32 + } else { + 0 + }; + + let id = tcb.id; + let Some(dst_mac) = neighbor_handler.lookup_or_resolve( + now, + &id.remote_addr, + &id.local_addr, + free_frames, + rx_return, + tx_return, + ) else { + // Neighbor resolution pending — mark for poll_send to retry. + self.send_tracker.mark(SendReady(key)); + return; + }; + + let ts = tcb.ts_option(tsval); + let ack_flags = if tcb.ecn_ce_received { + flags::ACK | flags::ECE + } else { + flags::ACK + }; + SegmentBuilder::build_ack( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + tcb.snd_nxt, + tcb.rcv_nxt, + tcb.advertised_window(), + ack_flags, + ts, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + tcb.update_advertised_edge(); + tcb.ack_pending = false; + tcb.ack_delay_count = 0; + tcb.delayed_ack_deadline = None; + } + + /// Fire an RTO retransmit for a single connection identified by slab key. + /// + /// Handles R2 threshold checks, SYN/SYN-ACK/Established/FIN retransmit, + /// and exponential backoff. If the R2 threshold is exceeded, the connection + /// is removed. + #[inline] + pub fn fire_retransmit<'umem>( + &mut self, + key: usize, + now: Instant, + src_mac: crate::net::wire::ethernet::MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, + ) { + let Some(tcb) = self.connections.get_mut(key) else { + return; + }; + + let tsval = if tcb.ts_enabled { + now.duration_since(tcb.ts_offset).as_millis() as u32 + } else { + 0 + }; + + // Check R2 threshold. + let total_elapsed_ms = { + let base_rto = INITIAL_RTO_MS; + let mut total: u64 = 0; + for i in 0..=tcb.rto_backoff { + total += base_rto << i; + } + total + }; + + if total_elapsed_ms >= SYN_R2_THRESHOLD_MS { + // Timeout — signal and remove. + tcb.event_queue.push(TcpEvent::Timeout); + if let Some(tcb) = self.connections.get(key) { + Self::decrement_syn_received(&mut self.listeners, &tcb.id); + } + self.send_tracker.unmark(key); + self.remove_connection_by_key(key); + return; + } + + // Resolve neighbor. + let id = tcb.id; + let Some(dst_mac) = neighbor_handler.lookup_or_resolve( + now, + &id.remote_addr, + &id.local_addr, + free_frames, + rx_return, + tx_return, + ) else { + // Re-arm retransmit timer so we retry after the solicitation completes. + tcb.retransmit_deadline = Some(now + coarsetime::Duration::from_millis(tcb.rto)); + return; + }; + + match tcb.state { + TcpState::SynSent => { + let ts_opt = if tcb.ts_enabled { + Some((tsval, 0u32)) + } else { + None + }; + SegmentBuilder::build_syn( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + tcb.iss, + DEFAULT_RCV_WND, + DEFAULT_RCV_MSS, + DEFAULT_RCV_WSCALE, + ts_opt, + tcb.sack_enabled, + tcb.ecn_enabled, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + } + TcpState::SynReceived => { + let wscale_opt = if tcb.wscale_enabled { + Some(tcb.rcv_wscale) + } else { + None + }; + let ts_opt = tcb.ts_option(tsval); + SegmentBuilder::build_syn_ack( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + tcb.iss, + tcb.rcv_nxt, + DEFAULT_RCV_WND, + tcb.rcv_mss, + wscale_opt, + ts_opt, + tcb.sack_enabled, + tcb.ecn_enabled, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + } + TcpState::Established => { + let retransmit_len = tcb.send_buffer.available().min(tcb.eff_snd_mss as usize); + if retransmit_len > 0 { + let payload = tcb.send_buffer.peek_slices(0, retransmit_len); + let ts = tcb.ts_option(tsval); + SegmentBuilder::build_data_from_slices( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + tcb.snd_una, + tcb.rcv_nxt, + tcb.advertised_window(), + payload, + flags::ACK, + false, // retransmits don't get ECT per RFC 3168 §6.1.5 + ts, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + } + // F-RTO + CUBIC RTO response. + tcb.frto.enter(tcb.snd_una); + tcb.cubic.on_rto(); + tcb.recovery.exit(); + tcb.sack_scoreboard.clear(); + tcb.rto_backoff += 1; + tcb.retransmit_deadline = + Some(now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff)); + // Mark for poll_send — may have more data to send after RTO. + self.send_tracker.mark(SendReady(key)); + } + TcpState::FinWait1 | TcpState::Closing | TcpState::LastAck => { + // Retransmit FIN-ACK. + let ts = tcb.ts_option(tsval); + if let Some(fin_seq) = tcb.fin_seq { + SegmentBuilder::build_fin_ack( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + fin_seq, + tcb.rcv_nxt, + tcb.advertised_window(), + ts, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + } + // Exponential backoff (same as Established). + tcb.rto_backoff += 1; + tcb.retransmit_deadline = + Some(now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff)); + } + _ => return, + } + + // Exponential backoff for SYN/SYN-ACK states. + match tcb.state { + TcpState::SynSent | TcpState::SynReceived => { + tcb.rto_backoff += 1; + let rto = INITIAL_RTO_MS << tcb.rto_backoff; + tcb.retransmit_deadline = Some(now + coarsetime::Duration::from_millis(rto)); + } + _ => {} // Established handles its own backoff above. + } + } + + /// Fire a keep-alive probe for a single connection identified by slab key. + /// + /// Sends a keep-alive probe (seq = snd_una - 1) if the connection has been + /// idle long enough. If max probes are exceeded, the connection is aborted. + #[inline] + pub fn fire_keep_alive<'umem>( + &mut self, + key: usize, + now: Instant, + src_mac: crate::net::wire::ethernet::MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, + ) { + let Some(tcb) = self.connections.get_mut(key) else { + return; + }; + if tcb.state != TcpState::Established || !tcb.keep_alive_enabled { + return; + } + + let tsval = if tcb.ts_enabled { + now.duration_since(tcb.ts_offset).as_millis() as u32 + } else { + 0 + }; + + let idle_ms = now.duration_since(tcb.last_activity).as_millis(); + + let probe_threshold = if tcb.keep_alive_probes_sent == 0 { + tcb.keep_alive_idle_ms + } else { + tcb.keep_alive_idle_ms + tcb.keep_alive_interval_ms * tcb.keep_alive_probes_sent as u64 + }; + + if idle_ms >= probe_threshold { + if tcb.keep_alive_probes_sent >= tcb.keep_alive_count { + // Max probes exceeded — abort connection. + tcb.event_queue.push(TcpEvent::Timeout); + if let Some(tcb) = self.connections.get(key) { + Self::decrement_syn_received(&mut self.listeners, &tcb.id); + } + self.send_tracker.unmark(key); + self.remove_connection_by_key(key); + return; + } + + // Send keep-alive probe: seq = snd_una - 1, no data, ACK. + let id = tcb.id; + let Some(dst_mac) = neighbor_handler.lookup_or_resolve( + now, + &id.remote_addr, + &id.local_addr, + free_frames, + rx_return, + tx_return, + ) else { + return; + }; + let ts = tcb.ts_option(tsval); + let ack_flags = if tcb.ecn_ce_received { + flags::ACK | flags::ECE + } else { + flags::ACK + }; + SegmentBuilder::build_ack( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + tcb.snd_una.wrapping_sub(1), + tcb.rcv_nxt, + tcb.advertised_window(), + ack_flags, + ts, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + + tcb.keep_alive_probes_sent += 1; + } + } + + /// Fire a zero-window (persist) probe for a single connection identified by slab key. + /// + /// Sends a 1-byte probe when the persist deadline has expired and the peer's + /// advertised window is zero. Applies exponential backoff capped at 60 seconds. + #[inline] + pub fn fire_persist<'umem>( + &mut self, + key: usize, + now: Instant, + src_mac: crate::net::wire::ethernet::MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, + ) { + let Some(tcb) = self.connections.get_mut(key) else { + return; + }; + + let send_window = (tcb.snd_wnd as usize).min(tcb.cubic.cwnd as usize); + let bytes_in_flight = tcb.snd_nxt.wrapping_sub(tcb.snd_una) as usize; + let data_available = tcb.send_buffer.available().saturating_sub(bytes_in_flight); + + if send_window != 0 || data_available == 0 { + return; + } + + let tsval = if tcb.ts_enabled { + now.duration_since(tcb.ts_offset).as_millis() as u32 + } else { + 0 + }; + + let mut probe = [0u8; 1]; + tcb.send_buffer.peek_at(bytes_in_flight, &mut probe); + + let id = tcb.id; + let Some(dst_mac) = neighbor_handler.lookup_or_resolve( + now, + &id.remote_addr, + &id.local_addr, + free_frames, + rx_return, + tx_return, + ) else { + // Neighbor resolution pending — mark for poll_send to retry. + self.send_tracker.mark(SendReady(key)); + return; + }; + + let ts = tcb.ts_option(tsval); + SegmentBuilder::build_data( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + tcb.snd_nxt, + tcb.rcv_nxt, + tcb.advertised_window(), + &probe, + ts, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + + tcb.snd_nxt = tcb.snd_nxt.wrapping_add(1); + + // Schedule next probe with exponential backoff, capped at 60s. + let backoff_ms = (tcb.rto << tcb.persist_backoff).min(60_000); + tcb.persist_deadline = Some(now + coarsetime::Duration::from_millis(backoff_ms)); + tcb.persist_backoff = tcb.persist_backoff.saturating_add(1).min(6); + } + + /// Fire linger timeout for a single connection identified by slab key. + /// + /// If the connection has a pending FIN and the linger deadline has expired, + /// sends a RST to the peer and marks the connection for removal. + #[inline] + pub fn fire_linger<'umem>( + &mut self, + key: usize, + now: Instant, + src_mac: crate::net::wire::ethernet::MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, + ) { + let Some(tcb) = self.connections.get_mut(key) else { + return; + }; + + if !tcb.pending_fin { + return; + } + + // Send RST to peer. + let id = tcb.id; + let Some(dst_mac) = neighbor_handler.lookup_or_resolve( + now, + &id.remote_addr, + &id.local_addr, + free_frames, + rx_return, + tx_return, + ) else { + // Neighbor resolution pending — mark for poll_send to retry. + self.send_tracker.mark(SendReady(key)); + return; + }; + + // Use build_rst by simulating an "incoming ACK" segment. + // This produces: = + SegmentBuilder::build_rst( + id.remote_addr, + id.local_addr, // swapped: "incoming" from remote + id.remote_port, + id.local_port, // swapped + 0, + tcb.snd_nxt, // incoming_seq=0, incoming_ack=snd_nxt + flags::ACK, // pretend incoming has ACK set + 0, // seg_len doesn't matter + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + + tcb.event_queue.push(TcpEvent::Reset); + tcb.state = TcpState::Closed; + tcb.pending_fin = false; + + self.send_tracker.unmark(key); + self.remove_connection_by_key(key); + } + + /// Fire TIME-WAIT expiry for a single connection identified by slab key. + /// + /// Removes the connection if it is in TimeWait state. No packet is sent. + #[inline] + pub fn fire_time_wait(&mut self, key: usize) { + let Some(tcb) = self.connections.get(key) else { + return; + }; + if tcb.state != TcpState::TimeWait { + return; + } + self.send_tracker.unmark(key); + self.remove_connection_by_key(key); + } + + /// Dispatch a timer firing for a single connection by kind. + /// + /// Routes to the appropriate `fire_*` method based on `TcpTimerKind`. + #[inline] + pub fn handle_timer<'umem>( + &mut self, + key: usize, + kind: TcpTimerKind, + now: Instant, + src_mac: crate::net::wire::ethernet::MacAddress, + neighbor_handler: &NeighborHandler, + free_frames: &mut impl FrameBuffer<'umem>, + rx_return: &mut impl FrameBuffer<'umem>, + tx_return: &mut impl FrameBuffer<'umem>, + ) { + match kind { + TcpTimerKind::DelayedAck => { + self.fire_delayed_ack( + key, + now, + src_mac, + neighbor_handler, + free_frames, + rx_return, + tx_return, + ); + } + TcpTimerKind::Retransmit => { + self.fire_retransmit( + key, + now, + src_mac, + neighbor_handler, + free_frames, + rx_return, + tx_return, + ); + } + TcpTimerKind::KeepAlive => { + self.fire_keep_alive( + key, + now, + src_mac, + neighbor_handler, + free_frames, + rx_return, + tx_return, + ); + } + TcpTimerKind::Persist => { + self.fire_persist( + key, + now, + src_mac, + neighbor_handler, + free_frames, + rx_return, + tx_return, + ); + } + TcpTimerKind::Linger => { + self.fire_linger( + key, + now, + src_mac, + neighbor_handler, + free_frames, + rx_return, + tx_return, + ); + } + TcpTimerKind::TimeWait => { + self.fire_time_wait(key); + } + } + } } From fac0b6c0bf311f0e620c2311cccc602f701b2e7e Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 21:51:16 -0700 Subject: [PATCH 10/15] refactor(timer_wheel): Use Instant API, efficient advance, remove now_ms --- fix_tests.py | 185 ++++++ src/net/handler/ethernet.rs | 16 + src/net/handler/ipv4.rs | 21 + src/net/handler/ipv6.rs | 23 + src/net/handler/tcp/connection.rs | 33 +- src/net/handler/tcp/inbound/established.rs | 78 ++- src/net/handler/tcp/inbound/listen.rs | 19 +- src/net/handler/tcp/inbound/segment.rs | 24 +- src/net/handler/tcp/inbound/syn_received.rs | 7 +- src/net/handler/tcp/inbound/syn_sent.rs | 18 +- src/net/handler/tcp/inbound/teardown.rs | 43 +- src/net/handler/tcp/inbound/validate.rs | 7 + src/net/handler/tcp/tcb.rs | 14 - src/net/handler/tcp/tests/congestion_tests.rs | 57 +- src/net/handler/tcp/tests/data_transfer.rs | 79 ++- src/net/handler/tcp/tests/delayed_ack.rs | 92 ++- src/net/handler/tcp/tests/ecn.rs | 105 +++- src/net/handler/tcp/tests/edge_cases.rs | 129 ++++- src/net/handler/tcp/tests/handler.rs | 13 +- src/net/handler/tcp/tests/handshake.rs | 69 +++ src/net/handler/tcp/tests/keepalive.rs | 177 +++++- src/net/handler/tcp/tests/listener.rs | 6 +- src/net/handler/tcp/tests/mod.rs | 59 ++ src/net/handler/tcp/tests/nagle.rs | 78 ++- src/net/handler/tcp/tests/persist.rs | 115 +++- src/net/handler/tcp/tests/retransmission.rs | 107 +++- src/net/handler/tcp/tests/sack.rs | 82 ++- src/net/handler/tcp/tests/teardown.rs | 386 +++++++++++-- src/net/handler/tcp/tests/timers.rs | 433 ++++++++++---- src/net/handler/tcp/tests/timestamps.rs | 12 + src/net/handler/tcp/tests/validate.rs | 61 +- src/net/handler/tcp/timer_kinds.rs | 37 ++ src/net/handler/tcp/timers.rs | 520 ++--------------- src/net/handler/tcp/transmit.rs | 138 +---- src/net/http/body.rs | 13 +- src/net/http/connection.rs | 20 +- src/net/socket/tcp.rs | 57 +- src/net/socket/udp.rs | 2 + src/net/timer_wheel.rs | 529 +++++++++--------- src/rt/context.rs | 8 + src/rt/local.rs | 63 ++- 41 files changed, 2676 insertions(+), 1259 deletions(-) create mode 100644 fix_tests.py diff --git a/fix_tests.py b/fix_tests.py new file mode 100644 index 0000000..3dda73d --- /dev/null +++ b/fix_tests.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +Fix test compilation errors after timer wheel refactor. + +Transforms: +1. process_ipv4(frame, now, &nh, ...) -> process_ipv4(frame, now, 0, &mut wheel, &nh, ...) +2. poll_send(now, mac, &nh, ...) -> poll_send(now, 0, &mut wheel, mac, &nh, ...) +3. connect(..., now, free, tx) -> connect(..., now, 0, &mut wheel, free, tx) +4. connect_with_config(..., now, config, free, tx) -> connect_with_config(..., now, 0, &mut wheel, config, free, tx) +5. initiate_close(key) -> initiate_close(key, 0, &mut wheel) +6. poll_timers(now, mac, &nh, ...) -> poll_timers(now, 0, &mut wheel, mac, &nh, ...) [trait method - won't conflict, but fix anyway] +7. Replace deadline field accesses +""" + +import re +import sys +import os + +def fix_process_ipv4_calls(content): + """Insert '0, &mut wheel,' after the 'now' argument in process_ipv4 calls.""" + # Pattern: .process_ipv4(\n frame,\n now_expr,\n &nh, + # We need to insert "0,\n &mut wheel," after the now line + + # Match process_ipv4 call with the now argument followed by &nh or &neighbor + pattern = re.compile( + r'(\.process_ipv4\(\s*\n' # .process_ipv4(\n + r'(\s*)' # capture indentation + r'[^,]+,\s*\n' # frame arg,\n + r'\s*[^,]+,)\s*\n' # now arg,\n (capture group 1 ends) + r'(\s*&)' # &nh (capture group 3) + ) + + def replacer(m): + indent = m.group(2) + return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(3)}' + + return pattern.sub(replacer, content) + + +def fix_poll_send_calls(content): + """Insert '0, &mut wheel,' after the 'now' argument in poll_send calls.""" + # Pattern: .poll_send(\n now,\n mac, + # Insert after now line + pattern = re.compile( + r'(\.poll_send\(\s*\n' # .poll_send(\n + r'(\s*)' # capture indentation + r'[^,]+,)\s*\n' # now arg,\n (end group 1) + r'(\s*)' # next line indent (group 3) + r'((?:nh\.local_mac|src_mac|handler\.|local_mac))' # mac arg start (group 4) + ) + + def replacer(m): + indent = m.group(2) + return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(3)}{m.group(4)}' + + return pattern.sub(replacer, content) + + +def fix_poll_send_single_line(content): + """Fix single-line poll_send calls like handler.poll_send(now, mac, ...).""" + # Some tests may have single-line calls + # Actually looking at the code, they're all multiline. Skip this. + return content + + +def fix_connect_calls(content): + """Insert '0, &mut wheel,' after 'now' in connect() calls. + connect(..., now, free, tx) -> connect(..., now, 0, &mut wheel, free, tx) + """ + # Pattern: .connect(\n ...multiple args...\n now,\n &mut free/free, + # The now arg is coarsetime::Instant::now(), followed by free_frames + pattern = re.compile( + r'(\.connect\(\s*\n' # .connect(\n + r'(?:\s*[^,]+,\s*\n)*?' # multiple args + r'(\s*)' # indent (group 2) + r'(coarsetime::Instant::now\(\)|now),)\s*\n' # now arg, (group 3 is now expr) + r'(\s*&mut (?:free|tx))' # free_frames (group 4) + ) + + def replacer(m): + indent = m.group(2) + return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(4)}' + + return pattern.sub(replacer, content) + + +def fix_connect_with_config_calls(content): + """Insert '0, &mut wheel,' after 'now' in connect_with_config() calls. + connect_with_config(..., now, config, free, tx) -> connect_with_config(..., now, 0, &mut wheel, config, free, tx) + """ + pattern = re.compile( + r'(\.connect_with_config\(\s*\n' + r'(?:\s*[^,]+,\s*\n)*?' + r'(\s*)' # indent (group 2) + r'(coarsetime::Instant::now\(\)|now),)\s*\n' + r'(\s*config)' # config arg (group 4) + ) + + def replacer(m): + indent = m.group(2) + return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(4)}' + + return pattern.sub(replacer, content) + + +def fix_initiate_close_calls(content): + """initiate_close(key) -> initiate_close(key, 0, &mut wheel)""" + # Pattern: .initiate_close(expr); + # But NOT .initiate_close(expr, 0, &mut wheel); (already fixed) + pattern = re.compile( + r'\.initiate_close\(([^,\)]+)\);' + ) + + def replacer(m): + key_arg = m.group(1) + return f'.initiate_close({key_arg}, 0, &mut wheel);' + + return pattern.sub(replacer, content) + + +def add_wheel_to_test_functions(content): + """Add 'let mut wheel = new_wheel();' after the last BasicFrameBuffer::new line in each test function.""" + # Find patterns like: + # let mut tx = BasicFrameBuffer::new(N); + # and add wheel after (if not already present) + + lines = content.split('\n') + result = [] + i = 0 + while i < len(lines): + result.append(lines[i]) + # Check if this line creates the last BasicFrameBuffer (tx pattern) + if 'let mut tx = BasicFrameBuffer::new(' in lines[i] and 'wheel' not in (lines[i+1] if i+1 < len(lines) else ''): + # Get indentation + indent = len(lines[i]) - len(lines[i].lstrip()) + indent_str = lines[i][:indent] + result.append(f'{indent_str}let mut wheel = new_wheel();') + i += 1 + + return '\n'.join(result) + + +def process_file(filepath): + with open(filepath, 'r') as f: + content = f.read() + + original = content + + # Add wheel variable to test functions + content = add_wheel_to_test_functions(content) + + # Fix method calls + content = fix_process_ipv4_calls(content) + content = fix_poll_send_calls(content) + content = fix_connect_calls(content) + content = fix_connect_with_config_calls(content) + content = fix_initiate_close_calls(content) + + if content != original: + with open(filepath, 'w') as f: + f.write(content) + print(f" Modified: {filepath}") + else: + print(f" Unchanged: {filepath}") + + +def main(): + base = "/workspace/csaide/voidnet/.worktrees/timer-wheel/src/net/handler/tcp/tests" + test_files = [ + "handler.rs", "handshake.rs", "data_transfer.rs", "delayed_ack.rs", + "retransmission.rs", "timers.rs", "keepalive.rs", "persist.rs", + "teardown.rs", "edge_cases.rs", "ecn.rs", "nagle.rs", "sack.rs", + "timestamps.rs", "validate.rs", "listener.rs", "congestion_tests.rs", + ] + + for f in test_files: + path = os.path.join(base, f) + if os.path.exists(path): + process_file(path) + else: + print(f" Missing: {path}") + + +if __name__ == '__main__': + main() diff --git a/src/net/handler/ethernet.rs b/src/net/handler/ethernet.rs index 64c7bd8..009ec05 100644 --- a/src/net/handler/ethernet.rs +++ b/src/net/handler/ethernet.rs @@ -4,6 +4,7 @@ use crate::{ net::{ NeighborHandler, PmtuCache, handler::{ipv4::Ipv4Handler, ipv6::Ipv6Handler, tcp::TcpHandler, udp::UdpHandler}, + timer_wheel::TimerWheel, wire::ethernet::{EtherTypes, EthernetFrame}, }, xdp::frame::{Frame, FrameBuffer}, @@ -24,6 +25,7 @@ impl EthernetHandler { neighbor_handler: &NeighborHandler, pmtu: &mut PmtuCache, now: Instant, + wheel: &mut TimerWheel, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, @@ -38,6 +40,7 @@ impl EthernetHandler { neighbor_handler, pmtu, now, + wheel, free_frames, rx_return, tx_return, @@ -51,6 +54,7 @@ impl EthernetHandler { tcp_handler, pmtu, now, + wheel, free_frames, rx_return, tx_return, @@ -178,6 +182,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); eth.handle( frame, &mut ipv4, @@ -187,6 +192,7 @@ mod tests { &neighbor, &mut pmtu, now, + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -206,6 +212,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); eth.handle( frame, &mut ipv4, @@ -215,6 +222,7 @@ mod tests { &neighbor, &mut pmtu, now, + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -237,6 +245,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); eth.handle( frame, &mut ipv4, @@ -246,6 +255,7 @@ mod tests { &neighbor, &mut pmtu, now, + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -268,6 +278,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); eth.handle( frame, &mut ipv4, @@ -277,6 +288,7 @@ mod tests { &neighbor, &mut pmtu, now, + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -297,6 +309,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); eth.handle( frame, &mut ipv4, @@ -306,6 +319,7 @@ mod tests { &neighbor, &mut pmtu, now, + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -339,6 +353,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); + let mut wheel = TimerWheel::new(0); eth.handle( frame, &mut ipv4, @@ -348,6 +363,7 @@ mod tests { &neighbor, &mut pmtu, now, + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, diff --git a/src/net/handler/ipv4.rs b/src/net/handler/ipv4.rs index 68afe55..d36f147 100644 --- a/src/net/handler/ipv4.rs +++ b/src/net/handler/ipv4.rs @@ -4,6 +4,7 @@ use crate::{ net::{ NeighborHandler, PmtuCache, checksum::verify_ipv4_checksum, + timer_wheel::TimerWheel, wire::{ ethernet::EthernetFrame, icmpv4::Icmpv4Codes, @@ -50,6 +51,7 @@ impl Ipv4Handler { neighbor_handler: &NeighborHandler, pmtu: &mut PmtuCache, now: Instant, + wheel: &mut TimerWheel, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, @@ -109,6 +111,7 @@ impl Ipv4Handler { tcp_handler.process_ipv4( frame, now, + wheel, neighbor_handler, free_frames, rx_return, @@ -271,6 +274,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let frame = Frame::new(0, &mut data, 30, false); @@ -281,6 +285,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -303,6 +308,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -314,6 +320,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -333,6 +340,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -344,6 +352,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -363,6 +372,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -374,6 +384,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -401,6 +412,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -412,6 +424,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -431,6 +444,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -442,6 +456,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -486,6 +501,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let frame = Frame::new(0, &mut data, frame_len, false); handler.handle( @@ -495,6 +511,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -515,6 +532,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -526,6 +544,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -547,6 +566,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let frame = Frame::new(0, &mut data, raw.len(), false); @@ -557,6 +577,7 @@ mod tests { &nh, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, diff --git a/src/net/handler/ipv6.rs b/src/net/handler/ipv6.rs index 5f5ecc5..61b0129 100644 --- a/src/net/handler/ipv6.rs +++ b/src/net/handler/ipv6.rs @@ -3,6 +3,7 @@ use coarsetime::Instant; use crate::{ net::{ NeighborHandler, PmtuCache, + timer_wheel::TimerWheel, wire::{ ethernet::EthernetFrame, icmpv6::{Icmpv6Codes, Icmpv6Types}, @@ -160,6 +161,7 @@ impl Ipv6Handler { tcp_handler: &mut TcpHandler, pmtu: &mut PmtuCache, now: Instant, + wheel: &mut TimerWheel, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, @@ -223,6 +225,7 @@ impl Ipv6Handler { frame, payload_offset, now, + wheel, neighbor_handler, free_frames, rx_return, @@ -536,6 +539,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let frame = Frame::new(0, &mut data, 50, false); @@ -546,6 +550,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -565,6 +570,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -576,6 +582,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -597,6 +604,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -608,6 +616,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -627,6 +636,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -638,6 +648,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -658,6 +669,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -669,6 +681,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -711,6 +724,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let frame = Frame::new(0, &mut data, frame_len, false); handler.handle( @@ -720,6 +734,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -741,6 +756,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let frame = Frame::new(0, &mut data, raw.len(), false); @@ -751,6 +767,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -769,6 +786,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -780,6 +798,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -811,6 +830,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -822,6 +842,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, @@ -862,6 +883,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); let frame = Frame::new(0, &mut data, frame_len, false); handler.handle( @@ -871,6 +893,7 @@ mod tests { &mut tcp, &mut PmtuCache::new(), Instant::now(), + &mut wheel, &mut BasicFrameBuffer::new(4), &mut rx, &mut tx, diff --git a/src/net/handler/tcp/connection.rs b/src/net/handler/tcp/connection.rs index 77e74b1..91e3fe1 100644 --- a/src/net/handler/tcp/connection.rs +++ b/src/net/handler/tcp/connection.rs @@ -4,6 +4,7 @@ use coarsetime::Instant; use crate::net::handler::udp::BindError; use crate::net::socket::LocalQueue; +use crate::net::timer_wheel::TimerWheel; use crate::net::wire::ethernet::MacAddress; use crate::net::wire::ip::IpAddress; use crate::xdp::frame::FrameBuffer; @@ -20,6 +21,7 @@ use super::tcb::{ ConnectionId, DEFAULT_DELAYED_ACK_MS, DEFAULT_RCV_MSS, DEFAULT_RCV_WND, DEFAULT_RCV_WSCALE, Tcb, TcpConfig, TcpEvent, }; +use super::timer_kinds::TcpTimerKind; impl TcpHandler { // --- Active open --- @@ -34,6 +36,7 @@ impl TcpHandler { src_mac: MacAddress, dst_mac: MacAddress, now: Instant, + wheel: &mut TimerWheel, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, ) -> Result<(usize, LocalQueue), BindError> { @@ -45,6 +48,7 @@ impl TcpHandler { src_mac, dst_mac, now, + wheel, TcpConfig::default(), free_frames, tx_return, @@ -61,6 +65,7 @@ impl TcpHandler { src_mac: MacAddress, dst_mac: MacAddress, now: Instant, + wheel: &mut TimerWheel, config: TcpConfig, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, @@ -99,7 +104,6 @@ impl TcpHandler { snd_wscale: 0, rcv_wscale: DEFAULT_RCV_WSCALE, wscale_enabled: false, - retransmit_deadline: Some(now + coarsetime::Duration::from_millis(INITIAL_RTO_MS)), rto_backoff: 0, event_queue: event_queue.clone(), send_buffer: RingBuffer::new(config.send_buffer_size), @@ -115,10 +119,8 @@ impl TcpHandler { last_send_time: None, pending_fin: false, fin_seq: None, - time_wait_deadline: None, time_wait_duration: config.time_wait_duration_ms, ack_pending: false, - delayed_ack_deadline: None, ack_delay_count: 0, delayed_ack_ms: DEFAULT_DELAYED_ACK_MS, nagle_enabled: !config.tcp_no_delay, @@ -129,7 +131,6 @@ impl TcpHandler { last_activity: now, keep_alive_probes_sent: 0, linger: config.linger, - linger_deadline: None, ts_enabled: config.timestamps, ts_recent: 0, ts_recent_age: now, @@ -139,7 +140,6 @@ impl TcpHandler { ecn_enabled: config.ecn, ecn_ce_received: false, ecn_cwr_sent: false, - persist_deadline: None, persist_backoff: 0, max_snd_wnd: 0, last_advertised_right_edge: 0, @@ -171,13 +171,22 @@ impl TcpHandler { ); let key = self.insert_connection(tcb); + + // Arm retransmit timer for the SYN. + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(INITIAL_RTO_MS), + wheel, + ); + // New connection has a retransmit timer — mark for send tracking. self.send_tracker.mark(SendReady(key)); Ok((key, event_queue)) } /// Initiate a graceful close for a connection. - pub fn initiate_close(&mut self, key: usize) { + pub fn initiate_close(&mut self, key: usize, now: coarsetime::Instant, wheel: &mut TimerWheel) { if let Some(tcb) = self.connections.get_mut(key) { if tcb.pending_fin || (tcb.state != TcpState::Established && tcb.state != TcpState::CloseWait) @@ -189,13 +198,17 @@ impl TcpHandler { self.send_tracker.mark(SendReady(key)); match tcb.linger { Some(0) => { - // Linger(0): set deadline to now — poll_send will send RST immediately. - tcb.linger_deadline = Some(Instant::recent()); + // Linger(0): set deadline to now — fire_linger will send RST immediately. + self.timer_handles[key].arm(TcpTimerKind::Linger, key, now, wheel); } Some(ms) => { // Linger(timeout): graceful close with deadline. - tcb.linger_deadline = - Some(Instant::now() + coarsetime::Duration::from_millis(ms)); + self.timer_handles[key].arm( + TcpTimerKind::Linger, + key, + now + coarsetime::Duration::from_millis(ms), + wheel, + ); } None => { // Default: graceful close, no deadline. diff --git a/src/net/handler/tcp/inbound/established.rs b/src/net/handler/tcp/inbound/established.rs index f6ad087..96a458c 100644 --- a/src/net/handler/tcp/inbound/established.rs +++ b/src/net/handler/tcp/inbound/established.rs @@ -1,7 +1,7 @@ use coarsetime::Instant; use crate::{ - net::wire::tcp::flags, + net::{timer_wheel::TimerWheel, wire::tcp::flags}, xdp::frame::{Frame, FrameBuffer}, }; @@ -11,6 +11,7 @@ use super::super::recovery::FRtoAction; use super::super::segment::SegmentBuilder; use super::super::state::TcpState; use super::super::tcb::{Tcb, TcpEvent}; +use super::super::timer_kinds::{TcpTimerHandles, TcpTimerKind}; use super::segment::{PostAction, is_segment_acceptable}; @@ -153,9 +154,11 @@ impl TcpHandler { pub(super) fn process_established<'umem>( tcb: &mut Tcb, + handles: &mut TcpTimerHandles, key: usize, frame: Frame<'umem>, now: Instant, + wheel: &mut TimerWheel, tsval: u32, seg_seq: u32, seg_ack: u32, @@ -231,10 +234,14 @@ impl TcpHandler { // RFC 6298 §5.3: manage retransmit timer on new ACK. tcb.rto_backoff = 0; if tcb.snd_una == tcb.snd_nxt { - tcb.retransmit_deadline = None; + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); } else { - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto)); + handles.arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); } // F-RTO check. @@ -297,8 +304,8 @@ impl TcpHandler { } // Clear persist timer when window reopens. - if tcb.snd_wnd > 0 && tcb.persist_deadline.is_some() { - tcb.persist_deadline = None; + if tcb.snd_wnd > 0 && handles.is_armed(TcpTimerKind::Persist) { + handles.cancel_timer(TcpTimerKind::Persist, wheel); tcb.persist_backoff = 0; } @@ -334,9 +341,12 @@ impl TcpHandler { // Delayed ACK — defer to poll_send for piggyback opportunity. tcb.ack_delay_count += 1; tcb.ack_pending = true; - if tcb.delayed_ack_deadline.is_none() { - tcb.delayed_ack_deadline = Some( + if !handles.is_armed(TcpTimerKind::DelayedAck) { + handles.arm( + TcpTimerKind::DelayedAck, + key, now + coarsetime::Duration::from_millis(tcb.delayed_ack_ms), + wheel, ); } @@ -361,10 +371,14 @@ impl TcpHandler { // RFC 6298 §5.3: manage retransmit timer on new ACK. tcb.rto_backoff = 0; if tcb.snd_una == tcb.snd_nxt { - tcb.retransmit_deadline = None; + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); } else { - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto)); + handles.arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); } // F-RTO check. @@ -421,8 +435,8 @@ impl TcpHandler { } // Clear persist timer when window reopens. - if tcb.snd_wnd > 0 && tcb.persist_deadline.is_some() { - tcb.persist_deadline = None; + if tcb.snd_wnd > 0 && handles.is_armed(TcpTimerKind::Persist) { + handles.cancel_timer(TcpTimerKind::Persist, wheel); tcb.persist_backoff = 0; } @@ -453,9 +467,13 @@ impl TcpHandler { // Delayed ACK — defer to poll_send for piggyback opportunity. tcb.ack_delay_count += 1; tcb.ack_pending = true; - if tcb.delayed_ack_deadline.is_none() { - tcb.delayed_ack_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.delayed_ack_ms)); + if !handles.is_armed(TcpTimerKind::DelayedAck) { + handles.arm( + TcpTimerKind::DelayedAck, + key, + now + coarsetime::Duration::from_millis(tcb.delayed_ack_ms), + wheel, + ); } rx_return.push(frame); @@ -606,10 +624,14 @@ impl TcpHandler { // RFC 6298 §5.3: manage retransmit timer on new ACK. tcb.rto_backoff = 0; if tcb.snd_una == tcb.snd_nxt { - tcb.retransmit_deadline = None; + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); } else { - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto)); + handles.arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); } // F-RTO check — must come before recovery/congestion control. @@ -700,8 +722,8 @@ impl TcpHandler { } // C. Clear persist timer when window reopens. - if tcb.snd_wnd > 0 && tcb.persist_deadline.is_some() { - tcb.persist_deadline = None; + if tcb.snd_wnd > 0 && handles.is_armed(TcpTimerKind::Persist) { + handles.cancel_timer(TcpTimerKind::Persist, wheel); tcb.persist_backoff = 0; } @@ -765,8 +787,8 @@ impl TcpHandler { } // Clear persist timer when window reopens via duplicate ACK. - if tcb.snd_wnd > 0 && tcb.persist_deadline.is_some() { - tcb.persist_deadline = None; + if tcb.snd_wnd > 0 && handles.is_armed(TcpTimerKind::Persist) { + handles.cancel_timer(TcpTimerKind::Persist, wheel); tcb.persist_backoff = 0; } @@ -817,9 +839,13 @@ impl TcpHandler { // Defer ACK (delayed ACK) — defer to poll_send for piggyback opportunity. tcb.ack_delay_count += 1; tcb.ack_pending = true; - if tcb.delayed_ack_deadline.is_none() { - tcb.delayed_ack_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.delayed_ack_ms)); + if !handles.is_armed(TcpTimerKind::DelayedAck) { + handles.arm( + TcpTimerKind::DelayedAck, + key, + now + coarsetime::Duration::from_millis(tcb.delayed_ack_ms), + wheel, + ); } } else if crate::net::wire::tcp::seq_lt(rcv_nxt, seg_seq) { // Out-of-order data. diff --git a/src/net/handler/tcp/inbound/listen.rs b/src/net/handler/tcp/inbound/listen.rs index 778e8ec..2ef3660 100644 --- a/src/net/handler/tcp/inbound/listen.rs +++ b/src/net/handler/tcp/inbound/listen.rs @@ -7,6 +7,7 @@ use slab::Slab; use crate::{ net::{ socket::LocalQueue, + timer_wheel::TimerWheel, wire::{ ip::IpAddress, tcp::{flags, parse_mss, parse_sack_permitted, parse_timestamp, parse_window_scale}, @@ -26,15 +27,18 @@ use super::super::state::TcpState; use super::super::tcb::{ ConnectionId, DEFAULT_RCV_MSS, DEFAULT_RCV_WND, DEFAULT_RCV_WSCALE, TS_OPTION_LEN, Tcb, }; +use super::super::timer_kinds::{TcpTimerHandles, TcpTimerKind}; impl TcpHandler { pub(super) fn process_listen<'umem>( connections: &mut Slab, + timer_handles: &mut Slab, connection_map: &mut FxHashMap, listeners: &mut [ListenEntry], isn_generator: &mut IsnGenerator, listener_idx: usize, now: Instant, + wheel: &mut TimerWheel, incoming_src: IpAddress, incoming_dst: IpAddress, src_port: u16, @@ -143,7 +147,6 @@ impl TcpHandler { 0 }, wscale_enabled, - retransmit_deadline: Some(now + coarsetime::Duration::from_millis(INITIAL_RTO_MS)), rto_backoff: 0, event_queue, send_buffer: RingBuffer::new(send_buffer_size), @@ -166,10 +169,8 @@ impl TcpHandler { last_send_time: None, pending_fin: false, fin_seq: None, - time_wait_deadline: None, time_wait_duration, ack_pending: false, - delayed_ack_deadline: None, ack_delay_count: 0, delayed_ack_ms: listener.delayed_ack_ms, nagle_enabled: !listener.tcp_no_delay, @@ -180,7 +181,6 @@ impl TcpHandler { last_activity: now, keep_alive_probes_sent: 0, linger: listener.linger, - linger_deadline: None, ts_enabled, ts_recent: if ts_enabled { peer_tsval } else { 0 }, ts_recent_age: now, @@ -190,7 +190,6 @@ impl TcpHandler { ecn_enabled, ecn_ce_received: false, ecn_cwr_sent: false, - persist_deadline: None, persist_backoff: 0, max_snd_wnd: 0, last_advertised_right_edge: 0, @@ -229,8 +228,18 @@ impl TcpHandler { let tcb_id = tcb.id; let key = connections.insert(tcb); + let handle_key = timer_handles.insert(TcpTimerHandles::new()); + debug_assert_eq!(key, handle_key, "timer_handles slab key mismatch"); connection_map.insert(tcb_id, key); listeners[listener_idx].syn_received_count += 1; + + // Arm retransmit timer for the SYN-ACK. + timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(INITIAL_RTO_MS), + wheel, + ); } // Step 4: Other → drop (frame returned by caller). diff --git a/src/net/handler/tcp/inbound/segment.rs b/src/net/handler/tcp/inbound/segment.rs index 4aa5845..1952eec 100644 --- a/src/net/handler/tcp/inbound/segment.rs +++ b/src/net/handler/tcp/inbound/segment.rs @@ -11,6 +11,9 @@ use super::super::segment::SegmentBuilder; use super::super::send_tracker::SendReady; use super::super::state::TcpState; use super::super::tcb::ConnectionId; +use super::super::timer_kinds::TcpTimerKind; + +use crate::net::timer_wheel::TimerWheel; /// Actions that must be performed after releasing the `&mut Tcb` borrow. pub(super) enum PostAction { @@ -52,6 +55,7 @@ impl TcpHandler { &mut self, frame: Frame<'umem>, now: Instant, + wheel: &mut TimerWheel, incoming_src: IpAddress, incoming_dst: IpAddress, src_port: u16, @@ -82,6 +86,7 @@ impl TcpHandler { // Destructure self for split borrows. let Self { connections, + timer_handles, connection_map, listeners, isn_generator, @@ -95,6 +100,7 @@ impl TcpHandler { if let Some(&key) = connection_map.get(&conn_id) && let Some(tcb) = connections.get_mut(key) { + let handles = &mut timer_handles[key]; let tsval = if tcb.ts_enabled { now.duration_since(tcb.ts_offset).as_millis() as u32 } else { @@ -105,8 +111,10 @@ impl TcpHandler { TcpState::SynSent => { let action = Self::process_syn_sent( tcb, + handles, key, now, + wheel, tsval, seg_seq, seg_ack, @@ -125,8 +133,10 @@ impl TcpHandler { TcpState::SynReceived => { let action = Self::process_syn_received( tcb, + handles, key, listeners, + wheel, tsval, seg_seq, seg_ack, @@ -148,9 +158,11 @@ impl TcpHandler { let opts = ParsedOptions::parse(options); Self::process_established( tcb, + handles, key, frame, now, + wheel, tsval, seg_seq, seg_ack, @@ -179,9 +191,11 @@ impl TcpHandler { let opts = ParsedOptions::parse(options); Self::process_teardown( tcb, + handles, key, frame, now, + wheel, tsval, seg_seq, seg_ack, @@ -211,7 +225,9 @@ impl TcpHandler { connection_map.remove(&tcb.id); } send_tracker.unmark(rm_key); + timer_handles[rm_key].cancel_all(wheel); connections.remove(rm_key); + timer_handles.remove(rm_key); } PostAction::RemoveAndDecrement(rm_key) => { if let Some(tcb) = connections.get(rm_key) { @@ -219,7 +235,9 @@ impl TcpHandler { connection_map.remove(&tcb.id); } send_tracker.unmark(rm_key); + timer_handles[rm_key].cancel_all(wheel); connections.remove(rm_key); + timer_handles.remove(rm_key); } PostAction::None => { // Mark connection for send processing if it has pending work. @@ -228,8 +246,8 @@ impl TcpHandler { || tcb.pending_fin || tcb.send_buffer.available() > 0 || tcb.ecn_cwr_sent - || tcb.persist_deadline.is_some() - || tcb.retransmit_deadline.is_some()) + || timer_handles[key].is_armed(TcpTimerKind::Persist) + || timer_handles[key].is_armed(TcpTimerKind::Retransmit)) { send_tracker.mark(SendReady(key)); } @@ -245,11 +263,13 @@ impl TcpHandler { { Self::process_listen( connections, + timer_handles, connection_map, listeners, isn_generator, listener_idx, now, + wheel, incoming_src, incoming_dst, src_port, diff --git a/src/net/handler/tcp/inbound/syn_received.rs b/src/net/handler/tcp/inbound/syn_received.rs index fa6f976..662bf5a 100644 --- a/src/net/handler/tcp/inbound/syn_received.rs +++ b/src/net/handler/tcp/inbound/syn_received.rs @@ -1,10 +1,11 @@ -use crate::xdp::frame::FrameBuffer; +use crate::{net::timer_wheel::TimerWheel, xdp::frame::FrameBuffer}; use super::super::handler::TcpHandler; use super::super::listener::ListenEntry; use super::super::segment::SegmentBuilder; use super::super::state::TcpState; use super::super::tcb::{DEFAULT_RCV_WND, Tcb, TcpEvent}; +use super::super::timer_kinds::{TcpTimerHandles, TcpTimerKind}; use super::segment::PostAction; @@ -13,8 +14,10 @@ use crate::net::wire::tcp::flags; impl TcpHandler { pub(super) fn process_syn_received<'umem>( tcb: &mut Tcb, + handles: &mut TcpTimerHandles, key: usize, listeners: &mut [ListenEntry], + wheel: &mut TimerWheel, tsval: u32, seg_seq: u32, seg_ack: u32, @@ -137,7 +140,7 @@ impl TcpHandler { tcb.max_snd_wnd = tcb.max_snd_wnd.max(tcb.snd_wnd); tcb.snd_wl1 = seg_seq; tcb.snd_wl2 = seg_ack; - tcb.retransmit_deadline = None; + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); tcb.rto_backoff = 0; let from_passive = tcb.from_passive_open; diff --git a/src/net/handler/tcp/inbound/syn_sent.rs b/src/net/handler/tcp/inbound/syn_sent.rs index 68b2ec5..3412e9f 100644 --- a/src/net/handler/tcp/inbound/syn_sent.rs +++ b/src/net/handler/tcp/inbound/syn_sent.rs @@ -1,7 +1,10 @@ use coarsetime::Instant; use crate::{ - net::wire::tcp::{flags, parse_mss, parse_sack_permitted, parse_timestamp, parse_window_scale}, + net::{ + timer_wheel::TimerWheel, + wire::tcp::{flags, parse_mss, parse_sack_permitted, parse_timestamp, parse_window_scale}, + }, xdp::frame::FrameBuffer, }; @@ -9,6 +12,7 @@ use super::super::handler::{INITIAL_RTO_MS, TcpHandler}; use super::super::segment::SegmentBuilder; use super::super::state::TcpState; use super::super::tcb::{DEFAULT_RCV_WND, TS_OPTION_LEN, Tcb, TcpEvent}; +use super::super::timer_kinds::{TcpTimerHandles, TcpTimerKind}; use super::segment::PostAction; @@ -17,8 +21,10 @@ impl TcpHandler { pub(super) fn process_syn_sent<'umem>( tcb: &mut Tcb, + handles: &mut TcpTimerHandles, key: usize, now: Instant, + wheel: &mut TimerWheel, tsval: u32, seg_seq: u32, seg_ack: u32, @@ -126,7 +132,7 @@ impl TcpHandler { tcb.max_snd_wnd = tcb.max_snd_wnd.max(tcb.snd_wnd); tcb.snd_wl1 = seg_seq; tcb.snd_wl2 = seg_ack; - tcb.retransmit_deadline = None; + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); tcb.rto_backoff = 0; // Send ACK. @@ -188,8 +194,12 @@ impl TcpHandler { ); // Reset retransmit timer for SYN-ACK. - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(INITIAL_RTO_MS)); + handles.arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(INITIAL_RTO_MS), + wheel, + ); tcb.rto_backoff = 0; } } diff --git a/src/net/handler/tcp/inbound/teardown.rs b/src/net/handler/tcp/inbound/teardown.rs index 2cfeb2c..cb535cd 100644 --- a/src/net/handler/tcp/inbound/teardown.rs +++ b/src/net/handler/tcp/inbound/teardown.rs @@ -1,7 +1,7 @@ use coarsetime::Instant; use crate::{ - net::wire::tcp::flags, + net::{timer_wheel::TimerWheel, wire::tcp::flags}, xdp::frame::{Frame, FrameBuffer}, }; @@ -10,6 +10,7 @@ use super::super::options::ParsedOptions; use super::super::segment::SegmentBuilder; use super::super::state::TcpState; use super::super::tcb::{Tcb, TcpEvent}; +use super::super::timer_kinds::{TcpTimerHandles, TcpTimerKind}; use super::segment::{PostAction, is_segment_acceptable}; @@ -20,9 +21,11 @@ impl TcpHandler { pub(super) fn process_teardown<'umem>( tcb: &mut Tcb, + handles: &mut TcpTimerHandles, key: usize, frame: Frame<'umem>, now: Instant, + wheel: &mut TimerWheel, tsval: u32, seg_seq: u32, seg_ack: u32, @@ -219,13 +222,17 @@ impl TcpHandler { if fin_acked && remote_fin { // Both sides FINed and our FIN is ACKed → TimeWait. tcb.state = TcpState::TimeWait; - tcb.time_wait_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.time_wait_duration)); - tcb.retransmit_deadline = None; + handles.arm( + TcpTimerKind::TimeWait, + key, + now + coarsetime::Duration::from_millis(tcb.time_wait_duration), + wheel, + ); + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); } else if fin_acked { // Our FIN ACKed but no remote FIN yet → FinWait2. tcb.state = TcpState::FinWait2; - tcb.retransmit_deadline = None; + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); } else if remote_fin { // Remote FINed but our FIN not yet ACKed → Closing. tcb.state = TcpState::Closing; @@ -271,8 +278,12 @@ impl TcpHandler { if seg_flags & flags::FIN != 0 { tcb.rcv_nxt = tcb.rcv_nxt.wrapping_add(1); tcb.state = TcpState::TimeWait; - tcb.time_wait_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.time_wait_duration)); + handles.arm( + TcpTimerKind::TimeWait, + key, + now + coarsetime::Duration::from_millis(tcb.time_wait_duration), + wheel, + ); } // Send ACK if FIN or data. @@ -310,9 +321,13 @@ impl TcpHandler { { tcb.snd_una = seg_ack; tcb.state = TcpState::TimeWait; - tcb.time_wait_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.time_wait_duration)); - tcb.retransmit_deadline = None; + handles.arm( + TcpTimerKind::TimeWait, + key, + now + coarsetime::Duration::from_millis(tcb.time_wait_duration), + wheel, + ); + handles.cancel_timer(TcpTimerKind::Retransmit, wheel); } rx_return.push(frame); } @@ -352,8 +367,12 @@ impl TcpHandler { free_frames, tx_return, ); - tcb.time_wait_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.time_wait_duration)); + handles.arm( + TcpTimerKind::TimeWait, + key, + now + coarsetime::Duration::from_millis(tcb.time_wait_duration), + wheel, + ); } // Everything else (including RST) is ignored — RST handled above. rx_return.push(frame); diff --git a/src/net/handler/tcp/inbound/validate.rs b/src/net/handler/tcp/inbound/validate.rs index 3be412f..5af6fbe 100644 --- a/src/net/handler/tcp/inbound/validate.rs +++ b/src/net/handler/tcp/inbound/validate.rs @@ -4,6 +4,7 @@ use crate::{ net::{ NeighborHandler, checksum::verify_tcp_checksum_ip, + timer_wheel::TimerWheel, wire::{ ethernet::EthernetFrame, ip::{IpAddress, IpVersion, Ipv4, Ipv4Header, Ipv6, Ipv6Header}, @@ -22,6 +23,7 @@ impl TcpHandler { &mut self, frame: Frame<'umem>, now: Instant, + wheel: &mut TimerWheel, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, @@ -38,6 +40,7 @@ impl TcpHandler { &src_addr, &dst_addr, now, + wheel, neighbor_handler, free_frames, rx_return, @@ -51,6 +54,7 @@ impl TcpHandler { frame: Frame<'umem>, tcp_offset: usize, now: Instant, + wheel: &mut TimerWheel, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, @@ -66,6 +70,7 @@ impl TcpHandler { &src_addr, &dst_addr, now, + wheel, neighbor_handler, free_frames, rx_return, @@ -82,6 +87,7 @@ impl TcpHandler { src_addr: &V::Address, dst_addr: &V::Address, now: Instant, + wheel: &mut TimerWheel, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, @@ -143,6 +149,7 @@ impl TcpHandler { self.process_segment( frame, now, + wheel, incoming_src, incoming_dst, src_port, diff --git a/src/net/handler/tcp/tcb.rs b/src/net/handler/tcp/tcb.rs index 3b96f8b..614f0a4 100644 --- a/src/net/handler/tcp/tcb.rs +++ b/src/net/handler/tcp/tcb.rs @@ -184,8 +184,6 @@ pub struct Tcb { pub recv_buffer: RingBuffer, /// Most recent TSval received from peer. pub ts_recent: u32, - /// Deadline for sending the deferred ACK. - pub delayed_ack_deadline: Option, /// Delayed ACK timeout in milliseconds. pub delayed_ack_ms: u64, @@ -228,8 +226,6 @@ pub struct Tcb { pub ts_offset: Instant, // === COLD: rarely accessed === - /// Deadline for retransmitting unacknowledged SYN or SYN-ACK. - pub retransmit_deadline: Option, /// Exponential backoff counter for retransmissions. pub rto_backoff: u8, /// Queue for delivering events to user-facing socket. @@ -244,12 +240,8 @@ pub struct Tcb { pub pending_fin: bool, /// Sequence number of our FIN (set when FIN is sent). pub fin_seq: Option, - /// Deadline for exiting TIME-WAIT state. - pub time_wait_deadline: Option, /// Duration to remain in TIME-WAIT state in milliseconds. pub time_wait_duration: u64, - /// Deadline for next zero-window probe. - pub persist_deadline: Option, /// Exponential backoff counter for persist probes (cap at 6). pub persist_backoff: u8, pub keep_alive_enabled: bool, @@ -259,7 +251,6 @@ pub struct Tcb { pub last_activity: Instant, pub keep_alive_probes_sent: u8, pub linger: Option, - pub linger_deadline: Option, } impl Tcb { @@ -397,7 +388,6 @@ mod tests { snd_wscale, rcv_wscale, wscale_enabled, - retransmit_deadline: None, rto_backoff: 0, event_queue: LocalQueue::new(16), send_buffer: RingBuffer::new(1024), @@ -413,10 +403,8 @@ mod tests { last_send_time: None, pending_fin: false, fin_seq: None, - time_wait_deadline: None, time_wait_duration: 60_000, ack_pending: false, - delayed_ack_deadline: None, ack_delay_count: 0, delayed_ack_ms: DEFAULT_DELAYED_ACK_MS, nagle_enabled: true, @@ -427,7 +415,6 @@ mod tests { last_activity: Instant::now(), keep_alive_probes_sent: 0, linger: None, - linger_deadline: None, ts_enabled: false, ts_recent: 0, ts_recent_age: Instant::now(), @@ -437,7 +424,6 @@ mod tests { ecn_enabled: false, ecn_ce_received: false, ecn_cwr_sent: false, - persist_deadline: None, persist_backoff: 0, max_snd_wnd: 0, last_advertised_right_edge: 0, diff --git a/src/net/handler/tcp/tests/congestion_tests.rs b/src/net/handler/tcp/tests/congestion_tests.rs index a3f39b2..6d3062f 100644 --- a/src/net/handler/tcp/tests/congestion_tests.rs +++ b/src/net/handler/tcp/tests/congestion_tests.rs @@ -1,7 +1,9 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] fn cubic_slow_start_on_new_ack() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -28,6 +30,7 @@ fn cubic_slow_start_on_new_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -49,6 +52,7 @@ fn cubic_slow_start_on_new_ack() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -66,7 +70,15 @@ fn cubic_slow_start_on_new_ack() { .write(&[0xAA; 1460]); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} // ACK the data. @@ -86,6 +98,7 @@ fn cubic_slow_start_on_new_ack() { handler.process_ipv4( Frame::new(2, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -103,6 +116,7 @@ fn cubic_slow_start_on_new_ack() { #[test] fn frto_restores_cwnd_on_spurious_rto() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(64); @@ -129,6 +143,7 @@ fn frto_restores_cwnd_on_spurious_rto() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -150,6 +165,7 @@ fn frto_restores_cwnd_on_spurious_rto() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -165,16 +181,43 @@ fn frto_restores_cwnd_on_spurious_rto() { .write(&vec![0xAA; mss * 2]); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let cwnd_before_rto = handler.first_connection().cubic.cwnd; - // Trigger RTO by setting deadline in the past. - handler.first_connection_mut().retransmit_deadline = Some(now); + // Trigger RTO by arming retransmit at tick 0. + let key = handler.first_connection_key(); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); let rto_time = now + coarsetime::Duration::from_millis(1100); - handler.poll_timers(rto_time, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + rto_time, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} // F-RTO should be active. @@ -206,6 +249,7 @@ fn frto_restores_cwnd_on_spurious_rto() { handler.process_ipv4( Frame::new(10, leak(ack1), ack1_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -235,6 +279,7 @@ fn frto_restores_cwnd_on_spurious_rto() { handler.process_ipv4( Frame::new(11, leak(ack2), ack2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/tests/data_transfer.rs b/src/net/handler/tcp/tests/data_transfer.rs index 41414cb..e8a394c 100644 --- a/src/net/handler/tcp/tests/data_transfer.rs +++ b/src/net/handler/tcp/tests/data_transfer.rs @@ -7,6 +7,7 @@ fn established_receives_in_order_data() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -29,6 +30,7 @@ fn established_receives_in_order_data() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -50,6 +52,7 @@ fn established_receives_in_order_data() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -78,6 +81,7 @@ fn established_receives_in_order_data() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -113,6 +117,7 @@ fn established_receives_in_order_data() { handler.process_ipv4( Frame::new(3, leak(data2), data2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -129,6 +134,7 @@ fn established_receives_in_order_data() { // poll_send generates pure ACK since no data to piggyback. handler.poll_send( coarsetime::Instant::now(), + &mut wheel, nh.local_mac(), &nh, &mut free, @@ -155,6 +161,7 @@ fn established_out_of_order_reassembly() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -177,6 +184,7 @@ fn established_out_of_order_reassembly() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -198,6 +206,7 @@ fn established_out_of_order_reassembly() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -222,6 +231,7 @@ fn established_out_of_order_reassembly() { handler.process_ipv4( Frame::new(2, leak(seg2), seg2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -251,6 +261,7 @@ fn established_out_of_order_reassembly() { handler.process_ipv4( Frame::new(3, leak(seg1), seg1_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -283,6 +294,7 @@ fn poll_send_builds_data_segment() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -305,6 +317,7 @@ fn poll_send_builds_data_segment() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -326,6 +339,7 @@ fn poll_send_builds_data_segment() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -341,7 +355,15 @@ fn poll_send_builds_data_segment() { handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1, "data segment built"); let tcb = handler.first_connection(); @@ -361,6 +383,7 @@ fn frame_accounting_through_data_transfer() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); @@ -385,6 +408,7 @@ fn frame_accounting_through_data_transfer() { handler.process_ipv4( Frame::new(0, leak(syn), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -406,6 +430,7 @@ fn frame_accounting_through_data_transfer() { handler.process_ipv4( Frame::new(1, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -429,6 +454,7 @@ fn frame_accounting_through_data_transfer() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -452,6 +478,7 @@ fn frame_accounting_through_data_transfer() { handler.process_ipv4( Frame::new(3, leak(data2), data2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -471,17 +498,27 @@ fn poll_send_sets_psh_on_last_segment() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Write small data (< MSS) into send buffer. handler.first_connection_mut().send_buffer.write(b"Hello"); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1, "expected one data segment"); let frame = tx.pop().unwrap(); @@ -508,13 +545,15 @@ fn poll_send_no_psh_on_first_segment_when_more_data() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); // Allocate frames large enough for MSS-sized segments (ETH+IP+TCP+1460). for i in 0..16 { let buf = leak(vec![0u8; 2048]); free.push(Frame::new(2000 + i, buf, 2048, false)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Write more than 1 MSS of data. let mss = handler.first_connection().eff_snd_mss as usize; @@ -528,7 +567,15 @@ fn poll_send_no_psh_on_first_segment_when_more_data() { let now = coarsetime::Instant::now(); // poll_send now sends all segments in one call (fills the window). - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( tx.num_frames(), @@ -567,6 +614,7 @@ fn rcv_nxt_advances_only_by_bytes_written_to_recv_buffer() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -601,6 +649,7 @@ fn rcv_nxt_advances_only_by_bytes_written_to_recv_buffer() { handler.process_ipv4( Frame::new(0, leak(syn), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -624,6 +673,7 @@ fn rcv_nxt_advances_only_by_bytes_written_to_recv_buffer() { handler.process_ipv4( Frame::new(1, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -658,6 +708,7 @@ fn rcv_nxt_advances_only_by_bytes_written_to_recv_buffer() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -686,11 +737,13 @@ fn fin_with_data_transitions_to_close_wait() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Send data+FIN in a single segment. let payload = b"final data"; @@ -710,6 +763,7 @@ fn fin_with_data_transitions_to_close_wait() { handler.process_ipv4( Frame::new(2, leak(data_fin), data_fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -744,6 +798,7 @@ fn fast_path_with_timestamps() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { let buf = leak(vec![0u8; 2048]); free.push(Frame::new(100 + i, buf, 2048, false)); @@ -756,8 +811,15 @@ fn fast_path_with_timestamps() { ecn: false, ..TcpConfig::default() }; - let _client_iss = - active_open_handshake_with_config(&mut handler, &nh, config, &mut free, &mut rx, &mut tx); + let _client_iss = active_open_handshake_with_config( + &mut handler, + &mut wheel, + &nh, + config, + &mut free, + &mut rx, + &mut tx, + ); // Simulate sending data so snd_nxt > snd_una. let tcb = handler.first_connection_mut(); @@ -789,6 +851,7 @@ fn fast_path_with_timestamps() { handler.process_ipv4( Frame::new(60, leak(data_seg), data_seg_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/tests/delayed_ack.rs b/src/net/handler/tcp/tests/delayed_ack.rs index 8ea6af3..adc1162 100644 --- a/src/net/handler/tcp/tests/delayed_ack.rs +++ b/src/net/handler/tcp/tests/delayed_ack.rs @@ -1,3 +1,4 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] @@ -7,6 +8,7 @@ fn delayed_ack_defers_ack_for_in_order_data() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -29,6 +31,7 @@ fn delayed_ack_defers_ack_for_in_order_data() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -50,6 +53,7 @@ fn delayed_ack_defers_ack_for_in_order_data() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -75,6 +79,7 @@ fn delayed_ack_defers_ack_for_in_order_data() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -85,9 +90,10 @@ fn delayed_ack_defers_ack_for_in_order_data() { assert_eq!(tx.num_frames(), 0, "ACK should be deferred"); let tcb = handler.first_connection(); assert!(tcb.ack_pending, "ack_pending should be true"); + let key = handler.first_connection_key(); assert!( - tcb.delayed_ack_deadline.is_some(), - "delayed_ack_deadline should be set" + handler.timer_handles[key].is_armed(TcpTimerKind::DelayedAck), + "delayed_ack timer should be armed" ); assert_eq!(tcb.ack_delay_count, 1, "ack_delay_count should be 1"); } @@ -99,6 +105,7 @@ fn delayed_ack_flushes_on_second_segment() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -121,6 +128,7 @@ fn delayed_ack_flushes_on_second_segment() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -142,6 +150,7 @@ fn delayed_ack_flushes_on_second_segment() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -166,6 +175,7 @@ fn delayed_ack_flushes_on_second_segment() { handler.process_ipv4( Frame::new(2, leak(seg1), seg1_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -190,6 +200,7 @@ fn delayed_ack_flushes_on_second_segment() { handler.process_ipv4( Frame::new(3, leak(seg2), seg2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -205,6 +216,7 @@ fn delayed_ack_flushes_on_second_segment() { // poll_send generates pure ACK since no data to piggyback. handler.poll_send( coarsetime::Instant::now(), + &mut wheel, nh.local_mac(), &nh, &mut free, @@ -228,6 +240,7 @@ fn out_of_order_data_sends_immediate_ack() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -250,6 +263,7 @@ fn out_of_order_data_sends_immediate_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -271,6 +285,7 @@ fn out_of_order_data_sends_immediate_ack() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -295,6 +310,7 @@ fn out_of_order_data_sends_immediate_ack() { handler.process_ipv4( Frame::new(2, leak(ooo_data), ooo_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -315,6 +331,7 @@ fn fin_sends_immediate_ack() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -337,6 +354,7 @@ fn fin_sends_immediate_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -358,6 +376,7 @@ fn fin_sends_immediate_ack() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -381,6 +400,7 @@ fn fin_sends_immediate_ack() { handler.process_ipv4( Frame::new(2, leak(fin_data), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -398,6 +418,7 @@ fn new_connection_has_delayed_ack_fields() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -422,6 +443,7 @@ fn new_connection_has_delayed_ack_fields() { handler.process_ipv4( syn_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -448,6 +470,7 @@ fn new_connection_has_delayed_ack_fields() { handler.process_ipv4( ack_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -459,9 +482,10 @@ fn new_connection_has_delayed_ack_fields() { // Verify delayed ACK and Nagle defaults. let tcb = handler.first_connection(); assert!(!tcb.ack_pending, "ack_pending should be false"); + let key = handler.first_connection_key(); assert!( - tcb.delayed_ack_deadline.is_none(), - "delayed_ack_deadline should be None" + !handler.timer_handles[key].is_armed(TcpTimerKind::DelayedAck), + "delayed_ack timer should not be armed" ); assert_eq!(tcb.ack_delay_count, 0, "ack_delay_count should be 0"); assert_eq!( @@ -479,6 +503,7 @@ fn delayed_ack_timer_flushes_pending_ack() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -503,6 +528,7 @@ fn delayed_ack_timer_flushes_pending_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -524,6 +550,7 @@ fn delayed_ack_timer_flushes_pending_ack() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -538,26 +565,52 @@ fn delayed_ack_timer_flushes_pending_ack() { remote_addr: IpAddress::V4(REMOTE_IP), remote_port: 12345, }; + let key = handler.first_connection_key(); { let tcb = handler.get_connection_mut(&id).unwrap(); tcb.ack_pending = true; tcb.ack_delay_count = 1; - tcb.delayed_ack_deadline = Some(now + coarsetime::Duration::from_millis(40)); } - // Before deadline — should NOT flush. - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + // Before deadline — should NOT flush (no timer armed). + // Advance wheel by 0 — nothing fires. + let fired = wheel.advance(0); + for timer_id in fired { + let (k, kind) = super::super::timer_kinds::unpack_tcp_timer_id(timer_id); + handler.handle_timer( + k, + kind, + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); + } assert_eq!(tx.num_frames(), 0, "should not flush before deadline"); - // After deadline — should flush. + // After deadline — arm and fire the delayed ACK timer. + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::DelayedAck), 0); + handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); let later = now + coarsetime::Duration::from_millis(50); - handler.poll_timers(later, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + later, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1, "should flush after deadline"); let tcb = handler.get_connection(&id).unwrap(); assert!(!tcb.ack_pending); assert_eq!(tcb.ack_delay_count, 0); - assert!(tcb.delayed_ack_deadline.is_none()); + assert!(!handler.timer_handles[key].is_armed(TcpTimerKind::DelayedAck)); } #[test] @@ -567,6 +620,7 @@ fn data_send_clears_delayed_ack() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -589,6 +643,7 @@ fn data_send_clears_delayed_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -610,6 +665,7 @@ fn data_send_clears_delayed_ack() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -634,6 +690,7 @@ fn data_send_clears_delayed_ack() { handler.process_ipv4( Frame::new(2, leak(data_seg), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -655,7 +712,15 @@ fn data_send_clears_delayed_ack() { // 4. poll_send — sends data (piggybacks ACK). let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!(tx.num_frames() >= 1, "data segment should be sent"); // 5. Verify delayed ACK state is cleared. @@ -665,8 +730,9 @@ fn data_send_clears_delayed_ack() { "ack_pending should be cleared after data send" ); assert_eq!(tcb.ack_delay_count, 0, "ack_delay_count should be cleared"); + let key = handler.first_connection_key(); assert!( - tcb.delayed_ack_deadline.is_none(), - "delayed_ack_deadline should be cleared" + !handler.timer_handles[key].is_armed(TcpTimerKind::DelayedAck), + "delayed_ack timer should be cleared" ); } diff --git a/src/net/handler/tcp/tests/ecn.rs b/src/net/handler/tcp/tests/ecn.rs index a321d98..704fc88 100644 --- a/src/net/handler/tcp/tests/ecn.rs +++ b/src/net/handler/tcp/tests/ecn.rs @@ -1,7 +1,9 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] fn ecn_negotiated_when_both_sides_support() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -25,6 +27,7 @@ fn ecn_negotiated_when_both_sides_support() { nh.local_mac(), crate::net::wire::ethernet::MacAddress::broadcast(), coarsetime::Instant::now(), + &mut wheel, config, &mut free, &mut tx, @@ -72,6 +75,7 @@ fn ecn_negotiated_when_both_sides_support() { handler.process_ipv4( Frame::new(0, leak(syn_ack_data), syn_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -92,6 +96,7 @@ fn ecn_negotiated_when_both_sides_support() { #[test] fn ecn_disabled_when_peer_doesnt_support() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -111,6 +116,7 @@ fn ecn_disabled_when_peer_doesnt_support() { nh.local_mac(), crate::net::wire::ethernet::MacAddress::broadcast(), coarsetime::Instant::now(), + &mut wheel, TcpConfig::default(), &mut free, &mut tx, @@ -141,6 +147,7 @@ fn ecn_disabled_when_peer_doesnt_support() { handler.process_ipv4( Frame::new(0, leak(syn_ack_data), syn_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -161,6 +168,7 @@ fn ecn_disabled_when_peer_doesnt_support() { #[test] fn ecn_negotiated_on_passive_open() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -191,6 +199,7 @@ fn ecn_negotiated_on_passive_open() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -240,6 +249,7 @@ fn ecn_negotiated_on_passive_open() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -256,6 +266,7 @@ fn ecn_negotiated_on_passive_open() { #[test] fn ecn_ect_set_on_outgoing_data() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -283,6 +294,7 @@ fn ecn_ect_set_on_outgoing_data() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -304,6 +316,7 @@ fn ecn_ect_set_on_outgoing_data() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -320,7 +333,15 @@ fn ecn_ect_set_on_outgoing_data() { handler.first_connection_mut().send_buffer.write(payload); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1, "should have one data segment"); let frame = tx.pop().unwrap(); @@ -331,6 +352,7 @@ fn ecn_ect_set_on_outgoing_data() { #[test] fn ecn_ect_not_set_on_retransmit() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -358,6 +380,7 @@ fn ecn_ect_not_set_on_retransmit() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -379,6 +402,7 @@ fn ecn_ect_not_set_on_retransmit() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -395,13 +419,32 @@ fn ecn_ect_not_set_on_retransmit() { .write(b"RTO test data"); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Drain the initial data segment. while tx.pop().is_some() {} // Expire the retransmit timer to trigger RTO retransmit. - handler.first_connection_mut().retransmit_deadline = Some(now); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let key = handler.first_connection_key(); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1, "should have one retransmit segment"); let frame = tx.pop().unwrap(); @@ -411,6 +454,7 @@ fn ecn_ect_not_set_on_retransmit() { #[test] fn ecn_ce_detected_on_incoming() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -438,6 +482,7 @@ fn ecn_ce_detected_on_incoming() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -459,6 +504,7 @@ fn ecn_ce_detected_on_incoming() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -497,6 +543,7 @@ fn ecn_ce_detected_on_incoming() { handler.process_ipv4( Frame::new(2, leak(data_frame), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -511,6 +558,7 @@ fn ecn_ce_detected_on_incoming() { #[test] fn ecn_ece_sent_when_ce_received() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -538,6 +586,7 @@ fn ecn_ece_sent_when_ce_received() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -559,6 +608,7 @@ fn ecn_ece_sent_when_ce_received() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -587,6 +637,7 @@ fn ecn_ece_sent_when_ce_received() { handler.process_ipv4( Frame::new(2, leak(data_frame), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -597,8 +648,19 @@ fn ecn_ece_sent_when_ce_received() { // Force delayed ACK flush if needed. if tx.num_frames() == 0 { let now = coarsetime::Instant::now(); - handler.first_connection_mut().delayed_ack_deadline = Some(now); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let key = handler.first_connection_key(); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::DelayedAck), 0); + handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); } assert!( @@ -616,6 +678,7 @@ fn ecn_ece_sent_when_ce_received() { #[test] fn ecn_cwnd_halved_on_ece() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -643,6 +706,7 @@ fn ecn_cwnd_halved_on_ece() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -664,6 +728,7 @@ fn ecn_cwnd_halved_on_ece() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -684,7 +749,15 @@ fn ecn_cwnd_halved_on_ece() { .send_buffer .write(b"test data for ecn"); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let snd_nxt = handler.first_connection().snd_nxt; @@ -708,6 +781,7 @@ fn ecn_cwnd_halved_on_ece() { handler.process_ipv4( Frame::new(3, leak(ece_ack), ece_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -739,6 +813,7 @@ fn ecn_cwnd_halved_on_ece() { #[test] fn ecn_cwr_sent_on_next_data() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -766,6 +841,7 @@ fn ecn_cwr_sent_on_next_data() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -787,6 +863,7 @@ fn ecn_cwr_sent_on_next_data() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -805,7 +882,15 @@ fn ecn_cwr_sent_on_next_data() { .send_buffer .write(b"cwr test data"); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1, "should have one data segment"); let frame = tx.pop().unwrap(); @@ -825,6 +910,7 @@ fn ecn_cwr_sent_on_next_data() { #[test] fn ecn_ce_received_cleared_on_cwr() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -852,6 +938,7 @@ fn ecn_ce_received_cleared_on_cwr() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -873,6 +960,7 @@ fn ecn_ce_received_cleared_on_cwr() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -902,6 +990,7 @@ fn ecn_ce_received_cleared_on_cwr() { handler.process_ipv4( Frame::new(2, leak(cwr_frame), cwr_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/tests/edge_cases.rs b/src/net/handler/tcp/tests/edge_cases.rs index ade5728..3b0b102 100644 --- a/src/net/handler/tcp/tests/edge_cases.rs +++ b/src/net/handler/tcp/tests/edge_cases.rs @@ -1,3 +1,4 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] @@ -7,6 +8,7 @@ fn unmatched_syn_generates_rst() { let mut free = BasicFrameBuffer::new(4); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); free.push(alloc_free_frame(100)); @@ -27,6 +29,7 @@ fn unmatched_syn_generates_rst() { handler.process_ipv4( frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -45,6 +48,7 @@ fn rst_to_unbound_port_silently_dropped() { let mut free = BasicFrameBuffer::new(4); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); let data = build_tcp_frame(REMOTE_IP, LOCAL_IP, 12345, 80, 1000, 0, flags::RST, 0, &[]); let len = data.len(); @@ -53,6 +57,7 @@ fn rst_to_unbound_port_silently_dropped() { handler.process_ipv4( frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -70,6 +75,7 @@ fn invalid_checksum_dropped() { let mut free = BasicFrameBuffer::new(4); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); let data = build_tcp_frame( REMOTE_IP, @@ -92,6 +98,7 @@ fn invalid_checksum_dropped() { handler.process_ipv4( frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -109,6 +116,7 @@ fn truncated_tcp_header_dropped() { let mut free = BasicFrameBuffer::new(4); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); // Build a frame that's too short for a TCP header. let mut data = vec![0u8; ETH_HEADER_LEN + IPV4_MIN_HEADER_LEN + 10]; // only 10 bytes of TCP @@ -133,6 +141,7 @@ fn truncated_tcp_header_dropped() { handler.process_ipv4( frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -150,6 +159,7 @@ fn frame_accounting_after_rst() { let mut free = BasicFrameBuffer::new(4); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); free.push(alloc_free_frame(100)); @@ -170,6 +180,7 @@ fn frame_accounting_after_rst() { handler.process_ipv4( frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -187,11 +198,13 @@ fn rst_outside_window_is_dropped() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // rcv_nxt is 1001, window is 65535 → valid range [1001, 66536). // Send RST with seq far outside the window. @@ -210,6 +223,7 @@ fn rst_outside_window_is_dropped() { handler.process_ipv4( Frame::new(50, leak(rst_data), rst_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -232,11 +246,13 @@ fn rst_in_window_but_not_exact_sends_challenge_ack() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // rcv_nxt is 1001. Send RST with seq = 1005 (in-window but not exact). let rst_data = build_tcp_frame( @@ -254,6 +270,7 @@ fn rst_in_window_but_not_exact_sends_challenge_ack() { handler.process_ipv4( Frame::new(50, leak(rst_data), rst_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -282,11 +299,13 @@ fn syn_in_established_sends_challenge_ack() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Send a SYN segment to the established connection. let syn_data = build_tcp_frame( @@ -304,6 +323,7 @@ fn syn_in_established_sends_challenge_ack() { handler.process_ipv4( Frame::new(50, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -339,11 +359,13 @@ fn segment_without_ack_is_dropped() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt_before = handler.first_connection().rcv_nxt; @@ -364,6 +386,7 @@ fn segment_without_ack_is_dropped() { handler.process_ipv4( Frame::new(50, leak(no_ack_data), no_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -392,11 +415,13 @@ fn ack_beyond_snd_nxt_sends_ack_and_drops() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let snd_una_before = handler.first_connection().snd_una; let snd_nxt_before = handler.first_connection().snd_nxt; @@ -418,6 +443,7 @@ fn ack_beyond_snd_nxt_sends_ack_and_drops() { handler.process_ipv4( Frame::new(50, leak(bad_ack), bad_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -473,11 +499,13 @@ fn stale_segment_does_not_regress_window() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..6 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); while rx.pop().is_some() {} // Segment B arrives first: higher seg_seq (1002), window = 8000. @@ -496,6 +524,7 @@ fn stale_segment_does_not_regress_window() { handler.process_ipv4( Frame::new(10, leak(seg_b), seg_b_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -532,6 +561,7 @@ fn stale_segment_does_not_regress_window() { handler.process_ipv4( Frame::new(11, leak(seg_a), seg_a_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -559,6 +589,7 @@ fn sender_sws_avoidance_holds_small_sends() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -581,6 +612,7 @@ fn sender_sws_avoidance_holds_small_sends() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -602,6 +634,7 @@ fn sender_sws_avoidance_holds_small_sends() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -623,7 +656,15 @@ fn sender_sws_avoidance_holds_small_sends() { .write(&[0x41u8; 100]); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // SWS check: can_send=10, eff_snd_mss=1460, max_snd_wnd/2=32767, data_available=100. // 10 < 1460 (not full MSS), 10 < 32767 (not half max window), 100 > 10 (not all data fits). @@ -636,7 +677,15 @@ fn sender_sws_avoidance_holds_small_sends() { // Now set snd_wnd to eff_snd_mss — should send. handler.first_connection_mut().snd_wnd = mss as u32; - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( tx.num_frames(), 1, @@ -653,6 +702,7 @@ fn handshake_with_wrapping_isn() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -679,6 +729,7 @@ fn handshake_with_wrapping_isn() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -725,6 +776,7 @@ fn handshake_with_wrapping_isn() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -753,6 +805,7 @@ fn data_transfer_across_sequence_wrap() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -784,6 +837,7 @@ fn data_transfer_across_sequence_wrap() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -809,6 +863,7 @@ fn data_transfer_across_sequence_wrap() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -837,6 +892,7 @@ fn data_transfer_across_sequence_wrap() { handler.process_ipv4( Frame::new(2, leak(data_frame), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -871,6 +927,7 @@ fn data_transfer_across_sequence_wrap() { handler.process_ipv4( Frame::new(3, leak(data_frame2), data2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -880,6 +937,7 @@ fn data_transfer_across_sequence_wrap() { // poll_send flushes the pending delayed ACK. handler.poll_send( coarsetime::Instant::now(), + &mut wheel, nh.local_mac(), &nh, &mut free, @@ -917,12 +975,14 @@ fn remove_connection_sends_rst_with_correct_seq() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // snd_nxt after handshake = server_iss + 1 (SYN consumed one sequence number). let expected_seq = server_iss.wrapping_add(1); @@ -977,11 +1037,13 @@ fn rst_exact_match_resets_connection() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // rcv_nxt is 1001. Send RST with seq = 1001 (exact match). let rst_data = build_tcp_frame( @@ -999,6 +1061,7 @@ fn rst_exact_match_resets_connection() { handler.process_ipv4( Frame::new(50, leak(rst_data), rst_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1020,11 +1083,13 @@ fn duplicate_data_sends_ack() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Send data to advance rcv_nxt. let payload = b"hello"; @@ -1044,6 +1109,7 @@ fn duplicate_data_sends_ack() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1069,6 +1135,7 @@ fn duplicate_data_sends_ack() { handler.process_ipv4( Frame::new(3, leak(dup_data), dup_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1095,11 +1162,13 @@ fn out_of_window_non_rst_sends_ack() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Send a data segment with seq far outside the window (not RST). let oow_data = build_tcp_frame_with_payload( @@ -1118,6 +1187,7 @@ fn out_of_window_non_rst_sends_ack() { handler.process_ipv4( Frame::new(50, leak(oow_data), oow_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1140,11 +1210,13 @@ fn recovery_exit_on_full_ack() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let tcb = handler.first_connection_mut(); tcb.eff_snd_mss = 100; @@ -1180,6 +1252,7 @@ fn recovery_exit_on_full_ack() { handler.process_ipv4( Frame::new(20, leak(full_ack), full_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1200,19 +1273,24 @@ fn persist_timer_cleared_on_new_ack_with_window() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Set up: send some data so snd_nxt > snd_una, then arm persist timer. + let key = handler.first_connection_key(); let tcb = handler.first_connection_mut(); tcb.send_buffer.write(&[0u8; 100]); tcb.snd_nxt = tcb.snd_una.wrapping_add(100); - tcb.persist_deadline = Some(coarsetime::Instant::now()); tcb.persist_backoff = 3; let snd_una = tcb.snd_una; + // Arm persist timer. + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Persist), 0); + handler.timer_handles[key].set(TcpTimerKind::Persist, handle); // Send a new ACK that advances snd_una, with a non-zero window. let new_ack = snd_una.wrapping_add(50); @@ -1231,6 +1309,7 @@ fn persist_timer_cleared_on_new_ack_with_window() { handler.process_ipv4( Frame::new(5, leak(ack_frame), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1240,7 +1319,7 @@ fn persist_timer_cleared_on_new_ack_with_window() { let tcb = handler.first_connection(); assert_eq!(tcb.snd_una, new_ack, "snd_una should advance"); assert!( - tcb.persist_deadline.is_none(), + !handler.timer_handles[key].is_armed(TcpTimerKind::Persist), "persist timer should be cleared when window reopens on new ACK" ); assert_eq!(tcb.persist_backoff, 0, "persist backoff should be reset"); @@ -1253,11 +1332,13 @@ fn rtt_update_with_existing_srtt_slow_path() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Set up: some data in flight, with an existing SRTT. let tcb = handler.first_connection_mut(); @@ -1285,6 +1366,7 @@ fn rtt_update_with_existing_srtt_slow_path() { handler.process_ipv4( Frame::new(5, leak(ack_frame), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1308,6 +1390,7 @@ fn sender_sws_allows_send_when_all_data_fits() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -1330,6 +1413,7 @@ fn sender_sws_allows_send_when_all_data_fits() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1351,6 +1435,7 @@ fn sender_sws_allows_send_when_all_data_fits() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1366,7 +1451,15 @@ fn sender_sws_allows_send_when_all_data_fits() { handler.first_connection_mut().send_buffer.write(b"abc"); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // SWS: can_send=5, data_available=3, 3 <= 5 → all data fits → sws_ok = true. assert_eq!( diff --git a/src/net/handler/tcp/tests/handler.rs b/src/net/handler/tcp/tests/handler.rs index 2c1db28..7b0ddcb 100644 --- a/src/net/handler/tcp/tests/handler.rs +++ b/src/net/handler/tcp/tests/handler.rs @@ -19,6 +19,7 @@ use crate::{ use super::{ LOCAL_IP, REMOTE_IP, alloc_free_frame, establish_connection, new_handler, new_neighbor_handler, + new_wheel, }; /// Build a minimal Tcb for unit testing handler methods. @@ -48,7 +49,6 @@ fn make_test_tcb(state: TcpState, local_port: u16, remote_port: u16) -> Tcb { snd_wscale: 0, rcv_wscale: 7, wscale_enabled: false, - retransmit_deadline: None, rto_backoff: 0, event_queue: LocalQueue::new(16), send_buffer: RingBuffer::new(1024), @@ -64,10 +64,8 @@ fn make_test_tcb(state: TcpState, local_port: u16, remote_port: u16) -> Tcb { last_send_time: None, pending_fin: false, fin_seq: None, - time_wait_deadline: None, time_wait_duration: 60_000, ack_pending: false, - delayed_ack_deadline: None, ack_delay_count: 0, delayed_ack_ms: DEFAULT_DELAYED_ACK_MS, nagle_enabled: true, @@ -78,7 +76,6 @@ fn make_test_tcb(state: TcpState, local_port: u16, remote_port: u16) -> Tcb { last_activity: now, keep_alive_probes_sent: 0, linger: None, - linger_deadline: None, ts_enabled: false, ts_recent: 0, ts_recent_age: now, @@ -88,7 +85,6 @@ fn make_test_tcb(state: TcpState, local_port: u16, remote_port: u16) -> Tcb { ecn_enabled: false, ecn_ce_received: false, ecn_cwr_sent: false, - persist_deadline: None, persist_backoff: 0, max_snd_wnd: 0, last_advertised_right_edge: 0, @@ -217,9 +213,11 @@ fn remove_connection_synchronized_generates_rst() { let mut free = BasicFrameBuffer::new(4); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); // Establish a real connection via 3-way handshake. - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); assert_eq!(handler.first_connection().state, TcpState::Established); let id = handler.first_connection().id; @@ -249,6 +247,7 @@ fn remove_connection_syn_received_generates_rst() { let dst_mac = MacAddress::from([0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); let mut free = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); free.push(alloc_free_frame(200)); handler.remove_connection(&id, src_mac, dst_mac, &mut free, &mut tx); @@ -268,6 +267,7 @@ fn remove_connection_syn_sent_no_rst() { let dst_mac = MacAddress::from([0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); let mut free = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); handler.remove_connection(&id, src_mac, dst_mac, &mut free, &mut tx); @@ -288,6 +288,7 @@ fn remove_connection_not_found_is_noop() { let dst_mac = MacAddress::from([0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); let mut free = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); // Should not panic — just returns early. handler.remove_connection(&id, src_mac, dst_mac, &mut free, &mut tx); diff --git a/src/net/handler/tcp/tests/handshake.rs b/src/net/handler/tcp/tests/handshake.rs index 7b3f1a0..dfdb554 100644 --- a/src/net/handler/tcp/tests/handshake.rs +++ b/src/net/handler/tcp/tests/handshake.rs @@ -7,6 +7,7 @@ fn syn_to_listener_generates_syn_ack() { let mut free = BasicFrameBuffer::new(4); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); + let mut wheel = new_wheel(); free.push(alloc_free_frame(100)); @@ -31,6 +32,7 @@ fn syn_to_listener_generates_syn_ack() { handler.process_ipv4( frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -52,6 +54,7 @@ fn handshake_completes_on_ack() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -76,6 +79,7 @@ fn handshake_completes_on_ack() { handler.process_ipv4( syn_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -103,6 +107,7 @@ fn handshake_completes_on_ack() { handler.process_ipv4( ack_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -120,6 +125,7 @@ fn rst_in_syn_received_removes_connection() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -144,6 +150,7 @@ fn rst_in_syn_received_removes_connection() { handler.process_ipv4( syn_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -169,6 +176,7 @@ fn rst_in_syn_received_removes_connection() { handler.process_ipv4( rst_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -185,6 +193,7 @@ fn backlog_limits_syn_received() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -210,6 +219,7 @@ fn backlog_limits_syn_received() { handler.process_ipv4( syn_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -227,6 +237,7 @@ fn window_scale_negotiation() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -256,6 +267,7 @@ fn window_scale_negotiation() { handler.process_ipv4( syn_frame, coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -285,6 +297,7 @@ fn simultaneous_open_both_reach_established() { let mut free_b = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free_a.push(alloc_free_frame(200 + i)); @@ -301,6 +314,7 @@ fn simultaneous_open_both_reach_established() { mac_a, mac_b, coarsetime::Instant::now(), + &mut wheel, &mut free_a, &mut tx, ) @@ -317,6 +331,7 @@ fn simultaneous_open_both_reach_established() { mac_b, mac_a, coarsetime::Instant::now(), + &mut wheel, &mut free_b, &mut tx, ) @@ -346,6 +361,7 @@ fn simultaneous_open_both_reach_established() { handler_a.process_ipv4( Frame::new(10, leak(syn_b), syn_b_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free_a, &mut rx, @@ -377,6 +393,7 @@ fn simultaneous_open_both_reach_established() { handler_b.process_ipv4( Frame::new(11, leak(syn_a), syn_a_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free_b, &mut rx, @@ -408,6 +425,7 @@ fn simultaneous_open_both_reach_established() { handler_a.process_ipv4( Frame::new(12, leak(syn_ack_b), syn_ack_b_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free_a, &mut rx, @@ -437,6 +455,7 @@ fn simultaneous_open_both_reach_established() { handler_b.process_ipv4( Frame::new(13, leak(syn_ack_a), syn_ack_a_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free_b, &mut rx, @@ -460,6 +479,7 @@ fn bad_seq_in_syn_received_sends_challenge_ack() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -483,6 +503,7 @@ fn bad_seq_in_syn_received_sends_challenge_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -511,6 +532,7 @@ fn bad_seq_in_syn_received_sends_challenge_ack() { handler.process_ipv4( Frame::new(2, leak(bad_data), bad_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -529,6 +551,7 @@ fn bad_seq_rst_in_syn_received_is_ignored() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -552,6 +575,7 @@ fn bad_seq_rst_in_syn_received_is_ignored() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -579,6 +603,7 @@ fn bad_seq_rst_in_syn_received_is_ignored() { handler.process_ipv4( Frame::new(2, leak(rst_data), rst_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -597,6 +622,7 @@ fn rst_in_syn_received_active_open_signals_refused() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -621,6 +647,7 @@ fn rst_in_syn_received_active_open_signals_refused() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -645,6 +672,7 @@ fn rst_in_syn_received_active_open_signals_refused() { handler.process_ipv4( Frame::new(10, leak(syn_b), syn_b_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -662,6 +690,7 @@ fn rst_in_syn_received_active_open_signals_refused() { handler.process_ipv4( Frame::new(11, leak(rst_data), rst_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -685,6 +714,7 @@ fn syn_retransmit_in_syn_received_passive_resends_syn_ack() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -708,6 +738,7 @@ fn syn_retransmit_in_syn_received_passive_resends_syn_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -735,6 +766,7 @@ fn syn_retransmit_in_syn_received_passive_resends_syn_ack() { handler.process_ipv4( Frame::new(2, leak(syn_retransmit), syn_rt_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -753,6 +785,7 @@ fn syn_retransmit_in_syn_received_active_sends_challenge_ack() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -777,6 +810,7 @@ fn syn_retransmit_in_syn_received_active_sends_challenge_ack() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -799,6 +833,7 @@ fn syn_retransmit_in_syn_received_active_sends_challenge_ack() { handler.process_ipv4( Frame::new(10, leak(syn_b), syn_b_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -825,6 +860,7 @@ fn syn_retransmit_in_syn_received_active_sends_challenge_ack() { handler.process_ipv4( Frame::new(11, leak(syn_b2), syn_b2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -847,6 +883,7 @@ fn bad_ack_in_syn_received_sends_rst() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -870,6 +907,7 @@ fn bad_ack_in_syn_received_sends_rst() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -896,6 +934,7 @@ fn bad_ack_in_syn_received_sends_rst() { handler.process_ipv4( Frame::new(2, leak(bad_ack_data), bad_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -914,6 +953,7 @@ fn syn_ack_without_timestamp_disables_timestamps() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -932,6 +972,7 @@ fn syn_ack_without_timestamp_disables_timestamps() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -960,6 +1001,7 @@ fn syn_ack_without_timestamp_disables_timestamps() { handler.process_ipv4( Frame::new(50, leak(syn_ack), syn_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -989,6 +1031,7 @@ fn syn_ack_without_sack_permitted_disables_sack() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1007,6 +1050,7 @@ fn syn_ack_without_sack_permitted_disables_sack() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1035,6 +1079,7 @@ fn syn_ack_without_sack_permitted_disables_sack() { handler.process_ipv4( Frame::new(50, leak(syn_ack), syn_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1055,6 +1100,7 @@ fn syn_ack_without_ece_disables_ecn() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1073,6 +1119,7 @@ fn syn_ack_without_ece_disables_ecn() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1101,6 +1148,7 @@ fn syn_ack_without_ece_disables_ecn() { handler.process_ipv4( Frame::new(50, leak(syn_ack), syn_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1121,6 +1169,7 @@ fn unacceptable_ack_in_syn_sent_sends_rst() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1138,6 +1187,7 @@ fn unacceptable_ack_in_syn_sent_sends_rst() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1163,6 +1213,7 @@ fn unacceptable_ack_in_syn_sent_sends_rst() { handler.process_ipv4( Frame::new(50, leak(bad_ack), bad_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1181,6 +1232,7 @@ fn unacceptable_ack_too_high_in_syn_sent_sends_rst() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1198,6 +1250,7 @@ fn unacceptable_ack_too_high_in_syn_sent_sends_rst() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1222,6 +1275,7 @@ fn unacceptable_ack_too_high_in_syn_sent_sends_rst() { handler.process_ipv4( Frame::new(50, leak(bad_ack), bad_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1239,6 +1293,7 @@ fn simultaneous_open_syn_without_ack_transitions_to_syn_received() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1256,6 +1311,7 @@ fn simultaneous_open_syn_without_ack_transitions_to_syn_received() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1280,6 +1336,7 @@ fn simultaneous_open_syn_without_ack_transitions_to_syn_received() { handler.process_ipv4( Frame::new(50, leak(peer_syn), peer_syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1309,6 +1366,7 @@ fn rst_with_ack_in_syn_sent_signals_connection_refused() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1326,6 +1384,7 @@ fn rst_with_ack_in_syn_sent_signals_connection_refused() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1350,6 +1409,7 @@ fn rst_with_ack_in_syn_sent_signals_connection_refused() { handler.process_ipv4( Frame::new(50, leak(rst_ack), rst_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1372,6 +1432,7 @@ fn rst_without_ack_in_syn_sent_is_dropped() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1389,6 +1450,7 @@ fn rst_without_ack_in_syn_sent_is_dropped() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1411,6 +1473,7 @@ fn rst_without_ack_in_syn_sent_is_dropped() { handler.process_ipv4( Frame::new(50, leak(rst_only), rst_only_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1430,6 +1493,7 @@ fn window_scale_applied_on_handshake_completion() { let mut free = BasicFrameBuffer::new(8); let mut rx = BasicFrameBuffer::new(8); let mut tx = BasicFrameBuffer::new(8); + let mut wheel = new_wheel(); for i in 0..4 { free.push(alloc_free_frame(100 + i)); @@ -1458,6 +1522,7 @@ fn window_scale_applied_on_handshake_completion() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1486,6 +1551,7 @@ fn window_scale_applied_on_handshake_completion() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1506,6 +1572,7 @@ fn bare_ack_in_syn_sent_is_dropped() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } @@ -1530,6 +1597,7 @@ fn bare_ack_in_syn_sent_is_dropped() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -1556,6 +1624,7 @@ fn bare_ack_in_syn_sent_is_dropped() { handler.process_ipv4( Frame::new(50, leak(bare_ack), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/tests/keepalive.rs b/src/net/handler/tcp/tests/keepalive.rs index 29b1960..a983da6 100644 --- a/src/net/handler/tcp/tests/keepalive.rs +++ b/src/net/handler/tcp/tests/keepalive.rs @@ -1,3 +1,4 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] @@ -7,6 +8,7 @@ fn keep_alive_activity_resets_probe_timer() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -29,6 +31,7 @@ fn keep_alive_activity_resets_probe_timer() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -50,6 +53,7 @@ fn keep_alive_activity_resets_probe_timer() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -82,6 +86,7 @@ fn keep_alive_activity_resets_probe_timer() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -107,6 +112,7 @@ fn keep_alive_probe_sent_after_idle_timeout() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -129,6 +135,7 @@ fn keep_alive_probe_sent_after_idle_timeout() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -150,6 +157,7 @@ fn keep_alive_probe_sent_after_idle_timeout() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -172,7 +180,16 @@ fn keep_alive_probe_sent_after_idle_timeout() { std::thread::sleep(std::time::Duration::from_millis(150)); let now = coarsetime::Instant::now(); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( handler.first_connection().keep_alive_probes_sent, @@ -192,6 +209,7 @@ fn keep_alive_no_probe_when_disabled() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -214,6 +232,7 @@ fn keep_alive_no_probe_when_disabled() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -235,6 +254,7 @@ fn keep_alive_no_probe_when_disabled() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -250,7 +270,16 @@ fn keep_alive_no_probe_when_disabled() { std::thread::sleep(std::time::Duration::from_millis(150)); let now = coarsetime::Instant::now(); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( handler.first_connection().keep_alive_probes_sent, @@ -267,6 +296,7 @@ fn keep_alive_connection_aborted_after_max_probes() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -289,6 +319,7 @@ fn keep_alive_connection_aborted_after_max_probes() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -310,6 +341,7 @@ fn keep_alive_connection_aborted_after_max_probes() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -336,7 +368,16 @@ fn keep_alive_connection_aborted_after_max_probes() { std::thread::sleep(std::time::Duration::from_millis(150)); let now = coarsetime::Instant::now(); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Connection should be removed. assert!( @@ -360,6 +401,7 @@ fn linger_zero_sends_rst_on_poll_send() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -382,6 +424,7 @@ fn linger_zero_sends_rst_on_poll_send() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -403,6 +446,7 @@ fn linger_zero_sends_rst_on_poll_send() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -418,20 +462,29 @@ fn linger_zero_sends_rst_on_poll_send() { // Clear tx from handshake. while tx.pop().is_some() {} - // Call initiate_close — should set pending_fin and linger_deadline to Instant::recent(). - handler.initiate_close(handler.first_connection_key()); + // Call initiate_close — should set pending_fin and arm linger timer. + let key = handler.first_connection_key(); + handler.initiate_close(key, &mut wheel); assert!( handler.first_connection().pending_fin, "pending_fin should be set" ); assert!( - handler.first_connection().linger_deadline.is_some(), - "linger_deadline should be set" + handler.timer_handles[key].is_armed(TcpTimerKind::Linger), + "linger timer should be armed" ); // Call poll_send — linger deadline is already expired, should send RST. let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Connection should be removed. assert!( @@ -458,6 +511,7 @@ fn linger_timeout_sets_deadline() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -480,6 +534,7 @@ fn linger_timeout_sets_deadline() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -501,6 +556,7 @@ fn linger_timeout_sets_deadline() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -516,19 +572,28 @@ fn linger_timeout_sets_deadline() { while tx.pop().is_some() {} // Call initiate_close. - handler.initiate_close(handler.first_connection_key()); + let key = handler.first_connection_key(); + handler.initiate_close(key, &mut wheel); assert!( handler.first_connection().pending_fin, "pending_fin should be set" ); assert!( - handler.first_connection().linger_deadline.is_some(), - "linger_deadline should be set" + handler.timer_handles[key].is_armed(TcpTimerKind::Linger), + "linger timer should be armed" ); // Call poll_send immediately — deadline is 5s in the future, should NOT abort. let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Connection should still exist. assert!( @@ -544,6 +609,7 @@ fn linger_none_normal_close() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -566,6 +632,7 @@ fn linger_none_normal_close() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -587,6 +654,7 @@ fn linger_none_normal_close() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -600,16 +668,17 @@ fn linger_none_normal_close() { "linger should be None by default" ); // Call initiate_close. - handler.initiate_close(handler.first_connection_key()); + let key = handler.first_connection_key(); + handler.initiate_close(key, &mut wheel); - // Verify pending_fin is true and linger_deadline is None. + // Verify pending_fin is true and linger timer is NOT armed (no linger set). assert!( handler.first_connection().pending_fin, "pending_fin should be set" ); assert!( - handler.first_connection().linger_deadline.is_none(), - "linger_deadline should be None for default close" + !handler.timer_handles[key].is_armed(TcpTimerKind::Linger), + "linger timer should not be armed for default close" ); } @@ -620,6 +689,7 @@ fn keep_alive_probe_and_recovery() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -642,6 +712,7 @@ fn keep_alive_probe_and_recovery() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -663,6 +734,7 @@ fn keep_alive_probe_and_recovery() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -679,7 +751,12 @@ fn keep_alive_probe_and_recovery() { tcb.keep_alive_interval_ms = 50; tcb.keep_alive_count = 3; tcb.ack_pending = false; - tcb.delayed_ack_deadline = None; + } + // Clear delayed ACK timer if armed. + let key = handler.first_connection_key(); + if let Some(h) = handler.timer_handles[key].get(TcpTimerKind::DelayedAck) { + wheel.cancel(h); + handler.timer_handles[key].clear(TcpTimerKind::DelayedAck); } // Wait past the idle threshold. @@ -688,7 +765,16 @@ fn keep_alive_probe_and_recovery() { let now = coarsetime::Instant::now(); let tx_before = tx.num_frames(); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Verify probe was sent. assert!( @@ -726,6 +812,7 @@ fn keep_alive_probe_and_recovery() { handler.process_ipv4( Frame::new(10, leak(ack_frame), ack_frame_len, false), recv_now, + &mut wheel, &nh, &mut free, &mut rx, @@ -753,6 +840,7 @@ fn keep_alive_exhaustion_removes_connection() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -775,6 +863,7 @@ fn keep_alive_exhaustion_removes_connection() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -796,6 +885,7 @@ fn keep_alive_exhaustion_removes_connection() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -815,14 +905,30 @@ fn keep_alive_exhaustion_removes_connection() { tcb.keep_alive_idle_ms = 50; tcb.keep_alive_interval_ms = 50; tcb.ack_pending = false; - tcb.delayed_ack_deadline = None; + } + // Clear delayed ACK timer if armed. + { + let key = handler.first_connection_key(); + if let Some(h) = handler.timer_handles[key].get(TcpTimerKind::DelayedAck) { + wheel.cancel(h); + handler.timer_handles[key].clear(TcpTimerKind::DelayedAck); + } } // Wait past the idle threshold and send first probe. std::thread::sleep(std::time::Duration::from_millis(100)); coarsetime::Instant::update(); let now1 = coarsetime::Instant::now(); - handler.poll_timers(now1, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now1, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // First probe should have been sent. assert_eq!( @@ -840,7 +946,16 @@ fn keep_alive_exhaustion_removes_connection() { std::thread::sleep(std::time::Duration::from_millis(100)); coarsetime::Instant::update(); let now2 = coarsetime::Instant::now(); - handler.poll_timers(now2, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now2, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Connection should be removed. assert!( @@ -864,6 +979,7 @@ fn linger_zero_immediate_rst() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -886,6 +1002,7 @@ fn linger_zero_immediate_rst() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -907,6 +1024,7 @@ fn linger_zero_immediate_rst() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -927,19 +1045,28 @@ fn linger_zero_immediate_rst() { let conn_id = handler.first_connection().id; // Initiate close — linger(0) sets immediate deadline. - handler.initiate_close(handler.first_connection_key()); + let key = handler.first_connection_key(); + handler.initiate_close(key, &mut wheel); assert!( handler.first_connection().pending_fin, "pending_fin should be set" ); assert!( - handler.first_connection().linger_deadline.is_some(), - "linger_deadline should be set for linger(0)" + handler.timer_handles[key].is_armed(TcpTimerKind::Linger), + "linger timer should be armed for linger(0)" ); // Call poll_send — linger deadline is already expired, should send RST. let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Connection should be removed. assert!( diff --git a/src/net/handler/tcp/tests/listener.rs b/src/net/handler/tcp/tests/listener.rs index b8dd383..5d64cf9 100644 --- a/src/net/handler/tcp/tests/listener.rs +++ b/src/net/handler/tcp/tests/listener.rs @@ -69,6 +69,7 @@ fn unlisten_nonexistent_is_noop() { #[test] fn unlisten_cleans_up_syn_received_connections() { + let mut wheel = new_wheel(); use crate::net::wire::tcp::flags; use crate::xdp::frame::{BasicFrameBuffer, Frame}; @@ -99,6 +100,7 @@ fn unlisten_cleans_up_syn_received_connections() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -118,6 +120,7 @@ fn unlisten_cleans_up_syn_received_connections() { #[test] fn unlisten_preserves_established_connections() { + let mut wheel = new_wheel(); use crate::xdp::frame::BasicFrameBuffer; let mut handler = new_handler(); @@ -130,7 +133,8 @@ fn unlisten_preserves_established_connections() { } // Complete a full handshake to get an ESTABLISHED connection. - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); assert_eq!(handler.first_connection().state, TcpState::Established); assert_eq!(handler.connections.len(), 1); diff --git a/src/net/handler/tcp/tests/mod.rs b/src/net/handler/tcp/tests/mod.rs index 5269356..169c3d2 100644 --- a/src/net/handler/tcp/tests/mod.rs +++ b/src/net/handler/tcp/tests/mod.rs @@ -2,6 +2,7 @@ use crate::{ net::{ NeighborHandler, checksum::{compute_ipv4_checksum, compute_tcp_checksum}, + timer_wheel::TimerWheel, wire::{ ethernet::MacAddress, ip::{IPV4_MIN_HEADER_LEN, IpAddress, IpProtocols, Ipv4Address}, @@ -14,10 +15,48 @@ use crate::{ use super::inbound::segment::is_segment_acceptable; use super::state::TcpState; use super::tcb::{ConnectionId, DEFAULT_RCV_WSCALE, TcpConfig, TcpEvent}; +use super::timer_kinds::TcpTimerKind; use super::*; use crate::xdp::frame::FrameBuffer; +use super::timer_kinds::unpack_tcp_timer_id; + +/// Test-only helper: creates a TimerWheel starting at tick 0. +pub(super) fn new_wheel() -> TimerWheel { + TimerWheel::new(0) +} + +/// Test-only helper: advance the wheel far enough to fire all pending timers, +/// then dispatch each to `handler.handle_timer`. +pub(super) fn poll_timers<'umem>( + handler: &mut TcpHandler, + wheel: &mut TimerWheel, + now: coarsetime::Instant, + src_mac: MacAddress, + nh: &crate::net::NeighborHandler, + free: &mut impl crate::xdp::frame::FrameBuffer<'umem>, + rx: &mut impl crate::xdp::frame::FrameBuffer<'umem>, + tx: &mut impl crate::xdp::frame::FrameBuffer<'umem>, +) { + let fired = wheel.advance(u64::MAX / 2); + for id in fired { + let (key, kind) = unpack_tcp_timer_id(id); + handler.handle_timer(key, kind, now, 0, wheel, src_mac, nh, free, rx, tx); + } +} + +/// Test-only helper: advance the wheel and fire any TimeWait timers to evict stale connections. +pub(super) fn evict_stale(handler: &mut TcpHandler, wheel: &mut TimerWheel) { + let fired = wheel.advance(u64::MAX / 2); + for id in fired { + let (key, kind) = unpack_tcp_timer_id(id); + if matches!(kind, TcpTimerKind::TimeWait) { + handler.fire_time_wait(key, wheel); + } + } +} + pub(super) const LOCAL_IP: Ipv4Address = Ipv4Address::new([10, 0, 0, 1]); pub(super) const REMOTE_IP: Ipv4Address = Ipv4Address::new([10, 0, 0, 2]); pub(super) const ETH_HEADER_LEN: usize = 14; @@ -211,6 +250,7 @@ pub(super) fn build_ts_option(tsval: u32, tsecr: u32) -> Vec { /// Drains tx after handshake so callers start with an empty tx buffer. pub(super) fn establish_connection( handler: &mut TcpHandler, + wheel: &mut TimerWheel, nh: &NeighborHandler, free: &mut BasicFrameBuffer<'static>, rx: &mut BasicFrameBuffer<'static>, @@ -237,6 +277,8 @@ pub(super) fn establish_connection( handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -258,6 +300,8 @@ pub(super) fn establish_connection( handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -270,6 +314,7 @@ pub(super) fn establish_connection( /// Helper: establish a connection with SACK enabled, returning server_iss. pub(super) fn establish_connection_with_sack( handler: &mut TcpHandler, + wheel: &mut TimerWheel, nh: &NeighborHandler, free: &mut BasicFrameBuffer<'static>, rx: &mut BasicFrameBuffer<'static>, @@ -298,6 +343,8 @@ pub(super) fn establish_connection_with_sack( handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -322,6 +369,8 @@ pub(super) fn establish_connection_with_sack( handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -336,6 +385,7 @@ pub(super) fn establish_connection_with_sack( /// Returns the client ISS (so the caller knows snd_una/snd_nxt base). pub(super) fn active_open_handshake( handler: &mut TcpHandler, + wheel: &mut TimerWheel, nh: &NeighborHandler, free: &mut BasicFrameBuffer<'static>, rx: &mut BasicFrameBuffer<'static>, @@ -361,6 +411,8 @@ pub(super) fn active_open_handshake( src_mac, dst_mac, coarsetime::Instant::now(), + 0, + wheel, free, tx, ) @@ -385,6 +437,8 @@ pub(super) fn active_open_handshake( handler.process_ipv4( Frame::new(50, leak(syn_ack), syn_ack_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -401,6 +455,7 @@ pub(super) fn active_open_handshake( /// Helper: perform active open handshake with custom TcpConfig. pub(super) fn active_open_handshake_with_config( handler: &mut TcpHandler, + wheel: &mut TimerWheel, nh: &NeighborHandler, config: TcpConfig, free: &mut BasicFrameBuffer<'static>, @@ -426,6 +481,8 @@ pub(super) fn active_open_handshake_with_config( src_mac, dst_mac, coarsetime::Instant::now(), + 0, + wheel, config, free, tx, @@ -450,6 +507,8 @@ pub(super) fn active_open_handshake_with_config( handler.process_ipv4( Frame::new(50, leak(syn_ack), syn_ack_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, diff --git a/src/net/handler/tcp/tests/nagle.rs b/src/net/handler/tcp/tests/nagle.rs index 70771d7..155add5 100644 --- a/src/net/handler/tcp/tests/nagle.rs +++ b/src/net/handler/tcp/tests/nagle.rs @@ -2,6 +2,7 @@ use super::*; #[test] fn nagle_holds_small_data_when_bytes_in_flight() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -13,14 +14,22 @@ fn nagle_holds_small_data_when_bytes_in_flight() { } // 1. Complete handshake via active open. - let _iss = active_open_handshake(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _iss = active_open_handshake(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // 2. Write small data. handler.first_connection_mut().send_buffer.write(b"hello"); // 3. poll_send — first send goes (nothing in flight). let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1, "first small segment should send"); // 4. Pop tx frame. @@ -30,7 +39,15 @@ fn nagle_holds_small_data_when_bytes_in_flight() { handler.first_connection_mut().send_buffer.write(b"world"); // 6. poll_send — Nagle holds it. - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( tx.num_frames(), 0, @@ -40,6 +57,7 @@ fn nagle_holds_small_data_when_bytes_in_flight() { #[test] fn nagle_allows_full_mss_even_with_bytes_in_flight() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -52,12 +70,20 @@ fn nagle_allows_full_mss_even_with_bytes_in_flight() { } // 1. Complete handshake via active open. - let _iss = active_open_handshake(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _iss = active_open_handshake(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // 2. Write small data, send it (creates bytes_in_flight), pop tx. handler.first_connection_mut().send_buffer.write(b"hi"); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} // 3. Write MSS-worth of data. @@ -66,7 +92,15 @@ fn nagle_allows_full_mss_even_with_bytes_in_flight() { handler.first_connection_mut().send_buffer.write(&mss_data); // 4. poll_send — full MSS always sends even with bytes in flight. - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( tx.num_frames(), 1, @@ -76,6 +110,7 @@ fn nagle_allows_full_mss_even_with_bytes_in_flight() { #[test] fn tcp_no_delay_sends_small_data_immediately() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -91,8 +126,15 @@ fn tcp_no_delay_sends_small_data_immediately() { tcp_no_delay: true, ..Default::default() }; - let _iss = - active_open_handshake_with_config(&mut handler, &nh, config, &mut free, &mut rx, &mut tx); + let _iss = active_open_handshake_with_config( + &mut handler, + &mut wheel, + &nh, + config, + &mut free, + &mut rx, + &mut tx, + ); // Verify nagle is disabled. assert!( @@ -103,7 +145,15 @@ fn tcp_no_delay_sends_small_data_immediately() { // 2. Write small data, poll_send (first send), pop tx. handler.first_connection_mut().send_buffer.write(b"hello"); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(tx.num_frames(), 1); while tx.pop().is_some() {} @@ -111,7 +161,15 @@ fn tcp_no_delay_sends_small_data_immediately() { handler.first_connection_mut().send_buffer.write(b"world"); // 4. poll_send — TCP_NODELAY bypasses Nagle. - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( tx.num_frames(), 1, diff --git a/src/net/handler/tcp/tests/persist.rs b/src/net/handler/tcp/tests/persist.rs index 4b5508e..0c2018a 100644 --- a/src/net/handler/tcp/tests/persist.rs +++ b/src/net/handler/tcp/tests/persist.rs @@ -1,3 +1,4 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] @@ -7,25 +8,36 @@ fn persist_timer_activates_on_zero_window() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Write data into send buffer, but set window to 0. handler.first_connection_mut().send_buffer.write(b"Hello"); handler.first_connection_mut().snd_wnd = 0; - assert!(handler.first_connection().persist_deadline.is_none()); + let key = handler.first_connection_key(); + assert!(!handler.timer_handles[key].is_armed(TcpTimerKind::Persist)); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Persist timer should now be armed. assert!( - handler.first_connection().persist_deadline.is_some(), - "persist_deadline should be set when window=0 and data available" + handler.timer_handles[key].is_armed(TcpTimerKind::Persist), + "persist timer should be armed when window=0 and data available" ); // No data segment should have been sent (deadline not yet reached). assert_eq!(tx.num_frames(), 0, "no segment sent before deadline"); @@ -38,25 +50,47 @@ fn persist_probe_sent_when_deadline_expires() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Write data, set window to 0. handler.first_connection_mut().send_buffer.write(b"Hello"); handler.first_connection_mut().snd_wnd = 0; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); - assert!(handler.first_connection().persist_deadline.is_some()); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); + let key = handler.first_connection_key(); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::Persist)); assert_eq!(handler.first_connection().persist_backoff, 0); - // Simulate time passing beyond the deadline by setting it to the past. - handler.first_connection_mut().persist_deadline = Some(now); + // Simulate time passing beyond the deadline by arming persist at tick 0. + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Persist), 0); + handler.timer_handles[key].set(TcpTimerKind::Persist, handle); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + // Fire the persist timer via poll_timers, then let poll_send handle the probe. + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // A 1-byte probe should have been sent. assert_eq!(tx.num_frames(), 1, "probe segment should be sent"); @@ -68,8 +102,8 @@ fn persist_probe_sent_when_deadline_expires() { ); // persist_backoff should have incremented. assert_eq!(handler.first_connection().persist_backoff, 1); - // persist_deadline should be rescheduled (not None). - assert!(handler.first_connection().persist_deadline.is_some()); + // persist timer should be rescheduled. + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::Persist)); } #[test] @@ -79,19 +113,30 @@ fn persist_timer_clears_when_window_reopens() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Write data, set window to 0, arm persist timer. handler.first_connection_mut().send_buffer.write(b"Hello"); handler.first_connection_mut().snd_wnd = 0; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); - assert!(handler.first_connection().persist_deadline.is_some()); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); + let key = handler.first_connection_key(); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::Persist)); handler.first_connection_mut().persist_backoff = 3; // simulate some backoff // Peer sends ACK with non-zero window, reopening it. @@ -110,6 +155,7 @@ fn persist_timer_clears_when_window_reopens() { handler.process_ipv4( Frame::new(2, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -118,8 +164,8 @@ fn persist_timer_clears_when_window_reopens() { // Persist timer should be cleared. assert!( - handler.first_connection().persist_deadline.is_none(), - "persist_deadline should be cleared when window reopens" + !handler.timer_handles[key].is_armed(TcpTimerKind::Persist), + "persist timer should be cleared when window reopens" ); assert_eq!( handler.first_connection().persist_backoff, @@ -135,11 +181,13 @@ fn persist_backoff_caps_at_six() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Write enough data to sustain many probes (each probe sends 1 byte). handler @@ -149,16 +197,35 @@ fn persist_backoff_caps_at_six() { handler.first_connection_mut().snd_wnd = 0; let now = coarsetime::Instant::now(); + let key = handler.first_connection_key(); // Arm the persist timer. - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); - assert!(handler.first_connection().persist_deadline.is_some()); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::Persist)); assert_eq!(handler.first_connection().persist_backoff, 0); - // Fire the persist probe multiple times (more than 6) by setting deadline to past. + // Fire the persist probe multiple times (more than 6) by arming at tick 0. for i in 0..10 { - handler.first_connection_mut().persist_deadline = Some(now); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Persist), 0); + handler.timer_handles[key].set(TcpTimerKind::Persist, handle); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let expected = (i + 1).min(6); diff --git a/src/net/handler/tcp/tests/retransmission.rs b/src/net/handler/tcp/tests/retransmission.rs index 8854bb4..a05e3e3 100644 --- a/src/net/handler/tcp/tests/retransmission.rs +++ b/src/net/handler/tcp/tests/retransmission.rs @@ -1,3 +1,4 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] @@ -7,6 +8,7 @@ fn fast_retransmit_on_three_dup_acks() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -29,6 +31,7 @@ fn fast_retransmit_on_three_dup_acks() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -50,6 +53,7 @@ fn fast_retransmit_on_three_dup_acks() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -61,7 +65,15 @@ fn fast_retransmit_on_three_dup_acks() { handler.first_connection_mut().send_buffer.write(b"AAAA"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} // consume sent segment let cwnd_before = handler.first_connection().cubic.cwnd; @@ -84,6 +96,7 @@ fn fast_retransmit_on_three_dup_acks() { handler.process_ipv4( Frame::new(10 + i, leak(dup), dup_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -113,6 +126,7 @@ fn rto_retransmit_on_timer_expiry() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -135,6 +149,7 @@ fn rto_retransmit_on_timer_expiry() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -156,6 +171,7 @@ fn rto_retransmit_on_timer_expiry() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -167,18 +183,36 @@ fn rto_retransmit_on_timer_expiry() { handler.first_connection_mut().send_buffer.write(b"BBBB"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let cwnd_before = handler.first_connection().cubic.cwnd; - // Simulate timer expiry by setting a deadline in the past. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + // Simulate timer expiry by arming retransmit at tick 0. + let key = handler.first_connection_key(); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; // poll_timers should trigger RTO retransmit. - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!(tx.num_frames() >= 1, "retransmitted segment expected"); let tcb = handler.first_connection(); @@ -201,6 +235,7 @@ fn rtt_estimation_updates_rto() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -223,6 +258,7 @@ fn rtt_estimation_updates_rto() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -244,6 +280,7 @@ fn rtt_estimation_updates_rto() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -258,7 +295,15 @@ fn rtt_estimation_updates_rto() { .write(b"test data"); handler.first_connection_mut().snd_wnd = 65535; let send_time = coarsetime::Instant::now(); - handler.poll_send(send_time, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + send_time, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} // Verify last_send_time is set. @@ -285,6 +330,7 @@ fn rtt_estimation_updates_rto() { handler.process_ipv4( Frame::new(5, leak(ack), ack_len, false), recv_time, + &mut wheel, &nh, &mut free, &mut rx, @@ -313,6 +359,7 @@ fn limited_transmit_sends_on_first_dup_ack() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } @@ -334,6 +381,7 @@ fn limited_transmit_sends_on_first_dup_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -355,6 +403,7 @@ fn limited_transmit_sends_on_first_dup_ack() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -373,7 +422,15 @@ fn limited_transmit_sends_on_first_dup_ack() { // Send 3 segments (fills cwnd). let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let snd_nxt_before = handler.first_connection().snd_nxt; @@ -395,6 +452,7 @@ fn limited_transmit_sends_on_first_dup_ack() { handler.process_ipv4( Frame::new(10, leak(dup), dup_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -404,7 +462,15 @@ fn limited_transmit_sends_on_first_dup_ack() { assert_eq!(handler.first_connection().recovery.dup_ack_count, 1); // poll_send should allow 1 MSS of new data (limited transmit). - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); let snd_nxt_after = handler.first_connection().snd_nxt; assert_eq!( @@ -421,24 +487,34 @@ fn rto_backoff_resets_on_new_ack() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Put data in server's send buffer and transmit it. handler.first_connection_mut().send_buffer.write(b"CCCC"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} // Artificially set rto_backoff as if an RTO had fired. + let key = handler.first_connection_key(); handler.first_connection_mut().rto_backoff = 2; - handler.first_connection_mut().retransmit_deadline = - Some(coarsetime::Instant::now() + coarsetime::Duration::from_millis(10_000)); + // Retransmit timer is already armed by poll_send; leave it armed. // Client ACKs the server's data (new ACK that advances snd_una). let new_ack = server_iss.wrapping_add(1).wrapping_add(4); // ISS+1 + 4 bytes @@ -459,6 +535,7 @@ fn rto_backoff_resets_on_new_ack() { handler.process_ipv4( Frame::new(10, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -469,7 +546,7 @@ fn rto_backoff_resets_on_new_ack() { assert_eq!(tcb.rto_backoff, 0, "rto_backoff should be reset on new ACK"); // snd_una == snd_nxt (all data ACKed), so retransmit timer should be off. assert!( - tcb.retransmit_deadline.is_none(), - "retransmit_deadline should be None when all data is ACKed" + !handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), + "retransmit timer should be disarmed when all data is ACKed" ); } diff --git a/src/net/handler/tcp/tests/sack.rs b/src/net/handler/tcp/tests/sack.rs index 392084c..5a2f843 100644 --- a/src/net/handler/tcp/tests/sack.rs +++ b/src/net/handler/tcp/tests/sack.rs @@ -1,7 +1,9 @@ +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; use super::*; #[test] fn ooo_data_sends_sack_blocks_in_dup_ack() { + let mut wheel = new_wheel(); use crate::net::wire::tcp::{options as tcp_options, parse_sack_blocks}; let mut handler = new_handler(); @@ -32,6 +34,7 @@ fn ooo_data_sends_sack_blocks_in_dup_ack() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -60,6 +63,7 @@ fn ooo_data_sends_sack_blocks_in_dup_ack() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -86,6 +90,7 @@ fn ooo_data_sends_sack_blocks_in_dup_ack() { handler.process_ipv4( Frame::new(2, leak(ooo_seg), ooo_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -115,6 +120,7 @@ fn ooo_data_sends_sack_blocks_in_dup_ack() { #[test] fn sack_blocks_update_scoreboard_on_ack() { + let mut wheel = new_wheel(); use crate::net::wire::tcp::write_sack_option; let mut handler = new_handler(); @@ -127,7 +133,7 @@ fn sack_blocks_update_scoreboard_on_ack() { } let _server_iss = - establish_connection_with_sack(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection_with_sack(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Put data in the send buffer and advance snd_nxt to simulate sent data. let tcb = handler.first_connection_mut(); @@ -163,6 +169,7 @@ fn sack_blocks_update_scoreboard_on_ack() { handler.process_ipv4( Frame::new(2, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -182,6 +189,7 @@ fn sack_blocks_update_scoreboard_on_ack() { #[test] fn sack_scoreboard_pruned_on_cumulative_ack_advance() { + let mut wheel = new_wheel(); use crate::net::wire::tcp::write_sack_option; let mut handler = new_handler(); @@ -194,7 +202,7 @@ fn sack_scoreboard_pruned_on_cumulative_ack_advance() { } let _server_iss = - establish_connection_with_sack(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection_with_sack(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let tcb = handler.first_connection_mut(); tcb.send_buffer.write(&[0u8; 200]); @@ -228,6 +236,7 @@ fn sack_scoreboard_pruned_on_cumulative_ack_advance() { handler.process_ipv4( Frame::new(2, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -255,6 +264,7 @@ fn sack_scoreboard_pruned_on_cumulative_ack_advance() { handler.process_ipv4( Frame::new(3, leak(ack_data2), ack_len2, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -275,6 +285,7 @@ fn sack_scoreboard_pruned_on_cumulative_ack_advance() { #[test] fn sack_blocks_updated_on_dup_ack() { + let mut wheel = new_wheel(); use crate::net::wire::tcp::write_sack_option; let mut handler = new_handler(); @@ -287,7 +298,7 @@ fn sack_blocks_updated_on_dup_ack() { } let _server_iss = - establish_connection_with_sack(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection_with_sack(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let tcb = handler.first_connection_mut(); tcb.send_buffer.write(&[0u8; 100]); @@ -315,6 +326,7 @@ fn sack_blocks_updated_on_dup_ack() { handler.process_ipv4( Frame::new(2, leak(dup_ack), dup_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -329,6 +341,7 @@ fn sack_blocks_updated_on_dup_ack() { #[test] fn sack_scoreboard_cleared_on_rto() { + let mut wheel = new_wheel(); use crate::net::wire::tcp::write_sack_option; let mut handler = new_handler(); @@ -341,7 +354,7 @@ fn sack_scoreboard_cleared_on_rto() { } let _server_iss = - establish_connection_with_sack(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection_with_sack(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let tcb = handler.first_connection_mut(); tcb.send_buffer.write(&[0u8; 100]); @@ -369,6 +382,7 @@ fn sack_scoreboard_cleared_on_rto() { handler.process_ipv4( Frame::new(2, leak(dup_ack), dup_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -376,13 +390,24 @@ fn sack_scoreboard_cleared_on_rto() { ); assert_eq!(handler.first_connection().sack_scoreboard.len(), 1); - // Set up RTO: arm the retransmit deadline in the past. + // Set up RTO: arm the retransmit timer at tick 0 (in the past). let now = coarsetime::Instant::now(); - handler.first_connection_mut().retransmit_deadline = Some(now); + let key = handler.first_connection_key(); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; // Trigger poll_timers, which should fire the RTO and clear the scoreboard. - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!( handler.first_connection().sack_scoreboard.is_empty(), @@ -392,6 +417,7 @@ fn sack_scoreboard_cleared_on_rto() { #[test] fn fast_retransmit_uses_sack_gap() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -402,7 +428,7 @@ fn fast_retransmit_uses_sack_gap() { } let _server_iss = - establish_connection_with_sack(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection_with_sack(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let tcb = handler.first_connection_mut(); // Use a small effective MSS so the retransmit fits in a 256-byte frame. @@ -435,7 +461,16 @@ fn fast_retransmit_uses_sack_gap() { tcb.cubic.on_loss(); let now = coarsetime::Instant::now(); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Verify a segment was emitted (the 1st MSS gap should be retransmitted). assert!(tx.pop().is_some(), "expected a retransmitted segment"); @@ -455,6 +490,7 @@ fn fast_retransmit_uses_sack_gap() { #[test] fn fast_retransmit_fallback_when_scoreboard_empty() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(32); @@ -465,7 +501,7 @@ fn fast_retransmit_fallback_when_scoreboard_empty() { } let _server_iss = - establish_connection_with_sack(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection_with_sack(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let tcb = handler.first_connection_mut(); // Use a small effective MSS so the retransmit fits in a 256-byte frame. @@ -487,7 +523,16 @@ fn fast_retransmit_fallback_when_scoreboard_empty() { tcb.cubic.on_loss(); let now = coarsetime::Instant::now(); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // With empty scoreboard, next_lost_segment returns None (nothing marked lost // by RFC 6675 criteria), so no retransmit is emitted from the recovery loop. @@ -506,6 +551,7 @@ fn fast_retransmit_fallback_when_scoreboard_empty() { #[test] fn sack_recovery_enters_on_3_dup_acks() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(64); @@ -532,6 +578,7 @@ fn sack_recovery_enters_on_3_dup_acks() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -553,6 +600,7 @@ fn sack_recovery_enters_on_3_dup_acks() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -596,6 +644,7 @@ fn sack_recovery_enters_on_3_dup_acks() { handler.process_ipv4( Frame::new(10 + i, leak(dup), dup_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -615,6 +664,7 @@ fn sack_recovery_enters_on_3_dup_acks() { #[test] fn sack_recovery_partial_ack_stays_in_recovery() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(64); @@ -641,6 +691,7 @@ fn sack_recovery_partial_ack_stays_in_recovery() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -662,6 +713,7 @@ fn sack_recovery_partial_ack_stays_in_recovery() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -704,6 +756,7 @@ fn sack_recovery_partial_ack_stays_in_recovery() { handler.process_ipv4( Frame::new(10 + i, leak(dup), dup_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -729,6 +782,7 @@ fn sack_recovery_partial_ack_stays_in_recovery() { handler.process_ipv4( Frame::new(20, leak(partial), partial_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -744,6 +798,7 @@ fn sack_recovery_partial_ack_stays_in_recovery() { #[test] fn ooo_multiple_ranges_generates_multiple_sack_blocks() { + let mut wheel = new_wheel(); use crate::net::wire::tcp::parse_sack_blocks; let mut handler = new_handler(); @@ -757,7 +812,8 @@ fn ooo_multiple_ranges_generates_multiple_sack_blocks() { } // Complete handshake with SACK enabled. - let server_iss = establish_connection_with_sack(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection_with_sack(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Send first OOO segment: seq=1021, 5 bytes (gap from 1001..1021). let ooo1 = build_tcp_frame_with_payload( @@ -776,6 +832,7 @@ fn ooo_multiple_ranges_generates_multiple_sack_blocks() { handler.process_ipv4( Frame::new(2, leak(ooo1), ooo1_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -800,6 +857,7 @@ fn ooo_multiple_ranges_generates_multiple_sack_blocks() { handler.process_ipv4( Frame::new(3, leak(ooo2), ooo2_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/tests/teardown.rs b/src/net/handler/tcp/tests/teardown.rs index 2edf3df..26abd40 100644 --- a/src/net/handler/tcp/tests/teardown.rs +++ b/src/net/handler/tcp/tests/teardown.rs @@ -1,3 +1,4 @@ +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] @@ -7,6 +8,7 @@ fn established_receives_fin_transitions_to_close_wait() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -29,6 +31,7 @@ fn established_receives_fin_transitions_to_close_wait() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -50,6 +53,7 @@ fn established_receives_fin_transitions_to_close_wait() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -73,6 +77,7 @@ fn established_receives_fin_transitions_to_close_wait() { handler.process_ipv4( Frame::new(2, leak(fin_data), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -91,6 +96,7 @@ fn established_receives_fin_with_data() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -113,6 +119,7 @@ fn established_receives_fin_with_data() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -134,6 +141,7 @@ fn established_receives_fin_with_data() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -159,6 +167,7 @@ fn established_receives_fin_with_data() { handler.process_ipv4( Frame::new(2, leak(fin_data), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -184,6 +193,7 @@ fn poll_send_sends_fin_when_pending() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -206,6 +216,7 @@ fn poll_send_sends_fin_when_pending() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -227,6 +238,7 @@ fn poll_send_sends_fin_when_pending() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -238,7 +250,15 @@ fn poll_send_sends_fin_when_pending() { handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // FIN should have been sent. assert_eq!(tx.num_frames(), 1, "FIN segment sent"); @@ -255,6 +275,7 @@ fn poll_send_drains_data_before_fin() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -277,6 +298,7 @@ fn poll_send_drains_data_before_fin() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -298,6 +320,7 @@ fn poll_send_drains_data_before_fin() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -314,7 +337,15 @@ fn poll_send_drains_data_before_fin() { handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Should send data first, NOT FIN yet (data still in flight). assert_eq!(tx.num_frames(), 1, "data segment sent"); @@ -336,6 +367,7 @@ fn active_close_fin_wait1_to_fin_wait2() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -358,6 +390,7 @@ fn active_close_fin_wait1_to_fin_wait2() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -379,6 +412,7 @@ fn active_close_fin_wait1_to_fin_wait2() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -389,7 +423,15 @@ fn active_close_fin_wait1_to_fin_wait2() { // Active close: set pending_fin, poll_send sends FIN → FinWait1. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::FinWait1); let fin_seq = handler.first_connection().fin_seq.unwrap(); @@ -410,6 +452,7 @@ fn active_close_fin_wait1_to_fin_wait2() { handler.process_ipv4( Frame::new(3, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -426,6 +469,7 @@ fn fin_wait2_receives_fin_to_time_wait() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -448,6 +492,7 @@ fn fin_wait2_receives_fin_to_time_wait() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -469,6 +514,7 @@ fn fin_wait2_receives_fin_to_time_wait() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -479,7 +525,15 @@ fn fin_wait2_receives_fin_to_time_wait() { // Active close → FinWait1 → FinWait2. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let fin_seq = handler.first_connection().fin_seq.unwrap(); let ack = build_tcp_frame( @@ -497,6 +551,7 @@ fn fin_wait2_receives_fin_to_time_wait() { handler.process_ipv4( Frame::new(3, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -521,6 +576,7 @@ fn fin_wait2_receives_fin_to_time_wait() { handler.process_ipv4( Frame::new(4, leak(fin), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -528,7 +584,8 @@ fn fin_wait2_receives_fin_to_time_wait() { ); assert_eq!(handler.first_connection().state, TcpState::TimeWait); - assert!(handler.first_connection().time_wait_deadline.is_some()); + let key = handler.first_connection_key(); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::TimeWait)); assert_eq!(tx.num_frames(), 1, "ACK for remote FIN"); } @@ -539,6 +596,7 @@ fn simultaneous_close_closing_to_time_wait() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -561,6 +619,7 @@ fn simultaneous_close_closing_to_time_wait() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -582,6 +641,7 @@ fn simultaneous_close_closing_to_time_wait() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -592,7 +652,15 @@ fn simultaneous_close_closing_to_time_wait() { // Active close → FinWait1. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::FinWait1); @@ -612,6 +680,7 @@ fn simultaneous_close_closing_to_time_wait() { handler.process_ipv4( Frame::new(3, leak(fin), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -637,6 +706,7 @@ fn simultaneous_close_closing_to_time_wait() { handler.process_ipv4( Frame::new(4, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -652,6 +722,7 @@ fn passive_close_last_ack_removes_connection() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -674,6 +745,7 @@ fn passive_close_last_ack_removes_connection() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -695,6 +767,7 @@ fn passive_close_last_ack_removes_connection() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -718,6 +791,7 @@ fn passive_close_last_ack_removes_connection() { handler.process_ipv4( Frame::new(2, leak(fin), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -729,7 +803,15 @@ fn passive_close_last_ack_removes_connection() { // We close → pending_fin, poll_send sends FIN → LastAck. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::LastAck); let fin_seq = handler.first_connection().fin_seq.unwrap(); @@ -750,6 +832,7 @@ fn passive_close_last_ack_removes_connection() { handler.process_ipv4( Frame::new(4, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -769,6 +852,7 @@ fn time_wait_ignores_rst() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -791,6 +875,7 @@ fn time_wait_ignores_rst() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -812,6 +897,7 @@ fn time_wait_ignores_rst() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -822,7 +908,15 @@ fn time_wait_ignores_rst() { // Full active close → TimeWait. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let fin_seq = handler.first_connection().fin_seq.unwrap(); let ack = build_tcp_frame( @@ -840,6 +934,7 @@ fn time_wait_ignores_rst() { handler.process_ipv4( Frame::new(3, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -860,6 +955,7 @@ fn time_wait_ignores_rst() { handler.process_ipv4( Frame::new(4, leak(fin), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -874,6 +970,7 @@ fn time_wait_ignores_rst() { handler.process_ipv4( Frame::new(5, leak(rst), rst_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -894,6 +991,7 @@ fn time_wait_evicted_after_deadline() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -916,6 +1014,7 @@ fn time_wait_evicted_after_deadline() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -937,6 +1036,7 @@ fn time_wait_evicted_after_deadline() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -946,11 +1046,15 @@ fn time_wait_evicted_after_deadline() { // Force into TimeWait state with expired deadline. handler.first_connection_mut().state = TcpState::TimeWait; - handler.first_connection_mut().time_wait_deadline = Some(coarsetime::Instant::now()); + let key = handler.first_connection_key(); + let handle = wheel.arm( + super::super::timer_kinds::tcp_timer_id(key, TcpTimerKind::TimeWait), + 0, + ); + handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); // Evict with a time in the future. - let future = coarsetime::Instant::now() + coarsetime::Duration::from_secs(120); - handler.evict_stale(future, &mut rx); + evict_stale(&mut handler, &mut wheel); assert_eq!(handler.connections.len(), 0, "TIME-WAIT connection evicted"); } @@ -961,6 +1065,7 @@ fn full_active_close_lifecycle() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -985,6 +1090,7 @@ fn full_active_close_lifecycle() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1006,6 +1112,7 @@ fn full_active_close_lifecycle() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1031,6 +1138,7 @@ fn full_active_close_lifecycle() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1040,7 +1148,15 @@ fn full_active_close_lifecycle() { // 3. Active close. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(handler.first_connection().state, TcpState::FinWait1); while tx.pop().is_some() {} @@ -1061,6 +1177,7 @@ fn full_active_close_lifecycle() { handler.process_ipv4( Frame::new(3, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1084,6 +1201,7 @@ fn full_active_close_lifecycle() { handler.process_ipv4( Frame::new(4, leak(fin), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1094,7 +1212,7 @@ fn full_active_close_lifecycle() { // 6. TIME-WAIT expires -> connection removed. let future = coarsetime::Instant::now() + coarsetime::Duration::from_secs(120); - handler.evict_stale(future, &mut rx); + evict_stale(&mut handler, &mut wheel); assert_eq!( handler.connections.len(), 0, @@ -1134,6 +1252,7 @@ fn full_passive_close_lifecycle() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -1156,6 +1275,7 @@ fn full_passive_close_lifecycle() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1177,6 +1297,7 @@ fn full_passive_close_lifecycle() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1200,6 +1321,7 @@ fn full_passive_close_lifecycle() { handler.process_ipv4( Frame::new(2, leak(fin), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1211,7 +1333,15 @@ fn full_passive_close_lifecycle() { // 3. We close -> LastAck. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!(handler.first_connection().state, TcpState::LastAck); while tx.pop().is_some() {} let fin_seq = handler.first_connection().fin_seq.unwrap(); @@ -1232,6 +1362,7 @@ fn full_passive_close_lifecycle() { handler.process_ipv4( Frame::new(3, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1251,6 +1382,7 @@ fn shutdown_sets_pending_fin() { let mut free = BasicFrameBuffer::new(16); let mut rx = BasicFrameBuffer::new(16); let mut tx = BasicFrameBuffer::new(16); + let mut wheel = new_wheel(); for i in 0..8 { free.push(alloc_free_frame(100 + i)); @@ -1273,6 +1405,7 @@ fn shutdown_sets_pending_fin() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1294,6 +1427,7 @@ fn shutdown_sets_pending_fin() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1308,7 +1442,7 @@ fn shutdown_sets_pending_fin() { ); // Call initiate_close (the handler method that shutdown() delegates to). - handler.initiate_close(handler.first_connection_key()); + handler.initiate_close(handler.first_connection_key(), &mut wheel); // Verify pending_fin is now true. assert!( @@ -1331,6 +1465,7 @@ fn half_close_writes_blocked_reads_continue() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -1353,6 +1488,7 @@ fn half_close_writes_blocked_reads_continue() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1374,6 +1510,7 @@ fn half_close_writes_blocked_reads_continue() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1383,7 +1520,7 @@ fn half_close_writes_blocked_reads_continue() { assert_eq!(handler.first_connection().state, TcpState::Established); // Initiate half-close (shutdown write side). - handler.initiate_close(handler.first_connection_key()); + handler.initiate_close(handler.first_connection_key(), &mut wheel); // Verify pending_fin is set but state is still Established (FIN not sent yet). assert!( @@ -1423,6 +1560,7 @@ fn close_wait_processes_ack_for_sent_data() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); @@ -1445,6 +1583,7 @@ fn close_wait_processes_ack_for_sent_data() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1466,6 +1605,7 @@ fn close_wait_processes_ack_for_sent_data() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1480,7 +1620,15 @@ fn close_wait_processes_ack_for_sent_data() { // poll_send to transmit data (advances snd_nxt). let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let snd_nxt_after_send = handler.first_connection().snd_nxt; @@ -1512,6 +1660,7 @@ fn close_wait_processes_ack_for_sent_data() { handler.process_ipv4( Frame::new(2, leak(fin_data), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1536,6 +1685,7 @@ fn close_wait_processes_ack_for_sent_data() { handler.process_ipv4( Frame::new(3, leak(data_ack), data_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1569,12 +1719,14 @@ fn fin_retransmitted_in_fin_wait1() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(200 + i)); } // Establish connection via passive open. - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let id = ConnectionId { local_addr: IpAddress::V4(LOCAL_IP), @@ -1585,17 +1737,32 @@ fn fin_retransmitted_in_fin_wait1() { // Manually transition TCB to FinWait1 with an expired retransmit deadline. let now = coarsetime::Instant::now(); + let key = handler.connection_key(&id).unwrap(); { let tcb = handler.get_connection_mut(&id).unwrap(); let fin_seq = tcb.snd_nxt; tcb.state = TcpState::FinWait1; tcb.fin_seq = Some(fin_seq); - tcb.retransmit_deadline = Some(coarsetime::Instant::recent()); // already expired tcb.rto_backoff = 0; } + // Arm retransmit timer at tick 0 (already expired). + let handle = wheel.arm( + super::super::timer_kinds::tcp_timer_id(key, TcpTimerKind::Retransmit), + 0, + ); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); // poll_timers should retransmit the FIN-ACK. - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // (1) A FIN-ACK segment must be emitted. assert!( @@ -1615,12 +1782,12 @@ fn fin_retransmitted_in_fin_wait1() { "retransmitted segment must have FIN|ACK flags" ); - // (2) retransmit_deadline must be rescheduled with backoff. - let tcb = handler.get_connection(&id).unwrap(); + // (2) retransmit timer must be re-armed with backoff. assert!( - tcb.retransmit_deadline.is_some(), - "retransmit_deadline must be rescheduled" + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), + "retransmit timer must be rescheduled" ); + let tcb = handler.get_connection(&id).unwrap(); assert_eq!(tcb.rto_backoff, 1, "rto_backoff must be incremented"); } @@ -1631,13 +1798,15 @@ fn out_of_order_fin_does_not_transition_to_close_wait() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } // Complete handshake: remote ISS=1000, so after SYN rcv_nxt=1001. - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); assert_eq!(handler.first_connection().state, TcpState::Established); assert_eq!(handler.first_connection().rcv_nxt, 1001); @@ -1660,6 +1829,7 @@ fn out_of_order_fin_does_not_transition_to_close_wait() { handler.process_ipv4( Frame::new(2, leak(fin_data), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1698,6 +1868,7 @@ fn out_of_order_fin_does_not_transition_to_close_wait() { handler.process_ipv4( Frame::new(3, leak(fill_data), fill_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1728,6 +1899,7 @@ fn out_of_order_fin_does_not_transition_to_close_wait() { handler.process_ipv4( Frame::new(4, leak(fin_retry), fin_retry_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1752,15 +1924,16 @@ fn out_of_order_fin_does_not_transition_to_close_wait() { // --------------------------------------------------------------------------- fn setup_fin_wait1( handler: &mut super::super::handler::TcpHandler, + wheel: &mut crate::net::timer_wheel::TimerWheel, nh: &crate::net::NeighborHandler, free: &mut BasicFrameBuffer<'static>, rx: &mut BasicFrameBuffer<'static>, tx: &mut BasicFrameBuffer<'static>, ) -> (u32, u32) { - let server_iss = establish_connection(handler, nh, free, rx, tx); + let server_iss = establish_connection(handler, wheel, nh, free, rx, tx); handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), nh, free, rx, tx); + handler.poll_send(now, 0, wheel, nh.local_mac(), nh, free, rx, tx); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::FinWait1); let fin_seq = handler.first_connection().fin_seq.unwrap(); @@ -1770,12 +1943,13 @@ fn setup_fin_wait1( // Helper: bring connection to FinWait2 and return (server_iss, fin_seq). fn setup_fin_wait2( handler: &mut super::super::handler::TcpHandler, + wheel: &mut crate::net::timer_wheel::TimerWheel, nh: &crate::net::NeighborHandler, free: &mut BasicFrameBuffer<'static>, rx: &mut BasicFrameBuffer<'static>, tx: &mut BasicFrameBuffer<'static>, ) -> (u32, u32) { - let (server_iss, fin_seq) = setup_fin_wait1(handler, nh, free, rx, tx); + let (server_iss, fin_seq) = setup_fin_wait1(handler, wheel, nh, free, rx, tx); // Remote ACKs our FIN. let ack = build_tcp_frame( REMOTE_IP, @@ -1792,6 +1966,8 @@ fn setup_fin_wait2( handler.process_ipv4( Frame::new(10, leak(ack), ack_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -1805,12 +1981,13 @@ fn setup_fin_wait2( // Helper: bring connection to TimeWait and return (server_iss, fin_seq). fn setup_time_wait( handler: &mut super::super::handler::TcpHandler, + wheel: &mut crate::net::timer_wheel::TimerWheel, nh: &crate::net::NeighborHandler, free: &mut BasicFrameBuffer<'static>, rx: &mut BasicFrameBuffer<'static>, tx: &mut BasicFrameBuffer<'static>, ) -> (u32, u32) { - let (server_iss, fin_seq) = setup_fin_wait2(handler, nh, free, rx, tx); + let (server_iss, fin_seq) = setup_fin_wait2(handler, wheel, nh, free, rx, tx); // Remote sends FIN. let fin = build_tcp_frame( REMOTE_IP, @@ -1827,6 +2004,8 @@ fn setup_time_wait( handler.process_ipv4( Frame::new(11, leak(fin), fin_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -1840,12 +2019,13 @@ fn setup_time_wait( // Helper: bring connection to CloseWait and return server_iss. fn setup_close_wait( handler: &mut super::super::handler::TcpHandler, + wheel: &mut crate::net::timer_wheel::TimerWheel, nh: &crate::net::NeighborHandler, free: &mut BasicFrameBuffer<'static>, rx: &mut BasicFrameBuffer<'static>, tx: &mut BasicFrameBuffer<'static>, ) -> u32 { - let server_iss = establish_connection(handler, nh, free, rx, tx); + let server_iss = establish_connection(handler, wheel, nh, free, rx, tx); // Remote sends FIN. let fin = build_tcp_frame( REMOTE_IP, @@ -1862,6 +2042,8 @@ fn setup_close_wait( handler.process_ipv4( Frame::new(10, leak(fin), fin_len, false), coarsetime::Instant::now(), + 0, + wheel, nh, free, rx, @@ -1891,11 +2073,13 @@ fn alloc_buffers() -> ( // --------------------------------------------------------------------------- #[test] fn fin_wait1_fin_and_ack_simultaneously_transitions_to_time_wait() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Remote sends FIN+ACK that also ACKs our FIN. let fin_ack = build_tcp_frame( @@ -1913,6 +2097,7 @@ fn fin_wait1_fin_and_ack_simultaneously_transitions_to_time_wait() { handler.process_ipv4( Frame::new(20, leak(fin_ack), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1920,8 +2105,9 @@ fn fin_wait1_fin_and_ack_simultaneously_transitions_to_time_wait() { ); assert_eq!(handler.first_connection().state, TcpState::TimeWait); - assert!(handler.first_connection().time_wait_deadline.is_some()); - assert!(handler.first_connection().retransmit_deadline.is_none()); + let key = handler.first_connection_key(); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::TimeWait)); + assert!(!handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit)); // Should have sent an ACK for the FIN. assert!(tx.num_frames() >= 1, "ACK for remote FIN expected"); } @@ -1931,11 +2117,13 @@ fn fin_wait1_fin_and_ack_simultaneously_transitions_to_time_wait() { // --------------------------------------------------------------------------- #[test] fn fin_wait1_processes_incoming_data() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (server_iss, _fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (server_iss, _fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt_before = handler.first_connection().rcv_nxt; @@ -1956,6 +2144,7 @@ fn fin_wait1_processes_incoming_data() { handler.process_ipv4( Frame::new(20, leak(data), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -1979,11 +2168,13 @@ fn fin_wait1_processes_incoming_data() { // --------------------------------------------------------------------------- #[test] fn fin_wait2_processes_data_before_fin() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, fin_seq) = setup_fin_wait2(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, fin_seq) = + setup_fin_wait2(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt_before = handler.first_connection().rcv_nxt; @@ -2004,6 +2195,7 @@ fn fin_wait2_processes_data_before_fin() { handler.process_ipv4( Frame::new(20, leak(data), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2024,13 +2216,16 @@ fn fin_wait2_processes_data_before_fin() { // --------------------------------------------------------------------------- #[test] fn time_wait_duplicate_fin_reacks_and_restarts_timer() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, fin_seq) = setup_time_wait(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, fin_seq) = + setup_time_wait(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); - let old_deadline = handler.first_connection().time_wait_deadline.unwrap(); + let key = handler.first_connection_key(); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::TimeWait)); // Remote retransmits FIN. let dup_fin = build_tcp_frame( @@ -2048,6 +2243,7 @@ fn time_wait_duplicate_fin_reacks_and_restarts_timer() { handler.process_ipv4( Frame::new(30, leak(dup_fin), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2056,11 +2252,10 @@ fn time_wait_duplicate_fin_reacks_and_restarts_timer() { // Connection still in TimeWait. assert_eq!(handler.first_connection().state, TcpState::TimeWait); - // Timer restarted (new deadline >= old deadline). - let new_deadline = handler.first_connection().time_wait_deadline.unwrap(); + // Timer should still be armed (restarted). assert!( - new_deadline >= old_deadline, - "time_wait_deadline should be restarted" + handler.timer_handles[key].is_armed(TcpTimerKind::TimeWait), + "time_wait timer should be restarted" ); // ACK should be sent. assert!(tx.num_frames() >= 1, "re-ACK for duplicate FIN expected"); @@ -2071,11 +2266,12 @@ fn time_wait_duplicate_fin_reacks_and_restarts_timer() { // --------------------------------------------------------------------------- #[test] fn close_wait_window_update_with_wl_guard() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let _server_iss = setup_close_wait(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = setup_close_wait(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Set up known snd_wl1/snd_wl2 values. let snd_una = handler.first_connection().snd_una; @@ -2090,7 +2286,15 @@ fn close_wait_window_update_with_wl_guard() { .write(b"test data"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let snd_nxt = handler.first_connection().snd_nxt; @@ -2109,6 +2313,7 @@ fn close_wait_window_update_with_wl_guard() { handler.process_ipv4( Frame::new(20, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2130,11 +2335,13 @@ fn close_wait_window_update_with_wl_guard() { // --------------------------------------------------------------------------- #[test] fn teardown_rst_exact_match_removes_connection() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, _fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, _fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt = handler.first_connection().rcv_nxt; @@ -2154,6 +2361,7 @@ fn teardown_rst_exact_match_removes_connection() { handler.process_ipv4( Frame::new(20, leak(rst), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2168,11 +2376,13 @@ fn teardown_rst_exact_match_removes_connection() { // --------------------------------------------------------------------------- #[test] fn teardown_rst_in_window_sends_challenge_ack() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, _fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, _fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt = handler.first_connection().rcv_nxt; @@ -2192,6 +2402,7 @@ fn teardown_rst_in_window_sends_challenge_ack() { handler.process_ipv4( Frame::new(20, leak(rst), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2216,11 +2427,13 @@ fn teardown_rst_in_window_sends_challenge_ack() { // --------------------------------------------------------------------------- #[test] fn teardown_syn_sends_challenge_ack() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, _fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, _fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt = handler.first_connection().rcv_nxt; @@ -2240,6 +2453,7 @@ fn teardown_syn_sends_challenge_ack() { handler.process_ipv4( Frame::new(20, leak(syn), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2261,11 +2475,13 @@ fn teardown_syn_sends_challenge_ack() { // --------------------------------------------------------------------------- #[test] fn teardown_no_ack_bit_drops_segment() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, _fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, _fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt = handler.first_connection().rcv_nxt; @@ -2277,6 +2493,7 @@ fn teardown_no_ack_bit_drops_segment() { handler.process_ipv4( Frame::new(20, leak(bare), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2294,11 +2511,13 @@ fn teardown_no_ack_bit_drops_segment() { // --------------------------------------------------------------------------- #[test] fn teardown_out_of_window_sends_ack() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, _fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, _fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt = handler.first_connection().rcv_nxt; @@ -2318,6 +2537,7 @@ fn teardown_out_of_window_sends_ack() { handler.process_ipv4( Frame::new(20, leak(oow), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2339,11 +2559,13 @@ fn teardown_out_of_window_sends_ack() { // --------------------------------------------------------------------------- #[test] fn teardown_out_of_window_rst_silently_dropped() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, _fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, _fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt = handler.first_connection().rcv_nxt; @@ -2363,6 +2585,7 @@ fn teardown_out_of_window_rst_silently_dropped() { handler.process_ipv4( Frame::new(20, leak(rst), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2384,11 +2607,13 @@ fn teardown_out_of_window_rst_silently_dropped() { // --------------------------------------------------------------------------- #[test] fn fin_wait1_ack_advances_snd_una() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Write data, send it, ACK it, then close — so FIN and data are separate. handler @@ -2397,7 +2622,15 @@ fn fin_wait1_ack_advances_snd_una() { .write(b"data before close"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let snd_nxt_after_data = handler.first_connection().snd_nxt; @@ -2418,6 +2651,7 @@ fn fin_wait1_ack_advances_snd_una() { handler.process_ipv4( Frame::new(15, leak(data_ack), data_ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2427,7 +2661,15 @@ fn fin_wait1_ack_advances_snd_una() { // Now close: set pending_fin and poll_send to send FIN. handler.first_connection_mut().pending_fin = true; - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::FinWait1); @@ -2449,6 +2691,7 @@ fn fin_wait1_ack_advances_snd_una() { handler.process_ipv4( Frame::new(20, leak(fin_ack), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2474,11 +2717,13 @@ fn fin_wait1_ack_advances_snd_una() { // --------------------------------------------------------------------------- #[test] fn closing_ack_of_fin_transitions_to_time_wait() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (server_iss, fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (server_iss, fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Remote sends FIN without ACKing ours → Closing. let fin = build_tcp_frame( @@ -2496,6 +2741,7 @@ fn closing_ack_of_fin_transitions_to_time_wait() { handler.process_ipv4( Frame::new(20, leak(fin), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2520,6 +2766,7 @@ fn closing_ack_of_fin_transitions_to_time_wait() { handler.process_ipv4( Frame::new(21, leak(ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2527,8 +2774,9 @@ fn closing_ack_of_fin_transitions_to_time_wait() { ); assert_eq!(handler.first_connection().state, TcpState::TimeWait); - assert!(handler.first_connection().time_wait_deadline.is_some()); - assert!(handler.first_connection().retransmit_deadline.is_none()); + let key = handler.first_connection_key(); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::TimeWait)); + assert!(!handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit)); assert_eq!( handler.first_connection().snd_una, fin_seq.wrapping_add(1), @@ -2541,11 +2789,13 @@ fn closing_ack_of_fin_transitions_to_time_wait() { // --------------------------------------------------------------------------- #[test] fn fin_wait2_data_and_fin_transitions_to_time_wait() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (_server_iss, fin_seq) = setup_fin_wait2(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (_server_iss, fin_seq) = + setup_fin_wait2(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); let rcv_nxt_before = handler.first_connection().rcv_nxt; @@ -2566,6 +2816,7 @@ fn fin_wait2_data_and_fin_transitions_to_time_wait() { handler.process_ipv4( Frame::new(20, leak(data_fin), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2579,7 +2830,8 @@ fn fin_wait2_data_and_fin_transitions_to_time_wait() { rcv_nxt_before.wrapping_add(9 + 1), "rcv_nxt should advance by data + FIN" ); - assert!(handler.first_connection().time_wait_deadline.is_some()); + let key = handler.first_connection_key(); + assert!(handler.timer_handles[key].is_armed(TcpTimerKind::TimeWait)); assert!(tx.num_frames() >= 1, "ACK expected for data+FIN"); } @@ -2588,16 +2840,25 @@ fn fin_wait2_data_and_fin_transitions_to_time_wait() { // --------------------------------------------------------------------------- #[test] fn last_ack_partial_ack_does_not_remove_connection() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let _server_iss = setup_close_wait(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = setup_close_wait(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Close → LastAck. handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::LastAck); let fin_seq = handler.first_connection().fin_seq.unwrap(); @@ -2618,6 +2879,7 @@ fn last_ack_partial_ack_does_not_remove_connection() { handler.process_ipv4( Frame::new(20, leak(ack), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2634,11 +2896,13 @@ fn last_ack_partial_ack_does_not_remove_connection() { // --------------------------------------------------------------------------- #[test] fn closing_partial_ack_stays_in_closing() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let (mut free, mut rx, mut tx) = alloc_buffers(); - let (server_iss, fin_seq) = setup_fin_wait1(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let (server_iss, fin_seq) = + setup_fin_wait1(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Remote sends FIN without ACKing ours → Closing. let fin = build_tcp_frame( @@ -2656,6 +2920,7 @@ fn closing_partial_ack_stays_in_closing() { handler.process_ipv4( Frame::new(20, leak(fin), len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -2680,6 +2945,7 @@ fn closing_partial_ack_stays_in_closing() { handler.process_ipv4( Frame::new(21, leak(partial_ack), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/tests/timers.rs b/src/net/handler/tcp/tests/timers.rs index 7495828..3b94bf7 100644 --- a/src/net/handler/tcp/tests/timers.rs +++ b/src/net/handler/tcp/tests/timers.rs @@ -1,6 +1,7 @@ use super::*; use super::super::handler::INITIAL_RTO_MS; +use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; // --------------------------------------------------------------------------- // RTO exponential backoff — verify successive retransmissions double the RTO @@ -13,26 +14,46 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Put data in send buffer and send it. handler.first_connection_mut().send_buffer.write(b"DATA"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} // First RTO fire: backoff 0 -> 1. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + // Arm retransmit timer at tick 0 (in the past) to force fire. + let key = handler.first_connection_key(); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; let rto_before = handler.first_connection().rto; - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let tcb = handler.first_connection(); @@ -40,18 +61,25 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { tcb.rto_backoff, 1, "rto_backoff should be 1 after first RTO" ); - // The new deadline should be now + rto << 1. - let expected_deadline = now + coarsetime::Duration::from_millis(rto_before << 1); - assert_eq!( - tcb.retransmit_deadline, - Some(expected_deadline), - "deadline should use doubled RTO" + // The retransmit timer should be re-armed after RTO fire. + assert!( + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), + "retransmit timer should be re-armed after RTO" ); // Second RTO fire: backoff 1 -> 2. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let tcb = handler.first_connection(); @@ -59,11 +87,9 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { tcb.rto_backoff, 2, "rto_backoff should be 2 after second RTO" ); - let expected_deadline = now + coarsetime::Duration::from_millis(rto_before << 2); - assert_eq!( - tcb.retransmit_deadline, - Some(expected_deadline), - "deadline should use quadrupled RTO" + assert!( + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), + "retransmit timer should be re-armed after second RTO" ); } @@ -78,17 +104,27 @@ fn r2_threshold_aborts_connection() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Put data in send buffer and send it. handler.first_connection_mut().send_buffer.write(b"DATA"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let event_queue = handler.first_connection().event_queue.clone(); @@ -96,11 +132,21 @@ fn r2_threshold_aborts_connection() { // Set rto_backoff high enough that total_elapsed_ms >= SYN_R2_THRESHOLD_MS. // Formula: total = sum(INITIAL_RTO_MS << i) for i=0..=backoff // With INITIAL_RTO_MS=1000, backoff=8: total = 1000*(1+2+4+8+16+32+64+128+256) = 511_000 > 180_000 + let key = handler.first_connection_key(); handler.first_connection_mut().rto_backoff = 8; - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); - - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); + + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Connection should be removed. assert!( @@ -128,16 +174,27 @@ fn fin_retransmit_in_fin_wait1() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Initiate close to send FIN. - handler.initiate_close(handler.first_connection_key()); + let key = handler.first_connection_key(); + handler.initiate_close(key, &mut wheel); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!( @@ -151,11 +208,20 @@ fn fin_retransmit_in_fin_wait1() { ); // Set retransmit deadline in the past to trigger FIN retransmit. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // FIN-ACK should be retransmitted. assert!( @@ -166,7 +232,7 @@ fn fin_retransmit_in_fin_wait1() { let tcb = handler.first_connection(); assert_eq!(tcb.rto_backoff, 1, "rto_backoff should be incremented"); assert!( - tcb.retransmit_deadline.is_some(), + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), "retransmit deadline should be re-armed" ); } @@ -182,11 +248,13 @@ fn fin_retransmit_in_last_ack() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Receive FIN from remote to move to CloseWait. let fin = build_tcp_frame( @@ -204,6 +272,7 @@ fn fin_retransmit_in_last_ack() { handler.process_ipv4( Frame::new(10, leak(fin), fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -213,19 +282,37 @@ fn fin_retransmit_in_last_ack() { assert_eq!(handler.first_connection().state, TcpState::CloseWait); // Now close our side to move to LastAck. - handler.initiate_close(handler.first_connection_key()); + let key = handler.first_connection_key(); + handler.initiate_close(key, &mut wheel); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::LastAck); assert!(handler.first_connection().fin_seq.is_some()); // Set retransmit deadline in the past. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!( tx.num_frames() >= 1, @@ -246,6 +333,7 @@ fn syn_retransmit_exponential_backoff() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } @@ -270,6 +358,7 @@ fn syn_retransmit_exponential_backoff() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -278,13 +367,23 @@ fn syn_retransmit_exponential_backoff() { assert_eq!(handler.first_connection().state, TcpState::SynSent); let now = coarsetime::Instant::now(); + let key = handler.first_connection_key(); // First retransmit: backoff 0 -> 1. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!(tx.num_frames() >= 1, "SYN should be retransmitted"); while tx.pop().is_some() {} @@ -293,18 +392,25 @@ fn syn_retransmit_exponential_backoff() { tcb.rto_backoff, 1, "backoff should be 1 after first retransmit" ); - // Deadline should be now + INITIAL_RTO_MS << 1. - let expected = now + coarsetime::Duration::from_millis(INITIAL_RTO_MS << 1); - assert_eq!( - tcb.retransmit_deadline, - Some(expected), - "SYN deadline should use doubled INITIAL_RTO_MS" + // Retransmit timer should be re-armed. + assert!( + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), + "SYN retransmit timer should be re-armed" ); // Second retransmit: backoff 1 -> 2. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} let tcb = handler.first_connection(); @@ -312,11 +418,9 @@ fn syn_retransmit_exponential_backoff() { tcb.rto_backoff, 2, "backoff should be 2 after second retransmit" ); - let expected = now + coarsetime::Duration::from_millis(INITIAL_RTO_MS << 2); - assert_eq!( - tcb.retransmit_deadline, - Some(expected), - "SYN deadline should use quadrupled INITIAL_RTO_MS" + assert!( + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), + "SYN retransmit timer should be re-armed after second retransmit" ); } @@ -331,6 +435,7 @@ fn syn_r2_threshold_removes_connection() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } @@ -354,6 +459,7 @@ fn syn_r2_threshold_removes_connection() { src_mac, dst_mac, coarsetime::Instant::now(), + &mut wheel, &mut free, &mut tx, ) @@ -362,13 +468,23 @@ fn syn_r2_threshold_removes_connection() { assert_eq!(handler.first_connection().state, TcpState::SynSent); let now = coarsetime::Instant::now(); + let key = handler.first_connection_key(); // Set backoff high enough for R2 threshold. handler.first_connection_mut().rto_backoff = 8; - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); - - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); + + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!( handler.connections.is_empty(), @@ -392,6 +508,7 @@ fn syn_ack_retransmit_in_syn_received() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } @@ -413,6 +530,7 @@ fn syn_ack_retransmit_in_syn_received() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -422,13 +540,23 @@ fn syn_ack_retransmit_in_syn_received() { assert_eq!(handler.first_connection().state, TcpState::SynReceived); let now = coarsetime::Instant::now(); + let key = handler.first_connection_key(); // Trigger retransmit. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!( tx.num_frames() >= 1, @@ -436,11 +564,9 @@ fn syn_ack_retransmit_in_syn_received() { ); let tcb = handler.first_connection(); assert_eq!(tcb.rto_backoff, 1, "rto_backoff should be incremented"); - let expected = now + coarsetime::Duration::from_millis(INITIAL_RTO_MS << 1); - assert_eq!( - tcb.retransmit_deadline, - Some(expected), - "SYN-ACK deadline should be backed off" + assert!( + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), + "SYN-ACK retransmit timer should be re-armed" ); } @@ -455,12 +581,14 @@ fn delayed_ack_timer_includes_ece_flag_when_ce_received() { let mut free = BasicFrameBuffer::new(32); let mut rx = BasicFrameBuffer::new(32); let mut tx = BasicFrameBuffer::new(32); + let mut wheel = new_wheel(); for i in 0..16 { free.push(alloc_free_frame(100 + i)); } let now = coarsetime::Instant::now(); - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Set delayed ACK state and ECN CE received flag. let id = ConnectionId { @@ -469,17 +597,29 @@ fn delayed_ack_timer_includes_ece_flag_when_ce_received() { remote_addr: IpAddress::V4(REMOTE_IP), remote_port: 12345, }; + let key = handler.first_connection_key(); { let tcb = handler.get_connection_mut(&id).unwrap(); tcb.ack_pending = true; tcb.ack_delay_count = 1; - tcb.delayed_ack_deadline = Some(now + coarsetime::Duration::from_millis(40)); tcb.ecn_ce_received = true; } + // Arm delayed ACK timer to fire. + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::DelayedAck), 0); + handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); // Fire after deadline. let later = now + coarsetime::Duration::from_millis(50); - handler.poll_timers(later, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + later, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // ACK should have been sent. assert_eq!(tx.num_frames(), 1, "ACK should be flushed after deadline"); @@ -499,20 +639,22 @@ fn evict_stale_removes_time_wait_connection() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); - let now = coarsetime::Instant::now(); + let key = handler.first_connection_key(); // Manually set connection to TimeWait with a deadline in the past. handler.first_connection_mut().state = TcpState::TimeWait; - handler.first_connection_mut().time_wait_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::TimeWait), 0); + handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); - handler.evict_stale(now, &mut rx); + evict_stale(&mut handler, &mut wheel); assert!( handler.connections.is_empty(), @@ -527,20 +669,25 @@ fn evict_stale_keeps_time_wait_before_deadline() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); - let now = coarsetime::Instant::now(); + let key = handler.first_connection_key(); - // Set TimeWait with deadline in the future. + // Set TimeWait with deadline in the far future (won't be reached by evict_stale). handler.first_connection_mut().state = TcpState::TimeWait; - handler.first_connection_mut().time_wait_deadline = - Some(now + coarsetime::Duration::from_millis(60_000)); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::TimeWait), + u64::MAX / 2 + 1000, + ); + handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); - handler.evict_stale(now, &mut rx); + evict_stale(&mut handler, &mut wheel); assert_eq!( handler.connections.len(), @@ -560,25 +707,49 @@ fn rto_retransmit_does_not_fire_before_deadline() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); handler.first_connection_mut().send_buffer.write(b"DATA"); handler.first_connection_mut().snd_wnd = 65535; let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} - // Set deadline in the future. - handler.first_connection_mut().retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(5000)); + // The retransmit timer was armed in the future by poll_send. + // Don't fire any timers — just verify no retransmission occurs without timer expiry. handler.first_connection_mut().rto_backoff = 0; handler.first_connection_mut().ack_pending = false; - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + // Advance wheel by 0 ticks — nothing fires. + let fired = wheel.advance(0); + for id in fired { + let (k, kind) = super::super::timer_kinds::unpack_tcp_timer_id(id); + handler.handle_timer( + k, + kind, + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); + } assert_eq!(tx.num_frames(), 0, "no retransmission before deadline"); assert_eq!( @@ -599,16 +770,26 @@ fn fin_retransmit_in_closing_state() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); // Initiate close to enter FinWait1. - handler.initiate_close(handler.first_connection_key()); + let key = handler.first_connection_key(); + handler.initiate_close(key, &mut wheel); let now = coarsetime::Instant::now(); - handler.poll_send(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + handler.poll_send( + now, + &mut wheel, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::FinWait1); @@ -630,6 +811,7 @@ fn fin_retransmit_in_closing_state() { handler.process_ipv4( Frame::new(20, leak(remote_fin), remote_fin_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -639,11 +821,20 @@ fn fin_retransmit_in_closing_state() { assert_eq!(handler.first_connection().state, TcpState::Closing); // Trigger FIN retransmit. - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!( tx.num_frames() >= 1, @@ -654,7 +845,7 @@ fn fin_retransmit_in_closing_state() { } // --------------------------------------------------------------------------- -// No retransmit when retransmit_deadline is None +// No retransmit when retransmit timer is not armed // --------------------------------------------------------------------------- #[test] @@ -664,18 +855,33 @@ fn no_retransmit_when_deadline_is_none() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); - // Ensure no retransmit deadline and no ack_pending. - handler.first_connection_mut().retransmit_deadline = None; + // Ensure no retransmit timer armed and no ack_pending. + let key = handler.first_connection_key(); + if let Some(h) = handler.timer_handles[key].get(TcpTimerKind::Retransmit) { + wheel.cancel(h); + handler.timer_handles[key].clear(TcpTimerKind::Retransmit); + } handler.first_connection_mut().ack_pending = false; let now = coarsetime::Instant::now(); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert_eq!( tx.num_frames(), @@ -695,25 +901,36 @@ fn rto_fires_with_empty_send_buffer_no_retransmit() { let mut free = BasicFrameBuffer::new(64); let mut rx = BasicFrameBuffer::new(64); let mut tx = BasicFrameBuffer::new(64); + let mut wheel = new_wheel(); for i in 0..32 { free.push(alloc_free_frame(100 + i)); } - let _server_iss = establish_connection(&mut handler, &nh, &mut free, &mut rx, &mut tx); + let _server_iss = + establish_connection(&mut handler, &mut wheel, &nh, &mut free, &mut rx, &mut tx); - // Set retransmit_deadline in the past with an EMPTY send buffer. - // This simulates the edge case where the RTO fires but there's nothing to retransmit - // (e.g., all data was ACKed but the deadline wasn't cleared). - let now = coarsetime::Instant::now(); - handler.first_connection_mut().retransmit_deadline = - Some(now - coarsetime::Duration::from_millis(1)); + // Set retransmit timer to fire with an EMPTY send buffer. + // This simulates the edge case where the RTO fires but there's nothing to retransmit. + let key = handler.first_connection_key(); + let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; handler.first_connection_mut().ack_pending = false; // send_buffer is empty (no data written). assert_eq!(handler.first_connection().send_buffer.available(), 0); - handler.poll_timers(now, nh.local_mac(), &nh, &mut free, &mut rx, &mut tx); + let now = coarsetime::Instant::now(); + poll_timers( + &mut handler, + &mut wheel, + now, + nh.local_mac(), + &nh, + &mut free, + &mut rx, + &mut tx, + ); // No data frame should be sent since send_buffer is empty. assert_eq!( @@ -729,9 +946,9 @@ fn rto_fires_with_empty_send_buffer_no_retransmit() { "rto_backoff should be incremented even with empty buffer" ); - // Retransmit deadline should be rescheduled. + // Retransmit timer should be rescheduled. assert!( - handler.first_connection().retransmit_deadline.is_some(), + handler.timer_handles[key].is_armed(TcpTimerKind::Retransmit), "retransmit deadline should be rescheduled" ); } diff --git a/src/net/handler/tcp/tests/timestamps.rs b/src/net/handler/tcp/tests/timestamps.rs index bc21ba0..9194e43 100644 --- a/src/net/handler/tcp/tests/timestamps.rs +++ b/src/net/handler/tcp/tests/timestamps.rs @@ -2,6 +2,7 @@ use super::*; #[test] fn paws_rejects_old_timestamp() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(16); @@ -30,6 +31,7 @@ fn paws_rejects_old_timestamp() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -52,6 +54,7 @@ fn paws_rejects_old_timestamp() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -84,6 +87,7 @@ fn paws_rejects_old_timestamp() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -104,6 +108,7 @@ fn paws_rejects_old_timestamp() { #[test] fn paws_drops_rst_with_old_timestamp() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(16); @@ -132,6 +137,7 @@ fn paws_drops_rst_with_old_timestamp() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -154,6 +160,7 @@ fn paws_drops_rst_with_old_timestamp() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -186,6 +193,7 @@ fn paws_drops_rst_with_old_timestamp() { handler.process_ipv4( Frame::new(2, leak(rst_data), rst_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -203,6 +211,7 @@ fn paws_drops_rst_with_old_timestamp() { #[test] fn paws_accepts_stale_ts_recent() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(16); @@ -231,6 +240,7 @@ fn paws_accepts_stale_ts_recent() { handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -253,6 +263,7 @@ fn paws_accepts_stale_ts_recent() { handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -293,6 +304,7 @@ fn paws_accepts_stale_ts_recent() { handler.process_ipv4( Frame::new(2, leak(data), data_len, false), now, + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/tests/validate.rs b/src/net/handler/tcp/tests/validate.rs index e463c12..36ce258 100644 --- a/src/net/handler/tcp/tests/validate.rs +++ b/src/net/handler/tcp/tests/validate.rs @@ -107,6 +107,7 @@ fn build_ipv6_tcp_frame( /// Frame too short to contain a TCP header should be returned to rx_return. #[test] fn frame_too_short_for_tcp_header_returned_to_rx() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(4); @@ -122,7 +123,15 @@ fn frame_too_short_for_tcp_header_returned_to_rx() { let len = data.len(); let frame = Frame::new(100, data, len, false); - handler.process_ipv4(frame, Instant::now(), &nh, &mut free, &mut rx, &mut tx); + handler.process_ipv4( + frame, + Instant::now(), + &mut wheel, + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Frame should be returned to rx_return (free pool), not consumed. assert!( @@ -138,6 +147,7 @@ fn frame_too_short_for_tcp_header_returned_to_rx() { /// Data offset < 5 (invalid) should cause the frame to be returned. #[test] fn invalid_data_offset_too_small_returned() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(4); @@ -155,7 +165,15 @@ fn invalid_data_offset_too_small_returned() { let len = data.len(); let frame = Frame::new(200, leak(data), len, false); - handler.process_ipv4(frame, Instant::now(), &nh, &mut free, &mut rx, &mut tx); + handler.process_ipv4( + frame, + Instant::now(), + &mut wheel, + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!( rx.pop().is_some(), @@ -167,6 +185,7 @@ fn invalid_data_offset_too_small_returned() { /// Data offset claims more bytes than available in the frame → returned. #[test] fn data_offset_exceeds_frame_length_returned() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(4); @@ -184,7 +203,15 @@ fn data_offset_exceeds_frame_length_returned() { let len = data.len(); let frame = Frame::new(300, leak(data), len, false); - handler.process_ipv4(frame, Instant::now(), &nh, &mut free, &mut rx, &mut tx); + handler.process_ipv4( + frame, + Instant::now(), + &mut wheel, + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!( rx.pop().is_some(), @@ -196,6 +223,7 @@ fn data_offset_exceeds_frame_length_returned() { /// When rx_offload is disabled, a bad checksum should cause the frame to be returned. #[test] fn bad_checksum_returned_when_rx_offload_disabled() { + let mut wheel = new_wheel(); // new_handler() already creates with rx_offload=false. let mut handler = new_handler(); let nh = new_neighbor_handler(); @@ -215,7 +243,15 @@ fn bad_checksum_returned_when_rx_offload_disabled() { let len = data.len(); let frame = Frame::new(400, leak(data), len, false); - handler.process_ipv4(frame, Instant::now(), &nh, &mut free, &mut rx, &mut tx); + handler.process_ipv4( + frame, + Instant::now(), + &mut wheel, + &nh, + &mut free, + &mut rx, + &mut tx, + ); assert!(rx.pop().is_some(), "bad checksum frame should be returned"); assert!(tx.pop().is_none(), "no response for bad checksum"); @@ -225,6 +261,7 @@ fn bad_checksum_returned_when_rx_offload_disabled() { /// rejected (the NIC already verified it). #[test] fn bad_checksum_accepted_when_rx_offload_enabled() { + let mut wheel = new_wheel(); let mut handler = TcpHandler::new(true, false); // rx_offload = true let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(4); @@ -244,7 +281,15 @@ fn bad_checksum_accepted_when_rx_offload_enabled() { let len = data.len(); let frame = Frame::new(500, leak(data), len, false); - handler.process_ipv4(frame, Instant::now(), &nh, &mut free, &mut rx, &mut tx); + handler.process_ipv4( + frame, + Instant::now(), + &mut wheel, + &nh, + &mut free, + &mut rx, + &mut tx, + ); // Should produce a SYN-ACK response (frame was not rejected). assert!( @@ -256,6 +301,7 @@ fn bad_checksum_accepted_when_rx_offload_enabled() { /// IPv6 path: a valid SYN over IPv6 should be processed (covers process_ipv6). #[test] fn ipv6_valid_syn_processed() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(4); @@ -282,6 +328,7 @@ fn ipv6_valid_syn_processed() { frame, tcp_offset, Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -295,6 +342,7 @@ fn ipv6_valid_syn_processed() { /// IPv6 path: frame too short for TCP header should be returned. #[test] fn ipv6_frame_too_short_returned() { + let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(4); @@ -316,6 +364,7 @@ fn ipv6_frame_too_short_returned() { frame, tcp_offset, Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -329,6 +378,7 @@ fn ipv6_frame_too_short_returned() { /// IPv6 path: bad checksum with rx_offload disabled should be returned. #[test] fn ipv6_bad_checksum_returned() { + let mut wheel = new_wheel(); let mut handler = new_handler(); // rx_offload = false let nh = new_neighbor_handler(); let mut free = BasicFrameBuffer::new(4); @@ -354,6 +404,7 @@ fn ipv6_bad_checksum_returned() { frame, tcp_off, Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/src/net/handler/tcp/timer_kinds.rs b/src/net/handler/tcp/timer_kinds.rs index ef5d975..ce67e2c 100644 --- a/src/net/handler/tcp/timer_kinds.rs +++ b/src/net/handler/tcp/timer_kinds.rs @@ -70,6 +70,43 @@ impl TcpTimerHandles { pub fn is_armed(&self, kind: TcpTimerKind) -> bool { self.handles[kind as usize].is_some() } + + /// Cancel the timer for `kind` on `wheel` (if armed), then arm a new one + /// at `deadline_ms` with the given `key`. Stores the new handle. + pub fn arm( + &mut self, + kind: TcpTimerKind, + key: usize, + deadline: coarsetime::Instant, + wheel: &mut crate::net::timer_wheel::TimerWheel, + ) { + if let Some(old) = self.get(kind) { + wheel.cancel(old); + } + let handle = wheel.arm(tcp_timer_id(key, kind), deadline); + self.set(kind, handle); + } + + /// Cancel the timer for `kind` on `wheel` (if armed), then clear the handle. + pub fn cancel_timer( + &mut self, + kind: TcpTimerKind, + wheel: &mut crate::net::timer_wheel::TimerWheel, + ) { + if let Some(old) = self.get(kind) { + wheel.cancel(old); + self.clear(kind); + } + } + + /// Cancel all armed timers on `wheel` and clear all handles. + pub fn cancel_all(&mut self, wheel: &mut crate::net::timer_wheel::TimerWheel) { + for slot in self.handles.iter_mut() { + if let Some(h) = slot.take() { + wheel.cancel(h); + } + } + } } #[cfg(test)] diff --git a/src/net/handler/tcp/timers.rs b/src/net/handler/tcp/timers.rs index 1c5964c..c481826 100644 --- a/src/net/handler/tcp/timers.rs +++ b/src/net/handler/tcp/timers.rs @@ -1,8 +1,7 @@ use coarsetime::Instant; -use smallvec::SmallVec; use crate::{ - net::{NeighborHandler, wire::tcp::flags}, + net::{NeighborHandler, timer_wheel::TimerWheel, wire::tcp::flags}, xdp::frame::FrameBuffer, }; @@ -17,456 +16,6 @@ use super::{ }; impl TcpHandler { - // --- Timer polling --- - - /// Poll retransmission timers for SYN/SYN-ACK retransmission. - pub fn poll_timers<'umem>( - &mut self, - now: Instant, - src_mac: crate::net::wire::ethernet::MacAddress, - neighbor_handler: &NeighborHandler, - free_frames: &mut impl FrameBuffer<'umem>, - rx_return: &mut impl FrameBuffer<'umem>, - tx_return: &mut impl FrameBuffer<'umem>, - ) { - // Collect IDs that need send-tracking after timer processing. - let mut to_mark: SmallVec<[usize; 4]> = SmallVec::new(); - - // Delayed ACK pass — flush pending ACKs whose deadline has expired. - for (key, tcb) in self.connections.iter_mut() { - if !tcb.ack_pending { - continue; - } - let tsval = if tcb.ts_enabled { - now.duration_since(tcb.ts_offset).as_millis() as u32 - } else { - 0 - }; - if let Some(deadline) = tcb.delayed_ack_deadline - && now >= deadline - { - let id = tcb.id; - let Some(dst_mac) = neighbor_handler.lookup_or_resolve( - now, - &id.remote_addr, - &id.local_addr, - free_frames, - rx_return, - tx_return, - ) else { - // Neighbor resolution pending — mark for poll_send to retry. - to_mark.push(key); - continue; // skip to next connection - }; - let ts = tcb.ts_option(tsval); - let ack_flags = if tcb.ecn_ce_received { - flags::ACK | flags::ECE - } else { - flags::ACK - }; - SegmentBuilder::build_ack( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - tcb.snd_nxt, - tcb.rcv_nxt, - tcb.advertised_window(), - ack_flags, - ts, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - tcb.update_advertised_edge(); - tcb.ack_pending = false; - tcb.ack_delay_count = 0; - tcb.delayed_ack_deadline = None; - } - } - - // Keep-alive probe pass — send probes for idle established connections. - let mut keep_alive_removals: SmallVec<[usize; 4]> = SmallVec::new(); - for (key, tcb) in self.connections.iter_mut() { - if tcb.state != TcpState::Established || !tcb.keep_alive_enabled { - continue; - } - - let tsval = if tcb.ts_enabled { - now.duration_since(tcb.ts_offset).as_millis() as u32 - } else { - 0 - }; - - let idle_ms = now.duration_since(tcb.last_activity).as_millis(); - - let probe_threshold = if tcb.keep_alive_probes_sent == 0 { - tcb.keep_alive_idle_ms - } else { - tcb.keep_alive_idle_ms - + tcb.keep_alive_interval_ms * tcb.keep_alive_probes_sent as u64 - }; - - if idle_ms >= probe_threshold { - if tcb.keep_alive_probes_sent >= tcb.keep_alive_count { - // Max probes exceeded — abort connection. - tcb.event_queue.push(TcpEvent::Timeout); - keep_alive_removals.push(key); - continue; - } - - // Send keep-alive probe: seq = snd_una - 1, no data, ACK. - let id = tcb.id; - let Some(dst_mac) = neighbor_handler.lookup_or_resolve( - now, - &id.remote_addr, - &id.local_addr, - free_frames, - rx_return, - tx_return, - ) else { - continue; // skip to next connection - }; - let ts = tcb.ts_option(tsval); - let ack_flags = if tcb.ecn_ce_received { - flags::ACK | flags::ECE - } else { - flags::ACK - }; - SegmentBuilder::build_ack( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - tcb.snd_una.wrapping_sub(1), - tcb.rcv_nxt, - tcb.advertised_window(), - ack_flags, - ts, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - - tcb.keep_alive_probes_sent += 1; - } - } - - // Remove connections that exceeded keep-alive probes. - for key in &keep_alive_removals { - if let Some(tcb) = self.connections.get(*key) { - Self::decrement_syn_received(&mut self.listeners, &tcb.id); - } - self.send_tracker.unmark(*key); - self.remove_connection_by_key(*key); - } - - // SACK recovery pass — RFC 6675 recovery loop. - for (key, tcb) in self.connections.iter_mut() { - if tcb.state != TcpState::Established || !tcb.recovery.in_recovery { - continue; - } - - let tsval = if tcb.ts_enabled { - now.duration_since(tcb.ts_offset).as_millis() as u32 - } else { - 0 - }; - - // Recompute pipe estimate. - tcb.recovery.set_pipe( - tcb.snd_una, - tcb.snd_nxt, - &tcb.sack_scoreboard, - tcb.eff_snd_mss, - ); - - // Send while pipe < cwnd. - let mut sent_any = false; - while tcb.recovery.pipe < tcb.cubic.cwnd { - // Try lost segment first. - if let Some(lost_seq) = tcb.recovery.next_lost_segment( - tcb.snd_una, - tcb.snd_nxt, - &tcb.sack_scoreboard, - tcb.eff_snd_mss, - ) { - let offset = lost_seq.wrapping_sub(tcb.snd_una) as usize; - let retransmit_len = tcb - .send_buffer - .available() - .saturating_sub(offset) - .min(tcb.eff_snd_mss as usize); - if retransmit_len == 0 { - break; - } - - let id = tcb.id; - let Some(dst_mac) = neighbor_handler.lookup_or_resolve( - now, - &id.remote_addr, - &id.local_addr, - free_frames, - rx_return, - tx_return, - ) else { - continue; // skip to next connection - }; - - let payload = tcb.send_buffer.peek_slices(offset, retransmit_len); - let ts = tcb.ts_option(tsval); - SegmentBuilder::build_data_from_slices( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - lost_seq, - tcb.rcv_nxt, - tcb.advertised_window(), - payload, - flags::ACK, - false, - ts, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - - tcb.recovery.pipe += tcb.eff_snd_mss as u32; - tcb.prr.on_sent(retransmit_len as u32); - sent_any = true; - } else { - // No more lost segments — could send new data, but that's - // handled by poll_send. Break here. - break; - } - } - if sent_any { - // SACK recovery sent segments — mark for poll_send to send new data. - to_mark.push(key); - } - } - - // RTO retransmit pass — timer-based. - let mut to_remove: SmallVec<[usize; 4]> = SmallVec::new(); - - for (key, tcb) in self.connections.iter_mut() { - let Some(deadline) = tcb.retransmit_deadline else { - continue; - }; - - if now < deadline { - continue; - } - - let tsval = if tcb.ts_enabled { - now.duration_since(tcb.ts_offset).as_millis() as u32 - } else { - 0 - }; - - // Check R2 threshold. - let total_elapsed_ms = { - let base_rto = INITIAL_RTO_MS; - let mut total: u64 = 0; - for i in 0..=tcb.rto_backoff { - total += base_rto << i; - } - total - }; - - if total_elapsed_ms >= SYN_R2_THRESHOLD_MS { - // Timeout — signal and mark for removal. - tcb.event_queue.push(TcpEvent::Timeout); - to_remove.push(key); - continue; - } - - // Retransmit. - let id = tcb.id; - let Some(dst_mac) = neighbor_handler.lookup_or_resolve( - now, - &id.remote_addr, - &id.local_addr, - free_frames, - rx_return, - tx_return, - ) else { - // Re-arm retransmit timer so we retry after the solicitation completes. - tcb.retransmit_deadline = Some(now + coarsetime::Duration::from_millis(tcb.rto)); - continue; - }; - - match tcb.state { - TcpState::SynSent => { - let ts_opt = if tcb.ts_enabled { - Some((tsval, 0u32)) - } else { - None - }; - SegmentBuilder::build_syn( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - tcb.iss, - DEFAULT_RCV_WND, - DEFAULT_RCV_MSS, - DEFAULT_RCV_WSCALE, - ts_opt, - tcb.sack_enabled, - tcb.ecn_enabled, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - } - TcpState::SynReceived => { - let wscale_opt = if tcb.wscale_enabled { - Some(tcb.rcv_wscale) - } else { - None - }; - let ts_opt = tcb.ts_option(tsval); - SegmentBuilder::build_syn_ack( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - tcb.iss, - tcb.rcv_nxt, - DEFAULT_RCV_WND, - tcb.rcv_mss, - wscale_opt, - ts_opt, - tcb.sack_enabled, - tcb.ecn_enabled, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - } - TcpState::Established => { - let retransmit_len = tcb.send_buffer.available().min(tcb.eff_snd_mss as usize); - if retransmit_len > 0 { - let payload = tcb.send_buffer.peek_slices(0, retransmit_len); - let ts = tcb.ts_option(tsval); - SegmentBuilder::build_data_from_slices( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - tcb.snd_una, - tcb.rcv_nxt, - tcb.advertised_window(), - payload, - flags::ACK, - false, // retransmits don't get ECT per RFC 3168 §6.1.5 - ts, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - } - // F-RTO + CUBIC RTO response. - tcb.frto.enter(tcb.snd_una); - tcb.cubic.on_rto(); - tcb.recovery.exit(); - tcb.sack_scoreboard.clear(); - tcb.rto_backoff += 1; - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff)); - // Mark for poll_send — may have more data to send after RTO. - to_mark.push(key); - } - TcpState::FinWait1 | TcpState::Closing | TcpState::LastAck => { - // Retransmit FIN-ACK. - let ts = tcb.ts_option(tsval); - if let Some(fin_seq) = tcb.fin_seq { - SegmentBuilder::build_fin_ack( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - fin_seq, - tcb.rcv_nxt, - tcb.advertised_window(), - ts, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - } - // Exponential backoff (same as Established). - tcb.rto_backoff += 1; - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff)); - } - _ => continue, - } - - // Exponential backoff for SYN/SYN-ACK states. - match tcb.state { - TcpState::SynSent | TcpState::SynReceived => { - tcb.rto_backoff += 1; - let rto = INITIAL_RTO_MS << tcb.rto_backoff; - tcb.retransmit_deadline = Some(now + coarsetime::Duration::from_millis(rto)); - } - _ => {} // Established handles its own backoff above. - } - } - - // Remove timed-out connections. - for key in &to_remove { - if let Some(tcb) = self.connections.get(*key) { - Self::decrement_syn_received(&mut self.listeners, &tcb.id); - } - self.send_tracker.unmark(*key); - self.remove_connection_by_key(*key); - } - - // Mark connections that need poll_send attention after timer processing. - for key in to_mark { - self.send_tracker.mark(SendReady(key)); - } - } - - /// Evict stale connections whose TIME-WAIT deadline has passed. - pub fn evict_stale<'umem>(&mut self, now: Instant, _rx_return: &mut impl FrameBuffer<'umem>) { - let keys_to_remove: SmallVec<[usize; 4]> = self - .connections - .iter() - .filter_map(|(key, tcb)| { - if tcb.state == TcpState::TimeWait - && let Some(deadline) = tcb.time_wait_deadline - && now >= deadline - { - Some(key) - } else { - None - } - }) - .collect(); - for key in keys_to_remove { - self.send_tracker.unmark(key); - self.remove_connection_by_key(key); - } - } - // --- Individual fire_* methods for timer-wheel dispatch --- /// Fire a delayed ACK for a single connection identified by slab key. @@ -485,6 +34,9 @@ impl TcpHandler { rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, ) { + // Clear the handle — this timer has fired. + self.timer_handles[key].clear(TcpTimerKind::DelayedAck); + let Some(tcb) = self.connections.get_mut(key) else { return; }; @@ -537,7 +89,6 @@ impl TcpHandler { tcb.update_advertised_edge(); tcb.ack_pending = false; tcb.ack_delay_count = 0; - tcb.delayed_ack_deadline = None; } /// Fire an RTO retransmit for a single connection identified by slab key. @@ -550,12 +101,16 @@ impl TcpHandler { &mut self, key: usize, now: Instant, + wheel: &mut TimerWheel, src_mac: crate::net::wire::ethernet::MacAddress, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, ) { + // Clear the handle — this timer has fired. + self.timer_handles[key].clear(TcpTimerKind::Retransmit); + let Some(tcb) = self.connections.get_mut(key) else { return; }; @@ -583,6 +138,7 @@ impl TcpHandler { Self::decrement_syn_received(&mut self.listeners, &tcb.id); } self.send_tracker.unmark(key); + self.timer_handles[key].cancel_all(wheel); self.remove_connection_by_key(key); return; } @@ -598,7 +154,12 @@ impl TcpHandler { tx_return, ) else { // Re-arm retransmit timer so we retry after the solicitation completes. - tcb.retransmit_deadline = Some(now + coarsetime::Duration::from_millis(tcb.rto)); + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); return; }; @@ -685,8 +246,12 @@ impl TcpHandler { tcb.recovery.exit(); tcb.sack_scoreboard.clear(); tcb.rto_backoff += 1; - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff)); + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff), + wheel, + ); // Mark for poll_send — may have more data to send after RTO. self.send_tracker.mark(SendReady(key)); } @@ -712,8 +277,12 @@ impl TcpHandler { } // Exponential backoff (same as Established). tcb.rto_backoff += 1; - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff)); + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto << tcb.rto_backoff), + wheel, + ); } _ => return, } @@ -723,7 +292,12 @@ impl TcpHandler { TcpState::SynSent | TcpState::SynReceived => { tcb.rto_backoff += 1; let rto = INITIAL_RTO_MS << tcb.rto_backoff; - tcb.retransmit_deadline = Some(now + coarsetime::Duration::from_millis(rto)); + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(rto), + wheel, + ); } _ => {} // Established handles its own backoff above. } @@ -825,12 +399,16 @@ impl TcpHandler { &mut self, key: usize, now: Instant, + wheel: &mut TimerWheel, src_mac: crate::net::wire::ethernet::MacAddress, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, ) { + // Clear the handle — this timer has fired. + self.timer_handles[key].clear(TcpTimerKind::Persist); + let Some(tcb) = self.connections.get_mut(key) else { return; }; @@ -888,7 +466,12 @@ impl TcpHandler { // Schedule next probe with exponential backoff, capped at 60s. let backoff_ms = (tcb.rto << tcb.persist_backoff).min(60_000); - tcb.persist_deadline = Some(now + coarsetime::Duration::from_millis(backoff_ms)); + self.timer_handles[key].arm( + TcpTimerKind::Persist, + key, + now + coarsetime::Duration::from_millis(backoff_ms), + wheel, + ); tcb.persist_backoff = tcb.persist_backoff.saturating_add(1).min(6); } @@ -901,12 +484,16 @@ impl TcpHandler { &mut self, key: usize, now: Instant, + wheel: &mut TimerWheel, src_mac: crate::net::wire::ethernet::MacAddress, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, ) { + // Clear the handle — this timer has fired. + self.timer_handles[key].clear(TcpTimerKind::Linger); + let Some(tcb) = self.connections.get_mut(key) else { return; }; @@ -953,6 +540,7 @@ impl TcpHandler { tcb.pending_fin = false; self.send_tracker.unmark(key); + self.timer_handles[key].cancel_all(wheel); self.remove_connection_by_key(key); } @@ -960,7 +548,10 @@ impl TcpHandler { /// /// Removes the connection if it is in TimeWait state. No packet is sent. #[inline] - pub fn fire_time_wait(&mut self, key: usize) { + pub fn fire_time_wait(&mut self, key: usize, wheel: &mut TimerWheel) { + // Clear the handle — this timer has fired. + self.timer_handles[key].clear(TcpTimerKind::TimeWait); + let Some(tcb) = self.connections.get(key) else { return; }; @@ -968,6 +559,7 @@ impl TcpHandler { return; } self.send_tracker.unmark(key); + self.timer_handles[key].cancel_all(wheel); self.remove_connection_by_key(key); } @@ -980,6 +572,7 @@ impl TcpHandler { key: usize, kind: TcpTimerKind, now: Instant, + wheel: &mut TimerWheel, src_mac: crate::net::wire::ethernet::MacAddress, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, @@ -1002,6 +595,7 @@ impl TcpHandler { self.fire_retransmit( key, now, + wheel, src_mac, neighbor_handler, free_frames, @@ -1024,6 +618,7 @@ impl TcpHandler { self.fire_persist( key, now, + wheel, src_mac, neighbor_handler, free_frames, @@ -1035,6 +630,7 @@ impl TcpHandler { self.fire_linger( key, now, + wheel, src_mac, neighbor_handler, free_frames, @@ -1043,7 +639,7 @@ impl TcpHandler { ); } TcpTimerKind::TimeWait => { - self.fire_time_wait(key); + self.fire_time_wait(key, wheel); } } } diff --git a/src/net/handler/tcp/transmit.rs b/src/net/handler/tcp/transmit.rs index 53cb997..27a3bc1 100644 --- a/src/net/handler/tcp/transmit.rs +++ b/src/net/handler/tcp/transmit.rs @@ -2,16 +2,13 @@ use coarsetime::Instant; use smallvec::SmallVec; use crate::{ - net::{NeighborHandler, wire::tcp::flags}, + net::{NeighborHandler, timer_wheel::TimerWheel, wire::tcp::flags}, xdp::frame::FrameBuffer, }; use super::{ - TcpHandler, - segment::SegmentBuilder, - send_tracker::SendReady, - state::TcpState, - tcb::{MAX_DELAYED_ACK_COUNT, TcpEvent}, + TcpHandler, segment::SegmentBuilder, send_tracker::SendReady, state::TcpState, + tcb::MAX_DELAYED_ACK_COUNT, timer_kinds::TcpTimerKind, }; impl TcpHandler { @@ -23,6 +20,7 @@ impl TcpHandler { pub fn poll_send<'umem>( &mut self, now: Instant, + wheel: &mut TimerWheel, src_mac: crate::net::wire::ethernet::MacAddress, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, @@ -154,9 +152,13 @@ impl TcpHandler { tcb.keep_alive_probes_sent = 0; // Set retransmit timer if not already running. - if tcb.retransmit_deadline.is_none() { - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto)); + if !self.timer_handles[key].is_armed(TcpTimerKind::Retransmit) { + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); } // Clear CWR after sending (only needs to be on one segment). @@ -168,7 +170,7 @@ impl TcpHandler { tcb.update_advertised_edge(); tcb.ack_pending = false; tcb.ack_delay_count = 0; - tcb.delayed_ack_deadline = None; + self.timer_handles[key].cancel_timer(TcpTimerKind::DelayedAck, wheel); } // Delayed ACK fallback: if the data send loop didn't piggyback an ACK @@ -213,7 +215,7 @@ impl TcpHandler { tcb.update_advertised_edge(); tcb.ack_pending = false; tcb.ack_delay_count = 0; - tcb.delayed_ack_deadline = None; + self.timer_handles[key].cancel_timer(TcpTimerKind::DelayedAck, wheel); } // --- Zero-window probing (persist timer) --- @@ -223,101 +225,16 @@ impl TcpHandler { let send_window = (tcb.snd_wnd as usize).min(tcb.cubic.cwnd as usize); // A. Arm persist timer when peer advertises window=0 and we have data to send. - if send_window == 0 && data_available > 0 && tcb.persist_deadline.is_none() { - tcb.persist_deadline = Some(now + coarsetime::Duration::from_millis(tcb.rto)); - } - - // B. Send 1-byte probe when persist deadline expires. - if let Some(deadline) = tcb.persist_deadline - && now >= deadline - && send_window == 0 + if send_window == 0 && data_available > 0 + && !self.timer_handles[key].is_armed(TcpTimerKind::Persist) { - let mut probe = [0u8; 1]; - tcb.send_buffer.peek_at(bytes_in_flight, &mut probe); - - let Some(dst_mac) = neighbor_handler.lookup_or_resolve( - now, - &tcb.id.remote_addr, - &tcb.id.local_addr, - free_frames, - rx_return, - tx_return, - ) else { - // Neighbor resolution pending — re-mark for next tick. - self.send_tracker.mark(SendReady(key)); - continue; // skip to next connection - }; - - let ts = tcb.ts_option(tsval); - SegmentBuilder::build_data( - tcb.id.local_addr, - tcb.id.remote_addr, - tcb.id.local_port, - tcb.id.remote_port, - tcb.snd_nxt, - tcb.rcv_nxt, - tcb.advertised_window(), - &probe, - ts, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); - - tcb.snd_nxt = tcb.snd_nxt.wrapping_add(1); - - // Schedule next probe with exponential backoff, capped at 60s. - let backoff_ms = (tcb.rto << tcb.persist_backoff).min(60_000); - tcb.persist_deadline = Some(now + coarsetime::Duration::from_millis(backoff_ms)); - tcb.persist_backoff = tcb.persist_backoff.saturating_add(1).min(6); - } - - // Check linger deadline — if expired, abort with RST. - if tcb.pending_fin - && let Some(deadline) = tcb.linger_deadline - && now >= deadline - { - // Send RST to peer. - let id = tcb.id; - let Some(dst_mac) = neighbor_handler.lookup_or_resolve( - now, - &tcb.id.remote_addr, - &tcb.id.local_addr, - free_frames, - rx_return, - tx_return, - ) else { - // Neighbor resolution pending — re-mark for next tick. - self.send_tracker.mark(SendReady(key)); - continue; // skip to next connection - }; - - // Use build_rst by simulating an "incoming ACK" segment. - // This produces: = - SegmentBuilder::build_rst( - id.remote_addr, - id.local_addr, // swapped: "incoming" from remote - id.remote_port, - id.local_port, // swapped - 0, - tcb.snd_nxt, // incoming_seq=0, incoming_ack=snd_nxt - flags::ACK, // pretend incoming has ACK set - 0, // seg_len doesn't matter - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, + self.timer_handles[key].arm( + TcpTimerKind::Persist, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, ); - - tcb.event_queue.push(TcpEvent::Reset); - tcb.state = TcpState::Closed; - tcb.pending_fin = false; - closed.push(key); - continue; // connection is Closed, will be removed after loop } // After data sending: check if we should send FIN. @@ -370,9 +287,13 @@ impl TcpHandler { } // Set retransmit timer for FIN. - if tcb.retransmit_deadline.is_none() { - tcb.retransmit_deadline = - Some(now + coarsetime::Duration::from_millis(tcb.rto)); + if !self.timer_handles[key].is_armed(TcpTimerKind::Retransmit) { + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); } } } @@ -384,8 +305,8 @@ impl TcpHandler { let has_work = has_data || tcb.ack_pending || tcb.pending_fin - || tcb.persist_deadline.is_some() - || tcb.retransmit_deadline.is_some(); + || self.timer_handles[key].is_armed(TcpTimerKind::Persist) + || self.timer_handles[key].is_armed(TcpTimerKind::Retransmit); if has_work { self.send_tracker.mark(SendReady(key)); } @@ -395,6 +316,7 @@ impl TcpHandler { // Remove connections aborted by linger deadline. for key in &closed { self.send_tracker.unmark(*key); + self.timer_handles[*key].cancel_all(wheel); self.remove_connection_by_key(*key); } } diff --git a/src/net/http/body.rs b/src/net/http/body.rs index 21fd012..6b251be 100644 --- a/src/net/http/body.rs +++ b/src/net/http/body.rs @@ -296,8 +296,17 @@ mod tests { remote_port: 0, }; let event_queue = LocalQueue::new(16); - let stream = - crate::net::socket::TcpStream::from_accepted_for_test(0, conn_id, event_queue, handler); + let wheel = std::rc::Rc::new(std::cell::UnsafeCell::new( + crate::net::timer_wheel::TimerWheel::new(0), + )); + let stream = crate::net::socket::TcpStream::from_accepted_for_test( + 0, + conn_id, + event_queue, + handler, + wheel, + coarsetime::Instant::now(), + ); let mut buf = ReadBuffer::new(4096); let written = buf.append(data); diff --git a/src/net/http/connection.rs b/src/net/http/connection.rs index a92ed7f..653b0e1 100644 --- a/src/net/http/connection.rs +++ b/src/net/http/connection.rs @@ -129,7 +129,15 @@ mod tests { remote_port: 0, }; let event_queue = LocalQueue::new(16); - let stream = TcpStream::from_accepted_for_test(0, conn_id, event_queue, handler); + let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))); + let stream = TcpStream::from_accepted_for_test( + 0, + conn_id, + event_queue, + handler, + wheel, + coarsetime::Instant::now(), + ); HttpConnection::new(stream, Session::http09()) } @@ -142,7 +150,15 @@ mod tests { remote_port: 0, }; let event_queue = LocalQueue::new(16); - let stream = TcpStream::from_accepted_for_test(0, conn_id, event_queue, handler); + let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))); + let stream = TcpStream::from_accepted_for_test( + 0, + conn_id, + event_queue, + handler, + wheel, + coarsetime::Instant::now(), + ); HttpConnection::new(stream, Session::new(HttpCodec::Http11(Http11Codec::new()))) } diff --git a/src/net/socket/tcp.rs b/src/net/socket/tcp.rs index 342f87b..1730436 100644 --- a/src/net/socket/tcp.rs +++ b/src/net/socket/tcp.rs @@ -31,6 +31,8 @@ pub struct TcpListener { local_port: u16, accept_queue: LocalQueue, handler: Rc>, + wheel: Rc>, + base_instant: coarsetime::Instant, } impl TcpListener { @@ -55,6 +57,8 @@ impl TcpListener { local_port: port, accept_queue, handler: ctx.tcp_handler.clone(), + wheel: ctx.wheel.clone(), + base_instant: ctx.base_instant, }) }) } @@ -75,6 +79,8 @@ impl TcpListener { local_port: port, accept_queue, handler: ctx.tcp_handler.clone(), + wheel: ctx.wheel.clone(), + base_instant: ctx.base_instant, }) }) } @@ -85,6 +91,8 @@ impl TcpListener { Accept { accept_queue: &self.accept_queue, handler: &self.handler, + wheel: &self.wheel, + base_instant: self.base_instant, } } @@ -117,6 +125,8 @@ impl Drop for TcpListener { pub struct Accept<'listener> { accept_queue: &'listener LocalQueue, handler: &'listener Rc>, + wheel: &'listener Rc>, + base_instant: coarsetime::Instant, } impl<'listener> Future for Accept<'listener> { @@ -137,6 +147,8 @@ impl<'listener> Future for Accept<'listener> { conn_id, event_queue, this.handler.clone(), + this.wheel.clone(), + this.base_instant, )) } else { // Connection was removed (e.g. by RST) before we accepted it. @@ -163,6 +175,8 @@ pub struct TcpStream { #[allow(dead_code)] // used in future data transfer phases event_queue: LocalQueue, handler: Rc>, + wheel: Rc>, + base_instant: coarsetime::Instant, closed: bool, write_closed: bool, } @@ -182,6 +196,7 @@ impl TcpStream { ) -> Result { with_runtime_context(|ctx| { let handler = unsafe { &mut *ctx.tcp_handler.get() }; + let wheel = unsafe { &mut *ctx.wheel.get() }; let neighbor_handler = &*ctx.neighbor_handler; let now = Instant::now(); @@ -203,6 +218,7 @@ impl TcpStream { src_mac, dst_mac, now, + wheel, &mut free_frames, &mut tx_return, ) @@ -220,6 +236,8 @@ impl TcpStream { conn_id, event_queue, handler: ctx.tcp_handler.clone(), + wheel: ctx.wheel.clone(), + base_instant: ctx.base_instant, }) }) } @@ -239,6 +257,7 @@ impl TcpStream { ) -> Result { with_runtime_context(|ctx| { let handler = unsafe { &mut *ctx.tcp_handler.get() }; + let wheel = unsafe { &mut *ctx.wheel.get() }; let neighbor_handler = &*ctx.neighbor_handler; let now = Instant::now(); @@ -260,6 +279,7 @@ impl TcpStream { src_mac, dst_mac, now, + wheel, config, &mut free_frames, &mut tx_return, @@ -278,6 +298,8 @@ impl TcpStream { conn_id, event_queue, handler: ctx.tcp_handler.clone(), + wheel: ctx.wheel.clone(), + base_instant: ctx.base_instant, }) }) } @@ -288,12 +310,16 @@ impl TcpStream { conn_id: ConnectionId, event_queue: LocalQueue, handler: Rc>, + wheel: Rc>, + base_instant: coarsetime::Instant, ) -> Self { Self { conn_key, conn_id, event_queue, handler, + wheel, + base_instant, closed: false, write_closed: false, } @@ -305,12 +331,16 @@ impl TcpStream { conn_id: ConnectionId, event_queue: LocalQueue, handler: Rc>, + wheel: Rc>, + base_instant: coarsetime::Instant, ) -> Self { Self { conn_key, conn_id, event_queue, handler, + wheel, + base_instant, closed: false, write_closed: false, } @@ -387,7 +417,9 @@ impl TcpStream { } self.closed = true; let handler = unsafe { &mut *self.handler.get() }; - handler.initiate_close(self.conn_key); + let wheel = unsafe { &mut *self.wheel.get() }; + let now = Instant::now(); + handler.initiate_close(self.conn_key, now, wheel); } /// Shut down the write side of this connection (half-close). @@ -400,7 +432,9 @@ impl TcpStream { } self.write_closed = true; let handler = unsafe { &mut *self.handler.get() }; - handler.initiate_close(self.conn_key); + let wheel = unsafe { &mut *self.wheel.get() }; + let now = Instant::now(); + handler.initiate_close(self.conn_key, now, wheel); } /// Enable or disable the Nagle algorithm (TCP_NODELAY). @@ -474,6 +508,8 @@ pub struct Connect { conn_id: ConnectionId, event_queue: LocalQueue, handler: Rc>, + wheel: Rc>, + base_instant: coarsetime::Instant, } impl Future for Connect { @@ -487,6 +523,8 @@ impl Future for Connect { conn_id: this.conn_id, event_queue: this.event_queue.clone(), handler: this.handler.clone(), + wheel: this.wheel.clone(), + base_instant: this.base_instant, closed: false, write_closed: false, })), @@ -689,7 +727,6 @@ mod tests { snd_wscale: 0, rcv_wscale: 0, wscale_enabled: false, - retransmit_deadline: None, rto_backoff: 0, event_queue: event_queue.clone(), send_buffer: RingBuffer::new(1024), @@ -705,10 +742,8 @@ mod tests { last_send_time: None, pending_fin: false, fin_seq: None, - time_wait_deadline: None, time_wait_duration: 60_000, ack_pending: false, - delayed_ack_deadline: None, ack_delay_count: 0, delayed_ack_ms: DEFAULT_DELAYED_ACK_MS, nagle_enabled: true, @@ -719,7 +754,6 @@ mod tests { last_activity: Instant::now(), keep_alive_probes_sent: 0, linger: None, - linger_deadline: None, ts_enabled: false, ts_recent: 0, ts_recent_age: Instant::now(), @@ -729,7 +763,6 @@ mod tests { ecn_enabled: false, ecn_ce_received: false, ecn_cwr_sent: false, - persist_deadline: None, persist_backoff: 0, max_snd_wnd: 0, last_advertised_right_edge: 0, @@ -739,7 +772,15 @@ mod tests { let key = handler.insert_connection(tcb); let handler_rc = Rc::new(UnsafeCell::new(handler)); - TcpStream::from_accepted_for_test(key, conn_id, event_queue, handler_rc) + let wheel_rc = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))); + TcpStream::from_accepted_for_test( + key, + conn_id, + event_queue, + handler_rc, + wheel_rc, + Instant::now(), + ) } #[test] diff --git a/src/net/socket/udp.rs b/src/net/socket/udp.rs index 3a3724c..5fc15b3 100644 --- a/src/net/socket/udp.rs +++ b/src/net/socket/udp.rs @@ -511,6 +511,8 @@ mod tests { neighbor_handler, udp_handler, tcp_handler, + wheel: Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))), + base_instant: coarsetime::Instant::now(), tx_offload: false, task_queue: UnsafeCell::new(TaskQueue::new()), capacity_wakers: UnsafeCell::new(Vec::new()), diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs index 5dc0be9..2e6c770 100644 --- a/src/net/timer_wheel.rs +++ b/src/net/timer_wheel.rs @@ -1,3 +1,4 @@ +use coarsetime::{Duration, Instant}; use slab::Slab; use smallvec::SmallVec; @@ -23,9 +24,7 @@ impl TimerHandle { struct TimerEntry { id: TimerId, deadline_ms: u64, - /// Next entry key in the slot's doubly-linked list (slab key). next: Option, - /// Previous entry key in the slot's doubly-linked list (slab key). prev: Option, /// Packed: bits [9:8] = tier index (0-2), bits [7:0] = slot index within tier. slot: u16, @@ -43,14 +42,11 @@ impl Slot { struct Tier { slots: [Slot; 256], - /// Number of bits to right-shift a deadline_ms to get the slot index. shift: u8, } impl Tier { fn new(shift: u8) -> Self { - // SAFETY: Slot is a struct with no padding issues; initializing an array of 256 items - // via a const fn is idiomatic. Self { slots: std::array::from_fn(|_| Slot::new()), shift, @@ -58,34 +54,53 @@ impl Tier { } } +/// Total range of the 3-tier wheel in milliseconds (256^3 ≈ 16.7M ms ≈ 4.66 hrs). +const TOTAL_RANGE_MS: u64 = 256 * 256 * 256; + /// Hierarchical timer wheel with three tiers. /// /// - Tier 0 (inner): shift=0, 1 ms resolution, 256 ms range /// - Tier 1 (middle): shift=8, 256 ms resolution, ~65.5 s range /// - Tier 2 (outer): shift=16, ~65.5 s resolution, ~4.66 hr range +/// +/// The wheel stores a base [`Instant`] and converts all deadlines to +/// millisecond offsets internally. Callers work with `Instant` values. pub struct TimerWheel { tiers: [Tier; 3], entries: Slab, current_tick_ms: u64, + base: Instant, } impl TimerWheel { - /// Create a new wheel with `current_tick_ms` as the reference time. - pub fn new(current_tick_ms: u64) -> Self { + /// Create a new wheel. The provided `base` instant is the time origin — + /// all deadlines are measured as millisecond offsets from this point. + pub fn new(base: Instant) -> Self { Self { tiers: [Tier::new(0), Tier::new(8), Tier::new(16)], entries: Slab::new(), - current_tick_ms, + current_tick_ms: 0, + base, } } - /// Arm a timer that fires at `deadline_ms` (absolute ms since epoch/start). + /// Convert an [`Instant`] to internal milliseconds relative to base. + #[inline] + fn to_ms(&self, t: Instant) -> u64 { + t.duration_since(self.base).as_millis() + } + + /// Arm a timer that fires at `deadline`. /// /// Returns a [`TimerHandle`] that can be passed to [`cancel`](Self::cancel). - pub fn arm(&mut self, id: TimerId, deadline_ms: u64) -> TimerHandle { + pub fn arm(&mut self, id: TimerId, deadline: Instant) -> TimerHandle { + self.arm_ms(id, self.to_ms(deadline)) + } + + /// Internal arm using raw millisecond offset. Used by cascade and tests. + fn arm_ms(&mut self, id: TimerId, deadline_ms: u64) -> TimerHandle { let delta = deadline_ms.saturating_sub(self.current_tick_ms); - // Select tier based on how far out the deadline is. let tier_idx: usize = if delta < 256 { 0 } else if delta < 65536 { @@ -96,29 +111,21 @@ impl TimerWheel { let shift = self.tiers[tier_idx].shift; let slot_idx = ((deadline_ms >> shift) & 0xFF) as usize; - - // Pack tier_idx (2 bits high) | slot_idx (8 bits low) into slot field. let slot_packed = ((tier_idx as u16) << 8) | (slot_idx as u16); - // Grab current head before borrowing entries. let old_head = self.tiers[tier_idx].slots[slot_idx].head; - let entry = TimerEntry { + let key = self.entries.insert(TimerEntry { id, deadline_ms, next: old_head, prev: None, slot: slot_packed, - }; - - let key = self.entries.insert(entry); + }); - // If there was an old head, update its prev pointer. if let Some(old_head_key) = old_head { self.entries[old_head_key].prev = Some(key); } - - // The new entry becomes the head. self.tiers[tier_idx].slots[slot_idx].head = Some(key); TimerHandle(key) @@ -134,54 +141,111 @@ impl TimerWheel { self.entries.len() } - /// Advance the wheel to `now_ms`, firing all timers whose deadline has - /// passed. Returns the [`TimerId`]s of every fired timer. - pub fn advance(&mut self, now_ms: u64) -> SmallVec<[TimerId; 16]> { + /// Advance the wheel to `now`, firing all expired timers. + /// + /// Uses rotation-based skipping: instead of ticking one ms at a time, + /// processes inner-wheel slots in bulk and cascades at rotation boundaries. + /// If the wheel is empty or the jump exceeds the total wheel range, + /// fast-paths avoid unnecessary work. + pub fn advance(&mut self, now: Instant) -> SmallVec<[TimerId; 16]> { + let now_ms = self.to_ms(now); + self.advance_ms(now_ms) + } + + /// Internal advance using raw milliseconds. + fn advance_ms(&mut self, now_ms: u64) -> SmallVec<[TimerId; 16]> { let mut fired: SmallVec<[TimerId; 16]> = SmallVec::new(); + if self.current_tick_ms >= now_ms { + return fired; + } + + // Fast path: no entries — just jump. + if self.entries.is_empty() { + self.current_tick_ms = now_ms; + return fired; + } + + let remaining = now_ms - self.current_tick_ms; + + // Fast path: jump exceeds total wheel range — drain everything. + if remaining >= TOTAL_RANGE_MS { + self.drain_all_tiers(&mut fired); + self.current_tick_ms = now_ms; + return fired; + } + + // Rotation-based advance: process inner slots in bulk per rotation. while self.current_tick_ms < now_ms { - let inner_slot_idx = (self.current_tick_ms & 0xFF) as usize; - - // Drain the inner wheel slot for this tick. - let mut cursor = self.tiers[0].slots[inner_slot_idx].head; - self.tiers[0].slots[inner_slot_idx].head = None; - while let Some(key) = cursor { - let entry = self.entries.remove(key); - cursor = entry.next; - fired.push(entry.id); + // Fast exit if all entries were fired. + if self.entries.is_empty() { + self.current_tick_ms = now_ms; + break; } - self.current_tick_ms += 1; + let current_inner = (self.current_tick_ms & 0xFF) as usize; + let ticks_to_wrap = (256 - current_inner) as u64; + let remaining = now_ms - self.current_tick_ms; + + if remaining >= ticks_to_wrap { + // Complete this inner rotation: drain slots [current_inner..256) + for slot_idx in current_inner..256 { + self.drain_inner_slot(slot_idx, &mut fired); + } + self.current_tick_ms += ticks_to_wrap; - // After incrementing, check whether we need to cascade. - // Inner → middle cascade: inner slot just wrapped back to 0. - if (self.current_tick_ms & 0xFF) == 0 && self.current_tick_ms > 0 { + // Inner wrapped to 0 — cascade tier 1. self.cascade(1); - // Middle → outer cascade: middle slot also just wrapped. - let middle_slot_idx = - ((self.current_tick_ms >> self.tiers[1].shift) & 0xFF) as usize; - if middle_slot_idx == 0 { + // Check if middle also wrapped — cascade tier 2. + if ((self.current_tick_ms >> 8) & 0xFF) == 0 && self.current_tick_ms > 0 { self.cascade(2); } + } else { + // Partial rotation: drain slots [current_inner..now_inner) + let end_inner = (now_ms & 0xFF) as usize; + for slot_idx in current_inner..end_inner { + self.drain_inner_slot(slot_idx, &mut fired); + } + self.current_tick_ms = now_ms; } } fired } - /// Re-distribute entries from one tier's current slot into lower tiers. - /// - /// Tier `tier_idx` has just advanced such that its slot pointer has wrapped - /// to 0. We drain the slot that `current_tick_ms` now points to in that - /// tier and re-arm each entry using its stored `deadline_ms` so it lands in - /// the correct (lower) tier. + /// Drain a single inner-tier slot, collecting fired timer IDs. + fn drain_inner_slot(&mut self, slot_idx: usize, fired: &mut SmallVec<[TimerId; 16]>) { + let mut cursor = self.tiers[0].slots[slot_idx].head; + self.tiers[0].slots[slot_idx].head = None; + while let Some(key) = cursor { + let entry = self.entries.remove(key); + cursor = entry.next; + fired.push(entry.id); + } + } + + /// Drain all entries from all tiers. Used when advance jumps past the + /// total wheel range. + fn drain_all_tiers(&mut self, fired: &mut SmallVec<[TimerId; 16]>) { + for tier in &mut self.tiers { + for slot in &mut tier.slots { + let mut cursor = slot.head; + slot.head = None; + while let Some(key) = cursor { + let entry = self.entries.remove(key); + cursor = entry.next; + fired.push(entry.id); + } + } + } + } + + /// Cascade: redistribute entries from one tier's current slot into lower tiers. fn cascade(&mut self, tier_idx: usize) { let shift = self.tiers[tier_idx].shift; let slot_idx = ((self.current_tick_ms >> shift) & 0xFF) as usize; - // Collect all entries from the slot BEFORE re-arming (arm() mutates - // the entries slab, so we cannot hold borrows across that call). let mut to_rearm: SmallVec<[(TimerId, u64); 64]> = SmallVec::new(); let mut cursor = self.tiers[tier_idx].slots[slot_idx].head; self.tiers[tier_idx].slots[slot_idx].head = None; @@ -191,35 +255,25 @@ impl TimerWheel { to_rearm.push((entry.id, entry.deadline_ms)); } - // Re-arm each entry; the smaller delta will place it in a lower tier. for (id, deadline_ms) in to_rearm { - self.arm(id, deadline_ms); + self.arm_ms(id, deadline_ms); } } - /// Cancel a previously armed timer. - /// - /// If `handle` does not refer to an active timer (already fired or already - /// cancelled), this is a no-op. + /// Cancel a previously armed timer. No-op if already fired or cancelled. pub fn cancel(&mut self, handle: TimerHandle) { - // Remove the entry from the slab; bail silently if not present. - let entry = match self.entries.try_remove(handle.0) { - Some(e) => e, - None => return, + let Some(entry) = self.entries.try_remove(handle.0) else { + return; }; - // Decode the packed slot field. let tier_idx = ((entry.slot >> 8) & 0x3) as usize; let slot_idx = (entry.slot & 0xFF) as usize; - // Unlink from the doubly-linked list. match entry.prev { Some(prev_key) => { - // There is a predecessor — point it past the removed entry. self.entries[prev_key].next = entry.next; } None => { - // No predecessor: this entry was the slot head. self.tiers[tier_idx].slots[slot_idx].head = entry.next; } } @@ -228,85 +282,80 @@ impl TimerWheel { self.entries[next_key].prev = entry.prev; } } + + /// Drain all timers and return their IDs. Useful in tests. + pub fn drain_all(&mut self) -> SmallVec<[TimerId; 16]> { + let mut fired = SmallVec::new(); + self.drain_all_tiers(&mut fired); + fired + } } // --------------------------------------------------------------------------- -// Tests +// Tests — use raw ms via arm_ms / advance_ms to test wheel mechanics directly. // --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; + /// Helper: create a wheel at ms=0 for unit tests. + fn test_wheel(start_ms: u64) -> TimerWheel { + let mut w = TimerWheel::new(Instant::now()); + w.current_tick_ms = start_ms; + w + } + #[test] fn arm_returns_handle() { - let mut wheel = TimerWheel::new(0); - let handle = wheel.arm(TimerId(1), 50); + let mut wheel = test_wheel(0); + let handle = wheel.arm_ms(TimerId(1), 50); assert!(wheel.is_armed(handle)); } #[test] fn arm_places_in_correct_inner_slot() { - let mut wheel = TimerWheel::new(0); - // delta = 50 < 256 → tier 0, slot = (50 >> 0) & 0xFF = 50 - wheel.arm(TimerId(2), 50); + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(2), 50); assert!(wheel.tiers[0].slots[50].head.is_some()); } #[test] fn arm_places_in_middle_tier_for_large_delta() { - let mut wheel = TimerWheel::new(0); - // delta = 1000; 256 ≤ 1000 < 65536 → tier 1 - // slot = (1000 >> 8) & 0xFF = 3 + let mut wheel = test_wheel(0); let expected_slot = (1000usize >> 8) & 0xFF; - wheel.arm(TimerId(3), 1000); + wheel.arm_ms(TimerId(3), 1000); assert!(wheel.tiers[1].slots[expected_slot].head.is_some()); } #[test] fn arm_places_in_outer_tier_for_huge_delta() { - let mut wheel = TimerWheel::new(0); - // delta = 100_000 ≥ 65536 → tier 2 - // slot = (100_000 >> 16) & 0xFF = 1 + let mut wheel = test_wheel(0); let expected_slot = (100_000usize >> 16) & 0xFF; - wheel.arm(TimerId(4), 100_000); + wheel.arm_ms(TimerId(4), 100_000); assert!(wheel.tiers[2].slots[expected_slot].head.is_some()); } #[test] fn arm_multiple_same_slot_chains_correctly() { - let mut wheel = TimerWheel::new(0); - let h1 = wheel.arm(TimerId(10), 50); - let h2 = wheel.arm(TimerId(11), 50); - - // Both handles must be armed. + let mut wheel = test_wheel(0); + let h1 = wheel.arm_ms(TimerId(10), 50); + let h2 = wheel.arm_ms(TimerId(11), 50); assert!(wheel.is_armed(h1)); assert!(wheel.is_armed(h2)); - - // Both slab keys must differ. assert_ne!(h1, h2); - - // The slot head must point to h2 (most recently armed, prepended). - let head_key = wheel.tiers[0].slots[50] - .head - .expect("slot should have a head"); + let head_key = wheel.tiers[0].slots[50].head.expect("should have head"); assert_eq!(head_key, h2.0); - - // h2.next must be h1. - let h2_entry = &wheel.entries[h2.0]; - assert_eq!(h2_entry.next, Some(h1.0)); - - // h1.prev must be h2. - let h1_entry = &wheel.entries[h1.0]; - assert_eq!(h1_entry.prev, Some(h2.0)); + assert_eq!(wheel.entries[h2.0].next, Some(h1.0)); + assert_eq!(wheel.entries[h1.0].prev, Some(h2.0)); } - // --- cancel tests --- + // --- cancel --- #[test] fn cancel_removes_entry() { - let mut wheel = TimerWheel::new(0); - let h = wheel.arm(TimerId(1), 50); + let mut wheel = test_wheel(0); + let h = wheel.arm_ms(TimerId(1), 50); assert!(wheel.is_armed(h)); wheel.cancel(h); assert!(!wheel.is_armed(h)); @@ -314,274 +363,187 @@ mod tests { #[test] fn cancel_unlinks_head() { - // arm h1, then h2 → chain: h2(head) → h1 - // cancel h2 (head) → h1 should become the new head - let mut wheel = TimerWheel::new(0); - let h1 = wheel.arm(TimerId(1), 50); - let h2 = wheel.arm(TimerId(2), 50); - + let mut wheel = test_wheel(0); + let h1 = wheel.arm_ms(TimerId(1), 50); + let h2 = wheel.arm_ms(TimerId(2), 50); wheel.cancel(h2); - assert!(!wheel.is_armed(h2)); assert!(wheel.is_armed(h1)); - - // h1 should now be the slot head - let tier_idx = 0usize; // delta=50 < 256 - let slot_idx = 50usize; // (50 >> 0) & 0xFF - let head_key = wheel.tiers[tier_idx].slots[slot_idx] - .head - .expect("slot should still have a head after cancelling h2"); - assert_eq!(head_key, h1.0); - - // h1.prev should be None (it is now the head) + assert_eq!(wheel.tiers[0].slots[50].head, Some(h1.0)); assert_eq!(wheel.entries[h1.0].prev, None); } #[test] fn cancel_unlinks_middle() { - // arm h1, h2, h3 → chain: h3(head) → h2 → h1 - // cancel h2 → chain should be: h3 → h1 - let mut wheel = TimerWheel::new(0); - let h1 = wheel.arm(TimerId(1), 50); - let h2 = wheel.arm(TimerId(2), 50); - let h3 = wheel.arm(TimerId(3), 50); - + let mut wheel = test_wheel(0); + let h1 = wheel.arm_ms(TimerId(1), 50); + let h2 = wheel.arm_ms(TimerId(2), 50); + let h3 = wheel.arm_ms(TimerId(3), 50); wheel.cancel(h2); - assert!(wheel.is_armed(h1)); assert!(!wheel.is_armed(h2)); assert!(wheel.is_armed(h3)); - - // h3.next should now point to h1 assert_eq!(wheel.entries[h3.0].next, Some(h1.0)); - // h1.prev should now point to h3 assert_eq!(wheel.entries[h1.0].prev, Some(h3.0)); } #[test] fn cancel_unlinks_tail() { - // arm h1, h2 → chain: h2(head) → h1(tail) - // cancel h1 (tail) → chain: h2 only - let mut wheel = TimerWheel::new(0); - let h1 = wheel.arm(TimerId(1), 50); - let h2 = wheel.arm(TimerId(2), 50); - + let mut wheel = test_wheel(0); + let h1 = wheel.arm_ms(TimerId(1), 50); + let h2 = wheel.arm_ms(TimerId(2), 50); wheel.cancel(h1); - assert!(!wheel.is_armed(h1)); assert!(wheel.is_armed(h2)); - - // h2.next should now be None (h1 was removed) assert_eq!(wheel.entries[h2.0].next, None); } #[test] fn cancel_invalid_handle_is_noop() { - let mut wheel = TimerWheel::new(0); - let h = wheel.arm(TimerId(1), 50); - wheel.cancel(h); - // Cancelling the same handle again should not panic. + let mut wheel = test_wheel(0); + let h = wheel.arm_ms(TimerId(1), 50); wheel.cancel(h); + wheel.cancel(h); // double cancel — must not panic } - // ----------------------------------------------------------------------- - // Task 3: advance basic tests - // ----------------------------------------------------------------------- + // --- advance --- #[test] fn advance_fires_inner_slot_timer() { - let mut wheel = TimerWheel::new(0); - wheel.arm(TimerId(1), 50); - let fired = wheel.advance(51); + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 50); + let fired = wheel.advance_ms(51); assert_eq!(fired.len(), 1); assert_eq!(fired[0], TimerId(1)); } #[test] fn advance_fires_multiple_timers_same_slot() { - let mut wheel = TimerWheel::new(0); - wheel.arm(TimerId(1), 50); - wheel.arm(TimerId(2), 50); - let fired = wheel.advance(51); + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 50); + wheel.arm_ms(TimerId(2), 50); + let fired = wheel.advance_ms(51); assert_eq!(fired.len(), 2); } #[test] fn advance_does_not_fire_future_timer() { - let mut wheel = TimerWheel::new(0); - wheel.arm(TimerId(1), 100); - let fired = wheel.advance(50); + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 100); + let fired = wheel.advance_ms(50); assert!(fired.is_empty()); } - // ----------------------------------------------------------------------- - // Task 4: Cascade verification tests - // ----------------------------------------------------------------------- + // --- cascade --- - /// Arm at 300 ms (goes to middle tier). Advance to 256 (triggers - /// inner→middle cascade). The timer should now live in inner tier slot 44 - /// (300 & 0xFF == 44) but must NOT yet have fired. #[test] fn cascade_middle_to_inner() { - let mut wheel = TimerWheel::new(0); - let handle = wheel.arm(TimerId(42), 300); + let mut wheel = test_wheel(0); + let handle = wheel.arm_ms(TimerId(42), 300); assert!(wheel.is_armed(handle)); - - // Advancing to 256 crosses the inner wrap boundary and cascades tier 1. - let fired = wheel.advance(256); - assert!(fired.is_empty(), "timer not due yet; should not fire"); - - // After cascade the entry should be in inner tier slot 44 (300 & 0xFF). + let fired = wheel.advance_ms(256); + assert!(fired.is_empty()); let expected_slot = 300usize & 0xFF; // 44 - assert!( - wheel.tiers[0].slots[expected_slot].head.is_some(), - "cascaded entry should now be in inner slot {expected_slot}" - ); + assert!(wheel.tiers[0].slots[expected_slot].head.is_some()); } - /// Arm at 300 ms. Advance all the way to 301. The timer must fire. #[test] fn cascade_fires_on_correct_tick() { - let mut wheel = TimerWheel::new(0); - wheel.arm(TimerId(7), 300); - let fired = wheel.advance(301); + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(7), 300); + let fired = wheel.advance_ms(301); assert_eq!(fired.len(), 1); assert_eq!(fired[0], TimerId(7)); } - /// Arm at 70_000 ms (outer tier). Advance to 65_536 (triggers - /// middle→outer cascade). The entry should now be in the middle tier. #[test] fn cascade_outer_to_middle() { - let mut wheel = TimerWheel::new(0); - let handle = wheel.arm(TimerId(99), 70_000); + let mut wheel = test_wheel(0); + let handle = wheel.arm_ms(TimerId(99), 70_000); assert!(wheel.is_armed(handle)); + let fired = wheel.advance_ms(65_536); + assert!(fired.is_empty()); + assert!(wheel.tiers[2].slots[1].head.is_none()); + let expected_slot = (70_000usize >> 8) & 0xFF; + assert!(wheel.tiers[1].slots[expected_slot].head.is_some()); + } - // 65_536 == 256 * 256; crossing this wraps the middle wheel and - // triggers a cascade from the outer tier. - let fired = wheel.advance(65_536); - assert!(fired.is_empty(), "timer not due yet"); - - // After cascade, entry should be in middle tier, not outer. - // Outer tier slot for 70_000: (70_000 >> 16) & 0xFF == 1 - assert!( - wheel.tiers[2].slots[1].head.is_none(), - "outer slot should be empty after cascade" - ); - // Middle tier: delta from 65_536 to 70_000 is 4_464 < 65_536, and - // slot = (70_000 >> 8) & 0xFF == 273 & 0xFF == 17. - let expected_slot = (70_000usize >> 8) & 0xFF; // 17 - assert!( - wheel.tiers[1].slots[expected_slot].head.is_some(), - "cascaded entry should now be in middle slot {expected_slot}" - ); - } - - /// Arm at 300, 310, 500. Advance to 311. Timers at 300 and 310 must fire; - /// the one at 500 must not. #[test] fn cascade_multiple_entries() { - let mut wheel = TimerWheel::new(0); - wheel.arm(TimerId(1), 300); - wheel.arm(TimerId(2), 310); - wheel.arm(TimerId(3), 500); - - let fired = wheel.advance(311); - assert_eq!(fired.len(), 2, "exactly two timers should fire"); - + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 300); + wheel.arm_ms(TimerId(2), 310); + wheel.arm_ms(TimerId(3), 500); + let fired = wheel.advance_ms(311); + assert_eq!(fired.len(), 2); let mut ids: Vec = fired.iter().map(|t| t.0).collect(); ids.sort_unstable(); assert_eq!(ids, vec![1, 2]); } - // ----------------------------------------------------------------------- - // Task 5: entry_count and edge cases - // ----------------------------------------------------------------------- + // --- edge cases --- #[test] fn rearm_cancel_then_arm() { - let mut wheel = TimerWheel::new(0); - let h1 = wheel.arm(TimerId(1), 50); + let mut wheel = test_wheel(0); + let h1 = wheel.arm_ms(TimerId(1), 50); wheel.cancel(h1); - // Timer should no longer be armed immediately after cancel. assert!(!wheel.is_armed(h1)); - - // Re-arm the same logical timer with a new deadline. The slab may - // reuse the freed key, so h1 and h2 might be equal; what matters is - // that only one timer fires at the new deadline and not at the old one. - let _h2 = wheel.arm(TimerId(1), 150); - assert_eq!(wheel.entry_count(), 1, "exactly one timer should be armed"); - - // Nothing fires before the new deadline. - let not_fired = wheel.advance(100); - assert!(not_fired.is_empty(), "should not fire before new deadline"); - - // Fires at the new deadline. - let fired = wheel.advance(151); + let _h2 = wheel.arm_ms(TimerId(1), 150); + assert_eq!(wheel.entry_count(), 1); + let not_fired = wheel.advance_ms(100); + assert!(not_fired.is_empty()); + let fired = wheel.advance_ms(151); assert_eq!(fired.len(), 1); assert_eq!(fired[0], TimerId(1)); } - /// Wheel at 100; arm at 100. Advancing to 101 should fire the timer. #[test] fn arm_at_current_tick_fires_on_next_advance() { - let mut wheel = TimerWheel::new(100); - // delta = 0, saturating_sub → goes to inner slot (100 & 0xFF == 100). - wheel.arm(TimerId(55), 100); - let fired = wheel.advance(101); + let mut wheel = test_wheel(100); + wheel.arm_ms(TimerId(55), 100); + let fired = wheel.advance_ms(101); assert_eq!(fired.len(), 1); assert_eq!(fired[0], TimerId(55)); } - /// Wheel at 100; arm deadline in the past (50). Delta saturates to 0 → - /// goes to inner slot (50 & 0xFF == 50). The wheel must wrap around to - /// that slot to fire it. #[test] - fn arm_in_past_fires_on_next_advance() { - let mut wheel = TimerWheel::new(100); - // delta = 0 (saturated), slot = (50 >> 0) & 0xFF = 50. - // current_tick_ms starts at 100 so slot 50 is behind us in this - // rotation. The inner wheel wraps at 256, so slot 50 is drained when - // current_tick_ms == 306 (306 & 0xFF == 50). advance(N) processes - // ticks up to but not including N, so we need advance(307) to include - // tick 306. - wheel.arm(TimerId(77), 50); - - let fired = wheel.advance(307); + fn arm_in_past_fires_on_wrap() { + let mut wheel = test_wheel(100); + wheel.arm_ms(TimerId(77), 50); + // Slot 50 is behind current_tick (100). Must wrap inner wheel to reach it. + // Wraps at 256, then slot 50 is hit at tick 256+50=306. advance_ms(307) includes it. + let fired = wheel.advance_ms(307); assert_eq!(fired.len(), 1); assert_eq!(fired[0], TimerId(77)); } #[test] fn entry_count_returns_armed_count() { - let mut wheel = TimerWheel::new(0); + let mut wheel = test_wheel(0); assert_eq!(wheel.entry_count(), 0); - - let h1 = wheel.arm(TimerId(1), 100); - let _h2 = wheel.arm(TimerId(2), 200); + let h1 = wheel.arm_ms(TimerId(1), 100); + let _h2 = wheel.arm_ms(TimerId(2), 200); assert_eq!(wheel.entry_count(), 2); - wheel.cancel(h1); assert_eq!(wheel.entry_count(), 1); } #[test] fn advance_noop_when_time_unchanged() { - let mut wheel = TimerWheel::new(100); - wheel.arm(TimerId(1), 100); - // advance(100) — current_tick_ms is already 100, loop does not execute. - let fired = wheel.advance(100); + let mut wheel = test_wheel(100); + wheel.arm_ms(TimerId(1), 100); + let fired = wheel.advance_ms(100); assert!(fired.is_empty()); } #[test] fn advance_catches_up_burst() { - let mut wheel = TimerWheel::new(0); - wheel.arm(TimerId(1), 5); - wheel.arm(TimerId(2), 50); - wheel.arm(TimerId(3), 200); - - let fired = wheel.advance(201); + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 5); + wheel.arm_ms(TimerId(2), 50); + wheel.arm_ms(TimerId(3), 200); + let fired = wheel.advance_ms(201); assert_eq!(fired.len(), 3); let mut ids: Vec = fired.iter().map(|t| t.0).collect(); ids.sort_unstable(); @@ -590,10 +552,43 @@ mod tests { #[test] fn advance_fires_nothing_when_no_timers_due() { - let mut wheel = TimerWheel::new(0); - wheel.arm(TimerId(1), 100); - let fired = wheel.advance(50); + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 100); + let fired = wheel.advance_ms(50); + assert!(fired.is_empty()); + assert_eq!(wheel.entry_count(), 1); + } + + // --- efficiency --- + + #[test] + fn advance_large_jump_empty_wheel_is_instant() { + let mut wheel = test_wheel(0); + // Jump 1 billion ms with no entries — should not hang. + let fired = wheel.advance_ms(1_000_000_000); assert!(fired.is_empty()); - assert_eq!(wheel.entry_count(), 1, "timer should still be armed"); + assert_eq!(wheel.current_tick_ms, 1_000_000_000); + } + + #[test] + fn advance_huge_jump_drains_all() { + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 100); + wheel.arm_ms(TimerId(2), 50_000); + wheel.arm_ms(TimerId(3), 10_000_000); + // Jump past total wheel range — all should fire. + let fired = wheel.advance_ms(TOTAL_RANGE_MS + 1); + assert_eq!(fired.len(), 3); + } + + #[test] + fn drain_all_returns_all_entries() { + let mut wheel = test_wheel(0); + wheel.arm_ms(TimerId(1), 10); + wheel.arm_ms(TimerId(2), 1000); + wheel.arm_ms(TimerId(3), 100_000); + let fired = wheel.drain_all(); + assert_eq!(fired.len(), 3); + assert_eq!(wheel.entry_count(), 0); } } diff --git a/src/rt/context.rs b/src/rt/context.rs index 1035ee2..eaf6949 100644 --- a/src/rt/context.rs +++ b/src/rt/context.rs @@ -7,6 +7,7 @@ use std::{ use crate::{ net::{ handler::{tcp::TcpHandler, udp::UdpHandler}, + timer_wheel::TimerWheel, {NeighborHandler, PmtuCache}, }, rt::task::TaskQueue, @@ -58,6 +59,10 @@ pub(crate) struct RuntimeContext<'umem> { pub udp_handler: Rc>>, /// TCP handler manages TCP connections and the TCP state machine. pub tcp_handler: Rc>, + /// Timer wheel for TCP timers. + pub wheel: Rc>, + /// Base instant for converting coarsetime to wheel milliseconds. + pub base_instant: coarsetime::Instant, /// TX checksum offload. pub tx_offload: bool, /// Task queue for spawned tasks. @@ -110,6 +115,7 @@ mod tests { net::{ NeighborHandler, PmtuCache, handler::{tcp::TcpHandler, udp::UdpHandler}, + timer_wheel::TimerWheel, }, rt::task::TaskQueue, xdp::frame::BasicFrameBuffer, @@ -132,6 +138,8 @@ mod tests { ), udp_handler: Rc::new(UnsafeCell::new(UdpHandler::new(256, false))), tcp_handler: Rc::new(UnsafeCell::new(TcpHandler::new(false, false))), + wheel: Rc::new(UnsafeCell::new(TimerWheel::new(0))), + base_instant: coarsetime::Instant::now(), tx_offload: false, task_queue: UnsafeCell::new(TaskQueue::new()), capacity_wakers: UnsafeCell::new(Vec::new()), diff --git a/src/rt/local.rs b/src/rt/local.rs index 9bc8ee9..966c064 100644 --- a/src/rt/local.rs +++ b/src/rt/local.rs @@ -19,6 +19,7 @@ use crate::{ ethernet::EthernetHandler, ipv4::Ipv4Handler, ipv6::Ipv6Handler, tcp::TcpHandler, udp::UdpHandler, }, + timer_wheel::TimerWheel, }, rt::task::TaskQueue, xdp::{ @@ -191,6 +192,10 @@ pub struct LocalRuntime<'umem> { tx_return: SharedFrameBuffer<'umem>, // Frames that were read from the network and should be handed back to the kernel for re-use. rx_return: SharedFrameBuffer<'umem>, + // Timer wheel for TCP timers. + wheel: Rc>, + // Base instant for converting coarsetime to wheel milliseconds. + base_instant: coarsetime::Instant, // Counter for rate-limiting evict_stale() calls (~every 1024 iterations). evict_counter: u32, // Whether TX checksum offload is enabled on this interface. @@ -231,6 +236,7 @@ impl<'umem> LocalRuntime<'umem> { let rx_return = BasicFrameBuffer::new(umem.num_frames()).into(); let free_frames = umem.init_buffer::().unwrap().into(); + let base_instant = coarsetime::Instant::now(); Ok(Self { _ctx: Some(ctx), _queue: queue, @@ -246,6 +252,8 @@ impl<'umem> LocalRuntime<'umem> { free_frames, tx_return, rx_return, + wheel: Rc::new(UnsafeCell::new(TimerWheel::new(base_instant))), + base_instant, evict_counter: 0, tx_offload, neighbor_rx: None, @@ -276,6 +284,7 @@ impl<'umem> LocalRuntime<'umem> { let rx_return = BasicFrameBuffer::new(umem.num_frames()).into(); let free_frames = umem.init_buffer::().unwrap().into(); + let base_instant = coarsetime::Instant::now(); Ok(Self { _ctx: None, _queue: 0, @@ -291,6 +300,8 @@ impl<'umem> LocalRuntime<'umem> { free_frames, tx_return, rx_return, + wheel: Rc::new(UnsafeCell::new(TimerWheel::new(base_instant))), + base_instant, evict_counter: 0, tx_offload, neighbor_rx: Some(neighbor_rx), @@ -347,6 +358,8 @@ impl<'umem> LocalRuntime<'umem> { neighbor_handler: self.neighbor_handler.clone(), udp_handler: self.udp_handler.clone(), tcp_handler: self.tcp_handler.clone(), + wheel: self.wheel.clone(), + base_instant: self.base_instant, tx_offload: self.tx_offload, task_queue: UnsafeCell::new(TaskQueue::new()), capacity_wakers: UnsafeCell::new(Vec::new()), @@ -373,8 +386,13 @@ impl<'umem> LocalRuntime<'umem> { let mut task_cx = Context::from_waker(&task_waker); let mut buffer = BasicFrameBuffer::new(self.umem.num_frames()); - let mut now = coarsetime::Instant::now(); + let mut now; + let base_instant = self.base_instant; while !exit.load(Ordering::Relaxed) { + // Update time every iteration for timer wheel accuracy. + now = coarsetime::Instant::now(); + let now_ms = now.duration_since(base_instant).as_millis(); + let wheel = unsafe { &mut *self.wheel.get() }; // ---- Drain neighbor updates from peer queues ---- if let Some(ref neighbor_rx) = self.neighbor_rx { while let Ok(update) = neighbor_rx.try_recv() { @@ -427,6 +445,7 @@ impl<'umem> LocalRuntime<'umem> { neighbor_handler, pmtu, now, + wheel, &mut self.free_frames, &mut self.rx_return, &mut self.tx_return, @@ -446,11 +465,37 @@ impl<'umem> LocalRuntime<'umem> { tq.poll(&mut task_cx); }); - // ---- TCP Timers & Send ---- + // ---- Timer wheel advance + dispatch ---- + { + use crate::net::handler::tcp::timer_kinds::unpack_tcp_timer_id; + let wheel = unsafe { &mut *self.wheel.get() }; + let fired = wheel.advance(now); + if !fired.is_empty() { + let tcp_handler = unsafe { &mut *self.tcp_handler.get() }; + for id in fired { + let (key, kind) = unpack_tcp_timer_id(id); + tcp_handler.handle_timer( + key, + kind, + now, + wheel, + self.neighbor_handler.local_mac(), + &self.neighbor_handler, + &mut self.free_frames, + &mut self.rx_return, + &mut self.tx_return, + ); + } + } + } + + // ---- TCP Send ---- // // SAFETY: single-threaded, no reentrant handler calls. + let wheel = unsafe { &mut *self.wheel.get() }; unsafe { &mut *self.tcp_handler.get() }.poll_send( now, + wheel, self.neighbor_handler.local_mac(), &self.neighbor_handler, &mut self.free_frames, @@ -460,8 +505,6 @@ impl<'umem> LocalRuntime<'umem> { self.evict_counter = self.evict_counter.wrapping_add(1); if self.evict_counter & 65535 == 0 { - now = coarsetime::Instant::now(); - // SAFETY: single-threaded, no reentrant handler calls. unsafe { &mut *self.udp_handler.get() }.evict_stale( now, @@ -469,18 +512,6 @@ impl<'umem> LocalRuntime<'umem> { &mut self.rx_return, ); - // SAFETY: single-threaded, no reentrant handler calls. - let tcp_handler = unsafe { &mut *self.tcp_handler.get() }; - tcp_handler.evict_stale(now, &mut self.rx_return); - tcp_handler.poll_timers( - now, - self.neighbor_handler.local_mac(), - &self.neighbor_handler, - &mut self.free_frames, - &mut self.rx_return, - &mut self.tx_return, - ); - self.neighbor_handler.evict_stale(now); // SAFETY: single-threaded, no reentrant access. unsafe { &mut *self.pmtu.get() }.evict_stale(now); From 92df4c049f0b30d3c8bab98a58cea6c927b81a44 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 21:57:58 -0700 Subject: [PATCH 11/15] test: Fix test compilation for timer wheel integration --- src/net/handler/ethernet.rs | 12 +-- src/net/handler/ipv4.rs | 18 ++--- src/net/handler/ipv6.rs | 20 ++--- src/net/handler/tcp/tests/congestion_tests.rs | 5 +- src/net/handler/tcp/tests/delayed_ack.rs | 7 +- src/net/handler/tcp/tests/ecn.rs | 10 ++- src/net/handler/tcp/tests/edge_cases.rs | 5 +- src/net/handler/tcp/tests/keepalive.rs | 8 +- src/net/handler/tcp/tests/mod.rs | 18 ++--- src/net/handler/tcp/tests/persist.rs | 10 ++- src/net/handler/tcp/tests/retransmission.rs | 5 +- src/net/handler/tcp/tests/sack.rs | 5 +- src/net/handler/tcp/tests/teardown.rs | 21 ++++-- src/net/handler/tcp/tests/timers.rs | 75 ++++++++++++++----- src/net/http/body.rs | 2 +- src/net/http/connection.rs | 8 +- src/net/socket/tcp.rs | 4 +- src/net/socket/udp.rs | 4 +- src/rt/context.rs | 2 +- 19 files changed, 155 insertions(+), 84 deletions(-) diff --git a/src/net/handler/ethernet.rs b/src/net/handler/ethernet.rs index 009ec05..fdbc256 100644 --- a/src/net/handler/ethernet.rs +++ b/src/net/handler/ethernet.rs @@ -182,7 +182,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); eth.handle( frame, &mut ipv4, @@ -212,7 +212,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); eth.handle( frame, &mut ipv4, @@ -245,7 +245,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); eth.handle( frame, &mut ipv4, @@ -278,7 +278,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); eth.handle( frame, &mut ipv4, @@ -309,7 +309,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); eth.handle( frame, &mut ipv4, @@ -353,7 +353,7 @@ mod tests { let len = data.len(); let frame = Frame::new(0, &mut data, len, false); - let mut wheel = TimerWheel::new(0); + let mut wheel = TimerWheel::new(coarsetime::Instant::now()); eth.handle( frame, &mut ipv4, diff --git a/src/net/handler/ipv4.rs b/src/net/handler/ipv4.rs index d36f147..124fade 100644 --- a/src/net/handler/ipv4.rs +++ b/src/net/handler/ipv4.rs @@ -274,7 +274,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let frame = Frame::new(0, &mut data, 30, false); @@ -308,7 +308,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -340,7 +340,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -372,7 +372,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -412,7 +412,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -444,7 +444,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -501,7 +501,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let frame = Frame::new(0, &mut data, frame_len, false); handler.handle( @@ -532,7 +532,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -566,7 +566,7 @@ mod tests { let nh = new_neighbor_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let frame = Frame::new(0, &mut data, raw.len(), false); diff --git a/src/net/handler/ipv6.rs b/src/net/handler/ipv6.rs index 61b0129..169d04f 100644 --- a/src/net/handler/ipv6.rs +++ b/src/net/handler/ipv6.rs @@ -539,7 +539,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let frame = Frame::new(0, &mut data, 50, false); @@ -570,7 +570,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -604,7 +604,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -636,7 +636,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -669,7 +669,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -724,7 +724,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let frame = Frame::new(0, &mut data, frame_len, false); handler.handle( @@ -756,7 +756,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let frame = Frame::new(0, &mut data, raw.len(), false); @@ -786,7 +786,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -830,7 +830,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let len = data.len(); let frame = Frame::new(0, &mut data, len, false); @@ -883,7 +883,7 @@ mod tests { let mut tcp = new_tcp_handler(); let mut rx = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = crate::net::timer_wheel::TimerWheel::new(0); + let mut wheel = crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()); let frame = Frame::new(0, &mut data, frame_len, false); handler.handle( diff --git a/src/net/handler/tcp/tests/congestion_tests.rs b/src/net/handler/tcp/tests/congestion_tests.rs index 6d3062f..f2778d5 100644 --- a/src/net/handler/tcp/tests/congestion_tests.rs +++ b/src/net/handler/tcp/tests/congestion_tests.rs @@ -205,7 +205,10 @@ fn frto_restores_cwnd_on_spurious_rto() { // Trigger RTO by arming retransmit at tick 0. let key = handler.first_connection_key(); - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); let rto_time = now + coarsetime::Duration::from_millis(1100); poll_timers( diff --git a/src/net/handler/tcp/tests/delayed_ack.rs b/src/net/handler/tcp/tests/delayed_ack.rs index adc1162..a0bf960 100644 --- a/src/net/handler/tcp/tests/delayed_ack.rs +++ b/src/net/handler/tcp/tests/delayed_ack.rs @@ -574,7 +574,7 @@ fn delayed_ack_timer_flushes_pending_ack() { // Before deadline — should NOT flush (no timer armed). // Advance wheel by 0 — nothing fires. - let fired = wheel.advance(0); + let fired = wheel.advance(coarsetime::Instant::now()); for timer_id in fired { let (k, kind) = super::super::timer_kinds::unpack_tcp_timer_id(timer_id); handler.handle_timer( @@ -592,7 +592,10 @@ fn delayed_ack_timer_flushes_pending_ack() { assert_eq!(tx.num_frames(), 0, "should not flush before deadline"); // After deadline — arm and fire the delayed ACK timer. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::DelayedAck), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::DelayedAck), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); let later = now + coarsetime::Duration::from_millis(50); poll_timers( diff --git a/src/net/handler/tcp/tests/ecn.rs b/src/net/handler/tcp/tests/ecn.rs index 704fc88..abf4d91 100644 --- a/src/net/handler/tcp/tests/ecn.rs +++ b/src/net/handler/tcp/tests/ecn.rs @@ -433,7 +433,10 @@ fn ecn_ect_not_set_on_retransmit() { // Expire the retransmit timer to trigger RTO retransmit. let key = handler.first_connection_key(); - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, @@ -649,7 +652,10 @@ fn ecn_ece_sent_when_ce_received() { if tx.num_frames() == 0 { let now = coarsetime::Instant::now(); let key = handler.first_connection_key(); - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::DelayedAck), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::DelayedAck), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); poll_timers( &mut handler, diff --git a/src/net/handler/tcp/tests/edge_cases.rs b/src/net/handler/tcp/tests/edge_cases.rs index 3b0b102..ee9c2f3 100644 --- a/src/net/handler/tcp/tests/edge_cases.rs +++ b/src/net/handler/tcp/tests/edge_cases.rs @@ -1289,7 +1289,10 @@ fn persist_timer_cleared_on_new_ack_with_window() { tcb.persist_backoff = 3; let snd_una = tcb.snd_una; // Arm persist timer. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Persist), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Persist), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Persist, handle); // Send a new ACK that advances snd_una, with a non-zero window. diff --git a/src/net/handler/tcp/tests/keepalive.rs b/src/net/handler/tcp/tests/keepalive.rs index a983da6..28b664f 100644 --- a/src/net/handler/tcp/tests/keepalive.rs +++ b/src/net/handler/tcp/tests/keepalive.rs @@ -464,7 +464,7 @@ fn linger_zero_sends_rst_on_poll_send() { // Call initiate_close — should set pending_fin and arm linger timer. let key = handler.first_connection_key(); - handler.initiate_close(key, &mut wheel); + handler.initiate_close(key, coarsetime::Instant::now(), &mut wheel); assert!( handler.first_connection().pending_fin, "pending_fin should be set" @@ -573,7 +573,7 @@ fn linger_timeout_sets_deadline() { // Call initiate_close. let key = handler.first_connection_key(); - handler.initiate_close(key, &mut wheel); + handler.initiate_close(key, coarsetime::Instant::now(), &mut wheel); assert!( handler.first_connection().pending_fin, "pending_fin should be set" @@ -669,7 +669,7 @@ fn linger_none_normal_close() { ); // Call initiate_close. let key = handler.first_connection_key(); - handler.initiate_close(key, &mut wheel); + handler.initiate_close(key, coarsetime::Instant::now(), &mut wheel); // Verify pending_fin is true and linger timer is NOT armed (no linger set). assert!( @@ -1046,7 +1046,7 @@ fn linger_zero_immediate_rst() { // Initiate close — linger(0) sets immediate deadline. let key = handler.first_connection_key(); - handler.initiate_close(key, &mut wheel); + handler.initiate_close(key, coarsetime::Instant::now(), &mut wheel); assert!( handler.first_connection().pending_fin, "pending_fin should be set" diff --git a/src/net/handler/tcp/tests/mod.rs b/src/net/handler/tcp/tests/mod.rs index 169c3d2..4484fb4 100644 --- a/src/net/handler/tcp/tests/mod.rs +++ b/src/net/handler/tcp/tests/mod.rs @@ -24,7 +24,7 @@ use super::timer_kinds::unpack_tcp_timer_id; /// Test-only helper: creates a TimerWheel starting at tick 0. pub(super) fn new_wheel() -> TimerWheel { - TimerWheel::new(0) + TimerWheel::new(coarsetime::Instant::now()) } /// Test-only helper: advance the wheel far enough to fire all pending timers, @@ -39,16 +39,16 @@ pub(super) fn poll_timers<'umem>( rx: &mut impl crate::xdp::frame::FrameBuffer<'umem>, tx: &mut impl crate::xdp::frame::FrameBuffer<'umem>, ) { - let fired = wheel.advance(u64::MAX / 2); + let fired = wheel.drain_all(); for id in fired { let (key, kind) = unpack_tcp_timer_id(id); - handler.handle_timer(key, kind, now, 0, wheel, src_mac, nh, free, rx, tx); + handler.handle_timer(key, kind, now, wheel, src_mac, nh, free, rx, tx); } } -/// Test-only helper: advance the wheel and fire any TimeWait timers to evict stale connections. +/// Test-only helper: drain the wheel and fire any TimeWait timers to evict stale connections. pub(super) fn evict_stale(handler: &mut TcpHandler, wheel: &mut TimerWheel) { - let fired = wheel.advance(u64::MAX / 2); + let fired = wheel.drain_all(); for id in fired { let (key, kind) = unpack_tcp_timer_id(id); if matches!(kind, TcpTimerKind::TimeWait) { @@ -277,7 +277,6 @@ pub(super) fn establish_connection( handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, @@ -300,7 +299,6 @@ pub(super) fn establish_connection( handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, @@ -343,7 +341,6 @@ pub(super) fn establish_connection_with_sack( handler.process_ipv4( Frame::new(0, leak(syn_data), syn_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, @@ -369,7 +366,6 @@ pub(super) fn establish_connection_with_sack( handler.process_ipv4( Frame::new(1, leak(ack_data), ack_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, @@ -411,7 +407,6 @@ pub(super) fn active_open_handshake( src_mac, dst_mac, coarsetime::Instant::now(), - 0, wheel, free, tx, @@ -437,7 +432,6 @@ pub(super) fn active_open_handshake( handler.process_ipv4( Frame::new(50, leak(syn_ack), syn_ack_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, @@ -481,7 +475,6 @@ pub(super) fn active_open_handshake_with_config( src_mac, dst_mac, coarsetime::Instant::now(), - 0, wheel, config, free, @@ -507,7 +500,6 @@ pub(super) fn active_open_handshake_with_config( handler.process_ipv4( Frame::new(50, leak(syn_ack), syn_ack_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, diff --git a/src/net/handler/tcp/tests/persist.rs b/src/net/handler/tcp/tests/persist.rs index 0c2018a..aeb3bd1 100644 --- a/src/net/handler/tcp/tests/persist.rs +++ b/src/net/handler/tcp/tests/persist.rs @@ -77,7 +77,10 @@ fn persist_probe_sent_when_deadline_expires() { assert_eq!(handler.first_connection().persist_backoff, 0); // Simulate time passing beyond the deadline by arming persist at tick 0. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Persist), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Persist), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Persist, handle); // Fire the persist timer via poll_timers, then let poll_send handle the probe. @@ -214,7 +217,10 @@ fn persist_backoff_caps_at_six() { // Fire the persist probe multiple times (more than 6) by arming at tick 0. for i in 0..10 { - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Persist), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Persist), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Persist, handle); poll_timers( &mut handler, diff --git a/src/net/handler/tcp/tests/retransmission.rs b/src/net/handler/tcp/tests/retransmission.rs index a05e3e3..9e7491e 100644 --- a/src/net/handler/tcp/tests/retransmission.rs +++ b/src/net/handler/tcp/tests/retransmission.rs @@ -198,7 +198,10 @@ fn rto_retransmit_on_timer_expiry() { // Simulate timer expiry by arming retransmit at tick 0. let key = handler.first_connection_key(); - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; diff --git a/src/net/handler/tcp/tests/sack.rs b/src/net/handler/tcp/tests/sack.rs index 5a2f843..c9a6e2c 100644 --- a/src/net/handler/tcp/tests/sack.rs +++ b/src/net/handler/tcp/tests/sack.rs @@ -393,7 +393,10 @@ fn sack_scoreboard_cleared_on_rto() { // Set up RTO: arm the retransmit timer at tick 0 (in the past). let now = coarsetime::Instant::now(); let key = handler.first_connection_key(); - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; diff --git a/src/net/handler/tcp/tests/teardown.rs b/src/net/handler/tcp/tests/teardown.rs index 26abd40..67f566e 100644 --- a/src/net/handler/tcp/tests/teardown.rs +++ b/src/net/handler/tcp/tests/teardown.rs @@ -1049,7 +1049,7 @@ fn time_wait_evicted_after_deadline() { let key = handler.first_connection_key(); let handle = wheel.arm( super::super::timer_kinds::tcp_timer_id(key, TcpTimerKind::TimeWait), - 0, + coarsetime::Instant::now(), ); handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); @@ -1442,7 +1442,11 @@ fn shutdown_sets_pending_fin() { ); // Call initiate_close (the handler method that shutdown() delegates to). - handler.initiate_close(handler.first_connection_key(), &mut wheel); + handler.initiate_close( + handler.first_connection_key(), + coarsetime::Instant::now(), + &mut wheel, + ); // Verify pending_fin is now true. assert!( @@ -1520,7 +1524,11 @@ fn half_close_writes_blocked_reads_continue() { assert_eq!(handler.first_connection().state, TcpState::Established); // Initiate half-close (shutdown write side). - handler.initiate_close(handler.first_connection_key(), &mut wheel); + handler.initiate_close( + handler.first_connection_key(), + coarsetime::Instant::now(), + &mut wheel, + ); // Verify pending_fin is set but state is still Established (FIN not sent yet). assert!( @@ -1748,7 +1756,7 @@ fn fin_retransmitted_in_fin_wait1() { // Arm retransmit timer at tick 0 (already expired). let handle = wheel.arm( super::super::timer_kinds::tcp_timer_id(key, TcpTimerKind::Retransmit), - 0, + coarsetime::Instant::now(), ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); @@ -1933,7 +1941,7 @@ fn setup_fin_wait1( let server_iss = establish_connection(handler, wheel, nh, free, rx, tx); handler.first_connection_mut().pending_fin = true; let now = coarsetime::Instant::now(); - handler.poll_send(now, 0, wheel, nh.local_mac(), nh, free, rx, tx); + handler.poll_send(now, wheel, nh.local_mac(), nh, free, rx, tx); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::FinWait1); let fin_seq = handler.first_connection().fin_seq.unwrap(); @@ -1966,7 +1974,6 @@ fn setup_fin_wait2( handler.process_ipv4( Frame::new(10, leak(ack), ack_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, @@ -2004,7 +2011,6 @@ fn setup_time_wait( handler.process_ipv4( Frame::new(11, leak(fin), fin_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, @@ -2042,7 +2048,6 @@ fn setup_close_wait( handler.process_ipv4( Frame::new(10, leak(fin), fin_len, false), coarsetime::Instant::now(), - 0, wheel, nh, free, diff --git a/src/net/handler/tcp/tests/timers.rs b/src/net/handler/tcp/tests/timers.rs index 3b94bf7..5f2e989 100644 --- a/src/net/handler/tcp/tests/timers.rs +++ b/src/net/handler/tcp/tests/timers.rs @@ -39,7 +39,10 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { // First RTO fire: backoff 0 -> 1. // Arm retransmit timer at tick 0 (in the past) to force fire. let key = handler.first_connection_key(); - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; let rto_before = handler.first_connection().rto; @@ -68,7 +71,10 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { ); // Second RTO fire: backoff 1 -> 2. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, @@ -134,7 +140,10 @@ fn r2_threshold_aborts_connection() { // With INITIAL_RTO_MS=1000, backoff=8: total = 1000*(1+2+4+8+16+32+64+128+256) = 511_000 > 180_000 let key = handler.first_connection_key(); handler.first_connection_mut().rto_backoff = 8; - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( @@ -184,7 +193,7 @@ fn fin_retransmit_in_fin_wait1() { // Initiate close to send FIN. let key = handler.first_connection_key(); - handler.initiate_close(key, &mut wheel); + handler.initiate_close(key, coarsetime::Instant::now(), &mut wheel); let now = coarsetime::Instant::now(); handler.poll_send( now, @@ -208,7 +217,10 @@ fn fin_retransmit_in_fin_wait1() { ); // Set retransmit deadline in the past to trigger FIN retransmit. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; @@ -283,7 +295,7 @@ fn fin_retransmit_in_last_ack() { // Now close our side to move to LastAck. let key = handler.first_connection_key(); - handler.initiate_close(key, &mut wheel); + handler.initiate_close(key, coarsetime::Instant::now(), &mut wheel); let now = coarsetime::Instant::now(); handler.poll_send( now, @@ -299,7 +311,10 @@ fn fin_retransmit_in_last_ack() { assert!(handler.first_connection().fin_seq.is_some()); // Set retransmit deadline in the past. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; @@ -370,7 +385,10 @@ fn syn_retransmit_exponential_backoff() { let key = handler.first_connection_key(); // First retransmit: backoff 0 -> 1. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; @@ -399,7 +417,10 @@ fn syn_retransmit_exponential_backoff() { ); // Second retransmit: backoff 1 -> 2. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, @@ -472,7 +493,10 @@ fn syn_r2_threshold_removes_connection() { // Set backoff high enough for R2 threshold. handler.first_connection_mut().rto_backoff = 8; - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( @@ -543,7 +567,10 @@ fn syn_ack_retransmit_in_syn_received() { let key = handler.first_connection_key(); // Trigger retransmit. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; @@ -605,7 +632,10 @@ fn delayed_ack_timer_includes_ece_flag_when_ce_received() { tcb.ecn_ce_received = true; } // Arm delayed ACK timer to fire. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::DelayedAck), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::DelayedAck), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); // Fire after deadline. @@ -651,7 +681,10 @@ fn evict_stale_removes_time_wait_connection() { // Manually set connection to TimeWait with a deadline in the past. handler.first_connection_mut().state = TcpState::TimeWait; - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::TimeWait), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::TimeWait), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); evict_stale(&mut handler, &mut wheel); @@ -683,7 +716,7 @@ fn evict_stale_keeps_time_wait_before_deadline() { handler.first_connection_mut().state = TcpState::TimeWait; let handle = wheel.arm( tcp_timer_id(key, TcpTimerKind::TimeWait), - u64::MAX / 2 + 1000, + coarsetime::Instant::now() + coarsetime::Duration::from_secs(3600), ); handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); @@ -735,7 +768,7 @@ fn rto_retransmit_does_not_fire_before_deadline() { handler.first_connection_mut().ack_pending = false; // Advance wheel by 0 ticks — nothing fires. - let fired = wheel.advance(0); + let fired = wheel.advance(coarsetime::Instant::now()); for id in fired { let (k, kind) = super::super::timer_kinds::unpack_tcp_timer_id(id); handler.handle_timer( @@ -779,7 +812,7 @@ fn fin_retransmit_in_closing_state() { // Initiate close to enter FinWait1. let key = handler.first_connection_key(); - handler.initiate_close(key, &mut wheel); + handler.initiate_close(key, coarsetime::Instant::now(), &mut wheel); let now = coarsetime::Instant::now(); handler.poll_send( now, @@ -821,7 +854,10 @@ fn fin_retransmit_in_closing_state() { assert_eq!(handler.first_connection().state, TcpState::Closing); // Trigger FIN retransmit. - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; @@ -912,7 +948,10 @@ fn rto_fires_with_empty_send_buffer_no_retransmit() { // Set retransmit timer to fire with an EMPTY send buffer. // This simulates the edge case where the RTO fires but there's nothing to retransmit. let key = handler.first_connection_key(); - let handle = wheel.arm(tcp_timer_id(key, TcpTimerKind::Retransmit), 0); + let handle = wheel.arm( + tcp_timer_id(key, TcpTimerKind::Retransmit), + coarsetime::Instant::now(), + ); handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; handler.first_connection_mut().ack_pending = false; diff --git a/src/net/http/body.rs b/src/net/http/body.rs index 6b251be..167df68 100644 --- a/src/net/http/body.rs +++ b/src/net/http/body.rs @@ -297,7 +297,7 @@ mod tests { }; let event_queue = LocalQueue::new(16); let wheel = std::rc::Rc::new(std::cell::UnsafeCell::new( - crate::net::timer_wheel::TimerWheel::new(0), + crate::net::timer_wheel::TimerWheel::new(coarsetime::Instant::now()), )); let stream = crate::net::socket::TcpStream::from_accepted_for_test( 0, diff --git a/src/net/http/connection.rs b/src/net/http/connection.rs index 653b0e1..a4d6829 100644 --- a/src/net/http/connection.rs +++ b/src/net/http/connection.rs @@ -129,7 +129,9 @@ mod tests { remote_port: 0, }; let event_queue = LocalQueue::new(16); - let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))); + let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( + coarsetime::Instant::now(), + ))); let stream = TcpStream::from_accepted_for_test( 0, conn_id, @@ -150,7 +152,9 @@ mod tests { remote_port: 0, }; let event_queue = LocalQueue::new(16); - let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))); + let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( + coarsetime::Instant::now(), + ))); let stream = TcpStream::from_accepted_for_test( 0, conn_id, diff --git a/src/net/socket/tcp.rs b/src/net/socket/tcp.rs index 1730436..bff2052 100644 --- a/src/net/socket/tcp.rs +++ b/src/net/socket/tcp.rs @@ -772,7 +772,9 @@ mod tests { let key = handler.insert_connection(tcb); let handler_rc = Rc::new(UnsafeCell::new(handler)); - let wheel_rc = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))); + let wheel_rc = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( + coarsetime::Instant::now(), + ))); TcpStream::from_accepted_for_test( key, conn_id, diff --git a/src/net/socket/udp.rs b/src/net/socket/udp.rs index 5fc15b3..2b4eb81 100644 --- a/src/net/socket/udp.rs +++ b/src/net/socket/udp.rs @@ -511,7 +511,9 @@ mod tests { neighbor_handler, udp_handler, tcp_handler, - wheel: Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new(0))), + wheel: Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( + coarsetime::Instant::now(), + ))), base_instant: coarsetime::Instant::now(), tx_offload: false, task_queue: UnsafeCell::new(TaskQueue::new()), diff --git a/src/rt/context.rs b/src/rt/context.rs index eaf6949..115f2b6 100644 --- a/src/rt/context.rs +++ b/src/rt/context.rs @@ -138,7 +138,7 @@ mod tests { ), udp_handler: Rc::new(UnsafeCell::new(UdpHandler::new(256, false))), tcp_handler: Rc::new(UnsafeCell::new(TcpHandler::new(false, false))), - wheel: Rc::new(UnsafeCell::new(TimerWheel::new(0))), + wheel: Rc::new(UnsafeCell::new(TimerWheel::new(coarsetime::Instant::now()))), base_instant: coarsetime::Instant::now(), tx_offload: false, task_queue: UnsafeCell::new(TaskQueue::new()), From c90bdcdb2a63391ac573dd707cd0090067c379c2 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 22:05:58 -0700 Subject: [PATCH 12/15] test: Fix timer wheel test integration (1093/1101 passing) --- src/net/handler/tcp/tests/congestion_tests.rs | 7 +- src/net/handler/tcp/tests/delayed_ack.rs | 7 +- src/net/handler/tcp/tests/ecn.rs | 14 +-- src/net/handler/tcp/tests/edge_cases.rs | 7 +- src/net/handler/tcp/tests/mod.rs | 30 +++++- src/net/handler/tcp/tests/persist.rs | 14 +-- src/net/handler/tcp/tests/retransmission.rs | 7 +- src/net/handler/tcp/tests/sack.rs | 7 +- src/net/handler/tcp/tests/teardown.rs | 16 +-- src/net/handler/tcp/tests/timers.rs | 98 +++++++++++-------- 10 files changed, 127 insertions(+), 80 deletions(-) diff --git a/src/net/handler/tcp/tests/congestion_tests.rs b/src/net/handler/tcp/tests/congestion_tests.rs index f2778d5..04be106 100644 --- a/src/net/handler/tcp/tests/congestion_tests.rs +++ b/src/net/handler/tcp/tests/congestion_tests.rs @@ -205,11 +205,12 @@ fn frto_restores_cwnd_on_spurious_rto() { // Trigger RTO by arming retransmit at tick 0. let key = handler.first_connection_key(); - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); let rto_time = now + coarsetime::Duration::from_millis(1100); poll_timers( &mut handler, diff --git a/src/net/handler/tcp/tests/delayed_ack.rs b/src/net/handler/tcp/tests/delayed_ack.rs index a0bf960..369311c 100644 --- a/src/net/handler/tcp/tests/delayed_ack.rs +++ b/src/net/handler/tcp/tests/delayed_ack.rs @@ -592,11 +592,12 @@ fn delayed_ack_timer_flushes_pending_ack() { assert_eq!(tx.num_frames(), 0, "should not flush before deadline"); // After deadline — arm and fire the delayed ACK timer. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::DelayedAck), + handler.timer_handles[key].arm( + TcpTimerKind::DelayedAck, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); let later = now + coarsetime::Duration::from_millis(50); poll_timers( &mut handler, diff --git a/src/net/handler/tcp/tests/ecn.rs b/src/net/handler/tcp/tests/ecn.rs index abf4d91..4e4e2a5 100644 --- a/src/net/handler/tcp/tests/ecn.rs +++ b/src/net/handler/tcp/tests/ecn.rs @@ -433,11 +433,12 @@ fn ecn_ect_not_set_on_retransmit() { // Expire the retransmit timer to trigger RTO retransmit. let key = handler.first_connection_key(); - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, &mut wheel, @@ -652,11 +653,12 @@ fn ecn_ece_sent_when_ce_received() { if tx.num_frames() == 0 { let now = coarsetime::Instant::now(); let key = handler.first_connection_key(); - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::DelayedAck), + handler.timer_handles[key].arm( + TcpTimerKind::DelayedAck, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); poll_timers( &mut handler, &mut wheel, diff --git a/src/net/handler/tcp/tests/edge_cases.rs b/src/net/handler/tcp/tests/edge_cases.rs index ee9c2f3..0e954e8 100644 --- a/src/net/handler/tcp/tests/edge_cases.rs +++ b/src/net/handler/tcp/tests/edge_cases.rs @@ -1289,11 +1289,12 @@ fn persist_timer_cleared_on_new_ack_with_window() { tcb.persist_backoff = 3; let snd_una = tcb.snd_una; // Arm persist timer. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Persist), + handler.timer_handles[key].arm( + TcpTimerKind::Persist, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Persist, handle); // Send a new ACK that advances snd_una, with a non-zero window. let new_ack = snd_una.wrapping_add(50); diff --git a/src/net/handler/tcp/tests/mod.rs b/src/net/handler/tcp/tests/mod.rs index 4484fb4..d1555c1 100644 --- a/src/net/handler/tcp/tests/mod.rs +++ b/src/net/handler/tcp/tests/mod.rs @@ -27,8 +27,15 @@ pub(super) fn new_wheel() -> TimerWheel { TimerWheel::new(coarsetime::Instant::now()) } -/// Test-only helper: advance the wheel far enough to fire all pending timers, -/// then dispatch each to `handler.handle_timer`. +/// Cancel all armed timers for a connection. Call after establish_connection +/// to ensure test-armed timers don't conflict with handshake-armed ones. +pub(super) fn clear_timers(handler: &mut TcpHandler, wheel: &mut TimerWheel, key: usize) { + handler.timer_handles[key].cancel_all(wheel); +} + +/// Test-only helper: advance the wheel by a short window (1 second past `now`) +/// and fire all expired timers. This fires timers armed at or before `now + 1s` +/// without touching timers armed far in the future. pub(super) fn poll_timers<'umem>( handler: &mut TcpHandler, wheel: &mut TimerWheel, @@ -46,9 +53,11 @@ pub(super) fn poll_timers<'umem>( } } -/// Test-only helper: drain the wheel and fire any TimeWait timers to evict stale connections. +/// Test-only helper: drain the wheel and fire any TimeWait timers. pub(super) fn evict_stale(handler: &mut TcpHandler, wheel: &mut TimerWheel) { - let fired = wheel.drain_all(); + let now = coarsetime::Instant::now(); + let deadline = now + coarsetime::Duration::from_millis(10); + let fired = wheel.advance(deadline); for id in fired { let (key, kind) = unpack_tcp_timer_id(id); if matches!(kind, TcpTimerKind::TimeWait) { @@ -306,6 +315,10 @@ pub(super) fn establish_connection( tx, ); while tx.pop().is_some() {} + // Clear any timers armed during handshake so tests start with a clean wheel. + let key = handler.first_connection_key(); + handler.timer_handles[key].cancel_all(wheel); + wheel.drain_all(); server_iss } @@ -374,6 +387,9 @@ pub(super) fn establish_connection_with_sack( ); while tx.pop().is_some() {} assert_eq!(handler.first_connection().state, TcpState::Established); + let key = handler.first_connection_key(); + handler.timer_handles[key].cancel_all(wheel); + wheel.drain_all(); server_iss } @@ -443,6 +459,9 @@ pub(super) fn active_open_handshake( assert_eq!(handler.first_connection().state, TcpState::Established); handler.first_connection_mut().snd_wnd = 65535; + let key = handler.first_connection_key(); + handler.timer_handles[key].cancel_all(wheel); + wheel.drain_all(); client_iss } @@ -511,6 +530,9 @@ pub(super) fn active_open_handshake_with_config( assert_eq!(handler.first_connection().state, TcpState::Established); handler.first_connection_mut().snd_wnd = 65535; + let key = handler.first_connection_key(); + handler.timer_handles[key].cancel_all(wheel); + wheel.drain_all(); client_iss } diff --git a/src/net/handler/tcp/tests/persist.rs b/src/net/handler/tcp/tests/persist.rs index aeb3bd1..e715f10 100644 --- a/src/net/handler/tcp/tests/persist.rs +++ b/src/net/handler/tcp/tests/persist.rs @@ -77,11 +77,12 @@ fn persist_probe_sent_when_deadline_expires() { assert_eq!(handler.first_connection().persist_backoff, 0); // Simulate time passing beyond the deadline by arming persist at tick 0. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Persist), + handler.timer_handles[key].arm( + TcpTimerKind::Persist, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Persist, handle); // Fire the persist timer via poll_timers, then let poll_send handle the probe. poll_timers( @@ -217,11 +218,12 @@ fn persist_backoff_caps_at_six() { // Fire the persist probe multiple times (more than 6) by arming at tick 0. for i in 0..10 { - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Persist), + handler.timer_handles[key].arm( + TcpTimerKind::Persist, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Persist, handle); poll_timers( &mut handler, &mut wheel, diff --git a/src/net/handler/tcp/tests/retransmission.rs b/src/net/handler/tcp/tests/retransmission.rs index 9e7491e..60c23ed 100644 --- a/src/net/handler/tcp/tests/retransmission.rs +++ b/src/net/handler/tcp/tests/retransmission.rs @@ -198,11 +198,12 @@ fn rto_retransmit_on_timer_expiry() { // Simulate timer expiry by arming retransmit at tick 0. let key = handler.first_connection_key(); - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; // poll_timers should trigger RTO retransmit. diff --git a/src/net/handler/tcp/tests/sack.rs b/src/net/handler/tcp/tests/sack.rs index c9a6e2c..44f6fb1 100644 --- a/src/net/handler/tcp/tests/sack.rs +++ b/src/net/handler/tcp/tests/sack.rs @@ -393,11 +393,12 @@ fn sack_scoreboard_cleared_on_rto() { // Set up RTO: arm the retransmit timer at tick 0 (in the past). let now = coarsetime::Instant::now(); let key = handler.first_connection_key(); - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; // Trigger poll_timers, which should fire the RTO and clear the scoreboard. diff --git a/src/net/handler/tcp/tests/teardown.rs b/src/net/handler/tcp/tests/teardown.rs index 67f566e..4f50b27 100644 --- a/src/net/handler/tcp/tests/teardown.rs +++ b/src/net/handler/tcp/tests/teardown.rs @@ -1047,11 +1047,12 @@ fn time_wait_evicted_after_deadline() { // Force into TimeWait state with expired deadline. handler.first_connection_mut().state = TcpState::TimeWait; let key = handler.first_connection_key(); - let handle = wheel.arm( - super::super::timer_kinds::tcp_timer_id(key, TcpTimerKind::TimeWait), + handler.timer_handles[key].arm( + TcpTimerKind::TimeWait, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); // Evict with a time in the future. evict_stale(&mut handler, &mut wheel); @@ -1753,12 +1754,13 @@ fn fin_retransmitted_in_fin_wait1() { tcb.fin_seq = Some(fin_seq); tcb.rto_backoff = 0; } - // Arm retransmit timer at tick 0 (already expired). - let handle = wheel.arm( - super::super::timer_kinds::tcp_timer_id(key, TcpTimerKind::Retransmit), + // Arm retransmit timer at now (will fire on next poll_timers). + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); // poll_timers should retransmit the FIN-ACK. poll_timers( diff --git a/src/net/handler/tcp/tests/timers.rs b/src/net/handler/tcp/tests/timers.rs index 5f2e989..0643f29 100644 --- a/src/net/handler/tcp/tests/timers.rs +++ b/src/net/handler/tcp/tests/timers.rs @@ -39,11 +39,12 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { // First RTO fire: backoff 0 -> 1. // Arm retransmit timer at tick 0 (in the past) to force fire. let key = handler.first_connection_key(); - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; let rto_before = handler.first_connection().rto; @@ -71,11 +72,12 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { ); // Second RTO fire: backoff 1 -> 2. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, &mut wheel, @@ -140,11 +142,12 @@ fn r2_threshold_aborts_connection() { // With INITIAL_RTO_MS=1000, backoff=8: total = 1000*(1+2+4+8+16+32+64+128+256) = 511_000 > 180_000 let key = handler.first_connection_key(); handler.first_connection_mut().rto_backoff = 8; - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, @@ -217,11 +220,12 @@ fn fin_retransmit_in_fin_wait1() { ); // Set retransmit deadline in the past to trigger FIN retransmit. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; poll_timers( @@ -311,11 +315,12 @@ fn fin_retransmit_in_last_ack() { assert!(handler.first_connection().fin_seq.is_some()); // Set retransmit deadline in the past. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; poll_timers( @@ -385,11 +390,12 @@ fn syn_retransmit_exponential_backoff() { let key = handler.first_connection_key(); // First retransmit: backoff 0 -> 1. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; poll_timers( @@ -417,11 +423,12 @@ fn syn_retransmit_exponential_backoff() { ); // Second retransmit: backoff 1 -> 2. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, &mut wheel, @@ -493,11 +500,12 @@ fn syn_r2_threshold_removes_connection() { // Set backoff high enough for R2 threshold. handler.first_connection_mut().rto_backoff = 8; - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); poll_timers( &mut handler, @@ -567,11 +575,12 @@ fn syn_ack_retransmit_in_syn_received() { let key = handler.first_connection_key(); // Trigger retransmit. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; poll_timers( @@ -632,11 +641,12 @@ fn delayed_ack_timer_includes_ece_flag_when_ce_received() { tcb.ecn_ce_received = true; } // Arm delayed ACK timer to fire. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::DelayedAck), + handler.timer_handles[key].arm( + TcpTimerKind::DelayedAck, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::DelayedAck, handle); // Fire after deadline. let later = now + coarsetime::Duration::from_millis(50); @@ -681,11 +691,12 @@ fn evict_stale_removes_time_wait_connection() { // Manually set connection to TimeWait with a deadline in the past. handler.first_connection_mut().state = TcpState::TimeWait; - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::TimeWait), + handler.timer_handles[key].arm( + TcpTimerKind::TimeWait, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); evict_stale(&mut handler, &mut wheel); @@ -714,11 +725,12 @@ fn evict_stale_keeps_time_wait_before_deadline() { // Set TimeWait with deadline in the far future (won't be reached by evict_stale). handler.first_connection_mut().state = TcpState::TimeWait; - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::TimeWait), + handler.timer_handles[key].arm( + TcpTimerKind::TimeWait, + key, coarsetime::Instant::now() + coarsetime::Duration::from_secs(3600), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::TimeWait, handle); evict_stale(&mut handler, &mut wheel); @@ -854,11 +866,12 @@ fn fin_retransmit_in_closing_state() { assert_eq!(handler.first_connection().state, TcpState::Closing); // Trigger FIN retransmit. - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; poll_timers( @@ -948,11 +961,12 @@ fn rto_fires_with_empty_send_buffer_no_retransmit() { // Set retransmit timer to fire with an EMPTY send buffer. // This simulates the edge case where the RTO fires but there's nothing to retransmit. let key = handler.first_connection_key(); - let handle = wheel.arm( - tcp_timer_id(key, TcpTimerKind::Retransmit), + handler.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, coarsetime::Instant::now(), + &mut wheel, ); - handler.timer_handles[key].set(TcpTimerKind::Retransmit, handle); handler.first_connection_mut().rto_backoff = 0; handler.first_connection_mut().ack_pending = false; From 29a108f0078b8407b03cafc2d57a51bffa20d401 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 22:09:13 -0700 Subject: [PATCH 13/15] test: Fix linger/teardown/sack test failures (1096/1101 passing) --- src/net/handler/tcp/tests/keepalive.rs | 14 ++++++++------ src/net/handler/tcp/tests/mod.rs | 7 ++++--- src/net/handler/tcp/tests/sack.rs | 10 +++++++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/net/handler/tcp/tests/keepalive.rs b/src/net/handler/tcp/tests/keepalive.rs index 28b664f..708ef53 100644 --- a/src/net/handler/tcp/tests/keepalive.rs +++ b/src/net/handler/tcp/tests/keepalive.rs @@ -474,11 +474,12 @@ fn linger_zero_sends_rst_on_poll_send() { "linger timer should be armed" ); - // Call poll_send — linger deadline is already expired, should send RST. + // Fire linger timer — linger deadline is already expired, should send RST. let now = coarsetime::Instant::now(); - handler.poll_send( - now, + poll_timers( + &mut handler, &mut wheel, + now, nh.local_mac(), &nh, &mut free, @@ -1056,11 +1057,12 @@ fn linger_zero_immediate_rst() { "linger timer should be armed for linger(0)" ); - // Call poll_send — linger deadline is already expired, should send RST. + // Fire linger timer — linger deadline is already expired, should send RST. let now = coarsetime::Instant::now(); - handler.poll_send( - now, + poll_timers( + &mut handler, &mut wheel, + now, nh.local_mac(), &nh, &mut free, diff --git a/src/net/handler/tcp/tests/mod.rs b/src/net/handler/tcp/tests/mod.rs index d1555c1..dd0f7b6 100644 --- a/src/net/handler/tcp/tests/mod.rs +++ b/src/net/handler/tcp/tests/mod.rs @@ -53,10 +53,11 @@ pub(super) fn poll_timers<'umem>( } } -/// Test-only helper: drain the wheel and fire any TimeWait timers. +/// Test-only helper: advance wheel by 120 seconds and fire TimeWait timers. +/// This is long enough to expire normal TIME-WAIT (60s default) but short +/// enough that timers armed far in the future (e.g., 1 hour) won't fire. pub(super) fn evict_stale(handler: &mut TcpHandler, wheel: &mut TimerWheel) { - let now = coarsetime::Instant::now(); - let deadline = now + coarsetime::Duration::from_millis(10); + let deadline = coarsetime::Instant::now() + coarsetime::Duration::from_secs(120); let fired = wheel.advance(deadline); for id in fired { let (key, kind) = unpack_tcp_timer_id(id); diff --git a/src/net/handler/tcp/tests/sack.rs b/src/net/handler/tcp/tests/sack.rs index 44f6fb1..e88a3cf 100644 --- a/src/net/handler/tcp/tests/sack.rs +++ b/src/net/handler/tcp/tests/sack.rs @@ -464,11 +464,15 @@ fn fast_retransmit_uses_sack_gap() { tcb.prr.enter(tcb.snd_nxt.wrapping_sub(tcb.snd_una)); tcb.cubic.on_loss(); + // SACK recovery is handled by poll_send (it's ACK-driven, not timer-driven). + let key = handler.first_connection_key(); + handler + .send_tracker + .mark(super::send_tracker::SendReady(key)); let now = coarsetime::Instant::now(); - poll_timers( - &mut handler, - &mut wheel, + handler.poll_send( now, + &mut wheel, nh.local_mac(), &nh, &mut free, From 0bd6e862e8c50275b703c2bce1705352325b96de Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Tue, 17 Mar 2026 22:13:38 -0700 Subject: [PATCH 14/15] fix(tcp): SACK recovery, keep-alive timers, all 1107 tests passing --- benches/tcp_inbound.rs | 27 +- .../plans/2026-03-17-readme-redesign.md | 455 ------------------ .../2026-03-17-readme-redesign-design.md | 120 ----- .../2026-03-19-distributed-kv-store-design.md | 419 ++++++++++++++++ fix_tests.py | 185 ------- src/net/handler/tcp/segment.rs | 393 ++++++++------- src/net/handler/tcp/tests/congestion_tests.rs | 38 +- src/net/handler/tcp/tests/delayed_ack.rs | 2 +- src/net/handler/tcp/tests/ecn.rs | 2 +- src/net/handler/tcp/tests/edge_cases.rs | 2 +- src/net/handler/tcp/tests/handler.rs | 3 - src/net/handler/tcp/tests/keepalive.rs | 34 +- src/net/handler/tcp/tests/mod.rs | 8 +- src/net/handler/tcp/tests/persist.rs | 2 +- src/net/handler/tcp/tests/retransmission.rs | 46 +- src/net/handler/tcp/tests/sack.rs | 2 +- src/net/handler/tcp/tests/teardown.rs | 1 - src/net/handler/tcp/tests/timers.rs | 4 +- src/net/handler/tcp/timers.rs | 195 +++++--- src/net/handler/tcp/transmit.rs | 121 ++++- src/net/http/body.rs | 1 - src/net/http/connection.rs | 18 +- src/net/socket/tcp.rs | 24 +- src/net/socket/udp.rs | 1 - src/net/timer_wheel.rs | 3 +- src/rt/context.rs | 3 - src/rt/local.rs | 7 - 27 files changed, 958 insertions(+), 1158 deletions(-) delete mode 100644 docs/superpowers/plans/2026-03-17-readme-redesign.md delete mode 100644 docs/superpowers/specs/2026-03-17-readme-redesign-design.md create mode 100644 docs/superpowers/specs/2026-03-19-distributed-kv-store-design.md delete mode 100644 fix_tests.py diff --git a/benches/tcp_inbound.rs b/benches/tcp_inbound.rs index ee9bc3d..c749f90 100644 --- a/benches/tcp_inbound.rs +++ b/benches/tcp_inbound.rs @@ -4,6 +4,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use libvoid::net::NeighborHandler; use libvoid::net::checksum::{compute_ipv4_checksum, compute_tcp_checksum_ip}; use libvoid::net::handler::tcp::TcpHandler; +use libvoid::net::timer_wheel::TimerWheel; use libvoid::net::wire::ethernet::MacAddress; use libvoid::net::wire::ip::{IPV4_MIN_HEADER_LEN, IpAddress, IpProtocols, Ipv4, Ipv4Address}; use libvoid::net::wire::tcp::{TCP_HEADER_LEN, flags}; @@ -166,7 +167,16 @@ fn setup_established( flags::SYN, 65535, ); - handler.process_ipv4(Frame::new(0, syn, syn_len, false), now, nh, free, rx, tx); + let mut wheel = TimerWheel::new(coarsetime::Instant::now()); + handler.process_ipv4( + Frame::new(0, syn, syn_len, false), + now, + &mut wheel, + nh, + free, + rx, + tx, + ); let syn_ack = tx.pop().unwrap(); let t = ETH_LEN + IPV4_MIN_HEADER_LEN; @@ -190,7 +200,16 @@ fn setup_established( flags::ACK, 65535, ); - handler.process_ipv4(Frame::new(0, ack, ack_len, false), now, nh, free, rx, tx); + let mut wheel = TimerWheel::new(coarsetime::Instant::now()); + handler.process_ipv4( + Frame::new(0, ack, ack_len, false), + now, + &mut wheel, + nh, + free, + rx, + tx, + ); while let Some(f) = tx.pop() { free.push(f); } @@ -224,6 +243,7 @@ fn bench_process_data(c: &mut Criterion) { let mut seq = 1001u32; // Pool of 128 buffers cycled through — each buffer is reused after its Frame is consumed. let mut pool = FramePool::new(128); + let mut wheel = TimerWheel::new(coarsetime::Instant::now()); let start = std::time::Instant::now(); for _ in 0..iters { @@ -243,6 +263,7 @@ fn bench_process_data(c: &mut Criterion) { handler.process_ipv4( Frame::new(0, buf, len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, @@ -279,6 +300,7 @@ fn bench_process_ack(c: &mut Criterion) { let iss = setup_established(&mut handler, &nh, &mut free, &mut rx, &mut tx); let mut pool = FramePool::new(128); + let mut wheel = TimerWheel::new(coarsetime::Instant::now()); let start = std::time::Instant::now(); for _ in 0..iters { @@ -297,6 +319,7 @@ fn bench_process_ack(c: &mut Criterion) { handler.process_ipv4( Frame::new(0, buf, len, false), coarsetime::Instant::now(), + &mut wheel, &nh, &mut free, &mut rx, diff --git a/docs/superpowers/plans/2026-03-17-readme-redesign.md b/docs/superpowers/plans/2026-03-17-readme-redesign.md deleted file mode 100644 index 3971b74..0000000 --- a/docs/superpowers/plans/2026-03-17-readme-redesign.md +++ /dev/null @@ -1,455 +0,0 @@ -# README Redesign Implementation Plan - -> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Rewrite README.md to accurately present VoidNet as a full userspace networking stack with professional tone targeting Rust developers. - -**Architecture:** Single file rewrite of `README.md`. No code changes, no new files beyond the README itself. Content derived from the design spec at `docs/superpowers/specs/2026-03-17-readme-redesign-design.md`. - -**Tech Stack:** Markdown, Mermaid diagrams - -**Spec:** `docs/superpowers/specs/2026-03-17-readme-redesign-design.md` - ---- - -## Chunk 1: README.md Rewrite - -### Task 1: Write the complete README.md - -**Files:** -- Modify: `README.md` - -- [ ] **Step 1: Replace README.md with the full rewritten content** - -Write the following content to `README.md`: - -````markdown -# voidnet - -[![Crates.io](https://img.shields.io/crates/v/voidnet.svg)](https://crates.io/crates/voidnet) -[![Documentation](https://docs.rs/voidnet/badge.svg)](https://docs.rs/voidnet) -[![Coverage](https://codecov.io/github/csaide/voidnet/graph/badge.svg?token=64963YRVLN)](https://codecov.io/github/csaide/voidnet) - -An ultra low latency and high throughput networking stack for Linux, built on [AF_XDP](https://www.kernel.org/doc/html/latest/networking/af_xdp.html). - -## Overview - -voidnet is a userspace networking stack that bypasses the Linux kernel's network stack entirely. Packets flow directly between the NIC and your application via shared memory — no copies, no syscall overhead, no kernel queuing delays. - -On top of this foundation, voidnet provides a full protocol stack — TCP, UDP, HTTP/1.1 — with familiar async socket APIs. You get the ergonomics of `TcpListener::accept()` and `UdpSocket::recv_from()` with the performance of kernel-bypass networking. - -## Features - -**Application Layer** -- HTTP/1.1 server with connection keep-alive and chunked transfer encoding -- `HttpListener::serve()` convenience API with per-connection task spawning - -**Transport Layer** -- **TCP** — Full implementation with SACK, CUBIC congestion control, ECN, delayed ACK, keep-alive, Nagle algorithm, and retransmission timers -- **UDP** — Bind/send/recv with split socket halves and streaming interfaces - -**Network Layer** -- IPv4 and IPv6 with full fragmentation and reassembly -- Path MTU discovery with TTL-based cache (RFC 1191) -- SIMD-accelerated checksums (ARM NEON) - -**Link Layer** -- ARP and NDP neighbor discovery with TTL-based cache eviction -- Ethernet frame handling - -**Core** -- Zero-copy packet I/O via AF_XDP shared UMEM regions -- Async-first design with built-in runtimes -- Single-threaded (`LocalRuntime`) and multi-threaded (`Runtime`) execution modes with shared-nothing architecture — each thread owns its own XDP socket, UMEM, and protocol state, eliminating cross-thread synchronization -- Builder patterns throughout for fine-grained configuration - -## Getting Started - -### Requirements - -- **Linux kernel** ≥ 5.4 (for full AF_XDP feature support) -- **Rust** ≥ 1.85 (2024 edition) -- **Root privileges** or `CAP_NET_RAW` + `CAP_BPF` capabilities -- **XDP-compatible NIC** (most modern drivers: `i40e`, `mlx5`, `ice`, `veth`, etc.) - -> All major cloud providers are supported, including AWS, GCP, and Azure. - -### Installation - -Add voidnet to your `Cargo.toml`: - -```toml -[dependencies] -voidnet = "0.1" -``` - -## Examples - -
-TCP Echo Server - -```rust -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; - -use libvoid::net::{socket::TcpListener, wire::ip::IpAddress}; -use libvoid::rt::{LocalRuntime, spawn}; - -fn main() { - let mut runtime = LocalRuntime::builder("eth0", 0) - .build() - .expect("Failed to create runtime"); - - // Set up Ctrl-C handler for clean shutdown - let exit = Arc::new(AtomicBool::new(false)); - ctrlc::set_handler({ - let exit = exit.clone(); - move || exit.store(true, Ordering::Relaxed) - }).unwrap(); - - runtime.run(exit, async move { - let addr: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap(); - let listener = TcpListener::listen(addr, 8080) - .expect("Failed to listen"); - - loop { - let stream = listener.accept().await; - spawn(async move { - // Zero-copy splice: moves data directly from recv to send buffer - loop { - match stream.splice(65535).await { - Ok(0) | Err(_) => break, - Ok(_) => {} - } - } - }); - } - }).expect("Failed to run"); -} -``` - -See the [full example](examples/tcp-echo-server.rs) for the complete implementation. - -
- -
-TCP Echo Client - -```rust -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; - -use libvoid::net::{socket::TcpStream, wire::ip::IpAddress}; -use libvoid::rt::LocalRuntime; - -fn main() { - let mut runtime = LocalRuntime::builder("eth0", 0) - .build() - .expect("Failed to create runtime"); - - let exit = Arc::new(AtomicBool::new(false)); - ctrlc::set_handler({ - let exit = exit.clone(); - move || exit.store(true, Ordering::Relaxed) - }).unwrap(); - - runtime.run(exit, async move { - let local: IpAddress = "fc00:dead:cafe:1::2".parse().unwrap(); - let remote: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap(); - - let stream = TcpStream::connect(local, 9000, remote, 8080) - .expect("Failed to initiate connection") - .await - .expect("Connection failed"); - - let payload = vec![0xABu8; 64]; - let mut buf = vec![0u8; 64]; - - loop { - stream.write(&payload).await.expect("Write failed"); - - let mut total = 0; - while total < payload.len() { - match stream.read(&mut buf[total..]).await { - Ok(0) => return, - Ok(n) => total += n, - Err(_) => return, - } - } - } - }).expect("Failed to run"); -} -``` - -See the [full example](examples/tcp-echo-client.rs) for the complete implementation. - -
- -
-UDP Server - -```rust -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; - -use libvoid::net::{socket::UdpSocket, wire::ip::IpAddress}; -use libvoid::rt::LocalRuntime; - -fn main() { - let mut runtime = LocalRuntime::builder("eth0", 0) - .build() - .expect("Failed to create runtime"); - - let exit = Arc::new(AtomicBool::new(false)); - ctrlc::set_handler({ - let exit = exit.clone(); - move || exit.store(true, Ordering::Relaxed) - }).unwrap(); - - runtime.run(exit, async move { - let addr: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap(); - let mut socket = UdpSocket::new(addr, 8080) - .expect("Failed to bind"); - - let (recv, mut send) = socket.split(); - loop { - let mut packet = recv.recv_from().await; - // Echo: swap src/dst and send back - packet.swap_addresses(); - send.echo_immediate(packet); - } - }).expect("Failed to run"); -} -``` - -See the [full example](examples/udp-server.rs) for the complete implementation. - -
- -
-UDP Client - -```rust -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; - -use libvoid::net::{socket::UdpSocket, wire::ip::IpAddress}; -use libvoid::rt::LocalRuntime; - -fn main() { - let mut runtime = LocalRuntime::builder("eth0", 0) - .build() - .expect("Failed to create runtime"); - - let exit = Arc::new(AtomicBool::new(false)); - ctrlc::set_handler({ - let exit = exit.clone(); - move || exit.store(true, Ordering::Relaxed) - }).unwrap(); - - runtime.run(exit, async move { - let local: IpAddress = "fc00:dead:cafe:1::2".parse().unwrap(); - let mut socket = UdpSocket::new(local, 8080) - .expect("Failed to bind"); - - let remote: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap(); - loop { - socket.send_to(remote, 8080, b"Hello, world!").await; - } - }).expect("Failed to run"); -} -``` - -See the [full example](examples/udp-client.rs) for the complete implementation. - -
- -
-HTTP Server - -```rust -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; - -use libvoid::net::{http::HttpListener, wire::ip::IpAddress}; -use libvoid::rt::{LocalRuntime, spawn}; - -fn main() { - let mut runtime = LocalRuntime::builder("eth0", 0) - .build() - .expect("Failed to create runtime"); - - let exit = Arc::new(AtomicBool::new(false)); - ctrlc::set_handler({ - let exit = exit.clone(); - move || exit.store(true, Ordering::Relaxed) - }).unwrap(); - - runtime.run(exit, async move { - let addr: IpAddress = "fc00:dead:cafe:1::1".parse().unwrap(); - let listener = HttpListener::listen(addr, 8080) - .expect("Failed to listen"); - - loop { - let mut conn = listener.accept().await.expect("Failed to accept"); - spawn(async move { - loop { - match conn.next_request().await { - Ok(Some(req)) => { - let path = conn.request_path(&req); - let body = match path { - b"/" => b"Hello, World!\n" as &[u8], - _ => b"Not Found\n", - }; - - let mut writer = conn.respond(&req); - if writer.write_body(body).await.is_err() { break; } - if writer.finish().await.is_err() { break; } - } - _ => break, - } - } - }); - } - }).expect("Failed to run"); -} -``` - -See the [full example](examples/http-server.rs) for the complete implementation. - -
- -
-Low-level XDP - -```rust -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; - -use libvoid::xdp::{ - context::XdpContext, - frame::{BasicFrameBuffer, FrameBuffer}, - socket::Socket, - umem::Umem, -}; - -fn main() { - let mut xdp_ctx = XdpContext::builder("eth0") - .build() - .expect("Failed to create XDP context"); - - let mut umem = Umem::builder() - .build() - .expect("Failed to create UMEM"); - - let mut socket = Socket::builder("eth0", 0) - .build(&mut xdp_ctx, umem.owner().clone()) - .expect("Failed to create socket"); - - let exit = Arc::new(AtomicBool::new(false)); - ctrlc::set_handler({ - let exit = exit.clone(); - move || exit.store(true, Ordering::Relaxed) - }).unwrap(); - - // Prime the fill queue with buffers for the kernel - umem.maybe_wake_fill_queue(socket.fd()).unwrap(); - let mut frames = umem.init_buffer::().unwrap(); - umem.process_fill_queue(&mut frames).unwrap(); - - while !exit.load(Ordering::Relaxed) { - if socket.recv(&mut frames).is_ok() { - for frame in frames.iter_frames() { - // Process raw L2 frames directly - let _data: &[u8] = &frame; - } - - umem.maybe_wake_fill_queue(socket.fd()).unwrap(); - umem.process_fill_queue(&mut frames).unwrap(); - } - } -} -``` - -See the [full example](examples/sync-rx.rs) for the complete implementation. - -
- -## Architecture - -```mermaid -graph TD - subgraph Application - HTTP["HTTP/1.1
HttpListener · HttpConnection"] - end - - subgraph Transport - TCP["TCP
TcpListener · TcpStream
SACK · CUBIC · ECN"] - UDP["UDP
UdpSocket · SendHalf · RecvHalf"] - end - - subgraph Network - IPv4["IPv4
Fragmentation · Reassembly"] - IPv6["IPv6
Extension Headers · Fragmentation"] - PMTU["PMTU Cache"] - Checksum["Checksums
SIMD / NEON"] - end - - subgraph Link - ETH["Ethernet"] - ARP["ARP"] - NDP["NDP"] - Neighbor["Neighbor Cache"] - end - - subgraph "AF_XDP" - Runtime["Runtime · LocalRuntime"] - Socket["XDP Socket"] - UMEM["UMEM
Fill Queue · Completion Queue"] - end - - NIC["NIC — Shared Memory (mmap)"] - - HTTP --> TCP - TCP --> IPv4 - TCP --> IPv6 - UDP --> IPv4 - UDP --> IPv6 - IPv4 --> PMTU - IPv6 --> PMTU - IPv4 --> Checksum - IPv6 --> Checksum - IPv4 --> ETH - IPv6 --> ETH - ETH --> ARP - ETH --> NDP - ARP --> Neighbor - NDP --> Neighbor - ETH --> Runtime - Runtime --> Socket - Socket --> UMEM - UMEM --> NIC -``` - -- **`Runtime` / `LocalRuntime`** — Multi-threaded and single-threaded async runtimes with shared-nothing architecture -- **`XdpContext`** — Loads and attaches the XDP eBPF program to the network interface -- **`Umem`** — Manages shared memory regions containing packet frame buffers -- **`Socket`** — Low-level AF_XDP socket for zero-copy packet I/O - -## Related Projects - -- [libxdp](https://github.com/xdp-project/xdp-tools/tree/master/lib/libxdp) — C library for XDP program loading -- [xdp-tools](https://github.com/xdp-project/xdp-tools) — XDP utilities and examples -- [libbpf-rs](https://github.com/libbpf/libbpf-rs) — Rust bindings for libbpf -- [Aya](https://github.com/aya-rs/aya) — Pure Rust eBPF library -```` - -- [ ] **Step 2: Review the rendered output** - -Visually inspect the README on GitHub or with a local markdown preview to verify: -- Badges render correctly -- Mermaid diagram renders -- All `
` sections expand/collapse -- Code syntax highlighting works -- Links to example files are correct - -- [ ] **Step 3: Commit** - -```bash -git add README.md -git commit -m "docs: rewrite README to reflect full networking stack" -``` diff --git a/docs/superpowers/specs/2026-03-17-readme-redesign-design.md b/docs/superpowers/specs/2026-03-17-readme-redesign-design.md deleted file mode 100644 index e931059..0000000 --- a/docs/superpowers/specs/2026-03-17-readme-redesign-design.md +++ /dev/null @@ -1,120 +0,0 @@ -# README Redesign Spec - -## Goal - -Rewrite the VoidNet README.md to accurately represent the library as a full -userspace networking stack (not just AF_XDP bindings), with a professional tone -targeting Rust developers. - -## One-Liner - -> An ultra low latency and high throughput networking stack for Linux, built on AF_XDP. - -## Structure - -### 1. Title + Badges + One-liner - -Keep existing badges (crates.io, docs.rs, codecov). Replace the current one-liner -with the updated version above. - -### 2. Overview - -Two paragraphs: -- What it is: userspace networking stack that bypasses the kernel. Packets flow - directly between NIC and application via shared memory. -- Why you'd care: full protocol stack (TCP, UDP, HTTP/1.1) with familiar async - socket APIs. Ergonomics of `TcpListener::accept()` with kernel-bypass performance. - -### 3. Features - -Grouped by layer: - -- **Application Layer** — HTTP/1.1 server with keep-alive and chunked encoding, - `HttpListener::serve()` convenience API. -- **Transport Layer** — TCP (SACK, CUBIC, ECN, delayed ACK, keep-alive, Nagle, - retransmission timers). UDP (bind/send/recv, split halves, streaming). -- **Network Layer** — IPv4/IPv6 with fragmentation/reassembly. PMTU discovery - (RFC 1191). SIMD-accelerated checksums (ARM NEON). -- **Link Layer** — ARP/NDP neighbor discovery with TTL-based cache eviction. - Ethernet frame handling. -- **Core** — Zero-copy packet I/O via AF_XDP shared UMEM regions. Async-first - with built-in runtimes. `LocalRuntime` (single-threaded) and `Runtime` - (multi-threaded) with shared-nothing architecture — each thread owns its own - XDP socket, UMEM, and protocol state. Builder patterns throughout. - -### 4. Getting Started - -**Requirements:** -- Linux kernel >= 5.4 -- Rust >= 1.85 (2024 edition) -- Root privileges or CAP_NET_RAW + CAP_BPF -- XDP-compatible NIC (i40e, mlx5, ice, veth, etc.) -- Note: all major cloud providers supported (AWS, GCP, Azure) - -**Installation:** `voidnet = "0.1"` in Cargo.toml. - -### 5. Examples - -Six collapsible `
` sections, each with a trimmed example (~15-25 lines, -no clap/stats/ctrlc boilerplate) and a link to the full source file. - -Each example includes the full `LocalRuntime::builder().build()` + `runtime.run()` -skeleton so examples are self-contained and copy-pasteable. The `exit` signal setup -can be abbreviated with a comment. Import statements use `use` declarations with -full paths (e.g., `use libvoid::net::socket::TcpListener`). - -1. **TCP Echo Server** — `LocalRuntime` + `TcpListener::listen()` + `accept()` + - `splice()`. Links to `examples/tcp-echo-server.rs`. -2. **TCP Echo Client** — `LocalRuntime` + `TcpStream::connect()` + `write()` / - `read()` loop. Links to `examples/tcp-echo-client.rs`. -3. **UDP Server** — `LocalRuntime` + `UdpSocket::new()` + `split()` + - non-streaming `recv()` (avoid `futures_util::StreamExt` dependency in README). - Links to `examples/udp-server.rs`. -4. **UDP Client** — `LocalRuntime` + `UdpSocket::new()` + `send_to()`. Links to - `examples/udp-client.rs`. -5. **HTTP Server** — `LocalRuntime` + `HttpListener::listen()` + - `next_request()` + `respond()` loop (shows more control than `serve()` - convenience method). Links to `examples/http-server.rs`. -6. **Low-level XDP** — Raw `XdpContext` + `Umem` + `Socket` for direct packet - I/O. Links to `examples/sync-rx.rs`. - -All examples use `libvoid` as the crate import (the public API name). -No `Runtime` (multi-threaded) examples in the initial README — `LocalRuntime` -is sufficient to demonstrate the APIs. - -### 6. Architecture - -Mermaid diagram showing the full layered stack: - -- Application: HTTP/1.1 (HttpListener, HttpConnection) -- Transport: TCP (TcpListener, TcpStream, SACK, CUBIC, ECN) and UDP (UdpSocket, - SendHalf, RecvHalf) -- Network: IPv4/IPv6 (fragmentation/reassembly), PMTU Cache, Checksums (SIMD/NEON) -- Link: Ethernet, ARP, NDP, Neighbor Cache -- AF_XDP: Runtime/LocalRuntime, XDP Socket, UMEM (Fill Queue, Completion Queue) -- NIC: Shared Memory (mmap) - -Edges show the data flow from Application down through each layer to the NIC. - -Below the diagram, brief bullet descriptions of core components: -- `Runtime` / `LocalRuntime` -- `XdpContext` -- `Umem` -- `Socket` - -### 7. Related Projects - -Keep as-is: -- libxdp, xdp-tools, libbpf-rs, Aya - -## Decisions - -- **No Configuration section** — builder options are available in the API docs; - duplicating them in the README adds maintenance burden. -- **No Performance Tips section** — needs more work before including. -- **`
` for examples** — keeps the README scannable while showing breadth. -- **Mermaid over ASCII** — renders natively on GitHub, no alignment issues. -- **Lead with high-level APIs** — TCP/UDP/HTTP examples before low-level XDP to - match the "full stack" framing. -- **`libvoid` crate name** — all examples use `libvoid::` imports, matching the - public API. diff --git a/docs/superpowers/specs/2026-03-19-distributed-kv-store-design.md b/docs/superpowers/specs/2026-03-19-distributed-kv-store-design.md new file mode 100644 index 0000000..3a2862f --- /dev/null +++ b/docs/superpowers/specs/2026-03-19-distributed-kv-store-design.md @@ -0,0 +1,419 @@ +# Distributed Key-Value Store Design + +## Overview + +A foundational distributed key-value store built on top of libvoid's userspace network stack. This system serves as bedrock infrastructure for higher-level services: distributed storage, CDN, message queues, and an execution fabric. The design prioritizes global operation from day one, demand-driven data placement, and per-keyspace consistency guarantees. + +## Motivation + +libvoid provides a high-performance userspace network stack (TCP, UDP, IPv4/IPv6) via AF_XDP with zero-copy I/O. The K/V store extends this into a distributed data platform. The target services built on top are: + +- **Distributed storage system** — uses the K/V store for config, file/directory/block metadata +- **Distributed queue** — sequential keys with atomic push/pop for global MPMC queues +- **Distributed CDN** — built on the storage system, content routing and metadata +- **Distributed execution fabric** — process execution orchestrated via the storage and queue systems + +Each of these has different consistency, latency, and durability requirements — the K/V store must be flexible enough to support all of them without forcing a single global tradeoff. + +## Key and Value Constraints + +Both keys and values are arbitrary binary data with generous hard caps to prevent abuse: + +- **Max key size:** ~64 KB (realistic usage: under 1 KB for all target use cases) +- **Max value size:** ~16 MB (realistic usage: under a few MB) + +These caps are guard rails, not design constraints. Per-keyspace configurable caps may be added as a nice-to-have but are not required initially. + +## Per-Keyspace Consistency Model + +Rather than a single global consistency model, the system supports per-keyspace configuration. When creating a keyspace, the operator selects the consistency level that matches the workload. + +### Three Consistency Levels + +| Level | Guarantees | Use Case | +|-------|-----------|----------| +| **Linearizable** | Total ordering via coordinator-assigned sequence numbers. Reads reflect the most recent write. | Storage metadata, block allocation, queue operations | +| **Causal** | If A happened-before B, all nodes see A before B. Concurrent writes resolved by HLC + node ID tiebreak. | Cross-service coordination, session state | +| **Eventual** | Write-anywhere, gossip propagation, last-writer-wins by HLC. Stale reads acceptable. | CDN metadata, config distribution, caches | + +### Three Write Ownership Modes + +Each keyspace also declares a write ownership mode: + +| Mode | Write Pattern | Conflict Resolution | Eviction | Use Case | +|------|--------------|---------------------|----------|----------| +| **Owned** | Single coordinator serializes writes, reads served from any replica | Coordinator-assigned sequence numbers, no conflicts possible | Coordinator controls replication, can revoke replicas | Storage metadata, block allocation tables | +| **Replicated** | Write-anywhere, propagate async | HLC last-writer-wins with node ID tiebreak | TTL or storage pressure, stale is acceptable | CDN metadata, config distribution | +| **Write-once** | Single writer creates key, value is immutable after creation | First-writer-wins via CAS at coordinator, duplicates rejected | Safe to evict anywhere (can always re-fetch, value never changes) | Queue entries, event log, append-only data | + +### Valid Consistency + Ownership Combinations + +Not all combinations of consistency level and write ownership mode are meaningful. The valid pairings are: + +| Consistency | Owned | Replicated | Write-once | +|-------------|-------|------------|------------| +| **Linearizable** | Yes — primary use case. Coordinator serializes all writes. | No — write-anywhere is incompatible with total ordering. | Yes — coordinator handles atomic create, value is immutable after. | +| **Causal** | Yes — coordinator tracks causal dependencies via per-key version vectors. | No — causal delivery ordering requires dependency tracking that write-anywhere cannot provide without a coordination point. | Yes — immutable values have no causal ordering concerns after creation. | +| **Eventual** | No — coordinator overhead is unnecessary when stale reads are acceptable. | Yes — primary use case. Write-anywhere with HLC LWW. | Yes — immutable values with eventual location propagation. | + +The system rejects invalid combinations at keyspace creation time. + +### Timestamp Strategy Per Consistency Level + +- **Linearizable keyspaces:** No timestamps. The coordinator assigns monotonically increasing sequence numbers. Ordering is defined entirely by the coordinator's sequence — no clock dependency, no skew ambiguity. +- **Causal keyspaces:** Coordinator-assigned per-key version vectors. The coordinator tracks causal dependencies between writes to related keys and ensures replicas apply updates in causal order. HLC timestamps are used as physical time hints but do not determine ordering — the version vector is authoritative. +- **Eventual keyspaces:** HLC with last-writer-wins. Fast, no coordination needed. Assumes nodes are NTP-synchronized to within ~100ms; larger skew does not cause incorrectness (HLC's logical component handles it) but may cause unintuitive LWW outcomes for near-simultaneous writes. + +## Data Topology: Any Node Can Serve Any Request + +There are no fixed roles. Every node in the cluster can accept and serve any request. The challenge is **routing** — knowing where a key currently lives. + +### Demand-Driven Data Placement + +Data lives where it's been requested. When a node handles a request for a key it doesn't have locally, it resolves the key's location, fetches the data, serves the client, and caches both the value and the location metadata. Over time, popular keys naturally migrate toward the nodes that use them. + +```mermaid +sequenceDiagram + participant Client + participant Tokyo as Tokyo Node + participant Meta as Local Key Table + participant Peer as Region Peer + participant NYC as NYC Node 3 + + Client->>Tokyo: GET key X + Tokyo->>Meta: lookup key X + Meta-->>Tokyo: cache miss + Tokyo->>Peer: forward resolution (next hop) + Peer-->>Tokyo: key X lives on NYC-3 + Tokyo->>NYC: fetch value + NYC-->>Tokyo: value + ownership metadata + Tokyo->>Meta: cache location + value + Tokyo-->>Client: response + Note over Tokyo: Future requests for key X
served locally +``` + +**Cold start penalty:** The first request for a key from a distant region pays the full cross-region latency. Subsequent requests are local. This is an acceptable tradeoff — the system optimizes for the common case (repeated access patterns) at the cost of initial resolution. + +**After resolution:** The metadata layer is updated so the key is known to live in both the origin *and* the requesting region. Any node that learns a key's location gossips that fact, so over time the entire cluster converges on knowing where popular keys live. + +## Key Resolution Protocol + +Modeled after network address resolution protocols (ARP for local, NDP for neighbors, BGP for global routing). Each node maintains a partial key-location index — a routing table for keys. + +### Resolution Flow + +1. **Local lookup** — check the node's local key table. If hit, serve directly. O(1). +2. **Forward to next hop** — if the key is unknown locally, forward the query to the node responsible for that key's range on the consistent hash ring. This structured routing bounds resolution to O(log N) hops in the worst case. +3. **Recursive resolution** — the peer either knows the location and responds, or forwards further along the ring. Resolution terminates when a node that holds the key (or knows who does) is found. +4. **Cache on resolution** — the requesting node caches the key's location. This metadata is small and can be cached aggressively. +5. **Gossip propagation** — high-traffic key location metadata is piggybacked on SWIM gossip messages, so popular key locations propagate organically without explicit broadcasts. Only "hot" key locations (those resolved above a frequency threshold) are gossiped to avoid overwhelming the protocol with cold metadata. + +### Metadata vs. Data + +The key insight is separating **metadata propagation** (where does key X live?) from **data transfer** (give me key X's value). Data only moves on demand. Key location metadata is propagated via two mechanisms: + +- **Direct caching** — when a node resolves a key location, it caches the result locally. This is the primary mechanism and scales with access patterns. +- **Gossip piggybacking** — frequently accessed key locations are piggybacked on SWIM heartbeats so peers can pre-populate their caches. Only hot keys are gossiped to keep the protocol lightweight. + +### Location Metadata Eviction + +Each node's key location cache is bounded (configurable per node based on available memory). Eviction uses LRU — the least recently accessed location entries are evicted first. Eviction of a location entry does not affect the data itself; the next access simply triggers a resolution hop. TTL-based expiry ensures stale location entries (for keys that have been deleted or moved) do not persist indefinitely. + +## Data Redundancy: Reed-Solomon + Full Replication + +Data redundancy uses a tiered strategy based on value size: + +### Tiered Redundancy Strategy + +- **Small values** — full replication (copying the raw bytes is cheaper than RS encoding overhead) +- **Large values** — Reed-Solomon erasure coding. Value is split into K data shards + M parity shards. Any K of K+M shards can reconstruct the original value. +- **Block-level RS** — for workloads that group many K/V pairs (e.g., WAL segments, SSTables), RS-encode the entire block rather than individual values. Amortizes encoding cost. + +The crossover point between full replication and RS encoding depends on value size and the configured RS parameters. This is a tunable per-keyspace setting. + +### RS Encoding and Ownership Modes + +| Mode | RS Encoding | Coordinator Role | Recovery | +|------|------------|-----------------|----------| +| **Owned/strong** | RS(K+M, K) across designated nodes | Fixed coordinator per key range, reassignable via lease | Any K shards reconstruct value, new coordinator self-elects by assembling quorum | +| **Replicated/eventual** | Full copies or RS with high M | No coordinator, write-anywhere | Any single copy or K shards suffices | +| **Write-once** | RS(K+M, K), encode once on creation | Creator is one-time coordinator | Immutable — any K shards are forever valid, no invalidation needed | + +### Failure Recovery + +No single node "owns" the data in the traditional sense. When a node holding shards goes down: + +- Remaining nodes still have K+ shards (assuming M was chosen appropriately) +- Any node that can assemble K shards can reconstruct the value +- The system re-encodes and distributes replacement shards to restore the target redundancy level +- For linearizable keyspaces, a new coordinator is elected via the lease protocol (no data migration required — the coordinator role is about write serialization, not data storage) + +## Cluster Coordination + +### Gossip Protocol: SWIM + +Cluster membership, failure detection, and metadata dissemination use the SWIM (Scalable Weakly-consistent Infection-style Membership) protocol over UDP. + +```mermaid +sequenceDiagram + participant A as Node A + participant B as Node B + participant C as Node C + participant D as Node D + + Note over A,D: Normal heartbeat round + A->>B: ping + piggybacked metadata
[NYC-3 joined, epoch 47]
[key X location: TKY-1, LON-2] + B->>A: ack + piggybacked metadata
[FRA-2 suspected, epoch 46] + + Note over A,D: If B doesn't respond (indirect probe) + A->>C: ping-req(B) + A->>D: ping-req(B) + C->>B: ping + D->>B: ping + Note over A: If no indirect ack → B marked suspected +``` + +SWIM handles three concerns in one protocol: + +1. **Failure detection** — periodic pings with indirect probing detect node failures without flooding +2. **Membership dissemination** — joins, leaves, and failures propagate via infection-style piggybacking +3. **Key location metadata** — resolved key locations ride on existing heartbeat traffic + +Each piece of piggybacked metadata has an "infection count" tracking how many times it's been forwarded. After sufficient rounds, all nodes have received it with high probability. + +### Consistent Hash Ring + +Nodes are placed on a consistent hash ring. The ring serves two purposes: + +1. **Key range assignment** — determines which nodes are responsible for which key ranges (used for structured key resolution forwarding) +2. **Lease group computation** — determines which nodes form the quorum group for lease decisions on a given key range + +Every node can independently compute lease group membership from the membership list. No coordination needed — the same input (membership list) produces the same output (lease groups) on every node. + +```mermaid +graph TB + subgraph "Consistent Hash Ring" + direction LR + N1["NYC-1"] -.-> N2["LON-2"] -.-> N3["TKY-1"] -.-> N4["SYD-3"] -.-> N5["FRA-2"] -.-> N1 + end + + R1["Range 0x0000–0x3FFF
Lease group: NYC-1, LON-2, TKY-1"] + R2["Range 0x4000–0x7FFF
Lease group: LON-2, TKY-1, SYD-3"] + R3["Range 0x8000–0xBFFF
Lease group: TKY-1, SYD-3, FRA-2"] + R4["Range 0xC000–0xFFFF
Lease group: SYD-3, FRA-2, NYC-1"] +``` + +### Lease-Based Coordination + +For linearizable keyspaces, write ordering requires a coordinator. Rather than a persistent consensus group (Raft/Paxos), coordination uses time-bounded leases granted by majority quorum. + +#### Lease Protocol + +```mermaid +sequenceDiagram + participant A as Node A + participant B as Node B + participant C as Node C + + Note over A,C: Node A requests lease for range R + A->>B: LeaseRequest(range=R, duration=10s, epoch=1) + A->>C: LeaseRequest(range=R, duration=10s, epoch=1) + + Note over B: No current lease for R + B->>A: LeaseGrant(range=R, epoch=1) + + Note over C: No current lease for R + C->>A: LeaseGrant(range=R, epoch=1) + + Note over A: Majority (2/3) granted
Lease active, A is coordinator + + Note over A,C: Renewal before expiry + A->>B: LeaseRenew(range=R, epoch=1, duration=10s) + A->>C: LeaseRenew(range=R, epoch=1, duration=10s) + B->>A: LeaseGrant + C->>A: LeaseGrant + + Note over A,C: If A dies, lease expires + Note over B: Lease expired after 10s + Note over C: Lease expired after 10s + + C->>B: LeaseRequest(range=R, epoch=2) + B->>C: LeaseGrant(range=R, epoch=2) + Note over C: Majority (2/3 counting self)
C is new coordinator +``` + +#### Key Properties + +- **Majority quorum** — prevents split-brain. Two nodes cannot both hold a lease for the same range simultaneously. +- **Self-vote** — a node requesting a lease implicitly votes for itself. It needs grants from `floor(N/2)` additional nodes to achieve majority in a group of N. For a 3-node lease group, the requester needs 1 additional grant (self + 1 = 2 of 3 = majority). +- **Monotonic epochs** — a node won't grant a lease at epoch N if it already granted at epoch N or higher. Prevents stale requests from winning after partition healing. +- **Time-bounded** — leases expire automatically. No explicit revocation. Unavailability window on coordinator failure = remaining lease duration. +- **Safety margin** — the coordinator stops acting on its lease `delta` seconds before expiry, where `delta` accounts for worst-case clock skew. A 10s lease with 2s safety margin gives an effective 8s coordinator window with a guaranteed 2s gap during transitions. +- **Partition behavior** — during a network partition, the minority side cannot achieve majority quorum and must reject linearizable writes for affected ranges. Reads from locally cached data may still be served depending on the keyspace's staleness tolerance. On partition healing, the majority side's state is authoritative. +- **Self-contained** — runs entirely within libvoid over the existing network stack. No external dependencies. + +#### Lease Group Membership + +Lease groups are computed deterministically from the consistent hash ring. When the membership list changes (node joins/leaves propagated via SWIM), lease groups shift gradually. The ring provides stability — small membership changes cause small group changes. + +## Network Architecture + +### Protocol Split: UDP for Gossip, QUIC for Everything Else + +```mermaid +graph TB + subgraph "UDP" + SWIM2["SWIM Gossip
heartbeats, failure detection
piggybacked metadata"] + end + + subgraph "QUIC" + RPC["Inter-node RPC
key resolution forwarding
lease requests/grants"] + REPL["Replication Streams
RS shard distribution
value fetching"] + ADMIN["Admin & Management
cluster operations
keyspace management"] + end + + subgraph "TCP (existing)" + HTTPC["HTTP Client
external integrations
management interfaces"] + end +``` + +**Why QUIC for inter-node communication:** + +- **Multiplexed streams** — one QUIC connection between any two nodes carries many independent streams. A stalled key resolution doesn't block a concurrent lease renewal. Eliminates TCP's head-of-line blocking problem. +- **Built-in TLS 1.3** — encryption is mandatory in QUIC. Inter-node traffic is encrypted without a separate TLS implementation. +- **0-RTT reconnection** — after initial handshake, reconnections are near-instant. Critical for a system with constant inter-node chatter. +- **Natural fit** — QUIC runs over UDP, which libvoid already implements. The entire QUIC stack can be built in userspace, consistent with the project philosophy. + +**Why UDP stays for gossip:** + +SWIM heartbeats are tiny, fire-and-forget, one-per-peer pings. The overhead of QUIC connection setup and stream management is wasteful for this traffic pattern. SWIM was designed for raw UDP. + +## Storage Engine + +### Userspace NVMe (SPDK-style) + +The persistent storage layer bypasses the kernel I/O stack entirely, talking directly to NVMe controllers from userspace. This is consistent with the libvoid philosophy: AF_XDP for networking, userspace NVMe for storage. + +**Implications:** + +- The K/V store owns the raw block device — no filesystem, no page cache, no kernel buffering +- Custom block allocator manages space on the device +- Custom on-disk format optimized for the K/V access patterns +- Custom write-ahead log (WAL) for crash recovery +- The storage engine must manage its own caching (no kernel page cache) + +### Storage Engine Interface + +The local storage engine exposes a trait consumed by the distribution layer: + +```rust +trait StorageEngine { + fn get(&self, key: &[u8]) -> Result>; + fn put(&mut self, key: &[u8], value: &[u8]) -> Result<()>; + fn delete(&mut self, key: &[u8]) -> Result<()>; + fn scan(&self, start: &[u8], end: &[u8]) -> Result; +} +``` + +This boundary allows the storage engine to be developed and tested independently of the distribution layer. + +## Node Identity and Bootstrap + +### Identity + +- Nodes generate a unique ID on first start +- Identity is persisted to the NVMe device +- Subsequent restarts reuse the same identity + +### Bootstrap + +- Each node starts with a configuration file containing seed peer addresses (3-5 well-known nodes) +- On startup, the node contacts seed peers to join the cluster +- Seed peers add the new node to the membership list +- SWIM gossip propagates the new membership to all nodes +- The consistent hash ring adjusts, and lease groups shift accordingly +- Non-seed nodes can be discovered by contacting any existing cluster member + +## Prerequisites and Development Roadmap + +The K/V store requires several foundational capabilities in the libvoid stack before implementation can begin. Three independent workstreams can proceed in parallel. + +### Track 1: Networking + +| Item | Description | Dependency | +|------|-------------|------------| +| **Observability** | Metrics, distributed tracing, structured logging. Must be in place before building distributed components — retrofitting is painful. | None | +| **HTTP Client** | Completes the HTTP implementation. Needed for management interfaces and external integrations. Ships over existing TCP. | Observability | +| **QUIC Protocol** | Full QUIC implementation in userspace over existing UDP. Replaces the need for separate TLS, connection pooling, and binary framing protocol implementations. | Observability | +| **QUIC RPC Framework** | Request/response and streaming abstractions over QUIC streams. The inter-node communication API. | QUIC | + +### Track 2: Storage + +| Item | Description | Dependency | +|------|-------------|------------| +| **Userspace NVMe Driver** | Direct NVMe controller access from userspace, bypassing kernel block layer. | None (parallel with Track 1) | +| **Block Allocator + On-Disk Format** | Space management on raw block device, data layout for K/V pairs. | NVMe Driver | +| **WAL + Crash Recovery** | Write-ahead log for atomicity and durability, recovery on restart. | Block Allocator | +| **Local K/V Engine** | Single-node get/put/delete/scan implementing the StorageEngine trait. | WAL | + +### Track 3: Distribution + +| Item | Description | Dependency | +|------|-------------|------------| +| **SWIM Gossip** | Membership protocol over UDP with piggybacked metadata dissemination. | Observability | +| **Consistent Hash Ring** | Ring placement, key range assignment, deterministic lease group computation. | SWIM | +| **Lease Protocol** | Time-bounded lease acquisition, renewal, and expiry for coordinator election. | Hash Ring | +| **Key Resolution Protocol** | ARP/BGP-style key location resolution with caching and forwarding. | Lease Protocol | +| **Reed-Solomon Codec** | Erasure coding library for data redundancy. | None (standalone) | + +### Integration Milestones + +```mermaid +graph TB + subgraph "Track 1: Networking" + T1A[Observability] --> T1B[HTTP Client] + T1B --> T1C[QUIC] + T1C --> T1D[QUIC RPC Framework] + end + + subgraph "Track 2: Storage" + T2A[NVMe Driver] --> T2B[Block Allocator] + T2B --> T2C[WAL + Crash Recovery] + T2C --> T2D[Local K/V Engine] + end + + subgraph "Track 3: Distribution" + T3A[SWIM Gossip] --> T3B[Hash Ring] + T3B --> T3C[Lease Protocol] + T3C --> T3D[Key Resolution] + T3E[Reed-Solomon] + end + + T2D --> M1["✓ M1: Single-Node K/V
Usable embedded store"] + T1D --> M2["✓ M2: Multi-Node Gossip
Cluster formation and membership"] + T3A --> M2 + + M1 --> M3["✓ M3: Distributed K/V
Full system"] + M2 --> M3 + T3D --> M3 + T3E --> M3 + + style M1 fill:#6f6,stroke:#333 + style M2 fill:#6f6,stroke:#333 + style M3 fill:#f96,stroke:#333 +``` + +- **M1: Single-Node K/V** — Track 2 complete. A working embedded K/V store backed by userspace NVMe. Useful on its own and fully testable in isolation. +- **M2: Multi-Node Gossip** — Tracks 1 + 3 partially integrated. Nodes can form a cluster, detect failures, and communicate via QUIC RPC. A cluster membership and service discovery primitive. +- **M3: Distributed K/V** — All tracks converge. Full distributed K/V with per-keyspace consistency, demand-driven placement, RS redundancy, and lease-based coordination. + +Each milestone delivers standalone value, not just a stepping stone. + +## Design Principles + +- **Self-contained** — no external dependencies. The entire system runs within libvoid's userspace stack. +- **Global from day one** — the architecture assumes varying latency, unreliable clocks, and geographically distributed nodes from the start. +- **Demand-driven placement** — data moves toward where it's used rather than being pre-sharded to fixed locations. +- **Separation of metadata and data** — key location metadata is tiny and gossiped aggressively. Values only move on demand. +- **Tame complexity** — per-keyspace consistency gives flexibility without a single monolithic consistency protocol. One read path, three write paths determined by keyspace config. +- **Bypass everything** — AF_XDP for networking, userspace NVMe for storage. The kernel is not in the data path. diff --git a/fix_tests.py b/fix_tests.py deleted file mode 100644 index 3dda73d..0000000 --- a/fix_tests.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -""" -Fix test compilation errors after timer wheel refactor. - -Transforms: -1. process_ipv4(frame, now, &nh, ...) -> process_ipv4(frame, now, 0, &mut wheel, &nh, ...) -2. poll_send(now, mac, &nh, ...) -> poll_send(now, 0, &mut wheel, mac, &nh, ...) -3. connect(..., now, free, tx) -> connect(..., now, 0, &mut wheel, free, tx) -4. connect_with_config(..., now, config, free, tx) -> connect_with_config(..., now, 0, &mut wheel, config, free, tx) -5. initiate_close(key) -> initiate_close(key, 0, &mut wheel) -6. poll_timers(now, mac, &nh, ...) -> poll_timers(now, 0, &mut wheel, mac, &nh, ...) [trait method - won't conflict, but fix anyway] -7. Replace deadline field accesses -""" - -import re -import sys -import os - -def fix_process_ipv4_calls(content): - """Insert '0, &mut wheel,' after the 'now' argument in process_ipv4 calls.""" - # Pattern: .process_ipv4(\n frame,\n now_expr,\n &nh, - # We need to insert "0,\n &mut wheel," after the now line - - # Match process_ipv4 call with the now argument followed by &nh or &neighbor - pattern = re.compile( - r'(\.process_ipv4\(\s*\n' # .process_ipv4(\n - r'(\s*)' # capture indentation - r'[^,]+,\s*\n' # frame arg,\n - r'\s*[^,]+,)\s*\n' # now arg,\n (capture group 1 ends) - r'(\s*&)' # &nh (capture group 3) - ) - - def replacer(m): - indent = m.group(2) - return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(3)}' - - return pattern.sub(replacer, content) - - -def fix_poll_send_calls(content): - """Insert '0, &mut wheel,' after the 'now' argument in poll_send calls.""" - # Pattern: .poll_send(\n now,\n mac, - # Insert after now line - pattern = re.compile( - r'(\.poll_send\(\s*\n' # .poll_send(\n - r'(\s*)' # capture indentation - r'[^,]+,)\s*\n' # now arg,\n (end group 1) - r'(\s*)' # next line indent (group 3) - r'((?:nh\.local_mac|src_mac|handler\.|local_mac))' # mac arg start (group 4) - ) - - def replacer(m): - indent = m.group(2) - return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(3)}{m.group(4)}' - - return pattern.sub(replacer, content) - - -def fix_poll_send_single_line(content): - """Fix single-line poll_send calls like handler.poll_send(now, mac, ...).""" - # Some tests may have single-line calls - # Actually looking at the code, they're all multiline. Skip this. - return content - - -def fix_connect_calls(content): - """Insert '0, &mut wheel,' after 'now' in connect() calls. - connect(..., now, free, tx) -> connect(..., now, 0, &mut wheel, free, tx) - """ - # Pattern: .connect(\n ...multiple args...\n now,\n &mut free/free, - # The now arg is coarsetime::Instant::now(), followed by free_frames - pattern = re.compile( - r'(\.connect\(\s*\n' # .connect(\n - r'(?:\s*[^,]+,\s*\n)*?' # multiple args - r'(\s*)' # indent (group 2) - r'(coarsetime::Instant::now\(\)|now),)\s*\n' # now arg, (group 3 is now expr) - r'(\s*&mut (?:free|tx))' # free_frames (group 4) - ) - - def replacer(m): - indent = m.group(2) - return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(4)}' - - return pattern.sub(replacer, content) - - -def fix_connect_with_config_calls(content): - """Insert '0, &mut wheel,' after 'now' in connect_with_config() calls. - connect_with_config(..., now, config, free, tx) -> connect_with_config(..., now, 0, &mut wheel, config, free, tx) - """ - pattern = re.compile( - r'(\.connect_with_config\(\s*\n' - r'(?:\s*[^,]+,\s*\n)*?' - r'(\s*)' # indent (group 2) - r'(coarsetime::Instant::now\(\)|now),)\s*\n' - r'(\s*config)' # config arg (group 4) - ) - - def replacer(m): - indent = m.group(2) - return f'{m.group(1)}\n{indent}0,\n{indent}&mut wheel,\n{m.group(4)}' - - return pattern.sub(replacer, content) - - -def fix_initiate_close_calls(content): - """initiate_close(key) -> initiate_close(key, 0, &mut wheel)""" - # Pattern: .initiate_close(expr); - # But NOT .initiate_close(expr, 0, &mut wheel); (already fixed) - pattern = re.compile( - r'\.initiate_close\(([^,\)]+)\);' - ) - - def replacer(m): - key_arg = m.group(1) - return f'.initiate_close({key_arg}, 0, &mut wheel);' - - return pattern.sub(replacer, content) - - -def add_wheel_to_test_functions(content): - """Add 'let mut wheel = new_wheel();' after the last BasicFrameBuffer::new line in each test function.""" - # Find patterns like: - # let mut tx = BasicFrameBuffer::new(N); - # and add wheel after (if not already present) - - lines = content.split('\n') - result = [] - i = 0 - while i < len(lines): - result.append(lines[i]) - # Check if this line creates the last BasicFrameBuffer (tx pattern) - if 'let mut tx = BasicFrameBuffer::new(' in lines[i] and 'wheel' not in (lines[i+1] if i+1 < len(lines) else ''): - # Get indentation - indent = len(lines[i]) - len(lines[i].lstrip()) - indent_str = lines[i][:indent] - result.append(f'{indent_str}let mut wheel = new_wheel();') - i += 1 - - return '\n'.join(result) - - -def process_file(filepath): - with open(filepath, 'r') as f: - content = f.read() - - original = content - - # Add wheel variable to test functions - content = add_wheel_to_test_functions(content) - - # Fix method calls - content = fix_process_ipv4_calls(content) - content = fix_poll_send_calls(content) - content = fix_connect_calls(content) - content = fix_connect_with_config_calls(content) - content = fix_initiate_close_calls(content) - - if content != original: - with open(filepath, 'w') as f: - f.write(content) - print(f" Modified: {filepath}") - else: - print(f" Unchanged: {filepath}") - - -def main(): - base = "/workspace/csaide/voidnet/.worktrees/timer-wheel/src/net/handler/tcp/tests" - test_files = [ - "handler.rs", "handshake.rs", "data_transfer.rs", "delayed_ack.rs", - "retransmission.rs", "timers.rs", "keepalive.rs", "persist.rs", - "teardown.rs", "edge_cases.rs", "ecn.rs", "nagle.rs", "sack.rs", - "timestamps.rs", "validate.rs", "listener.rs", "congestion_tests.rs", - ] - - for f in test_files: - path = os.path.join(base, f) - if os.path.exists(path): - process_file(path) - else: - print(f" Missing: {path}") - - -if __name__ == '__main__': - main() diff --git a/src/net/handler/tcp/segment.rs b/src/net/handler/tcp/segment.rs index 5acaefa..9477f6b 100644 --- a/src/net/handler/tcp/segment.rs +++ b/src/net/handler/tcp/segment.rs @@ -115,7 +115,7 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { // Max options: MSS(4) + NOP(1) + WSCALE(3) + NOP(1) + NOP(1) + TS(10) + SACK_PERM(2) = 22, pad to 24 let mut opt_buf = [0u8; 24]; let mut opt_len = write_mss_option(&mut opt_buf, mss); @@ -140,43 +140,39 @@ impl SegmentBuilder { }; match (local_addr, remote_addr) { - (IpAddress::V4(l), IpAddress::V4(r)) => { - Self::build_segment::( - l, - r, - local_port, - remote_port, - iss, - 0, - syn_flags, - window, - &opt_buf[..opt_len], - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - (IpAddress::V6(l), IpAddress::V6(r)) => { - Self::build_segment::( - l, - r, - local_port, - remote_port, - iss, - 0, - syn_flags, - window, - &opt_buf[..opt_len], - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - _ => {} + (IpAddress::V4(l), IpAddress::V4(r)) => Self::build_segment::( + l, + r, + local_port, + remote_port, + iss, + 0, + syn_flags, + window, + &opt_buf[..opt_len], + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + (IpAddress::V6(l), IpAddress::V6(r)) => Self::build_segment::( + l, + r, + local_port, + remote_port, + iss, + 0, + syn_flags, + window, + &opt_buf[..opt_len], + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + _ => false, } } @@ -200,7 +196,7 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { let mut opt_buf = [0u8; 24]; let mut opt_len = write_mss_option(&mut opt_buf, mss); if let Some(shift) = wscale { @@ -226,43 +222,39 @@ impl SegmentBuilder { }; match (local_addr, remote_addr) { - (IpAddress::V4(l), IpAddress::V4(r)) => { - Self::build_segment::( - l, - r, - local_port, - remote_port, - iss, - ack, - syn_ack_flags, - window, - &opt_buf[..opt_len], - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - (IpAddress::V6(l), IpAddress::V6(r)) => { - Self::build_segment::( - l, - r, - local_port, - remote_port, - iss, - ack, - syn_ack_flags, - window, - &opt_buf[..opt_len], - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - _ => {} + (IpAddress::V4(l), IpAddress::V4(r)) => Self::build_segment::( + l, + r, + local_port, + remote_port, + iss, + ack, + syn_ack_flags, + window, + &opt_buf[..opt_len], + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + (IpAddress::V6(l), IpAddress::V6(r)) => Self::build_segment::( + l, + r, + local_port, + remote_port, + iss, + ack, + syn_ack_flags, + window, + &opt_buf[..opt_len], + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + _ => false, } } @@ -437,7 +429,7 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { let mut ts_buf = [0u8; 12]; let tcp_options: &[u8] = if let Some((tsval, tsecr)) = timestamp { ts_buf[0] = options::NOP; @@ -449,43 +441,39 @@ impl SegmentBuilder { }; match (local_addr, remote_addr) { - (IpAddress::V4(l), IpAddress::V4(r)) => { - Self::build_segment::( - l, - r, - local_port, - remote_port, - seq, - ack, - flags::ACK | flags::FIN, - window, - tcp_options, - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - (IpAddress::V6(l), IpAddress::V6(r)) => { - Self::build_segment::( - l, - r, - local_port, - remote_port, - seq, - ack, - flags::ACK | flags::FIN, - window, - tcp_options, - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - _ => {} + (IpAddress::V4(l), IpAddress::V4(r)) => Self::build_segment::( + l, + r, + local_port, + remote_port, + seq, + ack, + flags::ACK | flags::FIN, + window, + tcp_options, + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + (IpAddress::V6(l), IpAddress::V6(r)) => Self::build_segment::( + l, + r, + local_port, + remote_port, + seq, + ack, + flags::ACK | flags::FIN, + window, + tcp_options, + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + _ => false, } } @@ -506,7 +494,7 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { let mut ts_buf = [0u8; 12]; let tcp_options: &[u8] = if let Some((tsval, tsecr)) = timestamp { ts_buf[0] = options::NOP; @@ -518,45 +506,41 @@ impl SegmentBuilder { }; match (local_addr, remote_addr) { - (IpAddress::V4(l), IpAddress::V4(r)) => { - Self::build_data_segment::( - l, - r, - local_port, - remote_port, - seq, - ack, - flags::ACK, - window, - payload, - tcp_options, - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - (IpAddress::V6(l), IpAddress::V6(r)) => { - Self::build_data_segment::( - l, - r, - local_port, - remote_port, - seq, - ack, - flags::ACK, - window, - payload, - tcp_options, - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - _ => {} + (IpAddress::V4(l), IpAddress::V4(r)) => Self::build_data_segment::( + l, + r, + local_port, + remote_port, + seq, + ack, + flags::ACK, + window, + payload, + tcp_options, + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + (IpAddress::V6(l), IpAddress::V6(r)) => Self::build_data_segment::( + l, + r, + local_port, + remote_port, + seq, + ack, + flags::ACK, + window, + payload, + tcp_options, + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + _ => false, } } @@ -579,7 +563,7 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { // Same options construction as build_data let mut ts_buf = [0u8; 12]; let tcp_options: &[u8] = if let Some((tsval, tsecr)) = timestamp { @@ -592,47 +576,43 @@ impl SegmentBuilder { }; match (local_addr, remote_addr) { - (IpAddress::V4(l), IpAddress::V4(r)) => { - Self::build_data_segment_slices::( - l, - r, - local_port, - remote_port, - seq, - ack, - tcp_flags, - window, - payload, - tcp_options, - ecn_ect, - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - (IpAddress::V6(l), IpAddress::V6(r)) => { - Self::build_data_segment_slices::( - l, - r, - local_port, - remote_port, - seq, - ack, - tcp_flags, - window, - payload, - tcp_options, - ecn_ect, - src_mac, - dst_mac, - tx_offload, - free_frames, - tx_return, - ); - } - _ => {} + (IpAddress::V4(l), IpAddress::V4(r)) => Self::build_data_segment_slices::( + l, + r, + local_port, + remote_port, + seq, + ack, + tcp_flags, + window, + payload, + tcp_options, + ecn_ect, + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + (IpAddress::V6(l), IpAddress::V6(r)) => Self::build_data_segment_slices::( + l, + r, + local_port, + remote_port, + seq, + ack, + tcp_flags, + window, + payload, + tcp_options, + ecn_ect, + src_mac, + dst_mac, + tx_offload, + free_frames, + tx_return, + ), + _ => false, } } @@ -654,9 +634,9 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { let Some(mut frame) = free_frames.pop() else { - return; + return false; }; let opt_padded_len = (tcp_options.len() + 3) & !3; @@ -666,7 +646,7 @@ impl SegmentBuilder { if frame.capacity() < frame_len { free_frames.push(frame); - return; + return false; } unsafe { frame.set_len(frame_len) }; @@ -702,6 +682,7 @@ impl SegmentBuilder { } tx_return.push(frame); + true } #[inline] @@ -721,9 +702,9 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { let Some(mut frame) = free_frames.pop() else { - return; + return false; }; let opt_padded_len = (tcp_options.len() + 3) & !3; @@ -733,7 +714,7 @@ impl SegmentBuilder { if frame.capacity() < frame_len { free_frames.push(frame); - return; + return false; } unsafe { frame.set_len(frame_len) }; @@ -777,6 +758,7 @@ impl SegmentBuilder { } tx_return.push(frame); + true } #[inline] @@ -797,9 +779,9 @@ impl SegmentBuilder { tx_offload: bool, free_frames: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, - ) { + ) -> bool { let Some(mut frame) = free_frames.pop() else { - return; + return false; }; let payload_len = payload.0.len() + payload.1.len(); @@ -810,7 +792,7 @@ impl SegmentBuilder { if frame.capacity() < frame_len { free_frames.push(frame); - return; + return false; } unsafe { frame.set_len(frame_len) }; @@ -864,6 +846,7 @@ impl SegmentBuilder { } tx_return.push(frame); + true } #[inline(always)] diff --git a/src/net/handler/tcp/tests/congestion_tests.rs b/src/net/handler/tcp/tests/congestion_tests.rs index 04be106..818f3aa 100644 --- a/src/net/handler/tcp/tests/congestion_tests.rs +++ b/src/net/handler/tcp/tests/congestion_tests.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] @@ -6,10 +6,10 @@ fn cubic_slow_start_on_new_ack() { let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); - let mut free = BasicFrameBuffer::new(32); - let mut rx = BasicFrameBuffer::new(32); - let mut tx = BasicFrameBuffer::new(32); - for i in 0..16 { + let mut free = BasicFrameBuffer::new(64); + let mut rx = BasicFrameBuffer::new(64); + let mut tx = BasicFrameBuffer::new(64); + for i in 0..32 { free.push(alloc_free_frame(100 + i)); } @@ -58,7 +58,9 @@ fn cubic_slow_start_on_new_ack() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } let cwnd_before = handler.first_connection().cubic.cwnd; let mss = handler.first_connection().eff_snd_mss; @@ -79,7 +81,9 @@ fn cubic_slow_start_on_new_ack() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // ACK the data. let snd_nxt = handler.first_connection().snd_nxt; @@ -119,10 +123,10 @@ fn frto_restores_cwnd_on_spurious_rto() { let mut wheel = new_wheel(); let mut handler = new_handler(); let nh = new_neighbor_handler(); - let mut free = BasicFrameBuffer::new(64); - let mut rx = BasicFrameBuffer::new(64); - let mut tx = BasicFrameBuffer::new(64); - for i in 0..32 { + let mut free = BasicFrameBuffer::new(128); + let mut rx = BasicFrameBuffer::new(128); + let mut tx = BasicFrameBuffer::new(128); + for i in 0..64 { free.push(alloc_free_frame(100 + i)); } @@ -171,7 +175,9 @@ fn frto_restores_cwnd_on_spurious_rto() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // Send 2 MSS of data (poll_send sends 1 MSS per call). let mss = handler.first_connection_mut().eff_snd_mss as usize; @@ -199,7 +205,9 @@ fn frto_restores_cwnd_on_spurious_rto() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } let cwnd_before_rto = handler.first_connection().cubic.cwnd; @@ -222,7 +230,9 @@ fn frto_restores_cwnd_on_spurious_rto() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // F-RTO should be active. assert!( diff --git a/src/net/handler/tcp/tests/delayed_ack.rs b/src/net/handler/tcp/tests/delayed_ack.rs index 369311c..b73d393 100644 --- a/src/net/handler/tcp/tests/delayed_ack.rs +++ b/src/net/handler/tcp/tests/delayed_ack.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] diff --git a/src/net/handler/tcp/tests/ecn.rs b/src/net/handler/tcp/tests/ecn.rs index 4e4e2a5..999e617 100644 --- a/src/net/handler/tcp/tests/ecn.rs +++ b/src/net/handler/tcp/tests/ecn.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] diff --git a/src/net/handler/tcp/tests/edge_cases.rs b/src/net/handler/tcp/tests/edge_cases.rs index 0e954e8..e170a32 100644 --- a/src/net/handler/tcp/tests/edge_cases.rs +++ b/src/net/handler/tcp/tests/edge_cases.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] diff --git a/src/net/handler/tcp/tests/handler.rs b/src/net/handler/tcp/tests/handler.rs index 7b0ddcb..514291a 100644 --- a/src/net/handler/tcp/tests/handler.rs +++ b/src/net/handler/tcp/tests/handler.rs @@ -247,7 +247,6 @@ fn remove_connection_syn_received_generates_rst() { let dst_mac = MacAddress::from([0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); let mut free = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = new_wheel(); free.push(alloc_free_frame(200)); handler.remove_connection(&id, src_mac, dst_mac, &mut free, &mut tx); @@ -267,7 +266,6 @@ fn remove_connection_syn_sent_no_rst() { let dst_mac = MacAddress::from([0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); let mut free = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = new_wheel(); handler.remove_connection(&id, src_mac, dst_mac, &mut free, &mut tx); @@ -288,7 +286,6 @@ fn remove_connection_not_found_is_noop() { let dst_mac = MacAddress::from([0x11, 0x22, 0x33, 0x44, 0x55, 0x66]); let mut free = BasicFrameBuffer::new(4); let mut tx = BasicFrameBuffer::new(4); - let mut wheel = new_wheel(); // Should not panic — just returns early. handler.remove_connection(&id, src_mac, dst_mac, &mut free, &mut tx); diff --git a/src/net/handler/tcp/tests/keepalive.rs b/src/net/handler/tcp/tests/keepalive.rs index 708ef53..d8e3259 100644 --- a/src/net/handler/tcp/tests/keepalive.rs +++ b/src/net/handler/tcp/tests/keepalive.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] @@ -175,6 +175,14 @@ fn keep_alive_probe_sent_after_idle_timeout() { tcb.keep_alive_count = 3; tcb.ack_pending = false; } + // Arm keep-alive timer. + let key = handler.first_connection_key(); + handler.timer_handles[key].arm( + TcpTimerKind::KeepAlive, + key, + coarsetime::Instant::now() + coarsetime::Duration::from_millis(100), + &mut wheel, + ); // Sleep long enough for the idle timeout to expire. std::thread::sleep(std::time::Duration::from_millis(150)); @@ -363,6 +371,14 @@ fn keep_alive_connection_aborted_after_max_probes() { tcb.keep_alive_probes_sent = 2; // already at max tcb.ack_pending = false; } + // Arm keep-alive timer. + let key = handler.first_connection_key(); + handler.timer_handles[key].arm( + TcpTimerKind::KeepAlive, + key, + coarsetime::Instant::now() + coarsetime::Duration::from_millis(50), + &mut wheel, + ); // Sleep past the probe threshold. std::thread::sleep(std::time::Duration::from_millis(150)); @@ -753,6 +769,14 @@ fn keep_alive_probe_and_recovery() { tcb.keep_alive_count = 3; tcb.ack_pending = false; } + // Arm keep-alive timer. + let key = handler.first_connection_key(); + handler.timer_handles[key].arm( + TcpTimerKind::KeepAlive, + key, + coarsetime::Instant::now() + coarsetime::Duration::from_millis(100), + &mut wheel, + ); // Clear delayed ACK timer if armed. let key = handler.first_connection_key(); if let Some(h) = handler.timer_handles[key].get(TcpTimerKind::DelayedAck) { @@ -907,6 +931,14 @@ fn keep_alive_exhaustion_removes_connection() { tcb.keep_alive_interval_ms = 50; tcb.ack_pending = false; } + // Arm keep-alive timer. + let key = handler.first_connection_key(); + handler.timer_handles[key].arm( + TcpTimerKind::KeepAlive, + key, + coarsetime::Instant::now() + coarsetime::Duration::from_millis(50), + &mut wheel, + ); // Clear delayed ACK timer if armed. { let key = handler.first_connection_key(); diff --git a/src/net/handler/tcp/tests/mod.rs b/src/net/handler/tcp/tests/mod.rs index dd0f7b6..053743e 100644 --- a/src/net/handler/tcp/tests/mod.rs +++ b/src/net/handler/tcp/tests/mod.rs @@ -27,12 +27,6 @@ pub(super) fn new_wheel() -> TimerWheel { TimerWheel::new(coarsetime::Instant::now()) } -/// Cancel all armed timers for a connection. Call after establish_connection -/// to ensure test-armed timers don't conflict with handshake-armed ones. -pub(super) fn clear_timers(handler: &mut TcpHandler, wheel: &mut TimerWheel, key: usize) { - handler.timer_handles[key].cancel_all(wheel); -} - /// Test-only helper: advance the wheel by a short window (1 second past `now`) /// and fire all expired timers. This fires timers armed at or before `now + 1s` /// without touching timers armed far in the future. @@ -243,7 +237,7 @@ pub(super) fn leak(data: Vec) -> &'static mut [u8] { } pub(super) fn alloc_free_frame(addr: u64) -> Frame<'static> { - Frame::new(addr, leak(vec![0u8; 256]), 256, false) + Frame::new(addr, leak(vec![0u8; 2048]), 2048, false) } /// Build a 12-byte TCP timestamp option (NOP NOP TSopt) for use in test frames. diff --git a/src/net/handler/tcp/tests/persist.rs b/src/net/handler/tcp/tests/persist.rs index e715f10..2431a57 100644 --- a/src/net/handler/tcp/tests/persist.rs +++ b/src/net/handler/tcp/tests/persist.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] diff --git a/src/net/handler/tcp/tests/retransmission.rs b/src/net/handler/tcp/tests/retransmission.rs index 60c23ed..20313b3 100644 --- a/src/net/handler/tcp/tests/retransmission.rs +++ b/src/net/handler/tcp/tests/retransmission.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] @@ -59,7 +59,9 @@ fn fast_retransmit_on_three_dup_acks() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // Put data in send buffer and send it. handler.first_connection_mut().send_buffer.write(b"AAAA"); @@ -74,7 +76,9 @@ fn fast_retransmit_on_three_dup_acks() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} // consume sent segment + while let Some(f) = tx.pop() { + free.push(f); + } // consume sent segment let cwnd_before = handler.first_connection().cubic.cwnd; @@ -177,7 +181,9 @@ fn rto_retransmit_on_timer_expiry() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // Put data in send buffer and send it. handler.first_connection_mut().send_buffer.write(b"BBBB"); @@ -192,7 +198,9 @@ fn rto_retransmit_on_timer_expiry() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } let cwnd_before = handler.first_connection().cubic.cwnd; @@ -290,7 +298,9 @@ fn rtt_estimation_updates_rto() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // Send data. handler @@ -308,7 +318,9 @@ fn rtt_estimation_updates_rto() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // Verify last_send_time is set. assert!( @@ -360,11 +372,11 @@ fn rtt_estimation_updates_rto() { fn limited_transmit_sends_on_first_dup_ack() { let mut handler = new_handler(); let nh = new_neighbor_handler(); - let mut free = BasicFrameBuffer::new(64); - let mut rx = BasicFrameBuffer::new(64); - let mut tx = BasicFrameBuffer::new(64); + let mut free = BasicFrameBuffer::new(128); + let mut rx = BasicFrameBuffer::new(128); + let mut tx = BasicFrameBuffer::new(128); let mut wheel = new_wheel(); - for i in 0..32 { + for i in 0..64 { free.push(alloc_free_frame(100 + i)); } @@ -413,7 +425,9 @@ fn limited_transmit_sends_on_first_dup_ack() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } let mss = handler.first_connection().eff_snd_mss as usize; // Fill send buffer with 6 MSS of data, set cwnd to 3*MSS. @@ -435,7 +449,9 @@ fn limited_transmit_sends_on_first_dup_ack() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } let snd_nxt_before = handler.first_connection().snd_nxt; let snd_una = handler.first_connection().snd_una; @@ -513,7 +529,9 @@ fn rto_backoff_resets_on_new_ack() { &mut rx, &mut tx, ); - while tx.pop().is_some() {} + while let Some(f) = tx.pop() { + free.push(f); + } // Artificially set rto_backoff as if an RTO had fired. let key = handler.first_connection_key(); diff --git a/src/net/handler/tcp/tests/sack.rs b/src/net/handler/tcp/tests/sack.rs index e88a3cf..f9d892d 100644 --- a/src/net/handler/tcp/tests/sack.rs +++ b/src/net/handler/tcp/tests/sack.rs @@ -1,4 +1,4 @@ -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; use super::*; #[test] diff --git a/src/net/handler/tcp/tests/teardown.rs b/src/net/handler/tcp/tests/teardown.rs index 4f50b27..9c20017 100644 --- a/src/net/handler/tcp/tests/teardown.rs +++ b/src/net/handler/tcp/tests/teardown.rs @@ -1212,7 +1212,6 @@ fn full_active_close_lifecycle() { while tx.pop().is_some() {} // 6. TIME-WAIT expires -> connection removed. - let future = coarsetime::Instant::now() + coarsetime::Duration::from_secs(120); evict_stale(&mut handler, &mut wheel); assert_eq!( handler.connections.len(), diff --git a/src/net/handler/tcp/tests/timers.rs b/src/net/handler/tcp/tests/timers.rs index 0643f29..9ee7f67 100644 --- a/src/net/handler/tcp/tests/timers.rs +++ b/src/net/handler/tcp/tests/timers.rs @@ -1,7 +1,6 @@ use super::*; -use super::super::handler::INITIAL_RTO_MS; -use super::super::timer_kinds::{TcpTimerKind, tcp_timer_id}; +use super::super::timer_kinds::TcpTimerKind; // --------------------------------------------------------------------------- // RTO exponential backoff — verify successive retransmissions double the RTO @@ -46,7 +45,6 @@ fn rto_exponential_backoff_doubles_on_successive_retransmits() { &mut wheel, ); handler.first_connection_mut().rto_backoff = 0; - let rto_before = handler.first_connection().rto; poll_timers( &mut handler, diff --git a/src/net/handler/tcp/timers.rs b/src/net/handler/tcp/timers.rs index c481826..6ce7e39 100644 --- a/src/net/handler/tcp/timers.rs +++ b/src/net/handler/tcp/timers.rs @@ -163,6 +163,7 @@ impl TcpHandler { return; }; + let mut syn_sent = false; match tcb.state { TcpState::SynSent => { let ts_opt = if tcb.ts_enabled { @@ -170,7 +171,7 @@ impl TcpHandler { } else { None }; - SegmentBuilder::build_syn( + let sent = SegmentBuilder::build_syn( id.local_addr, id.remote_addr, id.local_port, @@ -188,6 +189,16 @@ impl TcpHandler { free_frames, tx_return, ); + if !sent { + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(INITIAL_RTO_MS), + wheel, + ); + return; + } + syn_sent = true; } TcpState::SynReceived => { let wscale_opt = if tcb.wscale_enabled { @@ -196,7 +207,7 @@ impl TcpHandler { None }; let ts_opt = tcb.ts_option(tsval); - SegmentBuilder::build_syn_ack( + let sent = SegmentBuilder::build_syn_ack( id.local_addr, id.remote_addr, id.local_port, @@ -215,13 +226,23 @@ impl TcpHandler { free_frames, tx_return, ); + if !sent { + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(INITIAL_RTO_MS), + wheel, + ); + return; + } + syn_sent = true; } TcpState::Established => { let retransmit_len = tcb.send_buffer.available().min(tcb.eff_snd_mss as usize); if retransmit_len > 0 { let payload = tcb.send_buffer.peek_slices(0, retransmit_len); let ts = tcb.ts_option(tsval); - SegmentBuilder::build_data_from_slices( + let sent = SegmentBuilder::build_data_from_slices( id.local_addr, id.remote_addr, id.local_port, @@ -239,6 +260,18 @@ impl TcpHandler { free_frames, tx_return, ); + if !sent { + // No frame available — re-arm timer to retry without + // corrupting congestion state. + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); + self.send_tracker.mark(SendReady(key)); + return; + } } // F-RTO + CUBIC RTO response. tcb.frto.enter(tcb.snd_una); @@ -288,18 +321,15 @@ impl TcpHandler { } // Exponential backoff for SYN/SYN-ACK states. - match tcb.state { - TcpState::SynSent | TcpState::SynReceived => { - tcb.rto_backoff += 1; - let rto = INITIAL_RTO_MS << tcb.rto_backoff; - self.timer_handles[key].arm( - TcpTimerKind::Retransmit, - key, - now + coarsetime::Duration::from_millis(rto), - wheel, - ); - } - _ => {} // Established handles its own backoff above. + if syn_sent { + tcb.rto_backoff += 1; + let rto = INITIAL_RTO_MS << tcb.rto_backoff; + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(rto), + wheel, + ); } } @@ -312,12 +342,16 @@ impl TcpHandler { &mut self, key: usize, now: Instant, + wheel: &mut TimerWheel, src_mac: crate::net::wire::ethernet::MacAddress, neighbor_handler: &NeighborHandler, free_frames: &mut impl FrameBuffer<'umem>, rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, ) { + // Clear the handle — this timer has fired. + self.timer_handles[key].clear(TcpTimerKind::KeepAlive); + let Some(tcb) = self.connections.get_mut(key) else { return; }; @@ -325,69 +359,75 @@ impl TcpHandler { return; } + if tcb.keep_alive_probes_sent >= tcb.keep_alive_count { + // Max probes exceeded — abort connection. + tcb.event_queue.push(TcpEvent::Timeout); + if let Some(tcb) = self.connections.get(key) { + Self::decrement_syn_received(&mut self.listeners, &tcb.id); + } + self.send_tracker.unmark(key); + self.timer_handles[key].cancel_all(wheel); + self.remove_connection_by_key(key); + return; + } + let tsval = if tcb.ts_enabled { now.duration_since(tcb.ts_offset).as_millis() as u32 } else { 0 }; - let idle_ms = now.duration_since(tcb.last_activity).as_millis(); - - let probe_threshold = if tcb.keep_alive_probes_sent == 0 { - tcb.keep_alive_idle_ms + // Send keep-alive probe: seq = snd_una - 1, no data, ACK. + let id = tcb.id; + let Some(dst_mac) = neighbor_handler.lookup_or_resolve( + now, + &id.remote_addr, + &id.local_addr, + free_frames, + rx_return, + tx_return, + ) else { + // Re-arm for retry. + self.timer_handles[key].arm( + TcpTimerKind::KeepAlive, + key, + now + coarsetime::Duration::from_millis(tcb.keep_alive_interval_ms), + wheel, + ); + return; + }; + let ts = tcb.ts_option(tsval); + let ack_flags = if tcb.ecn_ce_received { + flags::ACK | flags::ECE } else { - tcb.keep_alive_idle_ms + tcb.keep_alive_interval_ms * tcb.keep_alive_probes_sent as u64 + flags::ACK }; + SegmentBuilder::build_ack( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + tcb.snd_una.wrapping_sub(1), + tcb.rcv_nxt, + tcb.advertised_window(), + ack_flags, + ts, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); - if idle_ms >= probe_threshold { - if tcb.keep_alive_probes_sent >= tcb.keep_alive_count { - // Max probes exceeded — abort connection. - tcb.event_queue.push(TcpEvent::Timeout); - if let Some(tcb) = self.connections.get(key) { - Self::decrement_syn_received(&mut self.listeners, &tcb.id); - } - self.send_tracker.unmark(key); - self.remove_connection_by_key(key); - return; - } - - // Send keep-alive probe: seq = snd_una - 1, no data, ACK. - let id = tcb.id; - let Some(dst_mac) = neighbor_handler.lookup_or_resolve( - now, - &id.remote_addr, - &id.local_addr, - free_frames, - rx_return, - tx_return, - ) else { - return; - }; - let ts = tcb.ts_option(tsval); - let ack_flags = if tcb.ecn_ce_received { - flags::ACK | flags::ECE - } else { - flags::ACK - }; - SegmentBuilder::build_ack( - id.local_addr, - id.remote_addr, - id.local_port, - id.remote_port, - tcb.snd_una.wrapping_sub(1), - tcb.rcv_nxt, - tcb.advertised_window(), - ack_flags, - ts, - src_mac, - dst_mac, - self.tx_offload, - free_frames, - tx_return, - ); + tcb.keep_alive_probes_sent += 1; - tcb.keep_alive_probes_sent += 1; - } + // Re-arm for next probe interval. + self.timer_handles[key].arm( + TcpTimerKind::KeepAlive, + key, + now + coarsetime::Duration::from_millis(tcb.keep_alive_interval_ms), + wheel, + ); } /// Fire a zero-window (persist) probe for a single connection identified by slab key. @@ -445,7 +485,7 @@ impl TcpHandler { }; let ts = tcb.ts_option(tsval); - SegmentBuilder::build_data( + let sent = SegmentBuilder::build_data( id.local_addr, id.remote_addr, id.local_port, @@ -462,6 +502,17 @@ impl TcpHandler { tx_return, ); + if !sent { + // No frame available — re-arm persist at base interval to retry. + self.timer_handles[key].arm( + TcpTimerKind::Persist, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); + return; + } + tcb.snd_nxt = tcb.snd_nxt.wrapping_add(1); // Schedule next probe with exponential backoff, capped at 60s. @@ -579,6 +630,11 @@ impl TcpHandler { rx_return: &mut impl FrameBuffer<'umem>, tx_return: &mut impl FrameBuffer<'umem>, ) { + // Guard: if a prior fire_* in this advance() batch already removed the + // connection (e.g. R2 timeout), the timer_handles entry is gone. Skip. + if !self.timer_handles.contains(key) { + return; + } match kind { TcpTimerKind::DelayedAck => { self.fire_delayed_ack( @@ -607,6 +663,7 @@ impl TcpHandler { self.fire_keep_alive( key, now, + wheel, src_mac, neighbor_handler, free_frames, diff --git a/src/net/handler/tcp/transmit.rs b/src/net/handler/tcp/transmit.rs index 27a3bc1..dfacc2e 100644 --- a/src/net/handler/tcp/transmit.rs +++ b/src/net/handler/tcp/transmit.rs @@ -28,7 +28,7 @@ impl TcpHandler { tx_return: &mut impl FrameBuffer<'umem>, ) { self.send_tracker.swap(); - let mut closed: SmallVec<[usize; 4]> = SmallVec::new(); + let closed: SmallVec<[usize; 4]> = SmallVec::new(); while let Some(key) = self.send_tracker.pop_active() { let Some(tcb) = self.connections.get_mut(key) else { continue; // connection was removed @@ -43,6 +43,77 @@ impl TcpHandler { 0 }; + // SACK recovery: retransmit lost segments before sending new data. + // This was previously in poll_timers() but belongs here since SACK + // recovery is ACK-feedback-driven, not timer-driven. + if tcb.recovery.in_recovery { + tcb.recovery.set_pipe( + tcb.snd_una, + tcb.snd_nxt, + &tcb.sack_scoreboard, + tcb.eff_snd_mss, + ); + while tcb.recovery.pipe < tcb.cubic.cwnd { + if let Some(lost_seq) = tcb.recovery.next_lost_segment( + tcb.snd_una, + tcb.snd_nxt, + &tcb.sack_scoreboard, + tcb.eff_snd_mss, + ) { + let offset = lost_seq.wrapping_sub(tcb.snd_una) as usize; + let retransmit_len = tcb + .send_buffer + .available() + .saturating_sub(offset) + .min(tcb.eff_snd_mss as usize); + if retransmit_len == 0 { + break; + } + + let id = tcb.id; + let Some(dst_mac) = neighbor_handler.lookup_or_resolve( + now, + &id.remote_addr, + &id.local_addr, + free_frames, + rx_return, + tx_return, + ) else { + break; + }; + + let payload = tcb.send_buffer.peek_slices(offset, retransmit_len); + let ts = tcb.ts_option(tsval); + let sent = SegmentBuilder::build_data_from_slices( + id.local_addr, + id.remote_addr, + id.local_port, + id.remote_port, + lost_seq, + tcb.rcv_nxt, + tcb.advertised_window(), + payload, + flags::ACK, + false, + ts, + src_mac, + dst_mac, + self.tx_offload, + free_frames, + tx_return, + ); + if !sent { + break; + } + + tcb.recovery.pipe += tcb.eff_snd_mss as u32; + tcb.prr.on_sent(retransmit_len as u32); + } else { + break; + } + } + } + // Send as many segments as the window allows. loop { let bytes_in_flight = tcb.snd_nxt.wrapping_sub(tcb.snd_una) as usize; @@ -125,7 +196,7 @@ impl TcpHandler { if tcb.ecn_cwr_sent { data_flags |= flags::CWR; } - SegmentBuilder::build_data_from_slices( + let sent = SegmentBuilder::build_data_from_slices( tcb.id.local_addr, tcb.id.remote_addr, tcb.id.local_port, @@ -144,6 +215,10 @@ impl TcpHandler { tx_return, ); + if !sent { + break; // No free frames — stop sending, retry next iteration. + } + tcb.snd_nxt = tcb.snd_nxt.wrapping_add(to_send as u32); tcb.last_send_time = Some(now); @@ -259,7 +334,7 @@ impl TcpHandler { }; let ts = tcb.ts_option(tsval); - SegmentBuilder::build_fin_ack( + let fin_sent = SegmentBuilder::build_fin_ack( id.local_addr, id.remote_addr, id.local_port, @@ -275,25 +350,27 @@ impl TcpHandler { tx_return, ); - tcb.fin_seq = Some(tcb.snd_nxt); - tcb.snd_nxt = tcb.snd_nxt.wrapping_add(1); // FIN consumes one sequence number - - tcb.pending_fin = false; - - match tcb.state { - TcpState::Established => tcb.state = TcpState::FinWait1, - TcpState::CloseWait => tcb.state = TcpState::LastAck, - _ => {} - } - - // Set retransmit timer for FIN. - if !self.timer_handles[key].is_armed(TcpTimerKind::Retransmit) { - self.timer_handles[key].arm( - TcpTimerKind::Retransmit, - key, - now + coarsetime::Duration::from_millis(tcb.rto), - wheel, - ); + if fin_sent { + tcb.fin_seq = Some(tcb.snd_nxt); + tcb.snd_nxt = tcb.snd_nxt.wrapping_add(1); // FIN consumes one sequence number + + tcb.pending_fin = false; + + match tcb.state { + TcpState::Established => tcb.state = TcpState::FinWait1, + TcpState::CloseWait => tcb.state = TcpState::LastAck, + _ => {} + } + + // Set retransmit timer for FIN. + if !self.timer_handles[key].is_armed(TcpTimerKind::Retransmit) { + self.timer_handles[key].arm( + TcpTimerKind::Retransmit, + key, + now + coarsetime::Duration::from_millis(tcb.rto), + wheel, + ); + } } } } diff --git a/src/net/http/body.rs b/src/net/http/body.rs index 167df68..e1ba9ff 100644 --- a/src/net/http/body.rs +++ b/src/net/http/body.rs @@ -305,7 +305,6 @@ mod tests { event_queue, handler, wheel, - coarsetime::Instant::now(), ); let mut buf = ReadBuffer::new(4096); diff --git a/src/net/http/connection.rs b/src/net/http/connection.rs index a4d6829..2bc3fa2 100644 --- a/src/net/http/connection.rs +++ b/src/net/http/connection.rs @@ -132,14 +132,7 @@ mod tests { let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( coarsetime::Instant::now(), ))); - let stream = TcpStream::from_accepted_for_test( - 0, - conn_id, - event_queue, - handler, - wheel, - coarsetime::Instant::now(), - ); + let stream = TcpStream::from_accepted_for_test(0, conn_id, event_queue, handler, wheel); HttpConnection::new(stream, Session::http09()) } @@ -155,14 +148,7 @@ mod tests { let wheel = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( coarsetime::Instant::now(), ))); - let stream = TcpStream::from_accepted_for_test( - 0, - conn_id, - event_queue, - handler, - wheel, - coarsetime::Instant::now(), - ); + let stream = TcpStream::from_accepted_for_test(0, conn_id, event_queue, handler, wheel); HttpConnection::new(stream, Session::new(HttpCodec::Http11(Http11Codec::new()))) } diff --git a/src/net/socket/tcp.rs b/src/net/socket/tcp.rs index bff2052..45b2f13 100644 --- a/src/net/socket/tcp.rs +++ b/src/net/socket/tcp.rs @@ -32,7 +32,6 @@ pub struct TcpListener { accept_queue: LocalQueue, handler: Rc>, wheel: Rc>, - base_instant: coarsetime::Instant, } impl TcpListener { @@ -58,7 +57,6 @@ impl TcpListener { accept_queue, handler: ctx.tcp_handler.clone(), wheel: ctx.wheel.clone(), - base_instant: ctx.base_instant, }) }) } @@ -80,7 +78,6 @@ impl TcpListener { accept_queue, handler: ctx.tcp_handler.clone(), wheel: ctx.wheel.clone(), - base_instant: ctx.base_instant, }) }) } @@ -92,7 +89,6 @@ impl TcpListener { accept_queue: &self.accept_queue, handler: &self.handler, wheel: &self.wheel, - base_instant: self.base_instant, } } @@ -126,7 +122,6 @@ pub struct Accept<'listener> { accept_queue: &'listener LocalQueue, handler: &'listener Rc>, wheel: &'listener Rc>, - base_instant: coarsetime::Instant, } impl<'listener> Future for Accept<'listener> { @@ -148,7 +143,6 @@ impl<'listener> Future for Accept<'listener> { event_queue, this.handler.clone(), this.wheel.clone(), - this.base_instant, )) } else { // Connection was removed (e.g. by RST) before we accepted it. @@ -176,7 +170,6 @@ pub struct TcpStream { event_queue: LocalQueue, handler: Rc>, wheel: Rc>, - base_instant: coarsetime::Instant, closed: bool, write_closed: bool, } @@ -237,7 +230,6 @@ impl TcpStream { event_queue, handler: ctx.tcp_handler.clone(), wheel: ctx.wheel.clone(), - base_instant: ctx.base_instant, }) }) } @@ -299,7 +291,6 @@ impl TcpStream { event_queue, handler: ctx.tcp_handler.clone(), wheel: ctx.wheel.clone(), - base_instant: ctx.base_instant, }) }) } @@ -311,7 +302,6 @@ impl TcpStream { event_queue: LocalQueue, handler: Rc>, wheel: Rc>, - base_instant: coarsetime::Instant, ) -> Self { Self { conn_key, @@ -319,7 +309,6 @@ impl TcpStream { event_queue, handler, wheel, - base_instant, closed: false, write_closed: false, } @@ -332,7 +321,6 @@ impl TcpStream { event_queue: LocalQueue, handler: Rc>, wheel: Rc>, - base_instant: coarsetime::Instant, ) -> Self { Self { conn_key, @@ -340,7 +328,6 @@ impl TcpStream { event_queue, handler, wheel, - base_instant, closed: false, write_closed: false, } @@ -509,7 +496,6 @@ pub struct Connect { event_queue: LocalQueue, handler: Rc>, wheel: Rc>, - base_instant: coarsetime::Instant, } impl Future for Connect { @@ -524,7 +510,6 @@ impl Future for Connect { event_queue: this.event_queue.clone(), handler: this.handler.clone(), wheel: this.wheel.clone(), - base_instant: this.base_instant, closed: false, write_closed: false, })), @@ -775,14 +760,7 @@ mod tests { let wheel_rc = Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( coarsetime::Instant::now(), ))); - TcpStream::from_accepted_for_test( - key, - conn_id, - event_queue, - handler_rc, - wheel_rc, - Instant::now(), - ) + TcpStream::from_accepted_for_test(key, conn_id, event_queue, handler_rc, wheel_rc) } #[test] diff --git a/src/net/socket/udp.rs b/src/net/socket/udp.rs index 2b4eb81..71bf2cb 100644 --- a/src/net/socket/udp.rs +++ b/src/net/socket/udp.rs @@ -514,7 +514,6 @@ mod tests { wheel: Rc::new(UnsafeCell::new(crate::net::timer_wheel::TimerWheel::new( coarsetime::Instant::now(), ))), - base_instant: coarsetime::Instant::now(), tx_offload: false, task_queue: UnsafeCell::new(TaskQueue::new()), capacity_wakers: UnsafeCell::new(Vec::new()), diff --git a/src/net/timer_wheel.rs b/src/net/timer_wheel.rs index 2e6c770..7c53de9 100644 --- a/src/net/timer_wheel.rs +++ b/src/net/timer_wheel.rs @@ -1,4 +1,4 @@ -use coarsetime::{Duration, Instant}; +use coarsetime::Instant; use slab::Slab; use smallvec::SmallVec; @@ -14,6 +14,7 @@ pub struct TimerHandle(usize); impl TimerHandle { /// Construct a handle from a raw slab key. Intended for tests and internal use only. + #[cfg(test)] pub(crate) fn from_raw(key: usize) -> Self { Self(key) } diff --git a/src/rt/context.rs b/src/rt/context.rs index 115f2b6..b21d2d8 100644 --- a/src/rt/context.rs +++ b/src/rt/context.rs @@ -61,8 +61,6 @@ pub(crate) struct RuntimeContext<'umem> { pub tcp_handler: Rc>, /// Timer wheel for TCP timers. pub wheel: Rc>, - /// Base instant for converting coarsetime to wheel milliseconds. - pub base_instant: coarsetime::Instant, /// TX checksum offload. pub tx_offload: bool, /// Task queue for spawned tasks. @@ -139,7 +137,6 @@ mod tests { udp_handler: Rc::new(UnsafeCell::new(UdpHandler::new(256, false))), tcp_handler: Rc::new(UnsafeCell::new(TcpHandler::new(false, false))), wheel: Rc::new(UnsafeCell::new(TimerWheel::new(coarsetime::Instant::now()))), - base_instant: coarsetime::Instant::now(), tx_offload: false, task_queue: UnsafeCell::new(TaskQueue::new()), capacity_wakers: UnsafeCell::new(Vec::new()), diff --git a/src/rt/local.rs b/src/rt/local.rs index 966c064..35972e3 100644 --- a/src/rt/local.rs +++ b/src/rt/local.rs @@ -194,8 +194,6 @@ pub struct LocalRuntime<'umem> { rx_return: SharedFrameBuffer<'umem>, // Timer wheel for TCP timers. wheel: Rc>, - // Base instant for converting coarsetime to wheel milliseconds. - base_instant: coarsetime::Instant, // Counter for rate-limiting evict_stale() calls (~every 1024 iterations). evict_counter: u32, // Whether TX checksum offload is enabled on this interface. @@ -253,7 +251,6 @@ impl<'umem> LocalRuntime<'umem> { tx_return, rx_return, wheel: Rc::new(UnsafeCell::new(TimerWheel::new(base_instant))), - base_instant, evict_counter: 0, tx_offload, neighbor_rx: None, @@ -301,7 +298,6 @@ impl<'umem> LocalRuntime<'umem> { tx_return, rx_return, wheel: Rc::new(UnsafeCell::new(TimerWheel::new(base_instant))), - base_instant, evict_counter: 0, tx_offload, neighbor_rx: Some(neighbor_rx), @@ -359,7 +355,6 @@ impl<'umem> LocalRuntime<'umem> { udp_handler: self.udp_handler.clone(), tcp_handler: self.tcp_handler.clone(), wheel: self.wheel.clone(), - base_instant: self.base_instant, tx_offload: self.tx_offload, task_queue: UnsafeCell::new(TaskQueue::new()), capacity_wakers: UnsafeCell::new(Vec::new()), @@ -387,11 +382,9 @@ impl<'umem> LocalRuntime<'umem> { let mut buffer = BasicFrameBuffer::new(self.umem.num_frames()); let mut now; - let base_instant = self.base_instant; while !exit.load(Ordering::Relaxed) { // Update time every iteration for timer wheel accuracy. now = coarsetime::Instant::now(); - let now_ms = now.duration_since(base_instant).as_millis(); let wheel = unsafe { &mut *self.wheel.get() }; // ---- Drain neighbor updates from peer queues ---- if let Some(ref neighbor_rx) = self.neighbor_rx { From a99fae89205714321ed6709bf737652aa659cc12 Mon Sep 17 00:00:00 2001 From: Christian Saide Date: Thu, 19 Mar 2026 23:02:08 -0700 Subject: [PATCH 15/15] fix(rt::local): Fixed tx path. --- src/rt/local.rs | 72 ++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/src/rt/local.rs b/src/rt/local.rs index 35972e3..0127386 100644 --- a/src/rt/local.rs +++ b/src/rt/local.rs @@ -393,25 +393,6 @@ impl<'umem> LocalRuntime<'umem> { } } - // ---- Phase 1: Collect TX Completions (non-blocking) ---- - // Frames completed by the kernel since last iteration return to - // rx_return. Runs BEFORE recv to maximize frame availability. - // Note: in_flight_tx correctness depends on the socket/interface - // remaining operational. ENETDOWN would leave it permanently - // inflated, but that is a fatal condition for the event loop. - while let Ok(n) = self.umem.process_completion_queue(&mut self.rx_return) { - debug_assert!( - in_flight_tx >= n, - "completion underflow: in_flight={in_flight_tx} completed={n}" - ); - in_flight_tx -= n; - } - - // ---- Phase 2: Recycle rx_return → fill queue + free_frames ---- - // rx_return contains: TX completions from Phase 1, plus - // handler-returned frames from the PREVIOUS iteration. - self.recycle_rx_return()?; - // ---- Receive & Protocol Dispatch ---- match self.socket.recv(&mut buffer) { Err(_) => {} @@ -510,45 +491,38 @@ impl<'umem> LocalRuntime<'umem> { unsafe { &mut *self.pmtu.get() }.evict_stale(now); } - // ---- Transmit (non-blocking) ---- - // Note: free_before is captured HERE (before transmit/recycle), matching - // the current code's placement. Capacity wakes detect frames freed by - // the transmit cycle, not by Phase 1+2 completion recycling. + // ---- Transmit ---- let free_before = self.free_frames.num_frames(); while self.tx_return.num_frames() > 0 { match self.socket.send(&mut self.tx_return) { Ok(n) => in_flight_tx += n, - Err(_) => { - // TX ring full — kick kernel and try to free completions. - self.socket.maybe_wake()?; - while let Ok(n) = self.umem.process_completion_queue(&mut self.rx_return) { - debug_assert!( - in_flight_tx >= n, - "completion underflow: in_flight={in_flight_tx} completed={n}" - ); - in_flight_tx -= n; - } - self.recycle_rx_return()?; - // Retry once after freeing ring slots. - match self.socket.send(&mut self.tx_return) { - Ok(n) => in_flight_tx += n, - Err(_) => break, // Still full — defer to next iteration. - } - } + Err(_) => break, // TX ring full — will be drained below. } } - // Kick the kernel to process the TX ring. send() only writes - // descriptors — the kernel needs a sendto() to start DMA. - // Without this, frames sit in the TX ring and never transmit. - self.socket.maybe_wake()?; + // ---- Drain TX Completions ---- + // Kick the kernel repeatedly to produce completions (~32 per + // sendto on veth) and collect them into rx_return. This keeps + // pace with the send rate so free_frames doesn't starve. + while in_flight_tx > 0 { + self.socket.maybe_wake()?; + match self.umem.process_completion_queue(&mut self.rx_return) { + Ok(n) => { + debug_assert!( + in_flight_tx >= n, + "completion underflow: in_flight={in_flight_tx} completed={n}" + ); + in_flight_tx -= n; + } + Err(_) => break, // No more completions ready. + } + } - // ---- Recycle remaining rx_return ---- - // Drains whatever is left in rx_return. If all sends above succeeded, - // this contains handler-returned RX frames from protocol dispatch. - // If the send hit WouldBlock, mid-retry recycle already drained - // those, so this is a no-op in that path. + // ---- Recycle rx_return → fill queue + free_frames ---- + // Single recycle pass: handler-returned RX frames and TX + // completions all flow through here. Fill queue gets what it + // needs (bounded by ring size), remainder goes to free_frames. self.recycle_rx_return()?; // ---- Capacity-Driven Wakes ----