1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4 crate::{
5 device::{DeviceQueue, NetworkDevice, QueueId, RingSizes, TxCompletionRing},
6 ecn_codepoint::EcnCodepoint,
7 gre::{
8 construct_gre_packet, gre_packet_size,
9 packet::{GRE_HEADER_BASE_SIZE, INNER_PACKET_HEADER_SIZE},
10 },
11 netlink::MacAddress,
12 packet::{
13 IP_HEADER_SIZE, PACKET_HEADER_SIZE, UDP_HEADER_SIZE, VLAN_PACKET_HEADER_SIZE,
14 construct_packet, construct_vlan_packet,
15 },
16 route::NextHop,
17 socket::{Socket, Tx, TxRing},
18 umem::{Frame, OwnedUmem, PageAlignedMemory, Umem},
19 },
20 agave_cpu_utils::set_cpu_affinity,
21 crossbeam_queue::ArrayQueue,
22 libc::{_SC_PAGESIZE, sysconf},
23 std::{
24 error::Error,
25 fmt, io,
26 net::{IpAddr, SocketAddr, SocketAddrV4},
27 sync::{
28 Arc,
29 atomic::{AtomicUsize, Ordering},
30 },
31 thread,
32 time::Duration,
33 },
34};
35
36pub struct TxLoopConfigBuilder {
37 zero_copy: bool,
38 maybe_src_mac: Option<MacAddress>,
39}
40
41impl TxLoopConfigBuilder {
42 pub fn new() -> Self {
43 Self {
44 zero_copy: false,
45 maybe_src_mac: None,
46 }
47 }
48
49 pub fn zero_copy(&mut self, enable: bool) -> &mut Self {
50 self.zero_copy = enable;
51 self
52 }
53
54 pub fn override_src_mac(&mut self, mac: MacAddress) -> &mut Self {
55 self.maybe_src_mac = Some(mac);
56 self
57 }
58
59 pub fn build_with_src_device(self, src_device: &NetworkDevice) -> TxLoopConfig {
60 let Self {
61 zero_copy,
62 maybe_src_mac,
63 } = self;
64
65 let src_mac = maybe_src_mac.unwrap_or_else(|| {
66 src_device
68 .mac_addr()
69 .expect("no src_mac provided, device must have a MAC address")
70 });
71
72 TxLoopConfig { zero_copy, src_mac }
73 }
74}
75
76impl Default for TxLoopConfigBuilder {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[derive(Clone, Debug)]
83pub struct TxLoopConfig {
84 zero_copy: bool,
85 src_mac: MacAddress,
86}
87
88pub struct TxLoopBuilder<U: Umem> {
89 cpu_id: usize,
90 zero_copy: bool,
91 src_mac: MacAddress,
92 queue: DeviceQueue,
93 tx_size: usize,
94 umem: U,
95}
96
97impl TxLoopBuilder<OwnedUmem> {
98 pub fn new(
99 cpu_id: usize,
100 queue_id: QueueId,
101 config: TxLoopConfig,
102 dev: &NetworkDevice,
103 ) -> TxLoopBuilder<OwnedUmem> {
104 let TxLoopConfig { zero_copy, src_mac } = config;
105
106 log::info!(
107 "starting xdp loop on {} queue {queue_id:?} cpu {cpu_id}",
108 dev.name()
109 );
110
111 let frame_size = unsafe { sysconf(_SC_PAGESIZE) } as usize;
116
117 let queue = dev
118 .open_queue(queue_id)
119 .expect("failed to open queue for AF_XDP socket");
120 let RingSizes {
121 rx: rx_size,
122 tx: tx_size,
123 } = queue.ring_sizes().unwrap_or_else(|| {
124 log::info!(
125 "using default ring sizes for {} queue {queue_id:?}",
126 dev.name()
127 );
128 RingSizes::default()
129 });
130
131 let frame_count = (rx_size + tx_size) * 2;
132
133 const HUGE_2MB: usize = 2 * 1024 * 1024;
135 let memory =
136 PageAlignedMemory::alloc_with_page_size(frame_size, frame_count, HUGE_2MB, true)
137 .or_else(|_| {
138 log::warn!("huge page alloc failed, falling back to regular page size");
139 PageAlignedMemory::alloc(frame_size, frame_count)
140 })
141 .unwrap();
142 let umem = OwnedUmem::new(memory, frame_size as u32).unwrap();
143
144 TxLoopBuilder {
145 cpu_id,
146 zero_copy,
147 src_mac,
148 queue,
149 tx_size,
150 umem,
151 }
152 }
153
154 pub fn build(self) -> Result<TxLoop<OwnedUmem>, io::Error> {
155 let TxLoopBuilder {
156 cpu_id,
157 zero_copy,
158 src_mac,
159 queue,
160 tx_size,
161 umem,
162 } = self;
163
164 let queue_id = queue.id();
165 let (socket, tx) =
166 Socket::tx(queue, umem, zero_copy, tx_size * 2, tx_size).map_err(|err| {
167 log::error!(
168 "failed to create AF_XDP TX socket for queue {queue_id:?} on CPU {cpu_id}: \
169 {err}"
170 );
171 err
172 })?;
173
174 let Tx {
175 ring,
177 completion,
179 } = tx;
180 let ring = ring.unwrap();
181
182 Ok(TxLoop {
183 cpu_id,
184 src_mac,
185 socket,
186 ring,
187 completion,
188 })
189 }
190}
191
192pub struct TxLoop<U: Umem> {
193 cpu_id: usize,
194 src_mac: MacAddress,
195 socket: Socket<U>,
196 ring: TxRing<U::Frame>,
197 completion: TxCompletionRing,
198}
199
200pub trait TxPacket {
203 type Addrs: AsRef<[SocketAddr]>;
204 type Payload: AsRef<[u8]>;
205
206 fn dst_addrs(&self) -> &Self::Addrs;
208
209 fn payload(&self) -> &Self::Payload;
211
212 fn src_addr(&self) -> SocketAddrV4;
214
215 fn ecn(&self) -> Option<EcnCodepoint>;
217
218 fn allow_mtu_overflow(&self) -> bool;
220}
221
222#[derive(Copy, Clone, Debug, PartialEq, Eq)]
223pub enum TryRecvError {
224 Empty,
225 Disconnected,
226}
227
228pub enum TrySendError<T> {
229 Full(T),
230 Disconnected(T),
231}
232
233impl<T> fmt::Debug for TrySendError<T> {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 match self {
236 Self::Full(_) => f.write_str("Full(..)"),
237 Self::Disconnected(_) => f.write_str("Disconnected(..)"),
238 }
239 }
240}
241
242impl<T> fmt::Display for TrySendError<T> {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 Self::Full(_) => f.write_str("sending into a full XDP transmit channel"),
246 Self::Disconnected(_) => f.write_str("sending into a closed XDP transmit channel"),
247 }
248 }
249}
250
251impl<T> Error for TrySendError<T> {}
252
253pub trait Receiver<T> {
254 fn try_recv(&self) -> Result<T, TryRecvError>;
255}
256
257pub struct TxSender<T> {
258 queue: Arc<SharedQueue<T>>,
259}
260
261pub struct TxReceiver<T> {
262 queue: Arc<SharedQueue<T>>,
263}
264
265struct SharedQueue<T> {
266 queue: ArrayQueue<T>,
267 senders: AtomicUsize,
268 receivers: AtomicUsize,
269}
270
271pub fn channel<T>(capacity: usize) -> (TxSender<T>, TxReceiver<T>) {
272 let queue = Arc::new(SharedQueue {
273 queue: ArrayQueue::new(capacity),
274 senders: AtomicUsize::new(1),
275 receivers: AtomicUsize::new(1),
276 });
277 (
278 TxSender {
279 queue: Arc::clone(&queue),
280 },
281 TxReceiver { queue },
282 )
283}
284
285impl<T> Clone for TxSender<T> {
286 fn clone(&self) -> Self {
287 self.queue.senders.fetch_add(1, Ordering::Relaxed);
288 Self {
289 queue: Arc::clone(&self.queue),
290 }
291 }
292}
293
294impl<T> Drop for TxSender<T> {
295 fn drop(&mut self) {
296 self.queue.senders.fetch_sub(1, Ordering::Release);
297 }
298}
299
300impl<T> TxSender<T> {
301 pub fn try_send(&self, item: T) -> Result<(), TrySendError<T>> {
302 if self.queue.receivers.load(Ordering::Relaxed) == 0 {
303 return Err(TrySendError::Disconnected(item));
304 }
305 self.queue.queue.push(item).map_err(TrySendError::Full)
306 }
307}
308
309impl<T> Drop for TxReceiver<T> {
310 fn drop(&mut self) {
311 self.queue.receivers.fetch_sub(1, Ordering::Relaxed);
312 }
313}
314
315impl<T> Receiver<T> for TxReceiver<T> {
316 fn try_recv(&self) -> Result<T, TryRecvError> {
317 if let Some(item) = self.queue.queue.pop() {
318 return Ok(item);
319 }
320 if self.queue.senders.load(Ordering::Acquire) != 0 {
321 return Err(TryRecvError::Empty);
322 }
323 self.queue.queue.pop().ok_or(TryRecvError::Disconnected)
324 }
325}
326
327impl<U: Umem> TxLoop<U> {
328 pub fn run<T, Rx, D, R>(self, receiver: Rx, mut drop_item: D, mut route_fn: R)
329 where
330 T: TxPacket,
331 Rx: Receiver<T>,
332 D: FnMut(T),
333 R: FnMut(&IpAddr) -> Option<NextHop>,
334 {
335 const RECV_TIMEOUT: Duration = Duration::from_nanos(1000);
337
338 const MAX_TIMEOUTS: usize = 1;
339
340 const BATCH_SIZE: usize = 64;
343
344 let TxLoop {
345 cpu_id,
346 src_mac,
347 socket,
348 mut ring,
349 mut completion,
350 } = self;
351
352 set_cpu_affinity(None, [agave_cpu_utils::CpuId::new(cpu_id).unwrap()]).unwrap();
354
355 let umem = socket.umem();
356 let umem_tx_capacity = umem.available();
357 let umem_frame_size = umem.frame_size();
358
359 let mut written_uncommitted = 0;
361
362 let mut timeouts = 0;
363 loop {
364 let item = match receiver.try_recv() {
365 Ok(item) => {
366 timeouts = 0;
367 item
368 }
369 Err(TryRecvError::Empty) => {
370 if timeouts < MAX_TIMEOUTS {
371 timeouts += 1;
372 thread::sleep(RECV_TIMEOUT);
373 } else {
374 timeouts = 0;
375 commit_pending(&mut ring, &mut written_uncommitted);
376 kick(&ring);
378 }
379 continue;
380 }
381 Err(TryRecvError::Disconnected) => break,
382 };
383
384 let src_addr = item.src_addr();
385 let src_ip = src_addr.ip();
386 let src_port = src_addr.port();
387 let ecn = item.ecn();
388 let can_overflow_mtu = item.allow_mtu_overflow();
389 for addr in item.dst_addrs().as_ref() {
390 if ring.available() == 0 || umem.available() == 0 {
391 commit_pending(&mut ring, &mut written_uncommitted);
392 kick(&ring);
393
394 loop {
396 completion.sync(true);
397 ring.sync(false);
399
400 while let Some(frame) = completion.read() {
402 umem.release_completed(frame);
403 }
404
405 if ring.available() > 0 && umem.available() > 0 {
406 break;
408 }
409
410 kick(&ring);
413 }
414 }
415
416 let mut frame = umem.reserve().unwrap();
419 let IpAddr::V4(dst_ip) = addr.ip() else {
420 panic!("IPv6 not supported");
421 };
422
423 let payload = item.payload().as_ref();
424 let len = payload.len();
425
426 let dst = addr.ip();
427 let Some(next_hop) = route_fn(&dst) else {
428 log::warn!("dropping packet: no route for peer {addr}");
429 umem.release(frame);
430 continue;
431 };
432
433 if let Some(gre) = &next_hop.gre {
434 let Some(dest_mac) = gre.underlay_mac_addr else {
435 log::warn!(
436 "dropping packet: GRE peer {addr} must be routed through {} on if{} \
437 which has no known MAC address",
438 gre.underlay_ip_addr,
439 gre.underlay_if_index
440 );
441 umem.release(frame);
442 continue;
443 };
444
445 let l3_inner_packet_len = INNER_PACKET_HEADER_SIZE + len;
446 let l3_outer_gre_packet_len =
447 IP_HEADER_SIZE + GRE_HEADER_BASE_SIZE + l3_inner_packet_len;
448
449 if l3_inner_packet_len > gre.mtu as usize
450 || l3_outer_gre_packet_len > next_hop.mtu as usize
451 {
452 if !can_overflow_mtu {
453 log::warn!(
454 "dropping packet: GRE payload exceeds MTU for {addr}: L3 inner \
455 packet length {l3_inner_packet_len}, L3 outer GRE packet length \
456 {l3_outer_gre_packet_len}, MTU: {mtu}, underlay_mtu: \
457 {underlay_mtu}.",
458 mtu = gre.mtu,
459 underlay_mtu = next_hop.mtu
460 );
461 }
462 umem.release(frame);
463 continue;
464 }
465
466 let packet_len = gre_packet_size(len);
467 if packet_len > umem_frame_size {
468 log::warn!(
469 "dropping packet: GRE packet size {packet_len} exceeds frame size \
470 {umem_frame_size} for {addr}"
471 );
472 umem.release(frame);
473 continue;
474 }
475
476 frame.set_len(packet_len);
477 let mut packet = umem.map_frame_mut(frame);
478 let inner_src_ip = next_hop.preferred_src_ip.as_ref().unwrap_or(src_ip);
479 if let Err(err) = construct_gre_packet(
480 &mut packet,
481 &src_mac,
482 &dest_mac,
483 inner_src_ip,
484 &dst_ip,
485 src_port,
486 addr.port(),
487 payload,
488 ecn,
489 &gre.tunnel_info,
490 ) {
491 log::warn!("dropping packet: {err}");
492 umem.release(packet.into_frame());
493 continue;
494 }
495 frame = packet.into_frame();
496 } else if let Some(vlan) = &next_hop.vlan {
497 let Some(dest_mac) = next_hop.mac_addr else {
499 log::warn!(
500 "dropping packet: peer {addr} must be routed through {} which has no \
501 known MAC address",
502 next_hop.ip_addr
503 );
504 umem.release(frame);
505 continue;
506 };
507
508 let l3_packet_len = IP_HEADER_SIZE + UDP_HEADER_SIZE + len;
511 if l3_packet_len > next_hop.mtu as usize {
512 if !can_overflow_mtu {
513 log::warn!(
514 "dropping packet: packet size {l3_packet_len} exceeds MTU {mtu} \
515 for {addr}",
516 mtu = next_hop.mtu
517 );
518 }
519 umem.release(frame);
520 continue;
521 }
522
523 let packet_len = VLAN_PACKET_HEADER_SIZE + len;
524 if packet_len > umem_frame_size {
525 log::warn!(
526 "dropping packet: VLAN packet size {packet_len} exceeds frame size \
527 {umem_frame_size} for {addr}"
528 );
529 umem.release(frame);
530 continue;
531 }
532
533 frame.set_len(packet_len);
534 let mut packet = umem.map_frame_mut(frame);
535
536 let inner_src_ip = next_hop.preferred_src_ip.as_ref().unwrap_or(src_ip);
540
541 if !construct_vlan_packet(
542 &mut packet,
543 &src_mac.0,
544 &dest_mac.0,
545 inner_src_ip,
546 &dst_ip,
547 src_port,
548 addr.port(),
549 vlan.vid,
550 vlan.pcp,
551 payload,
552 ecn,
553 ) {
554 log::warn!("dropping packet: VLAN frame did not fit in UMEM slot");
555 umem.release(packet.into_frame());
556 continue;
557 }
558 frame = packet.into_frame();
559 } else {
560 let Some(dest_mac) = next_hop.mac_addr else {
562 log::warn!(
563 "dropping packet: peer {addr} must be routed through {} which has no \
564 known MAC address",
565 next_hop.ip_addr
566 );
567 umem.release(frame);
568 continue;
569 };
570
571 let l3_packet_len = IP_HEADER_SIZE + UDP_HEADER_SIZE + len;
572 if l3_packet_len > next_hop.mtu as usize {
573 if !can_overflow_mtu {
574 log::warn!(
575 "dropping packet: packet size {l3_packet_len} exceeds MTU {mtu} \
576 for {addr}",
577 mtu = next_hop.mtu
578 );
579 }
580 umem.release(frame);
581 continue;
582 }
583
584 let packet_len = PACKET_HEADER_SIZE + len;
585 if packet_len > umem_frame_size {
586 log::warn!(
587 "dropping packet: packet size {packet_len} exceeds frame size \
588 {umem_frame_size} for {addr}"
589 );
590 umem.release(frame);
591 continue;
592 }
593
594 frame.set_len(packet_len);
595 let mut packet = umem.map_frame_mut(frame);
596
597 if !construct_packet(
598 &mut packet,
599 &src_mac.0,
600 &dest_mac.0,
601 src_ip,
602 &dst_ip,
603 src_port,
604 addr.port(),
605 payload,
606 ecn,
607 ) {
608 log::warn!("dropping packet: frame did not fit in UMEM slot");
609 umem.release(packet.into_frame());
610 continue;
611 }
612 frame = packet.into_frame();
613 }
614
615 ring.write(frame, 0)
616 .map_err(|_| "ring full")
617 .expect("failed to write to ring");
619
620 written_uncommitted += 1;
621
622 if written_uncommitted >= BATCH_SIZE {
624 commit_pending(&mut ring, &mut written_uncommitted);
625 kick(&ring);
626 }
627 }
628 drop_item(item);
629 }
630 commit_pending(&mut ring, &mut written_uncommitted);
631 kick(&ring);
632
633 while umem.available() < umem_tx_capacity || ring.available() < ring.capacity() {
635 log::debug!(
636 "draining xdp ring umem {}/{} ring {}/{}",
637 umem.available(),
638 umem_tx_capacity,
639 ring.available(),
640 ring.capacity()
641 );
642
643 completion.sync(true);
644 while let Some(frame) = completion.read() {
645 umem.release_completed(frame);
646 }
647
648 ring.sync(false);
649 kick(&ring);
650 }
651 }
652}
653
654#[inline(always)]
655fn commit_pending<F: Frame>(ring: &mut TxRing<F>, pending_uncommitted: &mut usize) {
656 if *pending_uncommitted == 0 {
657 return;
658 }
659 ring.commit();
660 *pending_uncommitted = 0;
661}
662
663#[inline(always)]
666fn kick<F: Frame>(ring: &TxRing<F>) {
667 if !ring.needs_wakeup() {
668 return;
669 }
670
671 if let Err(e) = ring.wake() {
672 kick_error(e);
673 }
674}
675
676#[inline(never)]
677fn kick_error(e: std::io::Error) {
678 match e.raw_os_error() {
679 Some(libc::EBUSY | libc::ENOBUFS | libc::EAGAIN) => {}
681 Some(libc::ENETDOWN) => {
684 log::warn!("network interface is down")
685 }
686 _ => {
688 log::error!("network interface driver error: {e:?}");
689 }
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use crate::tx_loop::{Receiver, TryRecvError, TrySendError, channel};
696
697 #[test]
698 fn test_send_full() {
699 let (sender, _receiver) = channel(1);
700
701 assert!(sender.try_send(1).is_ok());
702 match sender.try_send(2) {
703 Err(TrySendError::Full(item)) => assert_eq!(item, 2),
704 result => panic!("expected full queue, got {result:?}"),
705 }
706 }
707
708 #[test]
709 fn test_send_disconnected() {
710 let (sender, receiver) = channel(1);
711 drop(receiver);
712
713 match sender.try_send(1) {
714 Err(TrySendError::Disconnected(item)) => assert_eq!(item, 1),
715 result => panic!("expected disconnected queue, got {result:?}"),
716 }
717 }
718
719 #[test]
720 fn test_recv_disconnected() {
721 let (sender, receiver) = channel(1);
722 sender
723 .try_send(1)
724 .expect("send item before dropping sender");
725 drop(sender);
726
727 assert_eq!(receiver.try_recv().expect("receive queued item"), 1);
728 assert!(matches!(
729 receiver.try_recv(),
730 Err(TryRecvError::Disconnected)
731 ));
732 }
733
734 #[test]
735 fn test_recv_waits_for_cloned_sender() {
736 let (sender, receiver) = channel::<i32>(1);
737 let sender_clone = sender.clone();
738 drop(sender);
739
740 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
741 drop(sender_clone);
742 assert!(matches!(
743 receiver.try_recv(),
744 Err(TryRecvError::Disconnected)
745 ));
746 }
747}