Skip to content

Userspace SYN cookie core (#1374)#1393

Merged
psaab merged 2 commits into
masterfrom
codex/1374-userspace-syn-cookie-core
May 17, 2026
Merged

Userspace SYN cookie core (#1374)#1393
psaab merged 2 commits into
masterfrom
codex/1374-userspace-syn-cookie-core

Conversation

@psaab
Copy link
Copy Markdown
Owner

@psaab psaab commented May 17, 2026

Refs #1374.

Summary

  • Add a deterministic userspace SYN-cookie codec with [5-bit epoch][3-bit MSS index][24-bit MAC] ISN layout.
  • Derive per-zone epoch secrets from a master key plus the full monotonic epoch, and validate only current/previous full epochs so low-bit epoch wraps do not replay.
  • Cover tuple, zone, MSS-index, stale-secret, epoch-window, and low-bit-wrap rejection in screen tests.
  • Document that the Go syn-cookie capability gate must remain closed until SYN-ACK TX replies, returning-ACK handling, HA-secret publication, bounded validated-client state, and counters are wired.

Validation

  • cargo test syn_cookie --manifest-path userspace-dp/Cargo.toml
  • cargo test screen --manifest-path userspace-dp/Cargo.toml
  • git diff --check

Copilot AI review requested due to automatic review settings May 17, 2026 01:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces the userspace-dp SYN-cookie codec core (mint/validate) needed for #1374, along with focused unit tests and a documentation note clarifying that the Go capability gate must remain closed until the full SYN-cookie datapath is wired.

Changes:

  • Added a deterministic SYN-cookie ISN codec in screen.rs with a [5-bit epoch][3-bit MSS index][24-bit MAC] layout and per-zone/per-epoch secret derivation.
  • Added unit tests covering tuple binding, MSS index encoding, epoch window validation, low-bit wrap rejection, and stale-secret rejection.
  • Updated FEATURES.md to document the partial nature of the implementation and the remaining wiring requirements.

Reviewed changes

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

File Description
userspace-dp/src/screen.rs Adds SYN-cookie codec types, a SipHash-2-4 implementation, and minor formatting cleanup in IPv6 fragment parsing.
userspace-dp/src/screen_tests.rs Adds SYN-cookie core unit tests and minor assertion formatting.
userspace-dp/src/FEATURES.md Documents presence of SYN-cookie core and notes that the Go gate must remain closed until full plumbing exists.

Comment on lines +40 to +41
const SYN_COOKIE_MSS_SHIFT: u32 = SYN_COOKIE_MAC_BITS;

Comment on lines +911 to +920
// ================================================================
// SYN cookie core (#1374)
// ================================================================

fn syn_cookie_codec() -> SynCookieCodec {
SynCookieCodec::new([
0x10, 0x21, 0x32, 0x43, 0x54, 0x65, 0x76, 0x87, 0x98, 0xa9, 0xba, 0xcb, 0xdc, 0xed, 0xfe,
0x0f,
])
}
Comment on lines +160 to +185
let secret = self.epoch_secret(zone_id, full_epoch);
let mut sip = SipHash24::new(secret[0], secret[1]);
sip.write_u64(0x7870_662d_7379_6e63);
sip.write_u16(zone_id);
sip.write_u64(full_epoch);
sip.write_u8(mss_index);
sip.write_ip(tuple.src_ip);
sip.write_ip(tuple.dst_ip);
sip.write_u16(tuple.src_port);
sip.write_u16(tuple.dst_port);
(sip.finish() as u32) & SYN_COOKIE_MAC_MASK
}

fn epoch_secret(&self, zone_id: u16, full_epoch: u64) -> [u64; 2] {
let k0 = u64::from_le_bytes(self.master_key[0..8].try_into().expect("fixed slice"));
let k1 = u64::from_le_bytes(self.master_key[8..16].try_into().expect("fixed slice"));

let mut left = SipHash24::new(k0, k1);
left.write_u64(0x7870_662d_7363_6b30);
left.write_u16(zone_id);
left.write_u64(full_epoch);

let mut right = SipHash24::new(k0, k1);
right.write_u64(0x7870_662d_7363_6b31);
right.write_u16(zone_id);
right.write_u64(full_epoch);
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Claude round-1 review on 8ed36cba

Verdict: MERGE-READY (foundation only — capability gate remains closed)

This is the codec-only slice of #1374; SYN-ACK TX, returning-ACK handling, HA secrets, validated-client state, and counter wiring are explicitly deferred. FEATURES.md:23 confirms: "the Go syn-cookie capability gate must stay closed until SYN-ACK TX replies, returning-ACK handling, HA-secret publication, bounded validated-client state, and status counters are wired into the worker path."

Codec correctness against #1374 refined contract

Cookie layout screen.rs:33-44:

  • 5 bits epoch (low bits of full monotonic epoch transmitted)
  • 3 bits MSS index (8-entry MSS table)
  • 24 bits MAC

Matches the contract.

Full-epoch in MAC, 5-bit transmitted screen.rs mint_isn:

let mac = self.cookie_mac(tuple, zone_id, full_epoch, mss_index);  // full_epoch, not wire_epoch
((full_epoch as u32 & SYN_COOKIE_EPOCH_MASK) << SYN_COOKIE_EPOCH_SHIFT)  // wire = epoch & 0x1f

✓ matches contract.

32-epoch wrap rejection screen.rs validate_isn:

for candidate_epoch in [current_full_epoch, current_full_epoch.saturating_sub(1)] {
    if (candidate_epoch as u32 & SYN_COOKIE_EPOCH_MASK) != wire_epoch { continue; }
    if self.cookie_mac(tuple, zone_id, candidate_epoch, mss_index) == wire_mac {
        return Some(...);
    }
}
None

Worked failure of a 32-epochs-old cookie: low 5 bits match current; MAC was computed with full_epoch = current - 32; validation re-derives MAC with full_epoch ∈ {current, current-1}; secret derivation uses full_epoch so the keys differ; MAC mismatch → rejected. ✓

Per-zone HMAC screen.rs epoch_secret: two SipHash24 derivations with domain tags 0x7870_662d_7363_6b30 / _6b31, zone_id, full_epoch. Each zone gets a distinct secret pair derived from the cluster-shared master key. Cluster-consistent — requires HA sync of master_key, which is the deferred piece.

Hostile checks

  • 24-bit MAC width: 1-in-16M accidental collision. Brute force ~13M attempts/cookie (24-bit MAC × 3-bit MSS = 27 bits of search). At 1G PPS that's 13ms per cookie. SipHash24 is fast enough that a focused attacker can find collisions per-victim, but not at line rate against random victims. Acceptable for the standard syncookie threat model.
  • Hot-path allocation: cookie_mac allocates a SipHash24 struct on the stack (Copy struct), no heap. ✓
  • Determinism: mint with same (tuple, zone_id, full_epoch, mss_index) always produces the same MAC. ✓
  • Validation only checks current and current-1. No accidental admission of cookies from current+1 (clock backwards) — saturating_sub(1) at epoch=0 stays at 0, never goes negative.
  • MSS index encoding: mss_index selects the largest table entry ≤ peer_mss. ✓

Tests (need to verify coverage in screen_tests.rs)

Expected: mint→validate round-trip; 32-epoch wrap rejection; current+prev acceptance; secret tamper rejection; MSS table coverage. Will let Codex+Gemini confirm.

Recommendation

Block on: nothing structural. Codec implementation is correct.

Strongly consider in this PR: confirm the test suite covers all named scenarios from #1374 (screen::syn_cookie_epoch_low_bits_wrap_rejects_32_epoch_old_cookie, syn_cookie_validation_tries_current_and_previous_full_epoch).

Defer to follow-up PRs: SYN-ACK frame construction, returning ACK validation in dataplane path, HA secret publication, bounded validated-client cache, status counters, capability gate removal.

Awaiting Codex (task-mp955afe-04hhgd) and Gemini Pro 3 (task-mp955yhc-1lbbg8).

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Round-1 quad-review consolidated synthesis on 8ed36cba

Reviewer Verdict
Claude MERGE-READY (foundation)
Codex MERGE-NEEDS-MINOR
Gemini Pro 3 MERGE-READY
Copilot (no review yet on this HEAD)

Codex MINOR — HMAC vs SipHash naming + missing wiring

Codex's main finding is a naming-vs-implementation mismatch: the contract says "per-zone HMAC" but the code uses SipHash-2-4. My read: SipHash with secret keys is the standard cryptographic primitive for SYN cookies — Linux kernel does the same. The "HMAC" wording in #1374 was aspirational nomenclature; SipHash24 keyed by per-zone secret is the practical equivalent. Recommend updating the issue body to say "keyed SipHash" or "PRF (HMAC-equivalent)" rather than rewriting the code.

Codex also notes the explicit deferrals:

  • No SYN-ACK frame builder yet (deferred per FEATURES.md:23)
  • No returning-ACK integration (deferred)
  • No cluster-consistent secret publication (deferred)

These are correctly deferred — the capability gate stays closed.

Codec correctness — Codex and Gemini both verified

Both reviewers independently verified:

  • ✓ Full epoch in MAC, 5-bit transmitted
  • ✓ 32-epoch wrap rejection (validation only tries current and previous full epochs)
  • ✓ Per-zone secret derivation via SipHash with domain tags
  • ✓ Zero hot-path allocations
  • ✓ Tests cover epoch wrap, MAC integrity, stale secret, MSS tamper

Recommendation

Block on: nothing structural. Math core is correct and capability gate stays closed.

Strongly consider: update #1374 issue body to clarify "PRF/keyed SipHash" rather than "HMAC", which more accurately describes the implementation choice. SipHash-2-4 keyed by per-zone secret IS the right primitive for SYN cookies; HMAC-SHA256 would be overkill for a 24-bit MAC.

Defer to follow-up PRs: SYN-ACK frame construction, returning-ACK validation, cluster-consistent master-key publication, validated-client cache, status counters, capability gate removal.

Codex task: task-mp955afe-04hhgd. Gemini Pro 3 task: task-mp955yhc-1lbbg8. Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Round-1 naming nit resolved in the source issue: #1374 now describes the implementation as RFC 4987-style keyed-MAC/SipHash handling rather than HMAC. No code change needed; the codec math remains unchanged.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Copilot hygiene folded in

Pushed 451b7a21 with the three non-blocking maintenance suggestions addressed:

  • Added a compile-time layout invariant that epoch_bits + mss_bits + mac_bits == 32.
  • Replaced the raw SipHash domain-separation tag literals with named constants.
  • Added standard SipHash-2-4 known-good vector coverage for the private cookie MAC implementation.

Validation run:

  • cargo test --manifest-path userspace-dp/Cargo.toml siphash24
  • cargo test --manifest-path userspace-dp/Cargo.toml syn_cookie

Both passed. Cargo still emits the existing warning set.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

@copilot review\n\nPlease re-review current HEAD after the latest fix-forward/rebase push.

Copy link
Copy Markdown
Contributor

Copilot AI commented May 17, 2026

@copilot review\n\nPlease re-review current HEAD after the latest fix-forward/rebase push.

Re-reviewed current HEAD (451b7a2) with a hostile perf/network pass: no new correctness or hot-path allocation regressions found in the SYN-cookie core changes, and the layout/tag/vector hygiene updates look solid. Local cargo re-run in this runner is blocked by missing libelf headers/pkg-config (libbpf-sys build dependency), not by test failures in this patch set.

Copilot finished work on behalf of psaab May 17, 2026 06:35
Copilot AI review requested due to automatic review settings May 17, 2026 14:42
@psaab psaab force-pushed the codex/1374-userspace-syn-cookie-core branch from 451b7a2 to a8077a7 Compare May 17, 2026 14:42
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Rebased on current origin/master after #1394/#1396 landed.

New head: a8077a71

Light verification of the folded Copilot maintenance fixes:

  • Layout invariant is present (SYN_COOKIE_LAYOUT_BITS == SYN_COOKIE_ISN_BITS).
  • Domain-separation tags are named constants.
  • SipHash known-vector coverage is present in screen_tests.rs.

Validation:

  • cargo test --manifest-path userspace-dp/Cargo.toml syn_cookie -- --nocapture
  • git diff --check

PR remains mergeable.

@copilot review

@psaab psaab merged commit 9fc49c5 into master May 17, 2026
2 of 3 checks passed
Copilot stopped work on behalf of psaab due to an error May 17, 2026 14:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment on lines 32 to +52

const SYN_COOKIE_EPOCH_BITS: u32 = 5;
const SYN_COOKIE_MSS_BITS: u32 = 3;
const SYN_COOKIE_MAC_BITS: u32 = 24;
const SYN_COOKIE_ISN_BITS: u32 = 32;
const SYN_COOKIE_LAYOUT_BITS: u32 =
SYN_COOKIE_EPOCH_BITS + SYN_COOKIE_MSS_BITS + SYN_COOKIE_MAC_BITS;
const SYN_COOKIE_EPOCH_MASK: u32 = (1 << SYN_COOKIE_EPOCH_BITS) - 1;
const SYN_COOKIE_MSS_MASK: u32 = (1 << SYN_COOKIE_MSS_BITS) - 1;
const SYN_COOKIE_MAC_MASK: u32 = (1 << SYN_COOKIE_MAC_BITS) - 1;
const SYN_COOKIE_EPOCH_SHIFT: u32 = SYN_COOKIE_MSS_BITS + SYN_COOKIE_MAC_BITS;
const SYN_COOKIE_MSS_SHIFT: u32 = SYN_COOKIE_MAC_BITS;
const SYN_COOKIE_MAC_DOMAIN: u64 = u64::from_be_bytes(*b"xpf-sync");
const SYN_COOKIE_SECRET_LEFT_DOMAIN: u64 = u64::from_be_bytes(*b"xpf-sck0");
const SYN_COOKIE_SECRET_RIGHT_DOMAIN: u64 = u64::from_be_bytes(*b"xpf-sck1");
const _: [(); SYN_COOKIE_ISN_BITS as usize] = [(); SYN_COOKIE_LAYOUT_BITS as usize];

/// Three-bit MSS table encoded in userspace SYN cookies.
///
/// The index, not the raw MSS, is transmitted in the ISN. Values are sorted so
/// selection can choose the largest value not exceeding the peer-advertised MSS.
Comment on lines +198 to +206
#[derive(Debug, Clone, Copy)]
struct SipHash24 {
v0: u64,
v1: u64,
v2: u64,
v3: u64,
tail: [u8; 8],
tail_len: usize,
len: u64,
Comment on lines +923 to +929
fn syn_cookie_tuple() -> SynCookieTuple {
SynCookieTuple {
src_ip: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10)),
dst_ip: IpAddr::V4(Ipv4Addr::new(198, 51, 100, 20)),
src_port: 49152,
dst_port: 443,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants