rustflow_export 0.19.0

IPFIX exporter for captured network traffic; the `rustflow export` command
Documentation
use std::net::IpAddr;

use etherparse::{LaxNetSlice, LaxSlicedPacket, TransportSlice};
use log::debug;

use super::FlowKey;
use crate::capture::Link;

#[derive(Debug, Clone)]
pub struct PacketInfo {
    pub flow_key: FlowKey,
    pub packet_size: u64,
    pub tcp_flags: u16,
}

/// Lax slicing tolerates frames truncated by the snap length.
pub fn parse(link: Link, data: &[u8]) -> Option<PacketInfo> {
    let sliced = match link {
        Link::Ethernet => LaxSlicedPacket::from_ethernet(data).map_err(|e| e.to_string()),
        Link::Ip => LaxSlicedPacket::from_ip(data).map_err(|e| e.to_string()),
    };
    match sliced {
        Ok(sliced) => packet_info(&sliced),
        Err(e) => {
            debug!("Failed to parse packet: {}", e);
            None
        }
    }
}

fn packet_info(sliced: &LaxSlicedPacket) -> Option<PacketInfo> {
    let (source_ip, dest_ip, protocol, total_length) = match &sliced.net {
        Some(LaxNetSlice::Ipv4(ipv4)) => {
            let header = ipv4.header();
            (
                IpAddr::V4(header.source_addr()),
                IpAddr::V4(header.destination_addr()),
                header.protocol().0,
                header.total_len() as u64,
            )
        }
        Some(LaxNetSlice::Ipv6(ipv6)) => {
            let header = ipv6.header();
            (
                IpAddr::V6(header.source_addr()),
                IpAddr::V6(header.destination_addr()),
                // the upper-layer protocol, past any extension headers
                ipv6.payload().ip_number.0,
                u64::from(header.payload_length()) + 40,
            )
        }
        Some(LaxNetSlice::Arp(_)) | None => {
            debug!("No IP layer found");
            return None;
        }
    };

    let (source_port, dest_port, tcp_flags) = match &sliced.transport {
        Some(TransportSlice::Tcp(tcp)) => {
            let header = tcp.to_header();
            let flags = (header.ns as u16) << 8
                | (header.fin as u16)
                | ((header.syn as u16) << 1)
                | ((header.rst as u16) << 2)
                | ((header.psh as u16) << 3)
                | ((header.ack as u16) << 4)
                | ((header.urg as u16) << 5)
                | ((header.ece as u16) << 6)
                | ((header.cwr as u16) << 7);
            (header.source_port, header.destination_port, flags)
        }
        Some(TransportSlice::Udp(udp)) => {
            let header = udp.to_header();
            (header.source_port, header.destination_port, 0)
        }
        _ => (0, 0, 0),
    };

    Some(PacketInfo {
        flow_key: FlowKey {
            source_ip,
            destination_ip: dest_ip,
            protocol,
            source_port,
            destination_port: dest_port,
        },
        packet_size: total_length,
        tcp_flags,
    })
}