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 118 0.0 %
Date: 2025-07-05 04:47:39 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->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 :   fd_msan_unpoison( set_info.r.data, sizeof(uint) );
      76           0 :   uint const feature_cnt = fd_uint_min( set_info.r.data[0], MAX_FEATURES );
      77             : 
      78           0 :   static union {
      79           0 :     struct ethtool_gstrings r;
      80           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gstrings, uchar, MAX_FEATURES*ETH_GSTRING_LEN ) ];
      81           0 :   } get_strings;
      82           0 :   get_strings.r = (struct ethtool_gstrings) {
      83           0 :     .cmd        = ETHTOOL_GSTRINGS,
      84           0 :     .string_set = ETH_SS_FEATURES,
      85           0 :     .len        = feature_cnt
      86           0 :   };
      87           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &get_strings ) ) ) {
      88           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GSTRINGS) failed (%i-%s)",
      89           0 :                  errno, fd_io_strerror( errno ) ));
      90           0 :   }
      91           0 :   fd_msan_unpoison( get_strings.r.data, ETH_GSTRING_LEN*feature_cnt );
      92             : 
      93           0 :   for( uint j=0UL; j<feature_cnt; j++ ) {
      94           0 :     uchar const * str = get_strings.r.data + (j*ETH_GSTRING_LEN);
      95           0 :     if( 0==strncmp( (char const *)str, feature, ETH_GSTRING_LEN ) ) return (int)j;
      96           0 :   }
      97           0 :   return -1;
      98           0 : }
      99             : 
     100             : /* get_feature_state checks if the ethtool feature at index is set.
     101             :    Returns 1 if enabled, 0 if disabled.  Terminates app on failure. */
     102             : 
     103             : static _Bool
     104             : get_feature_state( int sock,
     105           0 :                    int index ) {
     106           0 :   FD_TEST( index>0 && index<MAX_FEATURES );
     107             : 
     108           0 :   union {
     109           0 :     struct ethtool_gfeatures r;
     110           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gfeatures, struct ethtool_get_features_block, (MAX_FEATURES+31)/32 ) ];
     111           0 :   } get_features;
     112           0 :   get_features.r = (struct ethtool_gfeatures) {
     113           0 :     .cmd  = ETHTOOL_GFEATURES,
     114           0 :     .size = (MAX_FEATURES+31)/32
     115           0 :   };
     116           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &get_features ) ) ) {
     117           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GFEATURES) failed (%i-%s)",
     118           0 :                  errno, fd_io_strerror( errno ) ));
     119           0 :   }
     120           0 :   fd_msan_unpoison( get_features.r.features, get_features.r.size*sizeof(struct ethtool_get_features_block) );
     121             : 
     122           0 :   uint bucket = (uint)index / 32u;
     123           0 :   uint offset = (uint)index % 32u;
     124           0 :   return fd_uint_extract_bit( get_features.r.features[ bucket ].active, (int)offset );
     125           0 : }
     126             : 
     127             : /* change_feature updates the ethtool feature at the specified index.
     128             :    state==1 implies enable, state==0 implies disable.  Terminates app on
     129             :    failure. */
     130             : 
     131             : static void
     132             : change_feature( int   sock,
     133             :                 int   index,
     134           0 :                 _Bool state ) {
     135           0 :   FD_TEST( index>0 && index<MAX_FEATURES );
     136           0 :   uint bucket = (uint)index / 32u;
     137           0 :   uint offset = (uint)index % 32u;
     138             : 
     139           0 :   union {
     140           0 :     struct ethtool_sfeatures r;
     141           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_sfeatures, struct ethtool_set_features_block, (MAX_FEATURES+31)/32 ) ];
     142           0 :   } set_features = {0};
     143           0 :   set_features.r = (struct ethtool_sfeatures) {
     144           0 :     .cmd  = ETHTOOL_SFEATURES,
     145           0 :     .size = bucket+1,
     146           0 :   };
     147             : 
     148           0 :   set_features.r.features[ bucket ].valid     = 1u<<offset;
     149           0 :   set_features.r.features[ bucket ].requested = ((uint)state)<<offset;
     150             : 
     151           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &set_features ) ) ) {
     152           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%i-%s)",
     153           0 :                  errno, fd_io_strerror( errno ) ));
     154           0 :   }
     155           0 : }
     156             : 
     157             : static void
     158           0 : init( config_t const * config FD_PARAM_UNUSED ) {
     159           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
     160           0 :   if( FD_UNLIKELY( sock < 0 ) )
     161           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
     162           0 :                  errno, fd_io_strerror( errno ) ));
     163             : 
     164           0 :   int feature_idx = find_feature_index( sock, udpseg_feature );
     165           0 :   if( feature_idx<0 ) return;
     166             : 
     167           0 :   FD_LOG_NOTICE(( "RUN: `ethtool --offload lo " UDPSEG_FEATURE " off`" ));
     168             : 
     169           0 :   change_feature( sock, feature_idx, 0 ); /* disable */
     170             : 
     171           0 :   if( FD_UNLIKELY( close( sock ) ) )
     172           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     173           0 : }
     174             : 
     175             : static configure_result_t
     176           0 : check( config_t const * config FD_PARAM_UNUSED ) {
     177           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
     178           0 :   if( FD_UNLIKELY( sock < 0 ) )
     179           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
     180           0 :                  errno, fd_io_strerror( errno ) ));
     181             : 
     182           0 :   int feature_idx = find_feature_index( sock, udpseg_feature );
     183           0 :   if( feature_idx<0 ) {
     184           0 :     FD_LOG_INFO(( "device `lo` missing ethtool offload `" UDPSEG_FEATURE "`, ignoring" ));
     185           0 :     CONFIGURE_OK(); /* returns */
     186           0 :   }
     187             : 
     188           0 :   _Bool udpseg_enabled = get_feature_state( sock, feature_idx );
     189             : 
     190           0 :   if( FD_UNLIKELY( close( sock ) ) )
     191           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     192             : 
     193           0 :   if( udpseg_enabled ) {
     194           0 :     NOT_CONFIGURED( "device `lo` has " UDPSEG_FEATURE " enabled. Should be disabled" );
     195           0 :   }
     196             : 
     197           0 :   CONFIGURE_OK();
     198           0 : }
     199             : 
     200             : configure_stage_t fd_cfg_stage_ethtool_loopback = {
     201             :   .name            = NAME,
     202             :   .always_recreate = 0,
     203             :   .enabled         = enabled,
     204             :   .init_perm       = init_perm,
     205             :   .fini_perm       = NULL,
     206             :   .init            = init,
     207             :   .fini            = NULL,
     208             :   .check           = check,
     209             : };
     210             : 
     211             : #undef NAME

Generated by: LCOV version 1.14