1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use std::marker::Unpin;
16 use std::mem::size_of;
17 use std::time::Duration;
18 use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
19 use zerocopy::{AsBytes, FromBytes};
20 use zerocopy_derive::{AsBytes, FromBytes, FromZeroes};
21
22 type Result<A> = std::result::Result<A, std::io::Error>;
23
24 /// Represents the global header of a pcap capture file.
25 ///
26 /// This struct defines the global header that appears at the beginning of a
27 /// pcap capture file. It contains metadata about the capture, such as the
28 /// file format version, the data link type, and the maximum snapshot length.
29 ///
30 /// # File Header format
31 /// ```text
32 /// 1 2 3
33 /// 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
34 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35 /// 0 | Magic Number |
36 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37 /// 4 | Major Version | Minor Version |
38 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
39 /// 8 | Reserved1 |
40 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
41 /// 12 | Reserved2 |
42 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
43 /// 16 | SnapLen |
44 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
45 /// 20 | FCS |f|0 0 0 0 0 0 0 0 0 0 0 0| LinkType |
46 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
47 /// ```
48 ///
49 /// * `magic`: A magic number that identifies the file format.
50 /// * `version_major`: The major version number of the file format.
51 /// * `version_minor`: The minor version number of the file format.
52 /// * `thiszone`: The time zone offset of the capture.
53 /// * `sigfigs`: The accuracy of the timestamps.
54 /// * `snaplen`: The maximum number of bytes captured from each packet.
55 /// * `linktype`: The data link type of the network interface used to capture the packets.
56 #[repr(C)]
57 #[derive(AsBytes, FromBytes, FromZeroes)]
58 /// Represents the global header of a pcap capture file.
59 pub struct FileHeader {
60 pub magic: u32,
61 pub version_major: u16,
62 pub version_minor: u16,
63 pub thiszone: i32,
64 pub sigfigs: u32,
65 pub snaplen: u32,
66 pub linktype: u32,
67 }
68
69 impl FileHeader {
70 const MAGIC: u32 = 0xa1b2c3d4;
71 const VERSION_MAJOR: u16 = 2u16;
72 const VERSION_MINOR: u16 = 4u16;
73 const RESERVED_1: i32 = 0;
74 const RESERVED_2: u32 = 0;
75 const SNAP_LEN: u32 = u32::MAX;
76 }
77
78 impl Default for FileHeader {
default() -> Self79 fn default() -> Self {
80 FileHeader {
81 magic: FileHeader::MAGIC,
82 version_major: FileHeader::VERSION_MAJOR,
83 version_minor: FileHeader::VERSION_MINOR,
84 thiszone: FileHeader::RESERVED_1,
85 sigfigs: FileHeader::RESERVED_2,
86 snaplen: FileHeader::SNAP_LEN,
87 linktype: LinkType::Null as u32,
88 }
89 }
90 }
91
92 /// Represents the link layer header type of a pcap capture.
93 ///
94 /// This enum defines the different link layer types that can be used in a
95 /// pcap capture file. These values specify the format of the link-layer
96 /// header that precedes the network layer (e.g., IP) header in each packet.
97 ///
98 /// For a complete list of supported link types and their descriptions,
99 /// refer to the tcpdump documentation:
100 /// https://www.tcpdump.org/linktypes.html
101 #[repr(u32)]
102 pub enum LinkType {
103 Null = 0,
104 /// Ethernet
105 Ethernet = 1,
106 /// Radiotap link-layer information followed by an 802.11
107 /// header. Radiotap is used with mac80211_hwsim networking.
108 Ieee80211RadioTap = 127,
109 /// Bluetooth HCI UART transport layer
110 BluetoothHciH4WithPhdr = 201,
111 /// Ultra-wideband controller interface protocol
112 FiraUci = 299,
113 }
114
115 impl From<LinkType> for u32 {
from(val: LinkType) -> Self116 fn from(val: LinkType) -> Self {
117 val as u32
118 }
119 }
120
121 /// Represents the header prepended to each packet in a pcap capture file.
122 ///
123 /// This struct defines the header that precedes each packet in a pcap
124 /// capture file. It provides information about the timestamp and length
125 /// of the captured packet.
126 ///
127 /// # Fields
128 /// ```text
129 /// 1 2 3
130 /// 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
131 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
132 /// 0 | Timestamp (Seconds) |
133 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
134 /// 4 | Timestamp (Microseconds or nanoseconds) |
135 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
136 /// 8 | Captured Packet Length |
137 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
138 /// 12 | Original Packet Length |
139 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
140 /// 16 / /
141 /// / Packet Data /
142 /// / variable length /
143 /// / /
144 /// +---------------------------------------------------------------+
145 /// ```
146 ///
147 /// * `tv_sec`: The seconds component of the timestamp.
148 /// * `tv_usec`: The microseconds component of the timestamp.
149 /// * `caplen`: The number of bytes of packet data actually captured and saved in the file.
150 /// * `len`: The original length of the packet on the network.
151 //
152 #[repr(C)]
153 #[derive(AsBytes, FromBytes, FromZeroes)]
154 /// Represents the header prepended to each packet in a pcap capture file.
155 pub struct PacketHeader {
156 /// Timestamp of the captured packet.
157 pub tv_sec: u32,
158 pub tv_usec: u32,
159 pub caplen: u32,
160 /// Original length of the packet on the network.
161 pub len: u32,
162 }
163
164 /// Reads a pcap file header from the given reader.
165 ///
166 /// # Arguments
167 ///
168 /// * `reader` - A reader to read the header from.
169 ///
170 /// # Returns
171 ///
172 /// * `Ok(FileHeader)` - If the header was successfully read.
173 /// * `Err(std::io::Error)` - If an error occurred while reading or parsing the header.
read_file_header(mut reader: impl AsyncRead + Unpin) -> Result<FileHeader>174 pub async fn read_file_header(mut reader: impl AsyncRead + Unpin) -> Result<FileHeader> {
175 let mut header_bytes = [0u8; size_of::<FileHeader>()];
176 reader.read_exact(&mut header_bytes).await?;
177 let header = FileHeader::read_from(&header_bytes[..]).ok_or(std::io::Error::new(
178 std::io::ErrorKind::InvalidData,
179 "Failed to parse pcap file header",
180 ))?;
181 if header.magic != FileHeader::MAGIC {
182 return Err(std::io::Error::new(
183 std::io::ErrorKind::InvalidData,
184 format!("Invalid magic in pcap file 0x{:x}", header.magic),
185 ));
186 }
187 Ok(header)
188 }
189
190 /// Reads a pcap record from the given reader.
191 /// A record consists of a packet header (`PacketHeader`) and the packet data itself.
192 ///
193 /// # Arguments
194 ///
195 /// * `reader` - A reader to read the record from.
196 ///
197 /// # Returns
198 ///
199 /// * `Ok((PacketHeader, Vec<u8>))` - If the record was successfully read.
200 /// * `Err(std::io::Error)` - If an error occurred while reading or parsing the record.
read_record(mut reader: impl AsyncRead + Unpin) -> Result<(PacketHeader, Vec<u8>)>201 pub async fn read_record(mut reader: impl AsyncRead + Unpin) -> Result<(PacketHeader, Vec<u8>)> {
202 let mut pkt_hdr_bytes = [0u8; std::mem::size_of::<PacketHeader>()];
203 reader.read_exact(&mut pkt_hdr_bytes).await?;
204 let pkt_hdr = PacketHeader::read_from(&pkt_hdr_bytes[..]).ok_or(std::io::Error::new(
205 std::io::ErrorKind::InvalidData,
206 "Failed to parse pcap record header",
207 ))?;
208 let mut packet_data = vec![0u8; pkt_hdr.caplen as usize];
209 reader.read_exact(&mut packet_data).await?;
210 Ok((pkt_hdr, packet_data))
211 }
212
213 /// Writes the header of a pcap file to the output writer.
214 ///
215 /// This function writes the global header of a pcap file to the provided
216 /// asynchronous writer. It returns the size of the header written.
217 ///
218 /// # Arguments
219 ///
220 /// * `link_type` - The link type of the network interface used to capture the packets.
221 /// * `output` - The asynchronous writer to write the header to.
222 ///
223 /// # Returns
224 ///
225 /// A `Result` containing the size of the header in bytes on success,
226 /// or a `std::io::Error` on failure.
write_file_header( link_type: LinkType, mut output: impl AsyncWrite + Unpin, ) -> Result<usize>227 pub async fn write_file_header(
228 link_type: LinkType,
229 mut output: impl AsyncWrite + Unpin,
230 ) -> Result<usize> {
231 // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-file-header
232 let header = FileHeader { linktype: link_type as u32, ..Default::default() };
233 output.write_all(header.as_bytes()).await?;
234 Ok(size_of::<FileHeader>())
235 }
236
237 /// Appends a single packet record to the output writer.
238 ///
239 /// This function writes a packet record to the provided asynchronous writer,
240 /// including the packet header and the packet data itself. It returns the
241 /// total number of bytes written to the writer.
242 ///
243 /// # Arguments
244 ///
245 /// * `timestamp` - The timestamp of the packet.
246 /// * `output` - The asynchronous writer to write the record to.
247 /// * `packet` - The packet data as a byte slice.
248 ///
249 /// # Returns
250 ///
251 /// A `Result` containing the total number of bytes written on success,
252 /// or a `std::io::Error` on failure.
write_record( timestamp: Duration, mut output: impl AsyncWrite + Unpin, packet: &[u8], ) -> Result<usize>253 pub async fn write_record(
254 timestamp: Duration,
255 mut output: impl AsyncWrite + Unpin,
256 packet: &[u8],
257 ) -> Result<usize> {
258 // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-packet-record
259 let pkt_len = packet.len();
260 let pkt_hdr_len = size_of::<PacketHeader>();
261 let header = PacketHeader {
262 tv_sec: timestamp.as_secs() as u32,
263 tv_usec: timestamp.subsec_micros(),
264 caplen: pkt_len as u32,
265 len: pkt_len as u32,
266 };
267 let mut bytes = Vec::<u8>::with_capacity(pkt_hdr_len + pkt_len);
268 bytes.extend(header.as_bytes());
269 bytes.extend(packet);
270 output.write_all(&bytes).await?;
271 Ok(pkt_hdr_len + pkt_len)
272 }
273