// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::marker::Unpin; use std::mem::size_of; use std::time::Duration; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use zerocopy::{AsBytes, FromBytes}; use zerocopy_derive::{AsBytes, FromBytes, FromZeroes}; type Result = std::result::Result; /// Represents the global header of a pcap capture file. /// /// This struct defines the global header that appears at the beginning of a /// pcap capture file. It contains metadata about the capture, such as the /// file format version, the data link type, and the maximum snapshot length. /// /// # File Header format /// ```text /// 1 2 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 0 | Magic Number | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 4 | Major Version | Minor Version | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 8 | Reserved1 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 12 | Reserved2 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 16 | SnapLen | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 20 | FCS |f|0 0 0 0 0 0 0 0 0 0 0 0| LinkType | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// ``` /// /// * `magic`: A magic number that identifies the file format. /// * `version_major`: The major version number of the file format. /// * `version_minor`: The minor version number of the file format. /// * `thiszone`: The time zone offset of the capture. /// * `sigfigs`: The accuracy of the timestamps. /// * `snaplen`: The maximum number of bytes captured from each packet. /// * `linktype`: The data link type of the network interface used to capture the packets. #[repr(C)] #[derive(AsBytes, FromBytes, FromZeroes)] /// Represents the global header of a pcap capture file. pub struct FileHeader { pub magic: u32, pub version_major: u16, pub version_minor: u16, pub thiszone: i32, pub sigfigs: u32, pub snaplen: u32, pub linktype: u32, } impl FileHeader { const MAGIC: u32 = 0xa1b2c3d4; const VERSION_MAJOR: u16 = 2u16; const VERSION_MINOR: u16 = 4u16; const RESERVED_1: i32 = 0; const RESERVED_2: u32 = 0; const SNAP_LEN: u32 = u32::MAX; } impl Default for FileHeader { fn default() -> Self { FileHeader { magic: FileHeader::MAGIC, version_major: FileHeader::VERSION_MAJOR, version_minor: FileHeader::VERSION_MINOR, thiszone: FileHeader::RESERVED_1, sigfigs: FileHeader::RESERVED_2, snaplen: FileHeader::SNAP_LEN, linktype: LinkType::Null as u32, } } } /// Represents the link layer header type of a pcap capture. /// /// This enum defines the different link layer types that can be used in a /// pcap capture file. These values specify the format of the link-layer /// header that precedes the network layer (e.g., IP) header in each packet. /// /// For a complete list of supported link types and their descriptions, /// refer to the tcpdump documentation: /// https://www.tcpdump.org/linktypes.html #[repr(u32)] pub enum LinkType { Null = 0, /// Ethernet Ethernet = 1, /// Radiotap link-layer information followed by an 802.11 /// header. Radiotap is used with mac80211_hwsim networking. Ieee80211RadioTap = 127, /// Bluetooth HCI UART transport layer BluetoothHciH4WithPhdr = 201, /// Ultra-wideband controller interface protocol FiraUci = 299, } impl From for u32 { fn from(val: LinkType) -> Self { val as u32 } } /// Represents the header prepended to each packet in a pcap capture file. /// /// This struct defines the header that precedes each packet in a pcap /// capture file. It provides information about the timestamp and length /// of the captured packet. /// /// # Fields /// ```text /// 1 2 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 0 | Timestamp (Seconds) | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 4 | Timestamp (Microseconds or nanoseconds) | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 8 | Captured Packet Length | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 12 | Original Packet Length | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// 16 / / /// / Packet Data / /// / variable length / /// / / /// +---------------------------------------------------------------+ /// ``` /// /// * `tv_sec`: The seconds component of the timestamp. /// * `tv_usec`: The microseconds component of the timestamp. /// * `caplen`: The number of bytes of packet data actually captured and saved in the file. /// * `len`: The original length of the packet on the network. // #[repr(C)] #[derive(AsBytes, FromBytes, FromZeroes)] /// Represents the header prepended to each packet in a pcap capture file. pub struct PacketHeader { /// Timestamp of the captured packet. pub tv_sec: u32, pub tv_usec: u32, pub caplen: u32, /// Original length of the packet on the network. pub len: u32, } /// Reads a pcap file header from the given reader. /// /// # Arguments /// /// * `reader` - A reader to read the header from. /// /// # Returns /// /// * `Ok(FileHeader)` - If the header was successfully read. /// * `Err(std::io::Error)` - If an error occurred while reading or parsing the header. pub async fn read_file_header(mut reader: impl AsyncRead + Unpin) -> Result { let mut header_bytes = [0u8; size_of::()]; reader.read_exact(&mut header_bytes).await?; let header = FileHeader::read_from(&header_bytes[..]).ok_or(std::io::Error::new( std::io::ErrorKind::InvalidData, "Failed to parse pcap file header", ))?; if header.magic != FileHeader::MAGIC { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("Invalid magic in pcap file 0x{:x}", header.magic), )); } Ok(header) } /// Reads a pcap record from the given reader. /// A record consists of a packet header (`PacketHeader`) and the packet data itself. /// /// # Arguments /// /// * `reader` - A reader to read the record from. /// /// # Returns /// /// * `Ok((PacketHeader, Vec))` - If the record was successfully read. /// * `Err(std::io::Error)` - If an error occurred while reading or parsing the record. pub async fn read_record(mut reader: impl AsyncRead + Unpin) -> Result<(PacketHeader, Vec)> { let mut pkt_hdr_bytes = [0u8; std::mem::size_of::()]; reader.read_exact(&mut pkt_hdr_bytes).await?; let pkt_hdr = PacketHeader::read_from(&pkt_hdr_bytes[..]).ok_or(std::io::Error::new( std::io::ErrorKind::InvalidData, "Failed to parse pcap record header", ))?; let mut packet_data = vec![0u8; pkt_hdr.caplen as usize]; reader.read_exact(&mut packet_data).await?; Ok((pkt_hdr, packet_data)) } /// Writes the header of a pcap file to the output writer. /// /// This function writes the global header of a pcap file to the provided /// asynchronous writer. It returns the size of the header written. /// /// # Arguments /// /// * `link_type` - The link type of the network interface used to capture the packets. /// * `output` - The asynchronous writer to write the header to. /// /// # Returns /// /// A `Result` containing the size of the header in bytes on success, /// or a `std::io::Error` on failure. pub async fn write_file_header( link_type: LinkType, mut output: impl AsyncWrite + Unpin, ) -> Result { // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-file-header let header = FileHeader { linktype: link_type as u32, ..Default::default() }; output.write_all(header.as_bytes()).await?; Ok(size_of::()) } /// Appends a single packet record to the output writer. /// /// This function writes a packet record to the provided asynchronous writer, /// including the packet header and the packet data itself. It returns the /// total number of bytes written to the writer. /// /// # Arguments /// /// * `timestamp` - The timestamp of the packet. /// * `output` - The asynchronous writer to write the record to. /// * `packet` - The packet data as a byte slice. /// /// # Returns /// /// A `Result` containing the total number of bytes written on success, /// or a `std::io::Error` on failure. pub async fn write_record( timestamp: Duration, mut output: impl AsyncWrite + Unpin, packet: &[u8], ) -> Result { // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-packet-record let pkt_len = packet.len(); let pkt_hdr_len = size_of::(); let header = PacketHeader { tv_sec: timestamp.as_secs() as u32, tv_usec: timestamp.subsec_micros(), caplen: pkt_len as u32, len: pkt_len as u32, }; let mut bytes = Vec::::with_capacity(pkt_hdr_len + pkt_len); bytes.extend(header.as_bytes()); bytes.extend(packet); output.write_all(&bytes).await?; Ok(pkt_hdr_len + pkt_len) }