Skip to main content

qudp/
lib.rs

1use std::{
2    future::Future,
3    io::{self, IoSlice, IoSliceMut},
4    net::SocketAddr,
5    num::NonZeroU32,
6    pin::Pin,
7    sync::atomic::AtomicI32,
8    task::{Context, Poll, ready},
9};
10
11use bytes::BytesMut;
12use qbase::net::route::Line;
13use socket2::{Domain, Socket, Type};
14use tokio::io::Interest;
15pub const BATCH_SIZE: usize = 64;
16cfg_if::cfg_if! {
17    if #[cfg(unix)]{
18        #[path = "unix.rs"]
19        mod unix;
20    } else if #[cfg(windows)] {
21        #[path = "windows.rs"]
22        mod windows;
23    } else {
24        compile_error!("Unsupported platform");
25    }
26}
27
28pub mod ext;
29
30#[derive(Debug)]
31pub struct UdpSocket {
32    io: tokio::net::UdpSocket,
33    ttl: AtomicI32,
34    bound_device: Option<BoundDevice>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct BoundDevice {
39    name: String,
40    index: NonZeroU32,
41}
42
43impl BoundDevice {
44    pub fn new(name: impl Into<String>, index: u32) -> io::Result<Self> {
45        let index = NonZeroU32::new(index).ok_or_else(|| {
46            io::Error::new(
47                io::ErrorKind::InvalidInput,
48                "interface index must be non-zero",
49            )
50        })?;
51        Ok(Self {
52            name: name.into(),
53            index,
54        })
55    }
56
57    pub fn name(&self) -> &str {
58        &self.name
59    }
60
61    pub fn index(&self) -> NonZeroU32 {
62        self.index
63    }
64}
65
66impl UdpSocket {
67    pub fn bind(addr: SocketAddr) -> io::Result<Self> {
68        Self::bind_scoped(addr, None)
69    }
70
71    pub fn bind_to_device(addr: SocketAddr, device: BoundDevice) -> io::Result<Self> {
72        Self::bind_scoped(addr, Some(device))
73    }
74
75    fn bind_scoped(addr: SocketAddr, bound_device: Option<BoundDevice>) -> io::Result<Self> {
76        let domain = if addr.is_ipv4() {
77            Domain::IPV4
78        } else {
79            Domain::IPV6
80        };
81
82        let socket = Socket::new(domain, Type::DGRAM, None)?;
83        socket.set_nonblocking(true)?;
84        Self::config(&socket, addr)?;
85        if let Some(device) = bound_device.as_ref() {
86            let socket_ref = socket2::SockRef::from(&socket);
87            Self::bind_device_to_socket(&socket_ref, addr, device)?;
88        }
89        let io = tokio::net::UdpSocket::from_std(socket.into())?;
90        let usc = Self {
91            io,
92            ttl: AtomicI32::new(Line::DEFAULT_TTL as i32),
93            bound_device,
94        };
95        Ok(usc)
96    }
97
98    pub fn local_addr(&self) -> io::Result<SocketAddr> {
99        self.io.local_addr()
100    }
101
102    pub fn bound_device(&self) -> Option<&BoundDevice> {
103        self.bound_device.as_ref()
104    }
105
106    pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
107        self.io.poll_send_ready(cx)
108    }
109
110    pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
111        self.io.poll_recv_ready(cx)
112    }
113
114    pub fn poll_send(
115        &self,
116        cx: &mut Context<'_>,
117        bufs: &[IoSlice<'_>],
118        line: &Line,
119    ) -> Poll<io::Result<usize>> {
120        loop {
121            ready!(self.poll_send_ready(cx))?;
122            self.set_ttl(line.ttl as i32)?;
123            match self
124                .io
125                .try_io(Interest::WRITABLE, || self.sendmsg(bufs, line))
126            {
127                Ok(n) => return Poll::Ready(Ok(n)),
128                Err(e) if e.kind() == io::ErrorKind::WouldBlock => continue,
129                Err(e) => return Poll::Ready(Err(e)),
130            }
131        }
132    }
133
134    pub fn poll_recv(
135        &self,
136        cx: &mut Context,
137        bufs: &mut [IoSliceMut<'_>],
138        lines: &mut [Line],
139    ) -> Poll<io::Result<usize>> {
140        loop {
141            ready!(self.poll_recv_ready(cx)?);
142            let f = || self.recvmsg(bufs, lines);
143            let ret = self.io.try_io(Interest::READABLE, f);
144            if matches!(&ret, Err(e) if e.kind() == io::ErrorKind::WouldBlock) {
145                continue;
146            } else {
147                return Poll::Ready(ret);
148            }
149        }
150    }
151
152    pub fn bind_device(&self, device: &str) -> io::Result<()> {
153        #[cfg(not(unix))]
154        {
155            let _ = device;
156            return Err(io::Error::new(
157                io::ErrorKind::Unsupported,
158                "binding an existing UDP socket by interface name is unsupported on this platform",
159            ));
160        }
161        #[cfg(unix)]
162        {
163            let index = nix::net::if_::if_nametoindex(device)?;
164            let device = BoundDevice::new(device, index)?;
165            let socket = socket2::SockRef::from(&self.io);
166            Self::bind_device_to_socket(&socket, self.io.local_addr()?, &device)
167        }
168    }
169}
170
171pub trait Io {
172    fn config(io: &socket2::Socket, addr: SocketAddr) -> io::Result<()>;
173
174    fn bind_device_to_socket(
175        io: &socket2::SockRef<'_>,
176        addr: SocketAddr,
177        device: &BoundDevice,
178    ) -> io::Result<()>;
179
180    fn sendmsg(&self, bufs: &[IoSlice<'_>], line: &Line) -> io::Result<usize>;
181
182    fn recvmsg(&self, bufs: &mut [IoSliceMut<'_>], line: &mut [Line]) -> io::Result<usize>;
183
184    fn set_ttl(&self, ttl: i32) -> io::Result<()>;
185}
186
187impl UdpSocket {
188    pub fn send<'a>(&'a self, iovecs: &'a [IoSlice<'a>], line: Line) -> Send<'a> {
189        Send {
190            socket: self,
191            iovecs,
192            line,
193        }
194    }
195
196    pub fn receive<'a>(&'a self, iovecs: &'a mut [BytesMut], lines: &'a mut [Line]) -> Receive<'a> {
197        Receive {
198            socket: self,
199            iovecs,
200            lines,
201        }
202    }
203}
204
205pub struct Send<'a> {
206    socket: &'a UdpSocket,
207    iovecs: &'a [IoSlice<'a>],
208    line: Line,
209}
210
211impl Future for Send<'_> {
212    type Output = io::Result<usize>;
213
214    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
215        let this = self.get_mut();
216        this.socket.poll_send(cx, this.iovecs, &this.line)
217    }
218}
219
220pub struct Receive<'a> {
221    socket: &'a UdpSocket,
222    iovecs: &'a mut [BytesMut],
223    lines: &'a mut [Line],
224}
225
226impl Future for Receive<'_> {
227    type Output = io::Result<usize>;
228
229    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
230        let this = self.get_mut();
231        let mut bufs = this
232            .iovecs
233            .iter_mut()
234            .map(|b| IoSliceMut::new(b))
235            .collect::<Vec<_>>();
236        this.socket.poll_recv(cx, &mut bufs, this.lines)
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use std::{
243        io,
244        net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
245        time::Duration,
246    };
247
248    use super::*;
249
250    #[tokio::test(flavor = "current_thread")]
251    async fn ipv6_wildcard_socket_does_not_receive_ipv4_packets() -> io::Result<()> {
252        let socket4 = UdpSocket::bind(SocketAddr::V6(SocketAddrV6::new(
253            Ipv6Addr::UNSPECIFIED,
254            0,
255            0,
256            0,
257        )))?;
258        let port = socket4.local_addr()?.port();
259
260        let socket6 =
261            std::net::UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)))?;
262        socket6.send_to(
263            b"ping",
264            SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)),
265        )?;
266
267        let mut iovecs = [BytesMut::with_capacity(1500); 1];
268        let mut lines = [Line::default(); 1];
269        let result = tokio::time::timeout(
270            Duration::from_millis(200),
271            socket4.receive(&mut iovecs, &mut lines),
272        )
273        .await;
274        assert!(
275            result.is_err(),
276            "unexpected ipv4 datagram arrived on ipv6 wildcard socket: {result:?}"
277        );
278        Ok(())
279    }
280
281    #[tokio::test(flavor = "current_thread")]
282    async fn ipv4_and_ipv6_wildcard_sockets_can_bind_same_port() -> io::Result<()> {
283        let v6 = UdpSocket::bind(SocketAddr::V6(SocketAddrV6::new(
284            Ipv6Addr::UNSPECIFIED,
285            0,
286            0,
287            0,
288        )))?;
289        let port = v6.local_addr()?.port();
290
291        let v4 = UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(
292            Ipv4Addr::UNSPECIFIED,
293            port,
294        )))?;
295
296        assert!(matches!(v6.local_addr()?, SocketAddr::V6(_)));
297        assert!(matches!(v4.local_addr()?, SocketAddr::V4(_)));
298        Ok(())
299    }
300}