Skip to main content

smoltcp/phy/
pcap_writer.rs

1use byteorder::{ByteOrder, NativeEndian};
2use core::cell::RefCell;
3use phy::Medium;
4#[cfg(feature = "std")]
5use std::io::Write;
6
7use crate::phy::{self, Device, DeviceCapabilities};
8use crate::time::Instant;
9
10enum_with_unknown! {
11    /// Captured packet header type.
12    pub enum PcapLinkType(u32) {
13        /// Ethernet frames
14        Ethernet =   1,
15        /// IPv4 or IPv6 packets (depending on the version field)
16        Ip       = 101,
17        /// IEEE 802.15.4 packets without FCS.
18        Ieee802154WithoutFcs = 230,
19    }
20}
21
22/// Packet capture mode.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25pub enum PcapMode {
26    /// Capture both received and transmitted packets.
27    Both,
28    /// Capture only received packets.
29    RxOnly,
30    /// Capture only transmitted packets.
31    TxOnly,
32}
33
34/// A packet capture sink.
35pub trait PcapSink {
36    /// Write data into the sink.
37    fn write(&mut self, data: &[u8]);
38
39    /// Flush data written into the sync.
40    fn flush(&mut self) {}
41
42    /// Write an `u16` into the sink, in native byte order.
43    fn write_u16(&mut self, value: u16) {
44        let mut bytes = [0u8; 2];
45        NativeEndian::write_u16(&mut bytes, value);
46        self.write(&bytes[..])
47    }
48
49    /// Write an `u32` into the sink, in native byte order.
50    fn write_u32(&mut self, value: u32) {
51        let mut bytes = [0u8; 4];
52        NativeEndian::write_u32(&mut bytes, value);
53        self.write(&bytes[..])
54    }
55
56    /// Write the libpcap global header into the sink.
57    ///
58    /// This method may be overridden e.g. if special synchronization is necessary.
59    fn global_header(&mut self, link_type: PcapLinkType) {
60        self.write_u32(0xa1b2c3d4); // magic number
61        self.write_u16(2); // major version
62        self.write_u16(4); // minor version
63        self.write_u32(0); // timezone (= UTC)
64        self.write_u32(0); // accuracy (not used)
65        self.write_u32(self.max_packet_size()); // maximum packet length
66        self.write_u32(link_type.into()); // link-layer header type
67    }
68
69    /// Write the libpcap packet header into the sink.
70    ///
71    /// See also the note for [global_header](#method.global_header).
72    ///
73    /// # Panics
74    /// This function panics if `length` is greater than [u32::MAX].
75    fn packet_header(&mut self, timestamp: Instant, length: usize) {
76        let original_length = length.try_into().unwrap();
77
78        self.write_u32(timestamp.secs() as u32); // timestamp seconds
79        self.write_u32(timestamp.micros() as u32); // timestamp microseconds
80        self.write_u32(self.max_packet_size().min(original_length)); // captured length
81        self.write_u32(original_length);
82    }
83
84    /// Write the libpcap packet header followed by packet data into the sink.
85    ///
86    /// The default implementation truncates packets that are larger than [Self::max_packet_size].
87    ///
88    /// See also the note for [global_header](#method.global_header).
89    fn packet(&mut self, timestamp: Instant, packet: &[u8]) {
90        let packet_len = packet.len();
91        let max_packet_size = usize::try_from(self.max_packet_size()).unwrap();
92
93        self.packet_header(timestamp, packet_len);
94        self.write(&packet[..max_packet_size.min(packet_len)]);
95        self.flush();
96    }
97
98    /// Return the maximum size for captured packets.
99    ///
100    /// The captures of packets larger than this size will be truncated by default. Excessively
101    /// large values may cause the software reading the captures to allocate unnecessarily large
102    /// buffers.
103    fn max_packet_size(&self) -> u32 {
104        // Use the default value used by [libpcap] and [Wireshark].
105        // [Wireshark]: https://gitlab.com/wireshark/wireshark/-/blob/v3.5.0/wiretap/wtap.h#L334
106        // [libpcap]: https://github.com/the-tcpdump-group/libpcap/blob/libpcap-1.6.0-bp/pcap-int.h#L106
107        262144
108    }
109}
110
111#[cfg(feature = "std")]
112impl<T: Write> PcapSink for T {
113    fn write(&mut self, data: &[u8]) {
114        T::write_all(self, data).expect("cannot write")
115    }
116
117    fn flush(&mut self) {
118        T::flush(self).expect("cannot flush")
119    }
120}
121
122/// A packet capture writer device.
123///
124/// Every packet transmitted or received through this device is timestamped
125/// and written (in the [libpcap] format) using the provided [sink].
126/// Note that writes are fine-grained, and buffering is recommended.
127///
128/// [libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat
129/// [sink]: trait.PcapSink.html
130#[derive(Debug)]
131pub struct PcapWriter<D, S>
132where
133    D: Device,
134    S: PcapSink,
135{
136    lower: D,
137    sink: RefCell<S>,
138    mode: PcapMode,
139}
140
141impl<D: Device, S: PcapSink> PcapWriter<D, S> {
142    /// Creates a packet capture writer.
143    pub fn new(lower: D, mut sink: S, mode: PcapMode) -> PcapWriter<D, S> {
144        let medium = lower.capabilities().medium;
145        let link_type = match medium {
146            #[cfg(feature = "medium-ip")]
147            Medium::Ip => PcapLinkType::Ip,
148            #[cfg(feature = "medium-ethernet")]
149            Medium::Ethernet => PcapLinkType::Ethernet,
150            #[cfg(feature = "medium-ieee802154")]
151            Medium::Ieee802154 => PcapLinkType::Ieee802154WithoutFcs,
152        };
153        sink.global_header(link_type);
154        PcapWriter {
155            lower,
156            sink: RefCell::new(sink),
157            mode,
158        }
159    }
160
161    /// Get a reference to the underlying device.
162    ///
163    /// Even if the device offers reading through a standard reference, it is inadvisable to
164    /// directly read from the device as doing so will circumvent the packet capture.
165    pub fn get_ref(&self) -> &D {
166        &self.lower
167    }
168
169    /// Get a mutable reference to the underlying device.
170    ///
171    /// It is inadvisable to directly read from the device as doing so will circumvent the packet capture.
172    pub fn get_mut(&mut self) -> &mut D {
173        &mut self.lower
174    }
175}
176
177impl<D: Device, S> Device for PcapWriter<D, S>
178where
179    S: PcapSink,
180{
181    type RxToken<'a>
182        = RxToken<'a, D::RxToken<'a>, S>
183    where
184        Self: 'a;
185    type TxToken<'a>
186        = TxToken<'a, D::TxToken<'a>, S>
187    where
188        Self: 'a;
189
190    fn capabilities(&self) -> DeviceCapabilities {
191        self.lower.capabilities()
192    }
193
194    fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
195        let sink = &self.sink;
196        let mode = self.mode;
197        self.lower
198            .receive(timestamp)
199            .map(move |(rx_token, tx_token)| {
200                let rx = RxToken {
201                    token: rx_token,
202                    sink,
203                    mode,
204                    timestamp,
205                };
206                let tx = TxToken {
207                    token: tx_token,
208                    sink,
209                    mode,
210                    timestamp,
211                };
212                (rx, tx)
213            })
214    }
215
216    fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
217        let sink = &self.sink;
218        let mode = self.mode;
219        self.lower.transmit(timestamp).map(move |token| TxToken {
220            token,
221            sink,
222            mode,
223            timestamp,
224        })
225    }
226}
227
228#[doc(hidden)]
229pub struct RxToken<'a, Rx: phy::RxToken, S: PcapSink> {
230    token: Rx,
231    sink: &'a RefCell<S>,
232    mode: PcapMode,
233    timestamp: Instant,
234}
235
236impl<'a, Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<'a, Rx, S> {
237    fn consume<R, F: FnOnce(&[u8]) -> R>(self, f: F) -> R {
238        self.token.consume(|buffer| {
239            match self.mode {
240                PcapMode::Both | PcapMode::RxOnly => self
241                    .sink
242                    .borrow_mut()
243                    .packet(self.timestamp, buffer.as_ref()),
244                PcapMode::TxOnly => (),
245            }
246            f(buffer)
247        })
248    }
249
250    fn meta(&self) -> phy::PacketMeta {
251        self.token.meta()
252    }
253}
254
255#[doc(hidden)]
256pub struct TxToken<'a, Tx: phy::TxToken, S: PcapSink> {
257    token: Tx,
258    sink: &'a RefCell<S>,
259    mode: PcapMode,
260    timestamp: Instant,
261}
262
263impl<'a, Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<'a, Tx, S> {
264    fn consume<R, F>(self, len: usize, f: F) -> R
265    where
266        F: FnOnce(&mut [u8]) -> R,
267    {
268        self.token.consume(len, |buffer| {
269            let result = f(buffer);
270            match self.mode {
271                PcapMode::Both | PcapMode::TxOnly => {
272                    self.sink.borrow_mut().packet(self.timestamp, buffer)
273                }
274                PcapMode::RxOnly => (),
275            };
276            result
277        })
278    }
279
280    fn set_meta(&mut self, meta: phy::PacketMeta) {
281        self.token.set_meta(meta)
282    }
283}