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

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_xdp1.h"
       3             : 
       4             : #include "fd_xdp_license.h"
       5             : #include "../ebpf/fd_linux_bpf.h"
       6             : #include "../ebpf/fd_ebpf_asm.h"
       7             : 
       8             : #include <errno.h>
       9             : #include <unistd.h>
      10             : #include <net/if.h>
      11             : #include <sys/syscall.h>
      12             : #include <linux/bpf.h>
      13             : #include <linux/if_link.h>
      14             : 
      15             : /* Define some kernel uapi constants in case the user is compiling
      16             :    with older kernel headers.  This is especially a problem on Ubuntu
      17             :    20.04 which supports these functions, but doesn't have them in
      18             :    the default headers. */
      19             : 
      20             : #ifndef BPF_LINK_CREATE
      21           0 : #define BPF_LINK_CREATE (28)
      22             : #endif
      23             : 
      24             : #ifndef BPF_XDP
      25           0 : #define BPF_XDP (37)
      26             : #endif
      27             : 
      28             : struct __attribute__((aligned(8))) bpf_link_create {
      29             :   uint prog_fd;
      30             :   uint target_ifindex;
      31             :   uint attach_type;
      32             :   uint flags;
      33             : };
      34             : 
      35             : 
      36             : /*
      37             : 
      38             :   ┌─────────────────────────────────────────┐
      39             :   │   Packet Classification Control Flow    │
      40             :   |                                         |
      41             :   │       ┌────────┐   ┌────┐               │
      42             :   │       │Ethernet│──►│VLAN│               │
      43             :   │       └───┬────┘   └─┬──┘               │
      44             :   │           │          │                  │
      45             :   │       ┌───▼────┐     │                  │
      46             :   │       │ IPV4   │◄────┘                  │
      47             :   │       └──┬───┬─┘                        │
      48             :   │          │   │                          │
      49             :   │          │   └────►──────┐              │
      50             :   │          │        │ GRE  │              │
      51             :   │          │        └───┬──┘              │
      52             :   │          │            │                 │
      53             :   │          │        ┌───▼────────┐        │
      54             :   │          │        │ Inner IPV4 │        │
      55             :   │          │        ├────────────┘        │
      56             :   │          │        │                     │
      57             :   │      ┌───▼──◄─────┘                     │
      58             :   │      │ UDP  │                           │
      59             :   │      └──┬───┘                           │
      60             :   |         │                               |
      61             :   |  ┌──────▼───────┐   ┌──────────────┐    |
      62             :   |  | LBL_REDIRECT |   |   LBL_PASS   |    |
      63             :   |  └──────────────┘   └──────────────┘    |
      64             :   └─────────────────────────────────────────┘
      65             : 
      66             :   fd_xdp_gen_program dynamically generates an eBPF bytecode program to
      67             :   classify incoming network packets in an XDP context. Each box in the above
      68             :   diagram represents a phase during the classification. NON-GRE packets follow
      69             :   the path of Ethernet-->IPV4-->UDP. GRE packets follow the path of
      70             :   Ethernet-->IPV4-->GRE-->Inner IPV4-->UDP. Packets carrying a single
      71             :   802.1Q tag (any VLAN ID) whose inner Ethertype is IPV4 take the VLAN
      72             :   phase between Ethernet and IPV4, then follow the same paths.
      73             :   Double-tagged packets are passed to the kernel. Based on the packet's
      74             :   protocol and specific header values, the function decides whether to
      75             :   redirect the packet to the firedancer net tile (LBL_REDIRECT) or pass
      76             :   the packet to the kernel (LBL_PASS).
      77             : */
      78             : ulong
      79             : fd_xdp_gen_program( ulong          code_buf[ 512 ],
      80             :                     int            xsks_fd,
      81             :                     uint           listen_ip4_addr,
      82             :                     ushort const * ports,
      83             :                     ulong          ports_cnt,
      84           0 :                     int            allowed_gre ) {
      85             : 
      86           0 :   #define LBL_PASS      1   // Pass the packet to the kernel
      87           0 :   #define LBL_REDIRECT  2   // Redirect the packet to firedancer software
      88             : 
      89           0 :   #define LBL_GRE_CHECK     3   // Ethernet-->IPV4-->GRE-->Inner IPV4-->UDP
      90           0 :   #define LBL_UDP_CHECK     4   // Ethernet-->IPV4-->UDP
      91           0 :   #define LBL_ETH_IP4       5   // Advance past the Ethernet hdr to the first ip4_hdr
      92             : 
      93           0 :   if( FD_UNLIKELY( ports_cnt>16UL ) ) {
      94           0 :     FD_LOG_ERR(( "Too many XDP UDP ports (%lu)", ports_cnt ));
      95           0 :   }
      96             : 
      97           0 :   ulong * code = code_buf;
      98           0 :   *(code++) = FD_EBPF( ldxw, r2, r1, 0                          );  // r2 = xdp_md->data
      99           0 :   *(code++) = FD_EBPF( ldxw, r3, r1, 4                          );  // r3 = xdp_md->data_end
     100             : 
     101           0 :   *(code++) = FD_EBPF( mov64_reg, r5, r2                        );
     102           0 :   *(code++) = FD_EBPF( add64_imm, r5, 34                        );  // Bound check accessing the eth_hdr (14 bytes) and the ip4_hdr (20 bytes)
     103           0 :   *(code++) = FD_EBPF( jgt_reg, r5, r3, LBL_PASS                );  // if r2+34 > r3 goto LBL_PASS
     104             : 
     105           0 :   *(code++) = FD_EBPF( ldxh, r5, r2, 12                         );  // r5 = eth_hdr->net_type
     106           0 :   *(code++) = FD_EBPF( jeq_imm, r5, 0x0008, LBL_ETH_IP4         );  // if eth_hdr->net_type == IP4 goto eth_ip4
     107           0 :   *(code++) = FD_EBPF( jne_imm, r5, 0x0081, LBL_PASS            );  // if eth_hdr->net_type != VLAN goto LBL_PASS
     108             : 
     109             :   /* next hdr is a single 802.1Q vlan_tag */
     110             : 
     111             :   /* Bound check accessing the eth_hdr, vlan_tag and ip4_hdr */
     112           0 :   *(code++) = FD_EBPF( mov64_reg, r5, r2                        );
     113           0 :   *(code++) = FD_EBPF( add64_imm, r5, 38                        );  // sizeof(eth_hdr) + sizeof(vlan_tag) + sizeof(ip4_hdr) = 14 + 4 + 20
     114           0 :   *(code++) = FD_EBPF( jgt_reg, r5, r3, LBL_PASS                );  // if r2+38 > r3 goto LBL_PASS
     115             : 
     116           0 :   *(code++) = FD_EBPF( ldxh, r5, r2, 16                         );  // r5 = vlan_tag->net_type
     117           0 :   *(code++) = FD_EBPF( jne_imm, r5, 0x0008, LBL_PASS            );  // if vlan_tag->net_type != IP4 goto LBL_PASS
     118             : 
     119             :   /* Advance r2 past the vlan_tag.  eth_ip4 then advances it past the
     120             :      eth_hdr, so r2 points at the first ip4_hdr on both paths. */
     121           0 :   *(code++) = FD_EBPF( add64_imm, r2, 4                         );  // r2 += sizeof(vlan_tag)
     122             : 
     123             :   /* Advance r2 to the start of first ip4_hdr */
     124           0 :   ulong * eth_ip4 = code;
     125           0 :   *(code++) = FD_EBPF( add64_imm, r2, 14                        );
     126             : 
     127             :   /* Calculate the start of next hdr and store in r4 */
     128           0 :   *(code++) = FD_EBPF( ldxb, r4, r2, 0                          );  // r4 = ip4_hdr->verihl
     129           0 :   *(code++) = FD_EBPF( and64_imm, r4, 0x0f                      );  // r4 = ip4_hdr->ihl (lsb of ip4_hrd->verihl)
     130           0 :   *(code++) = FD_EBPF( lsh64_imm, r4, 2                         );  // r4 = ip4_hdr->ihl*4 (length of ipv4 header)
     131           0 :   *(code++) = FD_EBPF( jlt_imm, r4, 20, LBL_PASS                );  // if r4<20 goto LBL_PASS
     132           0 :   *(code++) = FD_EBPF( add64_reg, r4, r2                        );  // r4 = &ip4_hdr + length of ip4_hdr = start of next hdr
     133             : 
     134             :   /* Check if the next hdr is udp or gre */
     135           0 :   *(code++) = FD_EBPF( ldxb, r5, r2, 9                          );  // r5 = ip4_hdr->protocol
     136             : 
     137           0 :   if( allowed_gre==1 ) {
     138           0 :     *(code++) = FD_EBPF( jeq_imm, r5, 47, LBL_GRE_CHECK );  // if ip4_hdr->protocol == GRE goto gre_check
     139           0 :   }
     140             : 
     141           0 :   *(code++) = FD_EBPF( jeq_imm, r5, 17, LBL_UDP_CHECK           );  // if ip4_hdr->protocol == UDP goto udp_check
     142           0 :   *(code++) = FD_EBPF( ja, LBL_PASS                             );  // goto LBL_PASS
     143             : 
     144             : 
     145             :   /* next hdr is gre */
     146           0 :   ulong * gre_check = code;
     147             : 
     148             :   /* Advance r2 to start of gre_hdr */
     149           0 :   *(code++) = FD_EBPF( mov64_reg, r2, r4                        );
     150             : 
     151             :   /*
     152             :    * At this point:
     153             :    * r1: xdp_md
     154             :    * r2: start of next header (gre_hdr)
     155             :    * r3: xdp_md->data_end
     156             :    * r4: clobber
     157             :    * r5: clobber
     158             :   */
     159             : 
     160             :   /* Bound check GRE and inner ip4_hdr access */
     161           0 :   *(code++) = FD_EBPF( mov64_reg, r5, r2                        );
     162           0 :   *(code++) = FD_EBPF( add64_imm, r5, 24                        );  // r5 = 1 byte past inner ip4_hdr. sizeof(gre_hdr) + sizeof(ip4_hdr) = 4 + 20
     163           0 :   *(code++) = FD_EBPF( jgt_reg, r5, r3, LBL_PASS                );  // if (end of inner ip4_hdr + 1) > r3, goto LBL_PASS
     164             : 
     165             : 
     166             :   /* Verify GRE fields */
     167           0 :   *(code++) = FD_EBPF( ldxh, r5, r2, 0                          );  // r5 = gre_hdr->flags/version
     168           0 :   *(code++) = FD_EBPF( jne_imm, r5, 0x0000, LBL_PASS            );  // if gre_hdr->flags/version != 0, goto LBL_PASS
     169           0 :   *(code++) = FD_EBPF( ldxh, r5, r2, 2                          );  // r5 = gre_hdr->protocol
     170           0 :   *(code++) = FD_EBPF( jne_imm, r5, 0x0008, LBL_PASS            );  // if gre_hdr->protocol != IP, goto LBL_PASS
     171             : 
     172             : 
     173             :   /* Advance r2 to start of inner ip4_hdr */
     174           0 :   *(code++) = FD_EBPF( add64_imm, r2, 4                         );  // r2 = start of inner ip4_hdr
     175             : 
     176             :   /* Check inner ip4's encapsulated protocol */
     177           0 :   *(code++) = FD_EBPF( ldxb, r5, r2, 9                          );  // r5 = inner ip4_hdr->protocol
     178           0 :   *(code++) = FD_EBPF( jne_imm, r5, 17, LBL_PASS                );  // if r5!=UDP, goto LBL_PASS
     179             : 
     180             :   /* Calculate the start of udp_hdr and store in r4 */
     181           0 :   *(code++) = FD_EBPF( ldxb, r4, r2, 0                          );  // r4 = inner ip4_hdr->verihl
     182           0 :   *(code++) = FD_EBPF( and64_imm, r4, 0x0f                      );  // r4 = inner ip4_hdr->ihl
     183           0 :   *(code++) = FD_EBPF( lsh64_imm, r4, 2                         );  // r4 = ip4_hdr->ihl*4 (length of ipv4 header)
     184           0 :   *(code++) = FD_EBPF( jlt_imm, r4, 20, LBL_PASS                );  // if r4<20 goto LBL_PASS
     185           0 :   *(code++) = FD_EBPF( add64_reg, r4, r2                        );  // r4 = start of udp_hdr
     186             : 
     187             :   /*
     188             :    * At this point:
     189             :    * r1: &xdp_md
     190             :    * r2: start of ip4_hdr  (inner ip4_hdr for gre)
     191             :    * r3: xdp_md->data_end
     192             :    * r4: start of udp_hdr
     193             :    * r5: clobber
     194             :   */
     195             : 
     196             :   /* udp check */
     197           0 :   ulong * udp_check = code;
     198             : 
     199             :   /* check ip4's dst addr */
     200           0 :   if( listen_ip4_addr!=0 ) {
     201           0 :     *(code++) = FD_EBPF( ldxw, r5, r2, 16                         );
     202           0 :     *(code++) = FD_EBPF( jne32_imm, r5, listen_ip4_addr, LBL_PASS );  // if ip4->daddr != listen_ip4_addr goto LBL_PASS
     203           0 :   }
     204             : 
     205             :   /* Advance r2 to start of udp_hdr */
     206           0 :   *(code++) = FD_EBPF( mov64_reg, r2, r4                        );
     207             : 
     208             :   /* bound check udp hdr access */
     209           0 :   *(code++) = FD_EBPF( add64_imm, r4, 8                         );  // r4 += sizeof(udp_hdr) = 1 byte pass the end of udp_hdr
     210           0 :   *(code++) = FD_EBPF( jgt_reg, r4, r3, LBL_PASS                );  // if (end of udp_hdr + 1) > r3 goto LBL_PASS
     211             : 
     212             :   /* get destination port from udp_hdr */
     213           0 :   *(code++) = FD_EBPF( ldxh, r4, r2, 2                          );  // r4 = udp_hdr->dst_port
     214             : 
     215             :   /* loop through the ports array and find a match with dst_port */
     216           0 :   for( ulong i=0UL; i<ports_cnt; i++ ) {
     217           0 :     ushort port = (ushort)fd_ushort_bswap( ports[ i ]           );
     218           0 :     if( !port ) continue;
     219           0 :     *(code++) = FD_EBPF( jeq_imm, r4, port, LBL_REDIRECT         );  // if dst_port == ports[i] goto LBL_REDIRECT
     220           0 :   }
     221             : 
     222           0 :   ulong * lbl_pass = code;
     223           0 :   *(code++) = FD_EBPF( mov64_imm, r0, XDP_PASS                   );
     224           0 :   *(code++) = FD_EBPF_exit;                                           // return XDP_PASS
     225           0 :   ulong * lbl_redirect = code;
     226           0 :   *(code++) = FD_EBPF( ldxw, r2, r1, 16                          );  // r2 = xdp_md->rx_queue_index
     227           0 :   *(code++) = FD_EBPF( lddw, r1, xsks_fd                         );  // r1 = xsk_map_fd ll
     228           0 :   *(code++) = 0;
     229           0 :   *(code++) = FD_EBPF( mov64_imm, r3, 0                          );  // r3 = 0
     230           0 :   *(code++) = FD_EBPF( call, 0x33                                );
     231           0 :   *(code++) = FD_EBPF_exit;                                           // return bpf_redirect_map(r1,r2,r3)
     232             : 
     233           0 :   ulong * code_end = code;
     234           0 :   ulong   code_cnt = (ulong)( code_end-code_buf );
     235             : 
     236             :   /* Fill in jump labels */
     237             : 
     238           0 :   for( ulong i=0UL; i<code_cnt; i++ ) {
     239           0 :     ulong ix_class = code_buf[ i ] & 0x07UL;
     240           0 :     if( (ix_class==5UL) | (ix_class==6UL) ) {
     241           0 :       ulong * jmp_target = 0;
     242           0 :       uint    jmp_label = (code_buf[ i ]>>16) & 0xFFFF;
     243           0 :       switch( jmp_label ) {
     244           0 :       case 0: continue;
     245           0 :       case LBL_PASS:      jmp_target = lbl_pass;     break;
     246           0 :       case LBL_REDIRECT:  jmp_target = lbl_redirect; break;
     247           0 :       case LBL_GRE_CHECK: jmp_target = gre_check;    break;
     248           0 :       case LBL_UDP_CHECK: jmp_target = udp_check;    break;
     249           0 :       case LBL_ETH_IP4:   jmp_target = eth_ip4;      break;
     250           0 :       default: FD_LOG_ERR(( "Invalid jump instruction (%016lx)", fd_ulong_bswap( code_buf[ i ] ) ));
     251           0 :       }
     252           0 :       long   off   = jmp_target-code_buf-(long)i-1;
     253           0 :       ushort off_u = (ushort)(short)off;
     254           0 :       code_buf[ i ] = (code_buf[ i ] & 0xFFFFFFFF0000FFFF) | ((ulong)off_u<<16UL);
     255           0 :     }
     256           0 :   }
     257             : 
     258           0 :   FD_LOG_HEXDUMP_DEBUG(( "XDP program", code_buf, code_cnt*sizeof(ulong) ));
     259             : 
     260             : 
     261           0 :   #undef LBL_PASS
     262           0 :   #undef LBL_REDIRECT
     263             : 
     264           0 :   #undef LBL_GRE_CHECK
     265           0 :   #undef LBL_UDP_CHECK
     266           0 :   #undef LBL_ETH_IP4
     267           0 :   return code_cnt;
     268           0 : }
     269             : 
     270             : fd_xdp_fds_t
     271             : fd_xdp_install( uint           if_idx,
     272             :                 uint           listen_ip4_addr,
     273             :                 ulong          ports_cnt,
     274             :                 ushort const * ports,
     275           0 :                 char const *   xdp_mode ) {
     276             :   /* Check args */
     277             : 
     278           0 :   uint uxdp_mode = 0;
     279           0 :   if(      !strcmp( xdp_mode, "skb"     ) ) uxdp_mode = XDP_FLAGS_SKB_MODE;
     280           0 :   else if( !strcmp( xdp_mode, "drv"     ) ) uxdp_mode = XDP_FLAGS_DRV_MODE;
     281           0 :   else if( !strcmp( xdp_mode, "hw"      ) ) uxdp_mode = XDP_FLAGS_HW_MODE;
     282           0 :   else if( !strcmp( xdp_mode, "default" ) ) uxdp_mode = 0U;
     283           0 :   else FD_LOG_ERR(( "unknown XDP mode `%s`", xdp_mode ));
     284             : 
     285           0 :   uint true_port_cnt = 0U;
     286           0 :   for( ulong i=0UL; i<ports_cnt; i++ ) true_port_cnt += !!ports[ i ];
     287           0 :   if( FD_UNLIKELY( !true_port_cnt ) ) FD_LOG_ERR(( "XDP program is not listening on any UDP ports" ));
     288             : 
     289             :   /* Create XSK map */
     290             : 
     291           0 :   union bpf_attr attr2;
     292           0 :   memset( &attr2, 0, sizeof(union bpf_attr) );
     293           0 :   attr2.map_type    = BPF_MAP_TYPE_XSKMAP;
     294           0 :   attr2.key_size    = 4U;
     295           0 :   attr2.value_size  = 4U;
     296           0 :   attr2.max_entries = 256U;
     297           0 :   fd_memcpy( attr2.map_name, "fd_xdp_xsks", sizeof("fd_xdp_xsks") );
     298           0 :   int xsk_map_fd = (int)bpf( BPF_MAP_CREATE, &attr2, sizeof(union bpf_attr) );
     299           0 :   if( FD_UNLIKELY( -1==xsk_map_fd ) ) FD_LOG_ERR(( "Failed to create XSKMAP (%i-%s)", errno, fd_io_strerror( errno ) ));
     300             : 
     301             :   /* Load eBPF program into kernel */
     302             : 
     303           0 :   ulong code_buf[ 512 ];
     304           0 :   ulong code_cnt = fd_xdp_gen_program( code_buf, xsk_map_fd, listen_ip4_addr, ports, ports_cnt, 1 );
     305             : 
     306           0 :   char ebpf_kern_log[ 32768UL ];
     307             : 
     308           0 :   union bpf_attr attr;
     309           0 :   memset( &attr, 0, sizeof(union bpf_attr) );
     310           0 :   attr.prog_type = BPF_PROG_TYPE_XDP;
     311           0 :   attr.insn_cnt  = (uint)code_cnt;
     312           0 :   attr.insns     = (ulong)code_buf;
     313           0 :   attr.license   = (ulong)FD_LICENSE;
     314           0 :   attr.log_level = 6;
     315           0 :   attr.log_size  = 32768UL;
     316           0 :   attr.log_buf   = (ulong)ebpf_kern_log;
     317             : 
     318           0 :   int prog_fd = (int)bpf( BPF_PROG_LOAD, &attr, sizeof(union bpf_attr) );
     319           0 :   if( FD_UNLIKELY( -1==prog_fd ) ) {
     320           0 :     FD_LOG_WARNING(( "bpf(BPF_PROG_LOAD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     321           0 :     FD_LOG_ERR(( "eBPF verifier log:\n%s", ebpf_kern_log ));
     322           0 :   }
     323             : 
     324             :   /* Install program to device */
     325             : 
     326           0 :   struct bpf_link_create link_create = {
     327           0 :     .prog_fd        = (uint)prog_fd,
     328           0 :     .target_ifindex = if_idx,
     329           0 :     .attach_type    = BPF_XDP,
     330           0 :     .flags          = uxdp_mode
     331           0 :   };
     332             : 
     333           0 :   int prog_link_fd = (int)bpf( BPF_LINK_CREATE, fd_type_pun( &link_create ), sizeof(struct bpf_link_create) );
     334           0 :   if( FD_UNLIKELY( -1==prog_link_fd ) ) {
     335           0 :     if( FD_LIKELY( errno==ENOSYS ) ) {
     336           0 :       FD_LOG_ERR(( "BPF_LINK_CREATE is not supported by your kernel (%i-%s). Firedancer requires a Linux "
     337           0 :                    "kernel version of v5.7 or newer to support fast XDP networking.  Please upgrade to a newer "
     338           0 :                    "kernel version.", errno, fd_io_strerror( errno ) ));
     339           0 :     } else if( FD_LIKELY( errno==EINVAL ) ) {
     340           0 :       char if_name[ IF_NAMESIZE ] = {0};
     341           0 :       FD_LOG_ERR(( "BPF_LINK_CREATE failed on interface %s (%i-%s).  This likely means the network device "
     342           0 :                    "does not have support for XDP.  If the device is a bonding device, you will need "
     343           0 :                    "a kernel version of v5.15 or newer.  For other devices, see the list of kernel "
     344           0 :                    "support at https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#xdp",
     345           0 :                    if_indextoname( if_idx, if_name ), errno, fd_io_strerror( errno ) ));
     346           0 :     } else {
     347           0 :       FD_LOG_ERR(( "BPF_LINK_CREATE(if_idx=%u) failed (%i-%s)", if_idx, errno, fd_io_strerror( errno ) ));
     348           0 :     }
     349           0 :   }
     350             : 
     351           0 :   if( FD_UNLIKELY( -1==close( prog_fd ) ) ) FD_LOG_ERR(( "close(%d) failed (%i-%s)", prog_fd, errno, fd_io_strerror( errno ) ));
     352             : 
     353           0 :   return (fd_xdp_fds_t){
     354           0 :     .xsk_map_fd   = xsk_map_fd,
     355           0 :     .prog_link_fd = prog_link_fd,
     356           0 :   };
     357           0 : }

Generated by: LCOV version 1.14