use std::time::Duration;
use flowscope::PacketView;
use flowscope::ip_fragment::{FragmentConfig, IpFragmentReassembler};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct IpFragConfig {
pub max_datagrams: usize,
pub max_datagram_bytes: usize,
pub timeout: Duration,
pub warn_on_overlap: bool,
}
impl Default for IpFragConfig {
fn default() -> Self {
let d = FragmentConfig::default();
Self {
max_datagrams: d.max_datagrams,
max_datagram_bytes: d.max_datagram_bytes,
timeout: d.timeout,
warn_on_overlap: true,
}
}
}
impl IpFragConfig {
fn to_flowscope(&self) -> FragmentConfig {
let mut c = FragmentConfig::default();
c.max_datagrams = self.max_datagrams;
c.max_datagram_bytes = self.max_datagram_bytes;
c.timeout = self.timeout;
c
}
}
pub(crate) enum FragAction {
PassThrough,
Buffered,
Reassembled(Vec<u8>),
}
pub(crate) struct IpFragReassembly {
reasm: IpFragmentReassembler,
warn_on_overlap: bool,
overlaps_seen: u64,
}
impl IpFragReassembly {
pub(crate) fn new(cfg: &IpFragConfig) -> Self {
Self {
reasm: IpFragmentReassembler::with_config(cfg.to_flowscope()),
warn_on_overlap: cfg.warn_on_overlap,
overlaps_seen: 0,
}
}
pub(crate) fn intercept(&mut self, view: &PacketView<'_>) -> FragAction {
let Ok(layers) = view.layers() else {
return FragAction::PassThrough;
};
let Some(ip) = layers.ipv4() else {
return FragAction::PassThrough;
};
if !ip.mf() && ip.fragment_offset() == 0 {
return FragAction::PassThrough;
}
let action = match self.reasm.push_ipv4(ip, view.timestamp) {
None => FragAction::Buffered,
Some(payload) => {
let frame = view.frame;
let header = ip.header();
let l2_len = (header.as_ptr().addr()).wrapping_sub(frame.as_ptr().addr());
let ihl = header.len();
let total = ihl + payload.len();
if total > 0xFFFF || l2_len + ihl > frame.len() {
FragAction::Buffered
} else {
let mut out = Vec::with_capacity(l2_len + total);
out.extend_from_slice(&frame[..l2_len + ihl]);
patch_ipv4_header(&mut out[l2_len..l2_len + ihl], total as u16);
out.extend_from_slice(&payload);
FragAction::Reassembled(out)
}
}
};
self.report_overlaps();
action
}
pub(crate) fn evict(&mut self, now: flowscope::Timestamp) {
self.reasm.evict_expired(now);
self.report_overlaps();
}
fn report_overlaps(&mut self) {
if !self.warn_on_overlap {
return;
}
let now = self.reasm.overlaps();
if now > self.overlaps_seen {
tracing::warn!(
overlaps = now - self.overlaps_seen,
total = now,
"ip_frag: overlapping IPv4 fragments dropped (RFC 5722 — fragmentation-evasion signal)"
);
self.overlaps_seen = now;
}
}
}
fn patch_ipv4_header(hdr: &mut [u8], total: u16) {
hdr[2..4].copy_from_slice(&total.to_be_bytes());
hdr[6] = 0;
hdr[7] = 0;
hdr[10] = 0;
hdr[11] = 0;
let ck = ipv4_header_checksum(hdr);
hdr[10..12].copy_from_slice(&ck.to_be_bytes());
}
fn ipv4_header_checksum(hdr: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut i = 0;
while i + 1 < hdr.len() {
sum += u16::from_be_bytes([hdr[i], hdr[i + 1]]) as u32;
i += 2;
}
if i < hdr.len() {
sum += (hdr[i] as u32) << 8;
}
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checksum_matches_a_known_header() {
let mut hdr = [
0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8,
0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7,
];
let ck = ipv4_header_checksum(&hdr);
assert_eq!(ck, 0xb861, "known-vector checksum");
hdr[10..12].copy_from_slice(&ck.to_be_bytes());
assert_eq!(ipv4_header_checksum(&hdr), 0, "header now verifies");
}
}