LCOV - code coverage report
Current view: top level - app/shared/commands/configure - ethtool-loopback.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 115 0.0 %
Date: 2025-03-20 12:08:36 Functions: 0 8 0.0 %

          Line data    Source code
       1             : /* This stage disables the "tx-udp-segmentation" offload on the loopback
       2             :    interface.  If left enabled, AF_XDP will drop loopback UDP packets sent
       3             :    by processes that enable TX segmentation via SOL_UDP/UDP_SEGMENT sockopt
       4             :    or cmsg.
       5             : 
       6             :    TLDR tx-udp-segmentation and AF_XDP are incompatible. */
       7             : 
       8             : #include "configure.h"
       9             : 
      10             : #include <errno.h>
      11             : #include <net/if.h>
      12             : #include <stdio.h>
      13             : #include <unistd.h>
      14             : #include <sys/ioctl.h>
      15             : #include <sys/stat.h>
      16             : #include <linux/if.h>
      17             : #include <linux/ethtool.h>
      18             : #include <linux/sockios.h>
      19             : 
      20           0 : #define NAME "ethtool-loopback"
      21           0 : #define MAX_FEATURES (1024)
      22             : 
      23             : #define UDPSEG_FEATURE "tx-udp-segmentation"
      24             : static char const udpseg_feature[] = UDPSEG_FEATURE;
      25             : 
      26             : #define ETHTOOL_CMD_SZ( base_t, data_t, data_len ) ( sizeof(base_t) + (sizeof(data_t)*(data_len)) )
      27             : 
      28             : static int
      29           0 : enabled( config_t const * config ) {
      30             : 
      31             :   /* if we're running in a network namespace, we configure ethtool on
      32             :      the virtual device as part of netns setup, not here */
      33           0 :   if( config->development.netns.enabled ) return 0;
      34             : 
      35             :   /* only enable if network stack is XDP */
      36           0 :   if( 0!=strcmp( config->development.net.provider, "xdp" ) ) return 0;
      37             : 
      38           0 :   return 1;
      39           0 : }
      40             : 
      41             : static void
      42             : init_perm( fd_cap_chk_t *   chk,
      43           0 :            config_t const * config FD_PARAM_UNUSED ) {
      44           0 :   fd_cap_chk_root( chk, NAME, "disable loopback " UDPSEG_FEATURE " with `ethtool --offload lo " UDPSEG_FEATURE " off`" );
      45           0 : }
      46             : 
      47             : /* ethtool_ioctl wraps ioctl(sock,SIOCETHTOOL,"lo",*) */
      48             : 
      49             : static int
      50             : ethtool_ioctl( int    sock,
      51           0 :                void * data ) {
      52           0 :   struct ifreq ifr = {0};
      53           0 :   strcpy( ifr.ifr_name, "lo" );
      54           0 :   ifr.ifr_data = data;
      55           0 :   return ioctl( sock, SIOCETHTOOL, &ifr );
      56           0 : }
      57             : 
      58             : /* find_feature_index finds the index of an ethtool feature. */
      59             : 
      60             : static int
      61             : find_feature_index( int          sock,
      62           0 :                     char const * feature ) {
      63             : 
      64           0 :   union {
      65           0 :     struct ethtool_sset_info r;
      66           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_sset_info, uint, 1 ) ];
      67           0 :   } set_info = { .r = {
      68           0 :     .cmd       = ETHTOOL_GSSET_INFO,
      69           0 :     .sset_mask = fd_ulong_mask_bit( ETH_SS_FEATURES )
      70           0 :   } };
      71           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &set_info ) ) ) {
      72           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GSSET_INFO) failed (%i-%s)",
      73           0 :                  errno, fd_io_strerror( errno ) ));
      74           0 :   }
      75           0 :   uint const feature_cnt = fd_uint_min( set_info.r.data[0], MAX_FEATURES );
      76             : 
      77           0 :   static union {
      78           0 :     struct ethtool_gstrings r;
      79           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gstrings, uchar, MAX_FEATURES*ETH_GSTRING_LEN ) ];
      80           0 :   } get_strings;
      81           0 :   get_strings.r = (struct ethtool_gstrings) {
      82           0 :     .cmd        = ETHTOOL_GSTRINGS,
      83           0 :     .string_set = ETH_SS_FEATURES,
      84           0 :     .len        = feature_cnt
      85           0 :   };
      86           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &get_strings ) ) ) {
      87           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GSTRINGS) failed (%i-%s)",
      88           0 :                  errno, fd_io_strerror( errno ) ));
      89           0 :   }
      90             : 
      91           0 :   for( uint j=0UL; j<feature_cnt; j++ ) {
      92           0 :     uchar const * str = get_strings.r.data + (j*ETH_GSTRING_LEN);
      93           0 :     if( 0==strncmp( (char const *)str, feature, ETH_GSTRING_LEN ) ) return (int)j;
      94           0 :   }
      95           0 :   return -1;
      96           0 : }
      97             : 
      98             : /* get_feature_state checks if the ethtool feature at index is set.
      99             :    Returns 1 if enabled, 0 if disabled.  Terminates app on failure. */
     100             : 
     101             : static _Bool
     102             : get_feature_state( int sock,
     103           0 :                    int index ) {
     104           0 :   FD_TEST( index>0 && index<MAX_FEATURES );
     105             : 
     106           0 :   union {
     107           0 :     struct ethtool_gfeatures r;
     108           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gfeatures, struct ethtool_get_features_block, (MAX_FEATURES+31)/32 ) ];
     109           0 :   } get_features;
     110           0 :   get_features.r = (struct ethtool_gfeatures) {
     111           0 :     .cmd  = ETHTOOL_GFEATURES,
     112           0 :     .size = (MAX_FEATURES+31)/32
     113           0 :   };
     114           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &get_features ) ) ) {
     115           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GFEATURES) failed (%i-%s)",
     116           0 :                  errno, fd_io_strerror( errno ) ));
     117           0 :   }
     118             : 
     119           0 :   uint bucket = (uint)index / 32u;
     120           0 :   uint offset = (uint)index % 32u;
     121           0 :   return fd_uint_extract_bit( get_features.r.features[ bucket ].active, (int)offset );
     122           0 : }
     123             : 
     124             : /* change_feature updates the ethtool feature at the specified index.
     125             :    state==1 implies enable, state==0 implies disable.  Terminates app on
     126             :    failure. */
     127             : 
     128             : static void
     129             : change_feature( int   sock,
     130             :                 int   index,
     131           0 :                 _Bool state ) {
     132           0 :   FD_TEST( index>0 && index<MAX_FEATURES );
     133           0 :   uint bucket = (uint)index / 32u;
     134           0 :   uint offset = (uint)index % 32u;
     135             : 
     136           0 :   union {
     137           0 :     struct ethtool_sfeatures r;
     138           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_sfeatures, struct ethtool_set_features_block, (MAX_FEATURES+31)/32 ) ];
     139           0 :   } set_features = {0};
     140           0 :   set_features.r = (struct ethtool_sfeatures) {
     141           0 :     .cmd  = ETHTOOL_SFEATURES,
     142           0 :     .size = bucket+1,
     143           0 :   };
     144             : 
     145           0 :   set_features.r.features[ bucket ].valid     = 1u<<offset;
     146           0 :   set_features.r.features[ bucket ].requested = ((uint)state)<<offset;
     147             : 
     148           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &set_features ) ) ) {
     149           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%i-%s)",
     150           0 :                  errno, fd_io_strerror( errno ) ));
     151           0 :   }
     152           0 : }
     153             : 
     154             : static void
     155           0 : init( config_t const * config FD_PARAM_UNUSED ) {
     156           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
     157           0 :   if( FD_UNLIKELY( sock < 0 ) )
     158           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
     159           0 :                  errno, fd_io_strerror( errno ) ));
     160             : 
     161           0 :   int feature_idx = find_feature_index( sock, udpseg_feature );
     162           0 :   if( feature_idx<0 ) return;
     163             : 
     164           0 :   FD_LOG_NOTICE(( "RUN: `ethtool --offload lo " UDPSEG_FEATURE " off`" ));
     165             : 
     166           0 :   change_feature( sock, feature_idx, 0 ); /* disable */
     167             : 
     168           0 :   if( FD_UNLIKELY( close( sock ) ) )
     169           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     170           0 : }
     171             : 
     172             : static configure_result_t
     173           0 : check( config_t const * config FD_PARAM_UNUSED ) {
     174           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
     175           0 :   if( FD_UNLIKELY( sock < 0 ) )
     176           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
     177           0 :                  errno, fd_io_strerror( errno ) ));
     178             : 
     179           0 :   int feature_idx = find_feature_index( sock, udpseg_feature );
     180           0 :   if( feature_idx<0 ) {
     181           0 :     FD_LOG_INFO(( "device `lo` missing ethtool offload `" UDPSEG_FEATURE "`, ignoring" ));
     182           0 :     CONFIGURE_OK(); /* returns */
     183           0 :   }
     184             : 
     185           0 :   _Bool udpseg_enabled = get_feature_state( sock, feature_idx );
     186             : 
     187           0 :   if( FD_UNLIKELY( close( sock ) ) )
     188           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     189             : 
     190           0 :   if( udpseg_enabled ) {
     191           0 :     NOT_CONFIGURED( "device `lo` has " UDPSEG_FEATURE " enabled. Should be disabled" );
     192           0 :   }
     193             : 
     194           0 :   CONFIGURE_OK();
     195           0 : }
     196             : 
     197             : configure_stage_t fd_cfg_stage_ethtool_loopback = {
     198             :   .name            = NAME,
     199             :   .always_recreate = 0,
     200             :   .enabled         = enabled,
     201             :   .init_perm       = init_perm,
     202             :   .fini_perm       = NULL,
     203             :   .init            = init,
     204             :   .fini            = NULL,
     205             :   .check           = check,
     206             : };
     207             : 
     208             : #undef NAME

Generated by: LCOV version 1.14