1
use std::{fmt, mem::MaybeUninit, ops::Deref, ptr};
2

            
3
#[cfg(test)]
4
use mockall::automock;
5

            
6
use crate::raw;
7

            
8
/// Represents a packet returned from pcap.
9
#[derive(Debug, Clone, PartialEq, Eq)]
10
pub struct Packet<'a> {
11
    /// The packet header provided by pcap, including the timeval, captured length, and packet
12
    /// length
13
    pub header: &'a PacketHeader,
14
    /// The captured packet data
15
    pub data: &'a [u8],
16
}
17

            
18
impl<'a> Packet<'a> {
19
    #[doc(hidden)]
20
1285
    pub fn new(header: &'a PacketHeader, data: &'a [u8]) -> Packet<'a> {
21
1285
        Packet { header, data }
22
1285
    }
23
}
24

            
25
impl Deref for Packet<'_> {
26
    type Target = [u8];
27

            
28
6
    fn deref(&self) -> &[u8] {
29
6
        self.data
30
6
    }
31
}
32

            
33
#[repr(C)]
34
#[derive(Copy, Clone)]
35
/// Represents a packet header provided by pcap, including the timeval, caplen and len.
36
///
37
/// On a 32-bit target whose C library uses a 64-bit `time_t`, `ts` holds libpcap's timestamp
38
/// converted to `libc::timeval`. A time after January 2038 does not fit.
39
pub struct PacketHeader {
40
    /// The time when the packet was captured
41
    pub ts: libc::timeval,
42
    /// The number of bytes of the packet that are available from the capture
43
    pub caplen: u32,
44
    /// The length of the packet, in bytes (which might be more than the number of bytes available
45
    /// from the capture, if the length of the packet is larger than the maximum number of bytes to
46
    /// capture)
47
    pub len: u32,
48
}
49

            
50
impl PacketHeader {
51
    /// Returns the header that libpcap handed out, borrowing it when the loaded libpcap lays
52
    /// headers out as `PacketHeader` does and converting it into `buf` when it does not.
53
    ///
54
    /// The two layouts can differ on a 32-bit target, where libpcap's `struct timeval` may be
55
    /// wider or narrower than `libc::timeval`, as [`Layout`] explains. Reading such a header as a
56
    /// `PacketHeader` would take `caplen` from the wrong offset and slice the packet data with it,
57
    /// which is why every header that libpcap hands to the crate goes through this method.
58
    ///
59
    /// # Safety
60
    ///
61
    /// `header` must point to a header filled in by libpcap that remains valid while the returned
62
    /// reference is in use.
63
    #[inline]
64
162
    pub(crate) unsafe fn from_raw(
65
162
        header: *const raw::pcap_pkthdr,
66
162
        buf: &mut MaybeUninit<PacketHeader>,
67
162
    ) -> &PacketHeader {
68
162
        unsafe { PacketHeader::borrow_or_read(header, layout(), buf) }
69
162
    }
70

            
71
    #[inline]
72
164
    unsafe fn borrow_or_read(
73
164
        header: *const raw::pcap_pkthdr,
74
164
        layout: Layout,
75
164
        buf: &mut MaybeUninit<PacketHeader>,
76
164
    ) -> &PacketHeader {
77
164
        match layout {
78
163
            Layout::Native => unsafe { &*header.cast::<PacketHeader>() },
79
1
            layout => buf.write(unsafe { PacketHeader::read(header, layout) }),
80
        }
81
164
    }
82

            
83
    /// Returns a pointer to the header laid out as the loaded libpcap reads it, for a call that
84
    /// takes one: the header itself when the layouts match, and a copy converted into `buf` when
85
    /// they do not. The pointer is valid as long as both `self` and `buf` are.
86
    ///
87
    /// This is the reverse of [`PacketHeader::from_raw`]. `pcap_dump` and `pcap_offline_filter`
88
    /// read the header in libpcap's layout, which can differ from that of `PacketHeader` as
89
    /// [`Layout`] explains, and would misread a `PacketHeader` passed to them as is.
90
    #[inline]
91
428
    pub(crate) fn as_raw(&self, buf: &mut MaybeUninit<RawHeader>) -> *const raw::pcap_pkthdr {
92
428
        self.borrow_or_write(layout(), buf)
93
428
    }
94

            
95
    #[inline]
96
430
    fn borrow_or_write(
97
430
        &self,
98
430
        layout: Layout,
99
430
        buf: &mut MaybeUninit<RawHeader>,
100
430
    ) -> *const raw::pcap_pkthdr {
101
430
        match layout {
102
429
            Layout::Native => (self as *const PacketHeader).cast(),
103
1
            layout => buf.write(self.write(layout)).as_ptr(),
104
        }
105
430
    }
106

            
107
7
    unsafe fn read(header: *const raw::pcap_pkthdr, layout: Layout) -> PacketHeader {
108
7
        match layout {
109
            Layout::Native => {
110
1
                let header = unsafe { ptr::read_unaligned(header) };
111
1
                PacketHeader {
112
1
                    ts: header.ts,
113
1
                    caplen: header.caplen,
114
1
                    len: header.len,
115
1
                }
116
            }
117
            Layout::Narrow => {
118
2
                let header = unsafe { ptr::read_unaligned(header.cast::<NarrowPkthdr>()) };
119
2
                PacketHeader {
120
2
                    ts: libc::timeval {
121
2
                        tv_sec: header.tv_sec as _,
122
2
                        tv_usec: header.tv_usec as _,
123
2
                    },
124
2
                    caplen: header.caplen,
125
2
                    len: header.len,
126
2
                }
127
            }
128
            Layout::Wide => {
129
4
                let header = unsafe { ptr::read_unaligned(header.cast::<WidePkthdr>()) };
130
4
                PacketHeader {
131
4
                    ts: libc::timeval {
132
4
                        tv_sec: header.tv_sec as _,
133
4
                        tv_usec: header.tv_usec as _,
134
4
                    },
135
4
                    caplen: header.caplen,
136
4
                    len: header.len,
137
4
                }
138
            }
139
        }
140
7
    }
141

            
142
6
    fn write(self, layout: Layout) -> RawHeader {
143
6
        match layout {
144
2
            Layout::Native => RawHeader::Native(raw::pcap_pkthdr {
145
2
                ts: self.ts,
146
2
                caplen: self.caplen,
147
2
                len: self.len,
148
2
            }),
149
1
            Layout::Narrow => RawHeader::Narrow(NarrowPkthdr {
150
1
                tv_sec: self.ts.tv_sec as _,
151
1
                tv_usec: self.ts.tv_usec as _,
152
1
                caplen: self.caplen,
153
1
                len: self.len,
154
1
            }),
155
3
            Layout::Wide => RawHeader::Wide(WidePkthdr {
156
3
                tv_sec: self.ts.tv_sec as _,
157
3
                tv_usec: self.ts.tv_usec as _,
158
3
                caplen: self.caplen,
159
3
                len: self.len,
160
3
            }),
161
        }
162
6
    }
163
}
164

            
165
impl fmt::Debug for PacketHeader {
166
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167
        // tv_usec holds nanoseconds when the capture was opened with Precision::Nano.
168
1
        write!(
169
1
            f,
170
            "PacketHeader {{ ts: timeval {{ tv_sec: {}, tv_usec: {} }}, caplen: {}, len: {} }}",
171
            self.ts.tv_sec, self.ts.tv_usec, self.caplen, self.len
172
        )
173
1
    }
174
}
175

            
176
impl PartialEq for PacketHeader {
177
428
    fn eq(&self, rhs: &PacketHeader) -> bool {
178
428
        self.ts.tv_sec == rhs.ts.tv_sec
179
428
            && self.ts.tv_usec == rhs.ts.tv_usec
180
428
            && self.caplen == rhs.caplen
181
428
            && self.len == rhs.len
182
428
    }
183
}
184

            
185
impl Eq for PacketHeader {}
186

            
187
/// How the loaded libpcap lays out `struct pcap_pkthdr`.
188
///
189
/// `struct timeval` holds two `long`s on most targets, and `libc::timeval` follows. A 32-bit
190
/// target is the exception: musl 1.2 and later, and glibc built with `_TIME_BITS=64`, widen both
191
/// fields to 64 bits, while `libc::timeval` keeps them at 32 by default.
192
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193
// Only a 32-bit target asks libpcap for the layout, so elsewhere nothing constructs `Narrow` or
194
// `Wide`.
195
#[cfg_attr(not(any(test, target_pointer_width = "32")), allow(dead_code))]
196
pub(crate) enum Layout {
197
    /// The layout of `raw::pcap_pkthdr`.
198
    Native,
199
    /// `ts` holds two 32-bit fields, where `libc::timeval` is wider.
200
    Narrow,
201
    /// `ts` holds two 64-bit fields, where `libc::timeval` is narrower.
202
    Wide,
203
}
204

            
205
/// A header whose `ts` holds two 32-bit fields: 16 bytes, with `caplen` at offset 8.
206
#[repr(C)]
207
#[derive(Clone, Copy)]
208
pub(crate) struct NarrowPkthdr {
209
    tv_sec: i32,
210
    tv_usec: i32,
211
    caplen: u32,
212
    len: u32,
213
}
214

            
215
/// A header whose `ts` holds two 64-bit fields: 24 bytes, with `caplen` at offset 16.
216
#[repr(C)]
217
#[derive(Clone, Copy)]
218
pub(crate) struct WidePkthdr {
219
    tv_sec: i64,
220
    tv_usec: i64,
221
    caplen: u32,
222
    len: u32,
223
}
224

            
225
/// A header laid out for the loaded libpcap.
226
pub(crate) enum RawHeader {
227
    Native(raw::pcap_pkthdr),
228
    Narrow(NarrowPkthdr),
229
    Wide(WidePkthdr),
230
}
231

            
232
impl RawHeader {
233
8
    pub(crate) fn as_ptr(&self) -> *const raw::pcap_pkthdr {
234
8
        match self {
235
3
            RawHeader::Native(header) => header,
236
1
            RawHeader::Narrow(header) => (header as *const NarrowPkthdr).cast(),
237
4
            RawHeader::Wide(header) => (header as *const WidePkthdr).cast(),
238
        }
239
8
    }
240
}
241

            
242
#[cfg_attr(test, automock)]
243
mod detect {
244
    use super::Layout;
245

            
246
    /// On a 32-bit target the C library may use a 64-bit `time_t`, so libpcap's `struct timeval`
247
    /// can differ from `libc::timeval` and the layout has to be asked of libpcap. Elsewhere the
248
    /// crate takes the layout to be that of `raw::pcap_pkthdr`.
249
    #[cfg(not(target_pointer_width = "32"))]
250
    #[cfg_attr(test, allow(dead_code))]
251
    #[inline]
252
570
    pub fn layout() -> Layout {
253
570
        Layout::Native
254
570
    }
255

            
256
    #[cfg(target_pointer_width = "32")]
257
    #[cfg_attr(test, allow(dead_code))]
258
    #[inline]
259
    pub fn layout() -> Layout {
260
        static LAYOUT: std::sync::OnceLock<Layout> = std::sync::OnceLock::new();
261
        *LAYOUT.get_or_init(super::probe)
262
    }
263
}
264

            
265
#[cfg(not(test))]
266
use detect::layout;
267

            
268
#[cfg(test)]
269
pub(crate) use mock_detect::*;
270

            
271
/// Asks the loaded libpcap where it reads `len`. A filter of two instructions, `ld len` and
272
/// `ret a`, returns the packet length, and the header passed with it holds 16 where a 16-byte
273
/// header keeps `len` and 24 where a 24-byte header does.
274
#[cfg(any(test, target_pointer_width = "32"))]
275
2
fn probe() -> Layout {
276
    const LD_LEN: u16 = 0x80; // BPF_LD | BPF_W | BPF_LEN
277
    const RET_A: u16 = 0x16; // BPF_RET | BPF_A
278

            
279
    #[repr(C, align(8))]
280
    struct Header([u32; 6]);
281

            
282
    // Words 3 and 5 are `len` in a 16-byte and a 24-byte header. `caplen` is zero in both, so
283
    // libpcap is given no packet data to read.
284
2
    let header = Header([0, 0, 0, 16, 0, 24]);
285
2
    let mut insns = [
286
2
        raw::bpf_insn {
287
2
            code: LD_LEN,
288
2
            jt: 0,
289
2
            jf: 0,
290
2
            k: 0,
291
2
        },
292
2
        raw::bpf_insn {
293
2
            code: RET_A,
294
2
            jt: 0,
295
2
            jf: 0,
296
2
            k: 0,
297
2
        },
298
2
    ];
299
2
    let program = raw::bpf_program {
300
2
        bf_len: insns.len() as _,
301
2
        bf_insns: insns.as_mut_ptr(),
302
2
    };
303
2
    let data = [0u8];
304

            
305
2
    let len =
306
2
        unsafe { raw::pcap_offline_filter(&program, header.0.as_ptr().cast(), data.as_ptr()) };
307
2
    layout_for(len, std::mem::size_of::<raw::pcap_pkthdr>())
308
2
}
309

            
310
/// The layout of a header `len` bytes long, where `raw::pcap_pkthdr` is `native` bytes long.
311
#[cfg(any(test, target_pointer_width = "32"))]
312
9
fn layout_for(len: libc::c_int, native: usize) -> Layout {
313
9
    match (len, native) {
314
3
        (16, 24) => Layout::Narrow,
315
1
        (24, 16) => Layout::Wide,
316
        // Either the sizes match, or the answer is neither size.
317
5
        _ => Layout::Native,
318
    }
319
9
}
320

            
321
#[cfg(test)]
322
mod tests {
323
    use std::{mem, slice};
324

            
325
    use crate::raw::{self, testmod::RAWMTX};
326

            
327
    use super::*;
328

            
329
    static HEADER: PacketHeader = PacketHeader {
330
        ts: libc::timeval {
331
            tv_sec: 5,
332
            tv_usec: 50,
333
        },
334
        caplen: 5,
335
        len: 9,
336
    };
337

            
338
    #[test]
339
    fn test_packet_header_size() {
340
        use std::mem::size_of;
341
        assert_eq!(size_of::<PacketHeader>(), size_of::<raw::pcap_pkthdr>());
342
    }
343

            
344
    #[test]
345
    fn test_packet_header_clone() {
346
        // For code coverag purposes.
347
        #[allow(clippy::clone_on_copy)]
348
        let header_clone = HEADER.clone();
349
        assert_eq!(header_clone, HEADER);
350
    }
351

            
352
    #[test]
353
    fn test_packet_header_display() {
354
        assert_eq!(
355
            format!("{HEADER:?}"),
356
            "PacketHeader { ts: timeval { tv_sec: 5, tv_usec: 50 }, caplen: 5, len: 9 }"
357
        );
358
    }
359

            
360
    #[test]
361
    fn test_layout_sizes() {
362
        assert_eq!(mem::size_of::<NarrowPkthdr>(), 16);
363
        assert_eq!(mem::offset_of!(NarrowPkthdr, caplen), 8);
364
        assert_eq!(mem::size_of::<WidePkthdr>(), 24);
365
        assert_eq!(mem::offset_of!(WidePkthdr, caplen), 16);
366
    }
367

            
368
    #[test]
369
    fn test_read_foreign_layouts() {
370
        let narrow = NarrowPkthdr {
371
            tv_sec: 5,
372
            tv_usec: 50,
373
            caplen: 5,
374
            len: 9,
375
        };
376
        let wide = WidePkthdr {
377
            tv_sec: 5,
378
            tv_usec: 50,
379
            caplen: 5,
380
            len: 9,
381
        };
382

            
383
        let header =
384
            unsafe { PacketHeader::read((&narrow as *const NarrowPkthdr).cast(), Layout::Narrow) };
385
        assert_eq!(header, HEADER);
386
        let header =
387
            unsafe { PacketHeader::read((&wide as *const WidePkthdr).cast(), Layout::Wide) };
388
        assert_eq!(header, HEADER);
389
    }
390

            
391
    #[test]
392
    fn test_read_write_every_layout() {
393
        for layout in [Layout::Native, Layout::Narrow, Layout::Wide] {
394
            let raw = HEADER.write(layout);
395
            let header = unsafe { PacketHeader::read(raw.as_ptr(), layout) };
396
            assert_eq!(header, HEADER, "{layout:?}");
397
        }
398
    }
399

            
400
    #[test]
401
    fn test_borrow_or_read() {
402
        let native = HEADER.write(Layout::Native);
403
        let mut buf = MaybeUninit::<PacketHeader>::uninit();
404
        let header =
405
            unsafe { PacketHeader::borrow_or_read(native.as_ptr(), Layout::Native, &mut buf) };
406
        assert!(ptr::eq(header, native.as_ptr().cast()));
407

            
408
        let wide = HEADER.write(Layout::Wide);
409
        let mut buf = MaybeUninit::<PacketHeader>::uninit();
410
        let converted = buf.as_ptr();
411
        let header = unsafe { PacketHeader::borrow_or_read(wide.as_ptr(), Layout::Wide, &mut buf) };
412
        assert!(ptr::eq(header, converted));
413
        assert_eq!(*header, HEADER);
414
    }
415

            
416
    #[test]
417
    fn test_borrow_or_write() {
418
        let mut buf = MaybeUninit::<RawHeader>::uninit();
419
        let native = HEADER.borrow_or_write(Layout::Native, &mut buf);
420
        assert!(ptr::eq(native, (&HEADER as *const PacketHeader).cast()));
421

            
422
        let wide = HEADER.borrow_or_write(Layout::Wide, &mut buf);
423
        assert!(ptr::eq(wide, unsafe { buf.assume_init_ref() }.as_ptr()));
424
        assert_eq!(unsafe { PacketHeader::read(wide, Layout::Wide) }, HEADER);
425
    }
426

            
427
    #[test]
428
    fn test_layout_for() {
429
        assert_eq!(layout_for(16, 24), Layout::Narrow);
430
        assert_eq!(layout_for(24, 16), Layout::Wide);
431
        assert_eq!(layout_for(16, 16), Layout::Native);
432
        assert_eq!(layout_for(24, 24), Layout::Native);
433
        assert_eq!(layout_for(0, 24), Layout::Native);
434
    }
435

            
436
    #[test]
437
    fn test_probe() {
438
        let _m = RAWMTX.lock();
439

            
440
        for answer in [16, 24] {
441
            let ctx = raw::pcap_offline_filter_context();
442
            ctx.checkpoint();
443
            ctx.expect()
444
                .withf_st(|program, header, _| unsafe {
445
                    let insns =
446
                        slice::from_raw_parts((**program).bf_insns, (**program).bf_len as _);
447
                    let words = slice::from_raw_parts(header.cast::<u32>(), 6);
448
                    insns.len() == 2
449
                        && insns[0].code == 0x80
450
                        && insns[1].code == 0x16
451
                        && words == [0, 0, 0, 16, 0, 24]
452
                })
453
                .return_once_st(move |_, _, _| answer);
454

            
455
            assert_eq!(
456
                probe(),
457
                layout_for(answer, mem::size_of::<raw::pcap_pkthdr>())
458
            );
459
        }
460
    }
461
}