use std::net::SocketAddrV4;
#[derive(Clone)]
pub struct SynCookie {
key: [u8; 16],
}
impl SynCookie {
#[must_use]
pub fn random() -> Self {
use rand::RngCore;
let mut key = [0u8; 16];
rand::rngs::OsRng.fill_bytes(&mut key);
Self { key }
}
#[must_use]
pub fn with_key(key: [u8; 16]) -> Self {
Self { key }
}
fn raw(&self, src: SocketAddrV4, dst: SocketAddrV4) -> u32 {
let mut buf = Vec::with_capacity(16 + 12);
buf.extend_from_slice(&self.key);
buf.extend_from_slice(&src.ip().octets());
buf.extend_from_slice(&src.port().to_be_bytes());
buf.extend_from_slice(&dst.ip().octets());
buf.extend_from_slice(&dst.port().to_be_bytes());
let d = md5::compute(&buf);
u32::from_be_bytes([d[0], d[1], d[2], d[3]])
}
#[must_use]
pub fn seq(&self, src: SocketAddrV4, dst: SocketAddrV4) -> u32 {
self.raw(src, dst)
}
#[must_use]
pub fn validate(&self, src: SocketAddrV4, dst: SocketAddrV4, ackno: u32) -> bool {
ackno == self.seq(src, dst).wrapping_add(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
fn sa(a: [u8; 4], p: u16) -> SocketAddrV4 {
SocketAddrV4::new(Ipv4Addr::from(a), p)
}
#[test]
fn seq_is_deterministic_per_key_and_tuple() {
let c = SynCookie::with_key([7; 16]);
let s = sa([10, 0, 0, 1], 40000);
let d = sa([93, 184, 216, 34], 443);
assert_eq!(c.seq(s, d), c.seq(s, d));
}
#[test]
fn different_destination_gives_different_seq() {
let c = SynCookie::with_key([7; 16]);
let s = sa([10, 0, 0, 1], 40000);
assert_ne!(
c.seq(s, sa([93, 184, 216, 34], 443)),
c.seq(s, sa([93, 184, 216, 34], 80))
);
assert_ne!(
c.seq(s, sa([93, 184, 216, 34], 443)),
c.seq(s, sa([1, 1, 1, 1], 443))
);
}
#[test]
fn validate_accepts_our_synack_and_rejects_others() {
let c = SynCookie::with_key([42; 16]);
let s = sa([10, 0, 0, 1], 40000);
let d = sa([93, 184, 216, 34], 443);
let ackno = c.seq(s, d).wrapping_add(1);
assert!(c.validate(s, d, ackno));
assert!(!c.validate(s, d, ackno.wrapping_add(1)));
assert!(!c.validate(s, d, c.seq(s, d)));
assert!(!c.validate(s, sa([93, 184, 216, 34], 80), ackno));
assert!(!SynCookie::with_key([43; 16]).validate(s, d, ackno));
}
#[test]
fn seq_wraps_safely_at_u32_max() {
let c = SynCookie::with_key([0; 16]);
let s = sa([0, 0, 0, 0], 1);
let d = sa([0, 0, 0, 0], 1);
let _ = c.validate(s, d, c.seq(s, d).wrapping_add(1));
}
}