LCOV - code coverage report
Current view: top level - app/fdctl/configure - ethtool-loopback.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 113 0.0 %
Date: 2025-03-10 12:29:05 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 <net/if.h>
      11             : #include <stdio.h>
      12             : #include <unistd.h>
      13             : #include <sys/ioctl.h>
      14             : #include <sys/stat.h>
      15             : #include <linux/if.h>
      16             : #include <linux/ethtool.h>
      17             : #include <linux/sockios.h>
      18             : 
      19           0 : #define NAME "ethtool-loopback"
      20           0 : #define MAX_FEATURES (1024)
      21             : 
      22             : #define UDPSEG_FEATURE "tx-udp-segmentation"
      23             : static char const udpseg_feature[] = UDPSEG_FEATURE;
      24             : 
      25             : #define ETHTOOL_CMD_SZ( base_t, data_t, data_len ) ( sizeof(base_t) + (sizeof(data_t)*(data_len)) )
      26             : 
      27             : static int
      28           0 : enabled( config_t const * config ) {
      29             :   /* FIXME support for netns is missing */
      30           0 :   return !config->development.netns.enabled;
      31           0 : }
      32             : 
      33             : static void
      34             : init_perm( fd_caps_ctx_t *  caps,
      35           0 :            config_t const * config FD_PARAM_UNUSED ) {
      36           0 :   fd_caps_check_root( caps, NAME, "disable loopback " UDPSEG_FEATURE " with `ethtool --offload lo " UDPSEG_FEATURE " off`" );
      37           0 : }
      38             : 
      39             : /* ethtool_ioctl wraps ioctl(sock,SIOCETHTOOL,"lo",*) */
      40             : 
      41             : static int
      42             : ethtool_ioctl( int    sock,
      43           0 :                void * data ) {
      44           0 :   struct ifreq ifr = {0};
      45           0 :   strcpy( ifr.ifr_name, "lo" );
      46           0 :   ifr.ifr_data = data;
      47           0 :   return ioctl( sock, SIOCETHTOOL, &ifr );
      48           0 : }
      49             : 
      50             : /* find_feature_index finds the index of an ethtool feature. */
      51             : 
      52             : static int
      53             : find_feature_index( int          sock,
      54           0 :                     char const * feature ) {
      55             : 
      56           0 :   union {
      57           0 :     struct ethtool_sset_info r;
      58           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_sset_info, uint, 1 ) ];
      59           0 :   } set_info = { .r = {
      60           0 :     .cmd       = ETHTOOL_GSSET_INFO,
      61           0 :     .sset_mask = fd_ulong_mask_bit( ETH_SS_FEATURES )
      62           0 :   } };
      63           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &set_info ) ) ) {
      64           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GSSET_INFO) failed (%i-%s)",
      65           0 :                  errno, fd_io_strerror( errno ) ));
      66           0 :   }
      67           0 :   uint const feature_cnt = fd_uint_min( set_info.r.data[0], MAX_FEATURES );
      68             : 
      69           0 :   static union {
      70           0 :     struct ethtool_gstrings r;
      71           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gstrings, uchar, MAX_FEATURES*ETH_GSTRING_LEN ) ];
      72           0 :   } get_strings;
      73           0 :   get_strings.r = (struct ethtool_gstrings) {
      74           0 :     .cmd        = ETHTOOL_GSTRINGS,
      75           0 :     .string_set = ETH_SS_FEATURES,
      76           0 :     .len        = feature_cnt
      77           0 :   };
      78           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &get_strings ) ) ) {
      79           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GSTRINGS) failed (%i-%s)",
      80           0 :                  errno, fd_io_strerror( errno ) ));
      81           0 :   }
      82             : 
      83           0 :   for( uint j=0UL; j<feature_cnt; j++ ) {
      84           0 :     uchar const * str = get_strings.r.data + (j*ETH_GSTRING_LEN);
      85           0 :     if( 0==strncmp( (char const *)str, feature, ETH_GSTRING_LEN ) ) return (int)j;
      86           0 :   }
      87           0 :   return -1;
      88           0 : }
      89             : 
      90             : /* get_feature_state checks if the ethtool feature at index is set.
      91             :    Returns 1 if enabled, 0 if disabled.  Terminates app on failure. */
      92             : 
      93             : static _Bool
      94             : get_feature_state( int sock,
      95           0 :                    int index ) {
      96           0 :   FD_TEST( index>0 && index<MAX_FEATURES );
      97             : 
      98           0 :   union {
      99           0 :     struct ethtool_gfeatures r;
     100           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_gfeatures, struct ethtool_get_features_block, (MAX_FEATURES+31)/32 ) ];
     101           0 :   } get_features;
     102           0 :   get_features.r = (struct ethtool_gfeatures) {
     103           0 :     .cmd  = ETHTOOL_GFEATURES,
     104           0 :     .size = (MAX_FEATURES+31)/32
     105           0 :   };
     106           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &get_features ) ) ) {
     107           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GFEATURES) failed (%i-%s)",
     108           0 :                  errno, fd_io_strerror( errno ) ));
     109           0 :   }
     110             : 
     111           0 :   uint bucket = (uint)index / 32u;
     112           0 :   uint offset = (uint)index % 32u;
     113           0 :   return fd_uint_extract_bit( get_features.r.features[ bucket ].active, (int)offset );
     114           0 : }
     115             : 
     116             : /* change_feature updates the ethtool feature at the specified index.
     117             :    state==1 implies enable, state==0 implies disable.  Terminates app on
     118             :    failure. */
     119             : 
     120             : static void
     121             : change_feature( int   sock,
     122             :                 int   index,
     123           0 :                 _Bool state ) {
     124           0 :   FD_TEST( index>0 && index<MAX_FEATURES );
     125           0 :   uint bucket = (uint)index / 32u;
     126           0 :   uint offset = (uint)index % 32u;
     127             : 
     128           0 :   union {
     129           0 :     struct ethtool_sfeatures r;
     130           0 :     uchar _[ ETHTOOL_CMD_SZ( struct ethtool_sfeatures, struct ethtool_set_features_block, (MAX_FEATURES+31)/32 ) ];
     131           0 :   } set_features = {0};
     132           0 :   set_features.r = (struct ethtool_sfeatures) {
     133           0 :     .cmd  = ETHTOOL_SFEATURES,
     134           0 :     .size = bucket+1,
     135           0 :   };
     136             : 
     137           0 :   set_features.r.features[ bucket ].valid     = 1u<<offset;
     138           0 :   set_features.r.features[ bucket ].requested = ((uint)state)<<offset;
     139             : 
     140           0 :   if( FD_UNLIKELY( ethtool_ioctl( sock, &set_features ) ) ) {
     141           0 :     FD_LOG_ERR(( "error configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%i-%s)",
     142           0 :                  errno, fd_io_strerror( errno ) ));
     143           0 :   }
     144           0 : }
     145             : 
     146             : static void
     147           0 : init( config_t * const config FD_PARAM_UNUSED ) {
     148           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
     149           0 :   if( FD_UNLIKELY( sock < 0 ) )
     150           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
     151           0 :                  errno, fd_io_strerror( errno ) ));
     152             : 
     153           0 :   int feature_idx = find_feature_index( sock, udpseg_feature );
     154           0 :   if( feature_idx<0 ) return;
     155             : 
     156           0 :   FD_LOG_NOTICE(( "RUN: `ethtool --offload lo " UDPSEG_FEATURE " off`" ));
     157             : 
     158           0 :   change_feature( sock, feature_idx, 0 ); /* disable */
     159             : 
     160           0 :   if( FD_UNLIKELY( close( sock ) ) )
     161           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     162           0 : }
     163             : 
     164             : static configure_result_t
     165           0 : check( config_t const * config FD_PARAM_UNUSED ) {
     166           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
     167           0 :   if( FD_UNLIKELY( sock < 0 ) )
     168           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
     169           0 :                  errno, fd_io_strerror( errno ) ));
     170             : 
     171           0 :   int feature_idx = find_feature_index( sock, udpseg_feature );
     172           0 :   if( feature_idx<0 ) {
     173           0 :     FD_LOG_INFO(( "device `lo` missing ethtool offload `" UDPSEG_FEATURE "`, ignoring" ));
     174           0 :     CONFIGURE_OK(); /* returns */
     175           0 :   }
     176             : 
     177           0 :   _Bool udpseg_enabled = get_feature_state( sock, feature_idx );
     178             : 
     179           0 :   if( FD_UNLIKELY( close( sock ) ) )
     180           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     181             : 
     182           0 :   if( udpseg_enabled ) {
     183           0 :     NOT_CONFIGURED( "device `lo` has " UDPSEG_FEATURE " enabled. Should be disabled" );
     184           0 :   }
     185             : 
     186           0 :   CONFIGURE_OK();
     187           0 : }
     188             : 
     189             : configure_stage_t fd_cfg_stage_ethtool_loopback = {
     190             :   .name            = NAME,
     191             :   .always_recreate = 0,
     192             :   .enabled         = enabled,
     193             :   .init_perm       = init_perm,
     194             :   .fini_perm       = NULL,
     195             :   .init            = init,
     196             :   .fini            = NULL,
     197             :   .check           = check,
     198             : };
     199             : 
     200             : #undef NAME

Generated by: LCOV version 1.14