125 lines
4.3 KiB
Rust
125 lines
4.3 KiB
Rust
//! Optional IP flow classifier for IP plugins, called before encryption.
|
|||
|
|
//! The router itself treats this identifier and the payload as opaque.
|
||
|
|
|
||
|
|
use super::FlowId;
|
||
|
|
|
||
|
|
fn hash(parts: &[&[u8]]) -> FlowId {
|
||
|
|
let mut value = 0xcbf29ce484222325u64;
|
||
|
|
for part in parts {
|
||
|
|
for byte in *part {
|
||
|
|
value = (value ^ u64::from(*byte)).wrapping_mul(0x100000001b3);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
value
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Stable directional IP flow hash, independent of TCP sequence numbers,
|
||
|
|
/// checksums and packet contents. Fragmented traffic uses addresses/protocol
|
||
|
|
/// instead of ports: even non-initial fragments and subsequent fragmented
|
||
|
|
/// datagrams stay together. This deliberately coalesces fragmented flows.
|
||
|
|
/// Malformed packets get a coarse hash; parsing is bounded and never allocates.
|
||
|
|
pub fn ip_flow(packet: &[u8]) -> FlowId {
|
||
|
|
match packet.first().map(|b| b >> 4) {
|
||
|
|
Some(4) if packet.len() >= 20 => {
|
||
|
|
let addresses = &packet[12..20];
|
||
|
|
let protocol = &packet[9..10];
|
||
|
|
let offset = usize::from(packet[0] & 15) * 4;
|
||
|
|
if u16::from_be_bytes([packet[6], packet[7]]) & 0x3fff != 0 {
|
||
|
|
return hash(&[addresses, protocol]);
|
||
|
|
}
|
||
|
|
if offset >= 20
|
||
|
|
&& matches!(packet[9], 6 | 17)
|
||
|
|
&& let Some(ports) = packet.get(offset..offset + 4)
|
||
|
|
{
|
||
|
|
return hash(&[addresses, protocol, ports]);
|
||
|
|
}
|
||
|
|
hash(&[addresses, protocol])
|
||
|
|
}
|
||
|
|
Some(6) if packet.len() >= 40 => {
|
||
|
|
let addresses = &packet[8..40];
|
||
|
|
let mut protocol = packet[6];
|
||
|
|
let mut offset = 40;
|
||
|
|
for _ in 0..8 {
|
||
|
|
match protocol {
|
||
|
|
44 => {
|
||
|
|
if let Some(fragment) = packet.get(offset..offset + 8) {
|
||
|
|
return hash(&[addresses, &fragment[..1]]);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
0 | 43 | 60 | 51 => {
|
||
|
|
let Some(header) = packet.get(offset..offset + 2) else {
|
||
|
|
break;
|
||
|
|
};
|
||
|
|
let size = if protocol == 51 {
|
||
|
|
(usize::from(header[1]) + 2) * 4
|
||
|
|
} else {
|
||
|
|
(usize::from(header[1]) + 1) * 8
|
||
|
|
};
|
||
|
|
protocol = header[0];
|
||
|
|
offset += size;
|
||
|
|
}
|
||
|
|
6 | 17 => {
|
||
|
|
if let Some(ports) = packet.get(offset..offset + 4) {
|
||
|
|
return hash(&[addresses, &[protocol], ports]);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
_ => break,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
hash(&[addresses, &[protocol]])
|
||
|
|
}
|
||
|
|
_ => 0,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn tcp_flow_ignores_packet_contents_but_distinguishes_ports() {
|
||
|
|
let mut packet = [0u8; 80];
|
||
|
|
packet[0] = 0x45;
|
||
|
|
packet[9] = 6;
|
||
|
|
packet[12..24].copy_from_slice(&[10, 0, 0, 1, 10, 0, 0, 2, 0, 22, 1, 2]);
|
||
|
|
let flow = ip_flow(&packet);
|
||
|
|
packet[4] = 123; // IP id changes for each unfragmented packet.
|
||
|
|
packet[24..].fill(47);
|
||
|
|
assert_eq!(flow, ip_flow(&packet));
|
||
|
|
packet[23] = 3;
|
||
|
|
assert_ne!(flow, ip_flow(&packet));
|
||
|
|
packet[6] = 0x20;
|
||
|
|
let fragment_flow = ip_flow(&packet);
|
||
|
|
packet[6] = 0;
|
||
|
|
packet[7] = 1;
|
||
|
|
packet[20..].fill(99);
|
||
|
|
assert_eq!(fragment_flow, ip_flow(&packet));
|
||
|
|
packet[4] = 97;
|
||
|
|
assert_eq!(fragment_flow, ip_flow(&packet));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn ipv6_extensions_and_fragments_are_bounded_and_stable() {
|
||
|
|
let mut packet = [0u8; 80];
|
||
|
|
packet[0] = 0x60;
|
||
|
|
packet[6] = 0;
|
||
|
|
packet[40] = 17;
|
||
|
|
packet[48..52].copy_from_slice(&[1, 2, 3, 4]);
|
||
|
|
let flow = ip_flow(&packet);
|
||
|
|
packet[52..].fill(5);
|
||
|
|
assert_eq!(flow, ip_flow(&packet));
|
||
|
|
packet[50] = 9;
|
||
|
|
assert_ne!(flow, ip_flow(&packet));
|
||
|
|
packet[6] = 44;
|
||
|
|
packet[40] = 6;
|
||
|
|
let flow = ip_flow(&packet);
|
||
|
|
packet[42] = 32;
|
||
|
|
packet[48..].fill(3);
|
||
|
|
assert_eq!(flow, ip_flow(&packet));
|
||
|
|
for len in 0..packet.len() {
|
||
|
|
let _ = ip_flow(&packet[..len]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|