1
//! Support for asynchronous packet iteration.
2
//!
3
//! See [`Capture::stream`](super::Capture::stream).
4
use std::io;
5
use std::marker::Unpin;
6
use std::pin::Pin;
7
use std::task::{self, Poll};
8

            
9
use futures::ready;
10
use tokio::io::unix::AsyncFd;
11

            
12
use crate::{
13
    Error,
14
    capture::{Activated, Capture, selectable::SelectableCapture},
15
    codec::PacketCodec,
16
};
17

            
18
/// Implement Stream for async use of pcap
19
pub struct PacketStream<T: Activated + ?Sized, C> {
20
    inner: AsyncFd<SelectableCapture<T>>,
21
    codec: C,
22
}
23

            
24
impl<T: Activated + ?Sized, C> PacketStream<T, C> {
25
3
    pub(crate) fn new(capture: Capture<T>, codec: C) -> Result<Self, Error> {
26
3
        let capture = SelectableCapture::new(capture)?;
27
        // SAFETY: The descriptor belongs to the capture that `AsyncFd` takes ownership of, and
28
        // stays open until that capture is dropped.
29
3
        let inner =
30
3
            unsafe { AsyncFd::register_with_interest(capture, tokio::io::Interest::READABLE) }
31
3
                .map_err(io::Error::from)?;
32
3
        Ok(PacketStream { inner, codec })
33
3
    }
34

            
35
    /// Returns a mutable reference to the inner [`Capture`].
36
    ///
37
    /// The caller must ensure the capture will not be set to be blocking.
38
1
    pub fn capture_mut(&mut self) -> &mut Capture<T> {
39
1
        self.inner.get_mut().get_inner_mut()
40
1
    }
41
}
42

            
43
impl<T: Activated + ?Sized, C> Unpin for PacketStream<T, C> {}
44

            
45
impl<T: Activated + ?Sized, C: PacketCodec> futures::Stream for PacketStream<T, C> {
46
    type Item = Result<C::Item, Error>;
47

            
48
4
    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
49
4
        let stream = Pin::into_inner(self);
50
4
        let codec = &mut stream.codec;
51

            
52
        loop {
53
5
            let mut guard = ready!(stream.inner.poll_read_ready_mut(cx))?;
54
3
            match guard.try_io(
55
3
                |inner| match inner.get_mut().get_inner_mut().next_packet() {
56
1
                    Ok(p) => Ok(Ok(codec.decode(p))),
57
1
                    Err(e @ Error::TimeoutExpired) => {
58
1
                        Err(io::Error::new(io::ErrorKind::WouldBlock, e))
59
                    }
60
1
                    Err(e) => Ok(Err(e)),
61
3
                },
62
            ) {
63
2
                Ok(result) => {
64
2
                    return Poll::Ready(Some(result?));
65
                }
66
1
                Err(_would_block) => continue,
67
            }
68
        }
69
4
    }
70
}
71

            
72
#[cfg(test)]
73
mod tests {
74
    use std::os::unix::io::RawFd;
75

            
76
    use futures::{Stream, StreamExt};
77

            
78
    use crate::{
79
        capture::{
80
            Active,
81
            activated::testmod::{PACKET, next_ex_expect},
82
            selectable::testmod::FdPair,
83
            testmod::test_capture,
84
        },
85
        codec::testmod::Codec,
86
        raw::{
87
            self,
88
            testmod::{RAWMTX, as_pcap_t, geterr_expect},
89
        },
90
    };
91

            
92
    use super::*;
93

            
94
    // The caller holds on to the TestCapture, which owns the pcap_close expectation that fires
95
    // when the stream is dropped.
96
    fn test_stream(
97
        pcap: *mut raw::pcap_t,
98
        capture: Capture<Active>,
99
        fd: RawFd,
100
    ) -> PacketStream<Active, Codec> {
101
        let ctx = raw::pcap_get_selectable_fd_context();
102
        ctx.expect()
103
            .withf_st(move |arg1| *arg1 == pcap)
104
            .return_once(move |_| fd);
105

            
106
        PacketStream::new(capture, Codec).unwrap()
107
    }
108

            
109
    #[tokio::test]
110
    async fn test_stream_ok() {
111
        let _m = RAWMTX.lock();
112

            
113
        let mut dummy: isize = 777;
114
        let pcap = as_pcap_t(&mut dummy);
115
        let fds = FdPair::new();
116
        let fd = fds.0[0];
117

            
118
        let test_capture = test_capture::<Active>(pcap);
119

            
120
        let ctx = raw::pcap_setnonblock_context();
121
        ctx.expect()
122
            .withf_st(move |arg1, arg2, _| (*arg1 == pcap) && (*arg2 == 1))
123
            .return_once(|_, _, _| 0);
124

            
125
        let capture = test_capture.capture.setnonblock().unwrap();
126

            
127
        let ctx = raw::pcap_get_selectable_fd_context();
128
        ctx.expect()
129
            .withf_st(move |arg1| *arg1 == pcap)
130
            .return_once(move |_| fd);
131

            
132
        let mut stream = capture.stream(Codec).unwrap();
133
        assert!(stream.capture_mut().is_nonblock());
134
    }
135

            
136
    #[tokio::test]
137
    async fn test_stream_reads() {
138
        let _m = RAWMTX.lock();
139

            
140
        let mut dummy: isize = 777;
141
        let pcap = as_pcap_t(&mut dummy);
142
        let fds = FdPair::new();
143

            
144
        let test_capture = test_capture::<Active>(pcap);
145
        let mut stream = test_stream(pcap, test_capture.capture, fds.0[0]);
146

            
147
        fds.make_readable();
148

            
149
        let _nxt = next_ex_expect(pcap);
150

            
151
        let next_packet = stream.next().await.unwrap().unwrap();
152
        assert_eq!(next_packet.header, *PACKET.header);
153
        assert_eq!(*next_packet.data, *PACKET.data);
154

            
155
        let ctx = raw::pcap_next_ex_context();
156
        ctx.checkpoint();
157
        ctx.expect()
158
            .withf_st(move |arg1, _, _| *arg1 == pcap)
159
            .return_once(|_, _, _| -1);
160

            
161
        let _err = geterr_expect(pcap);
162

            
163
        // What libpcap failed with arrives as an item. It does not end the stream.
164
        let result = stream.next().await.unwrap();
165
        assert!(matches!(result, Err(Error::PcapError(_))));
166
    }
167

            
168
    #[tokio::test]
169
    async fn test_stream_timeout() {
170
        let _m = RAWMTX.lock();
171

            
172
        let mut dummy: isize = 777;
173
        let pcap = as_pcap_t(&mut dummy);
174
        let fds = FdPair::new();
175

            
176
        let test_capture = test_capture::<Active>(pcap);
177
        let mut stream = test_stream(pcap, test_capture.capture, fds.0[0]);
178

            
179
        fds.make_readable();
180

            
181
        let ctx = raw::pcap_next_ex_context();
182
        ctx.expect()
183
            .withf_st(move |arg1, _, _| *arg1 == pcap)
184
            .return_once(|_, _, _| 0);
185

            
186
        // Wait until the socketpair is reported readable, so the poll below gets past the
187
        // readiness check and asks libpcap for a packet. The guard is dropped without clearing,
188
        // which leaves the readiness in place.
189
        drop(stream.inner.readable().await.unwrap());
190

            
191
        // A timeout is not the end of the stream. It waits for the capture to be readable again.
192
        let poll =
193
            futures::future::poll_fn(|cx| Poll::Ready(Pin::new(&mut stream).poll_next(cx))).await;
194
        assert!(poll.is_pending());
195
    }
196
}