LCOV - code coverage report
Current view: top level - waltz/xdp - fd_xsk.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 23 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 20 0.0 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_waltz_xdp_fd_xsk_h
       2             : #define HEADER_fd_src_waltz_xdp_fd_xsk_h
       3             : 
       4             : #if defined(__linux__)
       5             : 
       6             : /* fd_xsk manages an XSK file descriptor and provides RX/TX buffers.
       7             : 
       8             :    ### Background
       9             : 
      10             :    AF_XDP is a Linux API providing kernel-bypass networking in the form
      11             :    of shared memory ring buffers accessible from userspace.  The kernel
      12             :    redirects packets from/to these buffers with the appropriate XDP
      13             :    configuration (XDP_REDIRECT).  AF_XDP is hardware-agnostic and allows
      14             :    sharing a NIC with the Linux networking stack (unlike e.g. DPDK).
      15             :    This allows for deployment in existing, heterogeneous networks. An
      16             :    AF_XDP socket is called "XSK".  The shared memory region storing the
      17             :    packet data flowing through an XSK is called "UMEM".
      18             : 
      19             :    XDP (eXpress Data Path) is a framework for installing hooks in the
      20             :    form of eBPF programs at an early stage of packet processing (i.e.
      21             :    before tc and netfilter).  eBPF is user-deployable JIT-compiled
      22             :    bytecode that usually runs inside the kernel. Some hardware/driver
      23             :    combinations optionally allow offloading eBPF processing to NICs.
      24             :    This is not to be confused with other BPF-derived ISAs such as sBPF
      25             :    (Solana BPF).
      26             : 
      27             :      +--- Figure 1: AF_XDP RX Block Diagram -----------------+
      28             :      |                                                       |
      29             :      |   ┌─────┐  ┌────────┐  ┌─────┐ XDP_PASS ┌─────────┐   |
      30             :      |   │ NIC ├──> Driver ├──> XDP ├──────────> sk_buff │   |
      31             :      |   └─────┘  └────────┘  └─┬───┘          └─────────┘   |
      32             :      |                          │                            |
      33             :      |                          │ XDP_REDIRECT               |
      34             :      |                          │                            |
      35             :      |                       ┌──▼───────┐      ┌─────────┐   |
      36             :      |                       │ XSK/UMEM ├──────> app     │   |
      37             :      |                       └──────────┘      └─────────┘   |
      38             :      |                                                       |
      39             :      +-------------------------------------------------------+
      40             : 
      41             :    Figure 1 shows a simplified block diagram of RX packet flow within
      42             :    the kernel in `XDP_FLAGS_DRV_MODE` mode.  Notably, the chain of eBPF
      43             :    programs installed in the XDP facility get invoked for every incoming
      44             :    packet.  If all programs return the `XDP_PASS` action, the packet
      45             :    continues its usual path to the Linux networking stack, where it will
      46             :    be allocated in sk_buff, and eventually flow through ip_rcv(), tc,
      47             :    and netfilter before reaching downstream sockets.
      48             :    If the `XDP_REDIRECT` action is taken however, the packet is copied
      49             :    to the UMEM of an XSK, and a RX queue entry is allocated.
      50             :    The more generic `XDP_FLAGS_SKB_MODE` XDP mode falls back to sk_buff-
      51             :    based memory mgmt (still skipping the rest of the generic path), but
      52             :    is more widely available.
      53             : 
      54             :      +--- Figure 2: AF_XDP TX Block Diagram -------------+
      55             :      |                                                   |
      56             :      |   ┌────────┐  ┌──────────┐  ┌────────┐  ┌─────┐   |
      57             :      |   │ app    ├──> XSK/UMEM ├──> Driver ├──> NIC │   |
      58             :      |   └────────┘  └──────────┘  └────────┘  └─────┘   |
      59             :      |                                                   |
      60             :      +---------------------------------------------------+
      61             : 
      62             :    Figure 2 shows a simplified block diagram of the TX packet flow.
      63             :    Userspace applications deliver packets to the XSK/UMEM buffers.  The
      64             :    kernel then forwards these packets to the NIC.  This also means that
      65             :    the application is responsible for maintaining a routing table to
      66             :    resolve layer-3 dest addrs to NICs and layer-2 addrs.  As in the RX
      67             :    flow, netfilter (iptables, nftables) is not available.
      68             : 
      69             :    ### Memory Management
      70             : 
      71             :    The UMEM area is allocated from userspace.  It is recommended to use
      72             :    the fd_util shmem/wksp APIs to obtain large page-backed memory.  UMEM
      73             :    is divided into equally sized frames. At any point in time, each
      74             :    frame is either owned by userspace or the kernel.  On initialization,
      75             :    all frames are owned by userspace.
      76             : 
      77             :    Changes in UMEM frame ownership and packet RX/TX events are
      78             :    transmitted via four rings allocated by the kernel (mmap()ed in by
      79             :    the user). This allows for out-of-order processing of packets.
      80             : 
      81             :       Data flow:
      82             :       (U->K) is userspace-to-kernel communication, and
      83             :       (K->U) is kernel-to-userspace.
      84             : 
      85             :       FILL         Free frames are provided to the kernel using the FILL
      86             :       (U->K)       ring. The kernel may populate these frames with RX
      87             :                    packet data.
      88             : 
      89             :       RX           Once the kernel has populated a FILL frame with RX
      90             :       (K->U)       packet data, it passes back the frame to userspace
      91             :                    via the RX queue.
      92             : 
      93             :       TX           TX frames sent by userspace are provided to the
      94             :       (U->K)       kernel using the TX ring.
      95             : 
      96             :       COMPLETION   Once the kernel has processed a TX frame, it passes
      97             :       (K->U)       back the frame to the userspace via the COMPLETION
      98             :                    queue.
      99             : 
     100             :    Combined, the FILL-RX and TX-COMPLETION rings form two pairs.  The
     101             :    kernel will not move frames between the pairs. */
     102             : 
     103             : #include <linux/if_link.h>
     104             : #include <linux/if_xdp.h>
     105             : #include <net/if.h>
     106             : 
     107             : #include "../../util/fd_util_base.h"
     108             : 
     109             : /* FD_XSK_UMEM_ALIGN: byte alignment of UMEM area within fd_xsk_t.
     110             :    This requirement is set by the kernel as of Linux 4.18. */
     111             : #define FD_XSK_UMEM_ALIGN (4096UL)
     112             : 
     113             : /* fd_xdp_ring_t describes an XSK descriptor ring in the thread group's
     114             :    local address space.  All pointers fall into kernel-managed XSK
     115             :    descriptor buffer at [mem;mem+mem_sz) that are valid during the
     116             :    lifetime of an fd_xsk_t join.  The ring producer and consumer are
     117             :    synchronized via incrementing 32-bit sequence numbers that wrap at 2^32. */
     118             : 
     119             : struct __attribute__((aligned(64UL))) fd_xdp_ring {
     120             :   /* This point is 64-byte aligned */
     121             : 
     122             :   /* mmap() params, only used during join/leave for munmap() */
     123             : 
     124             :   void *  mem;    /* Points to start of shared descriptor ring mmap region */
     125             :   ulong   map_sz; /* Size of shared descriptor ring mmap region */
     126             :   ulong   _pad_0x10;
     127             :   ulong   _pad_0x18;
     128             : 
     129             :   /* This point is 64-byte aligned */
     130             : 
     131             :   /* Pointers to fields opaque XSK ring structure.
     132             :      This indirection is required because the memory layout of the
     133             :      kernel-provided descriptor rings is unstable.  The field offsets
     134             :      can be queried using getsockopt(SOL_XDP, XDP_MMAP_OFFSETS). */
     135             : 
     136             :   union {
     137             :     void *            ptr;         /* Opaque pointer */
     138             :     struct xdp_desc * packet_ring; /* For RX, TX rings */
     139             :     ulong *           frame_ring;  /* For FILL, COMPLETION rings */
     140             :   };
     141             :   uint *  flags;       /* Points to flags in shared descriptor ring */
     142             :   uint *  prod;        /* Points to producer seq in shared descriptor ring */
     143             :   uint *  cons;        /* Points to consumer seq in shared descriptor ring */
     144             : 
     145             :   /* This point is 64-byte aligned */
     146             : 
     147             :   /* Managed by fd_xsk_t */
     148             : 
     149             :   uint    depth;       /* Capacity of ring in no of entries */
     150             :   uint    cached_prod; /* Cached value of *prod */
     151             :   uint    cached_cons; /* Cached value of *cons */
     152             : };
     153             : typedef struct fd_xdp_ring fd_xdp_ring_t;
     154             : 
     155           0 : #define FD_XDP_RING_ROLE_PROD 0
     156           0 : #define FD_XDP_RING_ROLE_CONS 1
     157             : 
     158             : /* fd_xdp_ring_empty returns 1 if the ring is empty, 0 otherwise.
     159             :    'role' is FD_XDP_RING_ROLE_PROD if userspace is the producer (fill, tx),
     160             :    and FD_XDP_RING_ROLE_CONS if userspace is the consumer (rx, completion). */
     161             : 
     162             : static inline int
     163           0 : fd_xdp_ring_empty( fd_xdp_ring_t * ring, uint role ) {
     164           0 :   if( role == FD_XDP_RING_ROLE_PROD ) {
     165             :     /* If potentially stale cached_cons says everything consumed,
     166             :        it's definitely empty. Else, refresh cached seq. */
     167           0 :     if( FD_UNLIKELY( ring->cached_prod == ring->cached_cons ) ) return 1;
     168           0 :     ring->cached_cons = __atomic_load_n( ring->cons, __ATOMIC_ACQUIRE );
     169           0 :   } else {
     170             :     /* If potentially stale cached_prod says we have more to read,
     171             :        it's definitely non-empty. Else, refresh cached seq. */
     172           0 :     if( FD_LIKELY( ring->cached_cons < ring->cached_prod ) ) return 0;
     173           0 :     ring->cached_prod = __atomic_load_n( ring->prod, __ATOMIC_ACQUIRE );
     174           0 :   }
     175           0 :   return ring->cached_prod == ring->cached_cons;
     176           0 : }
     177             : 
     178             : /* fd_xdp_ring_full returns 1 if the ring is full, 0 otherwise.
     179             :    Assumes caller is the producer to this ring (fill, tx). */
     180             : static inline int
     181           0 : fd_xdp_ring_full( fd_xdp_ring_t * ring ) {
     182             :   /* If potentially stale cached_cons says we have more space,
     183             :       it's definitely not full. Else, refresh cached seq. */
     184           0 :   if( FD_LIKELY( ring->cached_prod - ring->cached_cons < ring->depth ) ) return 0;
     185           0 :   ring->cached_cons = __atomic_load_n( ring->cons, __ATOMIC_ACQUIRE );
     186           0 :   return ring->cached_prod - ring->cached_cons >= ring->depth;
     187           0 : }
     188             : 
     189             : /* fd_xsk_params_t: XSK poll configuration and memory layout
     190             :    parameters passed to fd_xsk_init. */
     191             : 
     192             : struct fd_xsk_params {
     193             :   /* {fr,rx,tx,cr}_depth: Number of frames allocated for the Fill, RX,
     194             :     TX, Completion XSK rings respectively. */
     195             :   ulong fr_depth;
     196             :   ulong rx_depth;
     197             :   ulong tx_depth;
     198             :   ulong cr_depth;
     199             : 
     200             :   /* umem_addr: Pointer to UMEM in local address space */
     201             :   void * umem_addr;
     202             : 
     203             :   /* frame_sz: Controls the frame size used in the UMEM ring buffers. */
     204             :   ulong frame_sz;
     205             : 
     206             :   /* umem_sz: Total size of XSK ring shared memory area (contiguous).
     207             :      Aligned by FD_XSK_UMEM_ALIGN. */
     208             :   ulong umem_sz;
     209             : 
     210             :   /* Linux interface index */
     211             :   uint if_idx;
     212             : 
     213             :   /* Interface queue index */
     214             :   uint if_queue_id;
     215             : 
     216             :   /* sockaddr_xdp.sxdp_flags additional params, e.g. XDP_ZEROCOPY */
     217             :   uint bind_flags;
     218             : 
     219             :   /* Whether to use softirqs to poll napi or prefbusy poll */
     220             :   char poll_mode[ 16 ];
     221             : 
     222             :   /* Max time during prefbusy napi poll. Referred to as
     223             :      'busy_poll_usecs' within Linux. */
     224             :   int prefbusy_time_budget_micros;
     225             : 
     226             :   /* Max RX processing budget during prefbusy napi poll.
     227             :      Referred to as 'busy_poll_budget' within Linux. */
     228             :   int prefbusy_rx_budget;
     229             : };
     230             : 
     231             : typedef struct fd_xsk_params fd_xsk_params_t;
     232             : 
     233             : struct fd_xsk {
     234             :   /* Informational */
     235             :   uint if_idx;       /* index of net device */
     236             :   uint if_queue_id;  /* net device combined queue index */
     237             :   long log_suppress_until_ns; /* suppress log messages until this time */
     238             : 
     239             :   /* Kernel descriptor of XSK rings in local address space
     240             :      returned by getsockopt(SOL_XDP, XDP_MMAP_OFFSETS) */
     241             :   struct xdp_mmap_offsets offsets;
     242             : 
     243             :   /* AF_XDP socket file descriptor */
     244             :   int xsk_fd;
     245             : 
     246             :   /* Whether preferred busy polling was successfully enabled
     247             :      during XSK socket setup. */
     248             :   int prefbusy_poll_enabled;
     249             : 
     250             :   /* ring_{rx,tx,fr,cr}: XSK ring descriptors */
     251             : 
     252             :   fd_xdp_ring_t ring_rx;
     253             :   fd_xdp_ring_t ring_tx;
     254             :   fd_xdp_ring_t ring_fr;
     255             :   fd_xdp_ring_t ring_cr;
     256             : };
     257             : 
     258             : typedef struct fd_xsk fd_xsk_t;
     259             : 
     260             : FD_PROTOTYPES_BEGIN
     261             : 
     262             : /* fd_xsk_init creates an XSK, registers UMEM, maps rings, and binds the
     263             :    socket to the given interface queue.  This is a potentially
     264             :    destructive operation.  As of 2024-Jun, AF_XDP zero copy support is
     265             :    still buggy in some device drivers.
     266             : 
     267             :    Assume that all traffic sent to this interface is compromised.  On
     268             :    some devices, the NIC is instructed to DMA all incoming packets into
     269             :    UMEM, even ones not belonging to Firedancer.  Those are then later
     270             :    on software-copied out to skbs again.  This further implies that
     271             :    enabling AF_XDP can slow down the regular kernel receive path.
     272             : 
     273             :    Requires CAP_SYS_ADMIN. May issue the following syscalls:
     274             : 
     275             :    - socket( AF_XDP, SOCK_RAW, 0 ) = fd
     276             :    - setsockopt( fd, SOL_XDP or SOL_SOCKET, ... )
     277             :    - getsockopt( fd, SOL_XDP, ... )
     278             :    - mmap( ..., fd, ... )
     279             :    - bind( fd, ... )
     280             :    - mlock( ... )
     281             :    - sendto( fd, ... )
     282             :    - fcntl( fd, F_GETFL or F_SETFL, ... )
     283             :    - munmap  ; on fail
     284             :    - close   ; on fail */
     285             : 
     286             : fd_xsk_t *
     287             : fd_xsk_init( fd_xsk_t *              xsk,
     288             :              fd_xsk_params_t const * params );
     289             : 
     290             : /* fd_xsk_rx_need_wakeup: returns whether a wakeup is required to
     291             :    complete a rx operation */
     292             : 
     293             : static inline int
     294           0 : fd_xsk_rx_need_wakeup( fd_xsk_t * xsk ) {
     295           0 :   return !!( *xsk->ring_fr.flags & XDP_RING_NEED_WAKEUP );
     296           0 : }
     297             : 
     298             : /* fd_xsk_tx_need_wakeup: returns whether a wakeup is required to
     299             :    complete a tx operation */
     300             : 
     301             : static inline int
     302           0 : fd_xsk_tx_need_wakeup( fd_xsk_t * xsk ) {
     303           0 :   return !!( *xsk->ring_tx.flags & XDP_RING_NEED_WAKEUP );
     304           0 : }
     305             : 
     306             : 
     307             : FD_PROTOTYPES_END
     308             : 
     309             : #endif /* defined(__linux__) */
     310             : #endif /* HEADER_fd_src_waltz_xdp_fd_xsk_h */

Generated by: LCOV version 1.14