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(65535); // 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 65535.
75    fn packet_header(&mut self, timestamp: Instant, length: usize) {
76        assert!(length <= 65535);
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(length as u32); // captured length
81        self.write_u32(length as u32); // original length
82    }
83
84    /// Write the libpcap packet header followed by packet data into the sink.
85    ///
86    /// See also the note for [global_header](#method.global_header).
87    fn packet(&mut self, timestamp: Instant, packet: &[u8]) {
88        self.packet_header(timestamp, packet.len());
89        self.write(packet);
90        self.flush();
91    }
92}
93
94#[cfg(feature = "std")]
95impl<T: Write> PcapSink for T {
96    fn write(&mut self, data: &[u8]) {
97        T::write_all(self, data).expect("cannot write")
98    }
99
100    fn flush(&mut self) {
101        T::flush(self).expect("cannot flush")
102    }
103}
104
105/// A packet capture writer device.
106///
107/// Every packet transmitted or received through this device is timestamped
108/// and written (in the [libpcap] format) using the provided [sink].
109/// Note that writes are fine-grained, and buffering is recommended.
110///
111/// [libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat
112/// [sink]: trait.PcapSink.html
113#[derive(Debug)]
114pub struct PcapWriter<D, S>
115where
116    D: Device,
117    S: PcapSink,
118{
119    lower: D,
120    sink: RefCell<S>,
121    mode: PcapMode,
122}
123
124impl<D: Device, S: PcapSink> PcapWriter<D, S> {
125    /// Creates a packet capture writer.
126    pub fn new(lower: D, mut sink: S, mode: PcapMode) -> PcapWriter<D, S> {
127        let medium = lower.capabilities().medium;
128        let link_type = match medium {
129            #[cfg(feature = "medium-ip")]
130            Medium::Ip => PcapLinkType::Ip,
131            #[cfg(feature = "medium-ethernet")]
132            Medium::Ethernet => PcapLinkType::Ethernet,
133            #[cfg(feature = "medium-ieee802154")]
134            Medium::Ieee802154 => PcapLinkType::Ieee802154WithoutFcs,
135        };
136        sink.global_header(link_type);
137        PcapWriter {
138            lower,
139            sink: RefCell::new(sink),
140            mode,
141        }
142    }
143
144    /// Get a reference to the underlying device.
145    ///
146    /// Even if the device offers reading through a standard reference, it is inadvisable to
147    /// directly read from the device as doing so will circumvent the packet capture.
148    pub fn get_ref(&self) -> &D {
149        &self.lower
150    }
151
152    /// Get a mutable reference to the underlying device.
153    ///
154    /// It is inadvisable to directly read from the device as doing so will circumvent the packet capture.
155    pub fn get_mut(&mut self) -> &mut D {
156        &mut self.lower
157    }
158}
159
160impl<D: Device, S> Device for PcapWriter<D, S>
161where
162    S: PcapSink,
163{
164    type RxToken<'a>
165        = RxToken<'a, D::RxToken<'a>, S>
166    where
167        Self: 'a;
168    type TxToken<'a>
169        = TxToken<'a, D::TxToken<'a>, S>
170    where
171        Self: 'a;
172
173    fn capabilities(&self) -> DeviceCapabilities {
174        self.lower.capabilities()
175    }
176
177    fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
178        let sink = &self.sink;
179        let mode = self.mode;
180        self.lower
181            .receive(timestamp)
182            .map(move |(rx_token, tx_token)| {
183                let rx = RxToken {
184                    token: rx_token,
185                    sink,
186                    mode,
187                    timestamp,
188                };
189                let tx = TxToken {
190                    token: tx_token,
191                    sink,
192                    mode,
193                    timestamp,
194                };
195                (rx, tx)
196            })
197    }
198
199    fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
200        let sink = &self.sink;
201        let mode = self.mode;
202        self.lower.transmit(timestamp).map(move |token| TxToken {
203            token,
204            sink,
205            mode,
206            timestamp,
207        })
208    }
209}
210
211#[doc(hidden)]
212pub struct RxToken<'a, Rx: phy::RxToken, S: PcapSink> {
213    token: Rx,
214    sink: &'a RefCell<S>,
215    mode: PcapMode,
216    timestamp: Instant,
217}
218
219impl<'a, Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<'a, Rx, S> {
220    fn consume<R, F: FnOnce(&[u8]) -> R>(self, f: F) -> R {
221        self.token.consume(|buffer| {
222            match self.mode {
223                PcapMode::Both | PcapMode::RxOnly => self
224                    .sink
225                    .borrow_mut()
226                    .packet(self.timestamp, buffer.as_ref()),
227                PcapMode::TxOnly => (),
228            }
229            f(buffer)
230        })
231    }
232
233    fn meta(&self) -> phy::PacketMeta {
234        self.token.meta()
235    }
236}
237
238#[doc(hidden)]
239pub struct TxToken<'a, Tx: phy::TxToken, S: PcapSink> {
240    token: Tx,
241    sink: &'a RefCell<S>,
242    mode: PcapMode,
243    timestamp: Instant,
244}
245
246impl<'a, Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<'a, Tx, S> {
247    fn consume<R, F>(self, len: usize, f: F) -> R
248    where
249        F: FnOnce(&mut [u8]) -> R,
250    {
251        self.token.consume(len, |buffer| {
252            let result = f(buffer);
253            match self.mode {
254                PcapMode::Both | PcapMode::TxOnly => {
255                    self.sink.borrow_mut().packet(self.timestamp, buffer)
256                }
257                PcapMode::RxOnly => (),
258            };
259            result
260        })
261    }
262
263    fn set_meta(&mut self, meta: phy::PacketMeta) {
264        self.token.set_meta(meta)
265    }
266}