1pub mod addr;
6pub mod af_xdp;
7pub mod batch;
8pub mod bpf;
9pub mod epoll;
10pub(crate) mod gso;
11pub mod nonce;
12pub mod poll;
13pub mod spsc;
14pub mod tun_offload;
15pub mod uring;
16
17#[cfg(target_env = "musl")]
18pub(crate) type MsgFlags = libc::c_uint;
19#[cfg(not(target_env = "musl"))]
20pub(crate) type MsgFlags = libc::c_int;
21
22#[cfg(target_env = "musl")]
23pub(crate) type CmsgLen = libc::socklen_t;
24#[cfg(not(target_env = "musl"))]
25pub(crate) type CmsgLen = usize;
26
27pub use addr::{sockaddr_to_std, std_to_sockaddr};
28pub use bpf::{BpfFilterStatus, XdpRedirectFilter};
29
30#[inline]
31pub(crate) fn empty_msghdr() -> libc::msghdr {
32 unsafe { std::mem::zeroed() }
35}
36
37#[inline]
38pub(crate) fn empty_mmsghdr() -> libc::mmsghdr {
39 unsafe { std::mem::zeroed() }
41}
42
43pub const MAX_DATAGRAM_BATCH: usize = 64;
45
46pub const MAX_WIRE_DATAGRAM: usize = 2048;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum Backend {
53 IoUring,
55 AfXdpZeroCopy,
57 AfXdpCopy,
59 Mmsg,
61}
62
63use io_uring::{opcode, types, IoUring};
64use std::io;
65use std::net::UdpSocket;
66use std::os::fd::AsRawFd;
67
68pub trait DataPlaneIo {
70 fn backend(&self) -> Backend;
72 fn send(&mut self, datagram: &[u8]) -> std::io::Result<()>;
74 fn recv(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;
76
77 fn send_batch(&mut self, datagrams: &[&[u8]]) -> io::Result<usize> {
82 let mut count = 0usize;
83 for dg in datagrams {
84 self.send(dg)?;
85 count += 1;
86 }
87 Ok(count)
88 }
89
90 fn recv_batch(
96 &mut self,
97 bufs: &mut [[u8; MAX_WIRE_DATAGRAM]],
98 lens: &mut [usize],
99 ) -> io::Result<usize> {
100 if bufs.is_empty() || lens.is_empty() {
101 return Ok(0);
102 }
103 let n = self.recv(&mut bufs[0])?;
104 lens[0] = n;
105 Ok(1)
106 }
107}
108
109pub struct IoUringIo {
112 ring: IoUring,
113 socket: UdpSocket,
114}
115
116impl IoUringIo {
117 pub fn new(socket: UdpSocket) -> io::Result<IoUringIo> {
119 let ring = IoUring::new(8)?;
120 Ok(IoUringIo { ring, socket })
121 }
122
123 fn submit_and_reap(&mut self, entry: &io_uring::squeue::Entry) -> io::Result<usize> {
124 unsafe {
128 self.ring
129 .submission()
130 .push(entry)
131 .map_err(|_| io::Error::other("submission queue full"))?;
132 }
133 self.ring.submit_and_wait(1)?;
134 let cqe = self
135 .ring
136 .completion()
137 .next()
138 .ok_or_else(|| io::Error::other("missing completion"))?;
139 let res = cqe.result();
140 if res < 0 {
141 return Err(io::Error::from_raw_os_error(-res));
142 }
143 Ok(usize::try_from(res).expect("non-negative result fits usize"))
144 }
145}
146
147impl DataPlaneIo for IoUringIo {
148 fn backend(&self) -> Backend {
149 Backend::IoUring
150 }
151
152 fn send(&mut self, datagram: &[u8]) -> io::Result<()> {
153 let len =
154 u32::try_from(datagram.len()).map_err(|_| io::Error::other("datagram too large"))?;
155 let entry = opcode::Write::new(types::Fd(self.socket.as_raw_fd()), datagram.as_ptr(), len)
156 .build()
157 .user_data(0);
158 self.submit_and_reap(&entry)?;
159 Ok(())
160 }
161
162 fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
163 let len = u32::try_from(buf.len()).map_err(|_| io::Error::other("buffer too large"))?;
164 let entry = opcode::Read::new(types::Fd(self.socket.as_raw_fd()), buf.as_mut_ptr(), len)
165 .build()
166 .user_data(1);
167 self.submit_and_reap(&entry)
168 }
169}
170
171pub struct PlainIo {
173 socket: UdpSocket,
174}
175
176impl PlainIo {
177 pub fn new(socket: UdpSocket) -> PlainIo {
179 PlainIo { socket }
180 }
181}
182
183impl DataPlaneIo for PlainIo {
184 fn backend(&self) -> Backend {
185 Backend::Mmsg
186 }
187
188 fn send(&mut self, datagram: &[u8]) -> io::Result<()> {
189 self.socket.send(datagram).map(|_| ())
190 }
191
192 fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
193 self.socket.recv(buf)
194 }
195
196 fn send_batch(&mut self, datagrams: &[&[u8]]) -> io::Result<usize> {
200 if datagrams.is_empty() {
201 return Ok(0);
202 }
203
204 let count = datagrams.len().min(MAX_DATAGRAM_BATCH);
207
208 let mut iovecs = [libc::iovec {
212 iov_base: std::ptr::null_mut(),
213 iov_len: 0,
214 }; MAX_DATAGRAM_BATCH];
215 let mut msgs = [empty_mmsghdr(); MAX_DATAGRAM_BATCH];
216
217 for (i, dg) in datagrams[..count].iter().enumerate() {
218 iovecs[i].iov_base = dg.as_ptr().cast_mut().cast::<libc::c_void>();
223 iovecs[i].iov_len = dg.len();
224 msgs[i].msg_hdr.msg_iov = &raw mut iovecs[i];
225 msgs[i].msg_hdr.msg_iovlen = 1;
226 }
227
228 let ret = unsafe {
235 libc::sendmmsg(
236 self.socket.as_raw_fd(),
237 msgs.as_mut_ptr(),
238 u32::try_from(count).expect("count ≤ MAX_DATAGRAM_BATCH ≤ 64 fits u32"),
239 libc::MSG_NOSIGNAL as MsgFlags,
240 )
241 };
242
243 if ret < 0 {
244 return Err(io::Error::last_os_error());
245 }
246 Ok(usize::try_from(ret).expect("non-negative sendmmsg return fits usize"))
247 }
248
249 fn recv_batch(
254 &mut self,
255 bufs: &mut [[u8; MAX_WIRE_DATAGRAM]],
256 lens: &mut [usize],
257 ) -> io::Result<usize> {
258 if bufs.is_empty() {
259 return Ok(0);
260 }
261
262 let count = bufs.len().min(lens.len()).min(MAX_DATAGRAM_BATCH);
263
264 let mut iovecs = [libc::iovec {
265 iov_base: std::ptr::null_mut(),
266 iov_len: 0,
267 }; MAX_DATAGRAM_BATCH];
268 let mut msgs = [empty_mmsghdr(); MAX_DATAGRAM_BATCH];
269
270 for i in 0..count {
271 iovecs[i].iov_base = bufs[i].as_mut_ptr().cast::<libc::c_void>();
276 iovecs[i].iov_len = MAX_WIRE_DATAGRAM;
277 msgs[i].msg_hdr.msg_iov = &raw mut iovecs[i];
278 msgs[i].msg_hdr.msg_iovlen = 1;
279 }
280
281 let ret = unsafe {
289 libc::recvmmsg(
290 self.socket.as_raw_fd(),
291 msgs.as_mut_ptr(),
292 u32::try_from(count).expect("count ≤ MAX_DATAGRAM_BATCH ≤ 64 fits u32"),
293 libc::MSG_WAITFORONE as MsgFlags,
294 std::ptr::null_mut(),
295 )
296 };
297
298 if ret < 0 {
299 return Err(io::Error::last_os_error());
300 }
301 let received = usize::try_from(ret).expect("non-negative recvmmsg return fits usize");
302 for i in 0..received {
303 lens[i] = usize::try_from(msgs[i].msg_len).expect("msg_len fits usize");
304 }
305 Ok(received)
306 }
307}
308
309pub fn set_socket_buffers(sock: &UdpSocket, bytes: usize) -> io::Result<()> {
319 let size = i32::try_from(bytes)
320 .map_err(|_| io::Error::other("buffer size too large for setsockopt"))?;
321 let fd = sock.as_raw_fd();
322
323 let ret_snd = unsafe {
331 libc::setsockopt(
332 fd,
333 libc::SOL_SOCKET,
334 libc::SO_SNDBUF,
335 std::ptr::addr_of!(size).cast::<libc::c_void>(),
336 libc::socklen_t::try_from(std::mem::size_of::<i32>())
337 .expect("size_of::<i32>() fits socklen_t"),
338 )
339 };
340 if ret_snd != 0 {
341 return Err(io::Error::last_os_error());
342 }
343
344 let ret_rcv = unsafe {
346 libc::setsockopt(
347 fd,
348 libc::SOL_SOCKET,
349 libc::SO_RCVBUF,
350 std::ptr::addr_of!(size).cast::<libc::c_void>(),
351 libc::socklen_t::try_from(std::mem::size_of::<i32>())
352 .expect("size_of::<i32>() fits socklen_t"),
353 )
354 };
355 if ret_rcv != 0 {
356 return Err(io::Error::last_os_error());
357 }
358
359 Ok(())
360}
361
362pub fn pin_current_thread(core: usize) -> io::Result<()> {
366 let rc = unsafe {
370 let mut set: libc::cpu_set_t = std::mem::zeroed();
371 libc::CPU_ZERO(&mut set);
372 libc::CPU_SET(core, &mut set);
373 libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &raw const set)
374 };
375 if rc != 0 {
376 Err(io::Error::last_os_error())
377 } else {
378 Ok(())
379 }
380}
381
382pub fn select_backend(socket: UdpSocket) -> Box<dyn DataPlaneIo> {
385 let clone = socket.try_clone().expect("clone udp socket");
386 match IoUringIo::new(clone) {
387 Ok(io) => Box::new(io),
388 Err(_) => Box::new(PlainIo::new(socket)),
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 static URING_SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
405
406 #[test]
407 fn backends_are_ordered_by_preference() {
408 assert_ne!(Backend::IoUring, Backend::Mmsg);
410 }
411
412 #[test]
413 fn iouring_sends_and_receives_over_udp() {
414 let _guard = URING_SERIAL.lock().unwrap();
415 use std::net::UdpSocket;
416 let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
417 let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
418 tx.connect(rx.local_addr().unwrap()).unwrap();
419 rx.connect(tx.local_addr().unwrap()).unwrap();
420
421 let mut tx_io = match IoUringIo::new(tx) {
423 Ok(io) => io,
424 Err(_) => {
425 eprintln!(
426 "SKIP iouring_sends_and_receives_over_udp: \
427 io_uring ring unavailable (RLIMIT_MEMLOCK)"
428 );
429 return;
430 }
431 };
432 let mut rx_io = match IoUringIo::new(rx) {
433 Ok(io) => io,
434 Err(_) => {
435 eprintln!(
436 "SKIP iouring_sends_and_receives_over_udp: \
437 io_uring ring unavailable (RLIMIT_MEMLOCK)"
438 );
439 return;
440 }
441 };
442 assert_eq!(tx_io.backend(), Backend::IoUring);
443
444 tx_io.send(b"datagram via uring").unwrap();
445 let mut buf = [0u8; 64];
446 let n = rx_io.recv(&mut buf).unwrap();
447 assert_eq!(&buf[..n], b"datagram via uring");
448 drop(tx_io);
451 drop(rx_io);
452 std::thread::sleep(std::time::Duration::from_millis(200));
453 }
454
455 #[test]
456 fn plain_io_sends_and_receives() {
457 use std::net::UdpSocket;
458 let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
459 let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
460 tx.connect(rx.local_addr().unwrap()).unwrap();
461 rx.connect(tx.local_addr().unwrap()).unwrap();
462 let mut t = PlainIo::new(tx);
463 let mut r = PlainIo::new(rx);
464 assert_eq!(t.backend(), Backend::Mmsg);
465 t.send(b"plain path").unwrap();
466 let mut buf = [0u8; 32];
467 let n = r.recv(&mut buf).unwrap();
468 assert_eq!(&buf[..n], b"plain path");
469 }
470
471 #[test]
472 fn select_backend_prefers_io_uring_or_falls_back() {
473 let _guard = URING_SERIAL.lock().unwrap();
474 use std::net::UdpSocket;
475
476 let probe_sock = UdpSocket::bind("127.0.0.1:0").unwrap();
479 let probe_peer = UdpSocket::bind("127.0.0.1:0").unwrap();
480 probe_sock
481 .connect(probe_peer.local_addr().unwrap())
482 .unwrap();
483 let uring_ok = IoUringIo::new(probe_sock).is_ok();
484 drop(probe_peer);
486 std::thread::sleep(std::time::Duration::from_millis(50));
487
488 let s = UdpSocket::bind("127.0.0.1:0").unwrap();
489 let io = select_backend(s);
490
491 if uring_ok {
493 assert_eq!(io.backend(), Backend::IoUring);
494 } else {
495 assert_eq!(io.backend(), Backend::Mmsg);
496 }
497 }
498
499 #[test]
500 fn set_socket_buffers_succeeds_on_loopback() {
501 use std::net::UdpSocket;
502 let sock = UdpSocket::bind("127.0.0.1:0").unwrap();
503 let result = set_socket_buffers(&sock, 4 * 1024 * 1024);
506 assert!(
507 result.is_ok(),
508 "set_socket_buffers returned error: {:?}",
509 result
510 );
511 }
512
513 #[test]
514 fn plainio_send_and_recv_batch_roundtrip() {
515 use std::net::UdpSocket;
516 let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
517 let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
518 tx.connect(rx.local_addr().unwrap()).unwrap();
519 rx.connect(tx.local_addr().unwrap()).unwrap();
520 let mut tx_io = PlainIo::new(tx);
521 let mut rx_io = PlainIo::new(rx);
522
523 let a = b"first-datagram".as_slice();
524 let b = b"second".as_slice();
525 let sent = tx_io.send_batch(&[a, b]).unwrap();
526 assert_eq!(sent, 2);
527
528 let mut bufs = vec![[0u8; MAX_WIRE_DATAGRAM]; 8];
529 let mut lens = [0usize; 8];
530 let mut got: Vec<Vec<u8>> = Vec::new();
532 while got.len() < 2 {
533 let n = rx_io.recv_batch(&mut bufs, &mut lens).unwrap();
534 for i in 0..n {
535 got.push(bufs[i][..lens[i]].to_vec());
536 }
537 }
538 assert!(got.contains(&a.to_vec()));
539 assert!(got.contains(&b.to_vec()));
540 }
541
542 #[test]
543 fn test_pin_current_thread_does_not_crash() {
544 let _ = pin_current_thread(0);
547 }
548
549 #[test]
550 fn test_plain_io_single_send_recv_and_defaults() {
551 use std::net::UdpSocket;
552 let rx = UdpSocket::bind("127.0.0.1:0").unwrap();
553 let tx = UdpSocket::bind("127.0.0.1:0").unwrap();
554 tx.connect(rx.local_addr().unwrap()).unwrap();
555 rx.connect(tx.local_addr().unwrap()).unwrap();
556 let mut tx_io = PlainIo::new(tx);
557 let mut rx_io = PlainIo::new(rx);
558
559 tx_io.send(b"single-packet").unwrap();
561 let mut buf = [0u8; 64];
562 let n = rx_io.recv(&mut buf).unwrap();
563 assert_eq!(&buf[..n], b"single-packet");
564
565 assert_eq!(tx_io.send_batch(&[]).unwrap(), 0);
567 let mut empty_lens = [0usize; 1];
568 assert_eq!(rx_io.recv_batch(&mut [], &mut empty_lens).unwrap(), 0);
569
570 assert_ne!(Backend::AfXdpZeroCopy, Backend::AfXdpCopy);
572
573 struct DefaultDataPlane {
575 payload: Vec<u8>,
576 }
577 impl DataPlaneIo for DefaultDataPlane {
578 fn backend(&self) -> Backend {
579 Backend::Mmsg
580 }
581 fn send(&mut self, datagram: &[u8]) -> std::io::Result<()> {
582 self.payload = datagram.to_vec();
583 Ok(())
584 }
585 fn recv(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
586 let n = self.payload.len().min(buf.len());
587 buf[..n].copy_from_slice(&self.payload[..n]);
588 Ok(n)
589 }
590 }
591
592 let mut ddp = DefaultDataPlane {
593 payload: Vec::new(),
594 };
595 let sent = ddp.send_batch(&[b"test-1", b"test-2"]).unwrap();
596 assert_eq!(sent, 2);
597 let mut bufs = vec![[0u8; MAX_WIRE_DATAGRAM]; 2];
598 let mut lens = [0usize; 2];
599 let recvd = ddp.recv_batch(&mut bufs, &mut lens).unwrap();
600 assert_eq!(recvd, 1);
601 assert_eq!(&bufs[0][..lens[0]], b"test-2");
602 assert_eq!(ddp.recv_batch(&mut [], &mut lens).unwrap(), 0);
603 }
604}