LCOV - code coverage report
Current view: top level - app/shared/commands/configure - fd_ethtool_ioctl.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 557 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 23 0.0 %

          Line data    Source code
       1             : #include <errno.h>
       2             : #include <unistd.h>
       3             : #include <linux/ethtool.h>
       4             : #include <linux/sockios.h>
       5             : #include <net/if.h>
       6             : #include <sys/ioctl.h>
       7             : #include <sys/socket.h>
       8             : #include <sys/sysinfo.h>
       9             : #include <netinet/in.h>
      10             : 
      11             : #include "fd_ethtool_ioctl.h"
      12             : #include "../../../../disco/net/fd_linux_bond.h"
      13             : #include "../../../../util/fd_util.h"
      14             : #include "../../../../util/net/fd_ip4.h"
      15             : 
      16             : #define MAX_RXFH_KEY_SIZE   (1024)
      17           0 : #define MAX_FEATURES        (2048)
      18             : #define MAX_NTUPLE_RULES    (8192)
      19             : 
      20             : #define ETHTOOL_CMD_SIZE( base_t, data_t, data_len ) ( sizeof(base_t) + (sizeof(data_t)*(data_len)) )
      21             : 
      22             : static int
      23             : run_ioctl( fd_ethtool_ioctl_t * ioc,
      24             :            char const *         cmd,
      25             :            void *               data,
      26             :            int                  log,
      27           0 :            int                  log_notsupp ) {
      28           0 :   ioc->ifr.ifr_data = data;
      29           0 :   if( FD_UNLIKELY( ioctl( ioc->fd, SIOCETHTOOL, &ioc->ifr ) ) ) {
      30           0 :     int error = errno;
      31           0 :     if( (!!log) & ((errno!=EOPNOTSUPP) | (!!log_notsupp)) )
      32           0 :       FD_LOG_WARNING(( "error configuring network device (%s), ioctl(SIOCETHTOOL,%s) failed (%i-%s)",
      33           0 :                        ioc->ifr.ifr_name, cmd, errno, fd_io_strerror( errno ) ));
      34           0 :     return error;
      35           0 :   }
      36           0 :   return 0;
      37           0 : }
      38             : 
      39             : #define TRY_RUN_IOCTL( ioc, cmd, data ) \
      40           0 :   do { int __ret__ = run_ioctl( (ioc), (cmd), (data), 1, 1 ); \
      41           0 :        if( FD_UNLIKELY( __ret__ != 0 ) ) { return __ret__; } } while(0)
      42             : 
      43             : fd_ethtool_ioctl_t *
      44             : fd_ethtool_ioctl_init( fd_ethtool_ioctl_t * ioc,
      45           0 :                        char const * device ) {
      46           0 :   if( FD_UNLIKELY( strlen( device ) >= IF_NAMESIZE ) ) {
      47           0 :     FD_LOG_WARNING(( "device name `%s` is too long", device ));
      48           0 :     return NULL;
      49           0 :   }
      50           0 :   if( FD_UNLIKELY( strlen( device ) == 0 ) ) {
      51           0 :     FD_LOG_WARNING(( "device name `%s` is empty", device ));
      52           0 :     return NULL;
      53           0 :   }
      54             : 
      55           0 :   ioc->fd = socket( AF_INET, SOCK_DGRAM, 0 );
      56           0 :   if( FD_UNLIKELY( ioc->fd < 0 ) ) {
      57           0 :     FD_LOG_WARNING(( "error configuring network device (%s), socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
      58           0 :                      device, errno, fd_io_strerror( errno ) ));
      59           0 :     return NULL;
      60           0 :   }
      61             : 
      62           0 :   fd_memset( &ioc->ifr, 0, sizeof(struct ifreq) );
      63           0 :   fd_cstr_fini( fd_cstr_append_cstr( fd_cstr_init( ioc->ifr.ifr_name ), device ) );
      64             : 
      65           0 :   return ioc;
      66           0 : }
      67             : 
      68             : void
      69           0 : fd_ethtool_ioctl_fini( fd_ethtool_ioctl_t * ioc ) {
      70           0 :   if( FD_UNLIKELY( close( ioc->fd ) ) ) {
      71           0 :     FD_LOG_WARNING(( "error configuring network device (%s), close() socket failed (%i-%s)",
      72           0 :                      ioc->ifr.ifr_name, errno, fd_io_strerror( errno ) ));
      73           0 :   }
      74           0 :   ioc->fd = -1;
      75           0 :   fd_memset( &ioc->ifr, 0, sizeof(struct ifreq) );
      76           0 : }
      77             : 
      78             : int
      79             : fd_ethtool_ioctl_channels_set_num( fd_ethtool_ioctl_t * ioc,
      80           0 :                                    uint                 num ) {
      81           0 :   struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
      82           0 :   int ret = run_ioctl( ioc, "ETHTOOL_GCHANNELS", &ech, 1, 0 );
      83           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
      84           0 :     if( (ret==EOPNOTSUPP) & ((num==0) | (num==1))) return 0;
      85           0 :     return ret;
      86           0 :   }
      87             : 
      88           0 :   ech.cmd = ETHTOOL_SCHANNELS;
      89           0 :   if( num == 0 ) {
      90           0 :     uint max_queue_count = ech.max_combined ? ech.max_combined : ech.max_rx;
      91           0 :     num = fd_uint_min( max_queue_count, (uint)get_nprocs() );
      92           0 :   }
      93           0 :   if( ech.max_combined ) {
      94           0 :     ech.combined_count = num;
      95           0 :     ech.rx_count       = 0;
      96           0 :     ech.tx_count       = 0;
      97           0 :     FD_LOG_NOTICE(( "%sRUN: `ethtool --set-channels %s combined %u`%s", fd_log_style_dim(), ioc->ifr.ifr_name, num , fd_log_style_normal() ));
      98           0 :   } else {
      99           0 :     ech.combined_count = 0;
     100           0 :     ech.rx_count       = num;
     101           0 :     ech.tx_count       = num;
     102           0 :     FD_LOG_NOTICE(( "%sRUN: `ethtool --set-channels %s rx %u tx %u`%s", fd_log_style_dim(), ioc->ifr.ifr_name, num, num , fd_log_style_normal() ));
     103           0 :   }
     104           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_SCHANNELS", &ech );
     105           0 :   return 0;
     106           0 : }
     107             : 
     108             : int
     109             : fd_ethtool_ioctl_channels_get_num( fd_ethtool_ioctl_t * ioc,
     110           0 :                                    fd_ethtool_ioctl_channels_t * channels ) {
     111           0 :   struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
     112           0 :   int ret = run_ioctl( ioc, "ETHTOOL_GCHANNELS", &ech, 1, 0 );
     113           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     114           0 :     if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
     115             :       /* network device doesn't support getting number of channels, so
     116             :          it must always be 1 */
     117           0 :       channels->supported = 0;
     118           0 :       channels->current = 1;
     119           0 :       channels->max = 1;
     120           0 :       return 0;
     121           0 :     }
     122           0 :     return ret;
     123           0 :   }
     124           0 :   channels->supported = 1;
     125             : 
     126           0 :   if( FD_LIKELY( ech.combined_count ) ) {
     127           0 :     channels->current = ech.combined_count;
     128           0 :     channels->max = fd_uint_min( ech.max_combined, (uint)get_nprocs() );
     129           0 :     return 0;
     130           0 :   }
     131           0 :   if( ech.rx_count || ech.tx_count ) {
     132           0 :     if( FD_UNLIKELY( ech.rx_count != ech.tx_count ) )
     133           0 :       FD_LOG_WARNING(( "device `%s` has unbalanced channel count: (got %u rx, %u tx)",
     134           0 :                        ioc->ifr.ifr_name, ech.rx_count, ech.tx_count ));
     135           0 :     channels->current = ech.rx_count;
     136           0 :     channels->max = fd_uint_min( ech.max_rx, (uint)get_nprocs() );
     137           0 :     return 0;
     138           0 :   }
     139           0 :   return EINVAL;
     140           0 : }
     141             : 
     142             : int
     143           0 : fd_ethtool_ioctl_rxfh_set_default( fd_ethtool_ioctl_t * ioc ) {
     144           0 :   FD_LOG_NOTICE(( "%sRUN: `ethtool --set-rxfh-indir %s default`%s", fd_log_style_dim(), ioc->ifr.ifr_name , fd_log_style_normal() ));
     145           0 :   struct ethtool_rxfh_indir rxfh = {
     146           0 :     .cmd = ETHTOOL_SRXFHINDIR,
     147           0 :     .size = 0, /* default indirection table */
     148           0 :   };
     149           0 :   int ret = run_ioctl( ioc, "ETHTOOL_SRXFHINDIR", &rxfh, 1, 0 );
     150           0 :   if( FD_UNLIKELY( ret==EOPNOTSUPP ) ) return 0;
     151           0 :   return ret;
     152           0 : }
     153             : 
     154             : int
     155             : fd_ethtool_ioctl_rxfh_set_suffix( fd_ethtool_ioctl_t * ioc,
     156           0 :                                   uint                 start_idx ) {
     157             :   /* Get current channel count */
     158           0 :   struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
     159           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GCHANNELS", &ech );
     160           0 :   uint const channels_cnt = ech.combined_count ? ech.combined_count : ech.rx_count;
     161             : 
     162             :   /* Get current RXFH queue count
     163             : 
     164             :      Note: One would expect that ethtool can always configure the RXFH
     165             :      indirection table to target all channels / queues supported by the
     166             :      device.  This is not the case.  Some drivers limit the max queue
     167             :      index in the table to less than the current channel count.  For
     168             :      example, see ixgbe_rss_indir_tbl_max(). */
     169           0 :   struct ethtool_rxnfc nfc = { .cmd = ETHTOOL_GRXRINGS };
     170           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXRINGS", &nfc );
     171           0 :   uint const queue_cnt = (uint)nfc.data;
     172           0 :   if( FD_UNLIKELY( start_idx>=queue_cnt || queue_cnt>channels_cnt ) ) return EINVAL;
     173             : 
     174           0 :   union {
     175           0 :     struct ethtool_rxfh_indir m;
     176           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxfh_indir, uint, FD_ETHTOOL_MAX_RXFH_TABLE_CNT ) ];
     177           0 :   } rxfh = { 0 };
     178             : 
     179             :   /* Get count of rx indirection table */
     180           0 :   rxfh.m.cmd = ETHTOOL_GRXFHINDIR;
     181           0 :   rxfh.m.size = 0;
     182           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXFHINDIR", &rxfh );
     183           0 :   uint const table_ele_cnt = rxfh.m.size;
     184           0 :   if( FD_UNLIKELY( (table_ele_cnt == 0) | (table_ele_cnt > FD_ETHTOOL_MAX_RXFH_TABLE_CNT) ) )
     185           0 :     return EINVAL;
     186             : 
     187             :   /* Set table to round robin over all channels from [start_idx, queue_cnt) */
     188           0 :   FD_LOG_NOTICE(( "%sRUN: `ethtool --set-rxfh-indir %s start %u equal %u`%s", fd_log_style_dim(),
     189           0 :                   ioc->ifr.ifr_name, start_idx, queue_cnt - start_idx , fd_log_style_normal() ));
     190           0 :   rxfh.m.cmd = ETHTOOL_SRXFHINDIR;
     191           0 :   rxfh.m.size = table_ele_cnt;
     192           0 :   for( uint j=0u, q=start_idx; j<table_ele_cnt; j++ ) {
     193           0 :     rxfh.m.ring_index[ j ] = q++;
     194           0 :     if( FD_UNLIKELY( q>=queue_cnt ) ) q = start_idx;
     195           0 :   }
     196           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXFHINDIR", &rxfh );
     197             : 
     198           0 :   return 0;
     199           0 : }
     200             : 
     201             : int
     202             : fd_ethtool_ioctl_rxfh_get_queue_cnt( fd_ethtool_ioctl_t * ioc,
     203             :                                      uint *               queue_cnt )
     204           0 : {
     205           0 :   struct ethtool_rxnfc nfc = { .cmd = ETHTOOL_GRXRINGS };
     206           0 :   int ret = run_ioctl( ioc, "ETHTOOL_GRXRINGS", &nfc, 1, 0 );
     207           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     208           0 :     if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
     209           0 :       *queue_cnt = 1;
     210           0 :       return 0;
     211           0 :     }
     212           0 :     return ret;
     213           0 :   }
     214           0 :   *queue_cnt = (uint)nfc.data;
     215           0 :   FD_TEST( *queue_cnt>0U );
     216           0 :   return 0;
     217           0 : }
     218             : 
     219             : int
     220             : fd_ethtool_ioctl_rxfh_get_table( fd_ethtool_ioctl_t * ioc,
     221             :                                  uint *               table,
     222           0 :                                  uint *               table_ele_cnt ) {
     223             :   /* Note: A simpler implementation of this would use ETHTOOL_GRXFHINDIR
     224             :      as we are only concerned with the indirection table and do not need
     225             :      the other information. However, it appears that the ice driver has
     226             :      a bugged implementation of this command. */
     227             : 
     228           0 :   union {
     229           0 :     struct ethtool_rxfh m;
     230           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxfh, uint, FD_ETHTOOL_MAX_RXFH_TABLE_CNT ) + MAX_RXFH_KEY_SIZE ];
     231           0 :   } rxfh = { 0 };
     232             : 
     233             :   /* First get the count of the indirection table and hash key */
     234           0 :   rxfh.m.cmd = ETHTOOL_GRSSH;
     235           0 :   int ret = run_ioctl( ioc, "ETHTOOL_GRSSH", &rxfh, 1, 0 );
     236           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     237           0 :     if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
     238           0 :       *table_ele_cnt = 0;
     239           0 :       return 0;
     240           0 :     }
     241           0 :     return ret;
     242           0 :   }
     243           0 :   if( FD_UNLIKELY( (rxfh.m.indir_size > FD_ETHTOOL_MAX_RXFH_TABLE_CNT) |
     244           0 :                    (rxfh.m.key_size   > MAX_RXFH_KEY_SIZE) ) )
     245           0 :     return EINVAL;
     246           0 :   *table_ele_cnt = rxfh.m.indir_size;
     247             : 
     248           0 :   if( 0!=*table_ele_cnt ) {
     249             :     /* Now get the table contents itself. We also get the key bytes. */
     250           0 :     TRY_RUN_IOCTL( ioc, "ETHTOOL_GRSSH", &rxfh );
     251           0 :     fd_memcpy( table, rxfh.m.rss_config, *table_ele_cnt * sizeof(uint) );
     252           0 :   }
     253           0 :   return 0;
     254           0 : }
     255             : 
     256             : static int
     257             : get_feature_idx( fd_ethtool_ioctl_t * ioc,
     258             :                  char const *         name,
     259             :                  uint *               feature_idx,
     260           0 :                  uint *               feature_cnt ) {
     261             :   /* Check size of features string set is not too large (prevent overflow) */
     262           0 :   union {
     263           0 :     struct ethtool_sset_info m;
     264           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_sset_info, uint, 1 ) ];
     265           0 :   } esi = { .m = {
     266           0 :     .cmd = ETHTOOL_GSSET_INFO,
     267           0 :     .sset_mask = fd_ulong_mask_bit( ETH_SS_FEATURES )
     268           0 :   } };
     269           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GSSET_INFO", &esi );
     270           0 :   if( FD_UNLIKELY( (esi.m.data[0] == 0) | (esi.m.data[0] > MAX_FEATURES) ) )
     271           0 :     return EINVAL;
     272           0 :   *feature_cnt = esi.m.data[0];
     273             : 
     274             :   /* Get strings from features string set */
     275           0 :   union {
     276           0 :     struct ethtool_gstrings m;
     277           0 :     uchar _[ sizeof(struct ethtool_gstrings) + (MAX_FEATURES * ETH_GSTRING_LEN) ];
     278           0 :   } egs = { 0 };
     279           0 :   egs.m.cmd = ETHTOOL_GSTRINGS;
     280           0 :   egs.m.string_set = ETH_SS_FEATURES;
     281           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GSTRINGS", &egs );
     282             : 
     283             :   /* Find index of matching string from the set */
     284           0 :   for( uint i=0U; i<egs.m.len; i++) {
     285           0 :     uchar const * gstring = egs.m.data + (i * ETH_GSTRING_LEN);
     286           0 :     if( 0==strncmp( (char const *)gstring, name, ETH_GSTRING_LEN ) ) {
     287           0 :       *feature_idx = i;
     288           0 :       return 0;
     289           0 :     }
     290           0 :   }
     291           0 :   return -1;
     292           0 : }
     293             : 
     294             : int
     295             : fd_ethtool_ioctl_feature_set( fd_ethtool_ioctl_t * ioc,
     296             :                               char const *         name,
     297           0 :                               int                  enabled ) {
     298           0 :   uint feature_idx;
     299           0 :   uint feature_cnt;
     300           0 :   int ret = get_feature_idx( ioc, name, &feature_idx, &feature_cnt );
     301           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     302           0 :     if( (ret==-1) & (!enabled) ) return 0;
     303           0 :     return EINVAL;
     304           0 :   }
     305           0 :   uint feature_block = feature_idx / 32U;
     306           0 :   uint feature_offset = feature_idx % 32U;
     307             : 
     308           0 :   union {
     309           0 :     struct ethtool_gfeatures m;
     310           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_gfeatures, struct ethtool_get_features_block, MAX_FEATURES / 32U ) ];
     311           0 :   } egf = { 0 };
     312           0 :   egf.m.cmd = ETHTOOL_GFEATURES;
     313           0 :   egf.m.size = MAX_FEATURES / 32U;
     314           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GFEATURES", &egf );
     315           0 :   if( enabled == !!(egf.m.features[ feature_block ].active & fd_uint_mask_bit( (int)feature_offset )) )
     316           0 :     return 0;
     317             : 
     318           0 :   FD_LOG_NOTICE(( "%sRUN: `ethtool --features %s %s %s`%s", fd_log_style_dim(),
     319           0 :                   ioc->ifr.ifr_name, name, enabled ? "on" : "off" , fd_log_style_normal() ));
     320           0 :   union {
     321           0 :     struct ethtool_sfeatures m;
     322           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_sfeatures, struct ethtool_set_features_block, MAX_FEATURES / 32U ) ];
     323           0 :   } esf = { 0 };
     324           0 :   esf.m.cmd = ETHTOOL_SFEATURES;
     325           0 :   esf.m.size = fd_uint_align_up( feature_cnt, 32U ) / 32U;
     326           0 :   esf.m.features[ feature_block ].valid = fd_uint_mask_bit( (int)feature_offset );
     327           0 :   esf.m.features[ feature_block ].requested = enabled ? fd_uint_mask_bit( (int)feature_offset ) : 0;
     328             : 
     329             :   /* Note: ETHTOOL_SFEATURES has special behavior where it returns a
     330             :      positive nonzero number with flags set for specific things.
     331             :      ETHTOOL_F_UNSUPPORTED is set if the feature is not able to be
     332             :      changed, i.e. it is forever fixed on or fixed off. */
     333           0 :   ioc->ifr.ifr_data = &esf;
     334           0 :   ret = ioctl( ioc->fd, SIOCETHTOOL, &ioc->ifr );
     335           0 :   if( FD_UNLIKELY( ret < 0 ) ) {
     336           0 :     int error = errno;
     337           0 :     FD_LOG_WARNING(( "error configuring network device (%s), ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%i-%s)",
     338           0 :                      ioc->ifr.ifr_name, errno, fd_io_strerror( errno ) ));
     339           0 :     return error;
     340           0 :   }
     341           0 :   if( FD_UNLIKELY( ret==ETHTOOL_F_UNSUPPORTED ) ) {
     342           0 :     FD_LOG_WARNING(( "error configuring network device (%s), unable to change fixed feature (%s)",
     343           0 :                      ioc->ifr.ifr_name, name ));
     344           0 :     return EINVAL;
     345           0 :   }
     346           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     347           0 :     FD_LOG_WARNING(( "error configuring network device (%s), ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%d)",
     348           0 :                      ioc->ifr.ifr_name, ret ));
     349           0 :     return EINVAL;
     350           0 :   }
     351           0 :   return 0;
     352           0 : }
     353             : 
     354             : int
     355             : fd_ethtool_ioctl_feature_test( fd_ethtool_ioctl_t * ioc,
     356             :                                char const *         name,
     357           0 :                                int *                enabled ) {
     358           0 :   uint feature_idx;
     359           0 :   uint feature_cnt;
     360           0 :   int ret = get_feature_idx( ioc, name, &feature_idx, &feature_cnt );
     361           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     362           0 :     if( ret==-1 ) {
     363           0 :       *enabled = 0;
     364           0 :       return 0;
     365           0 :     }
     366           0 :     return EINVAL;
     367           0 :   }
     368           0 :   uint feature_block = feature_idx / 32U;
     369           0 :   uint feature_offset = feature_idx % 32U;
     370             : 
     371           0 :   union {
     372           0 :     struct ethtool_gfeatures m;
     373           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_gfeatures, struct ethtool_get_features_block, MAX_FEATURES / 32U ) ];
     374           0 :   } egf = { 0 };
     375           0 :   egf.m.cmd = ETHTOOL_GFEATURES;
     376           0 :   egf.m.size = MAX_FEATURES / 32U;
     377           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GFEATURES", &egf );
     378             : 
     379           0 :   *enabled = !!(egf.m.features[ feature_block ].active & fd_uint_mask_bit( (int)feature_offset ));
     380           0 :   return 0;
     381           0 : }
     382             : 
     383             : int
     384             : fd_ethtool_ioctl_feature_gro_set( fd_ethtool_ioctl_t * ioc,
     385           0 :                                   int                  enabled ) {
     386           0 :   FD_LOG_NOTICE(( "%sRUN: `ethtool --offload %s generic-receive-offload %s`%s", fd_log_style_dim(),
     387           0 :                   ioc->ifr.ifr_name, enabled ? "on" : "off" , fd_log_style_normal() ));
     388           0 :   struct ethtool_value gro = {
     389           0 :     .cmd = ETHTOOL_SGRO,
     390           0 :     .data = !!enabled
     391           0 :   };
     392           0 :   int ret = run_ioctl( ioc, "ETHTOOL_SGRO", &gro, 1, 0 );
     393           0 :   if( FD_UNLIKELY( (ret==EOPNOTSUPP) & (!enabled) ) ) return 0;
     394           0 :   return ret;
     395           0 : }
     396             : 
     397             : int
     398             : fd_ethtool_ioctl_feature_gro_test( fd_ethtool_ioctl_t * ioc,
     399             :                                    int *                enabled,
     400           0 :                                    int *                supported ) {
     401           0 :   struct ethtool_value gro = { .cmd = ETHTOOL_GGRO };
     402           0 :   int ret = run_ioctl( ioc, "ETHTOOL_GGRO", &gro, 1, 0 );
     403           0 :   *supported = (ret!=EOPNOTSUPP);
     404           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     405           0 :     if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
     406           0 :       *enabled = 0;
     407           0 :       return 0;
     408           0 :     }
     409           0 :     return ret;
     410           0 :   }
     411           0 :   *enabled = !!gro.data;
     412           0 :   return 0;
     413           0 : }
     414             : 
     415             : int
     416           0 : fd_ethtool_ioctl_ntuple_clear( fd_ethtool_ioctl_t * ioc ) {
     417           0 :   union {
     418           0 :     struct ethtool_rxnfc m;
     419           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxnfc, uint, MAX_NTUPLE_RULES ) ];
     420           0 :   } efc = { 0 };
     421             : 
     422             :   /* Get count of currently defined rules, return if none exist */
     423           0 :   efc.m.cmd = ETHTOOL_GRXCLSRLCNT;
     424           0 :   int ret = run_ioctl( ioc, "ETHTOOL_GRXCLSRLCNT", &efc, 1, 0 );
     425           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     426           0 :     if( ret==EOPNOTSUPP ) return 0;
     427           0 :     return ret;
     428           0 :   }
     429           0 :   uint const rule_cnt = efc.m.rule_cnt;
     430           0 :   if( FD_UNLIKELY( rule_cnt > MAX_NTUPLE_RULES ) )
     431           0 :     return EINVAL;
     432           0 :   if( rule_cnt == 0 )
     433           0 :     return 0;
     434             : 
     435             :   /* Get location indices of all rules */
     436           0 :   efc.m.cmd = ETHTOOL_GRXCLSRLALL;
     437           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLALL", &efc );
     438             : 
     439             :   /* Delete all rules */
     440           0 :   for( uint i=0u; i<rule_cnt; i++) {
     441           0 :     FD_LOG_NOTICE(( "%sRUN: `ethtool --config-ntuple %s delete %u`%s", fd_log_style_dim(), ioc->ifr.ifr_name, efc.m.rule_locs[ i ] , fd_log_style_normal() ));
     442           0 :     struct ethtool_rxnfc del = {
     443           0 :       .cmd = ETHTOOL_SRXCLSRLDEL,
     444           0 :       .fs = { .location = efc.m.rule_locs[ i ] }
     445           0 :     };
     446           0 :     TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLDEL", &del );
     447           0 :   }
     448             : 
     449           0 :   return 0;
     450           0 : }
     451             : 
     452             : int
     453             : fd_ethtool_ioctl_ntuple_set_udp_dport( fd_ethtool_ioctl_t * ioc,
     454             :                                        uint                 rule_idx,
     455             :                                        ushort               dport,
     456             :                                        uint                 rule_group_idx,
     457             :                                        uint                 rule_group_cnt,
     458           0 :                                        uint                 queue_idx ) {
     459             :   /* Note: Most drivers do not support RX_CLS_LOC_ANY, instead requiring
     460             :      an explicit location index.  However, some drivers require it
     461             :      (e.g. bnxt).  We can tell based on the RX_CLS_LOC_SPECIAL flag. */
     462           0 :   struct ethtool_rxnfc get = { .cmd = ETHTOOL_GRXCLSRLCNT };
     463           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLCNT", &get );
     464           0 :   int const any_location = !!(get.data & RX_CLS_LOC_SPECIAL);
     465             : 
     466           0 :   FD_TEST( rule_group_idx<rule_group_cnt && fd_uint_is_pow2( rule_group_cnt ) && rule_group_cnt>0U );
     467           0 :   uint const src_addr      = fd_uint_bswap( rule_group_idx );
     468           0 :   uint const src_addr_mask = fd_uint_bswap( rule_group_cnt-1U );
     469           0 :   FD_LOG_NOTICE(( "%sRUN: `ethtool --config-ntuple %s flow-type udp4 dst-port %hu src-ip "
     470           0 :                   FD_IP4_ADDR_FMT " m " FD_IP4_ADDR_FMT " queue %u`%s",
     471           0 :                   fd_log_style_dim(), ioc->ifr.ifr_name, dport, FD_IP4_ADDR_FMT_ARGS( src_addr ),
     472           0 :                   FD_IP4_ADDR_FMT_ARGS( ~src_addr_mask ), queue_idx, fd_log_style_normal() ));
     473           0 :   struct ethtool_rxnfc efc = {
     474           0 :     .cmd = ETHTOOL_SRXCLSRLINS,
     475           0 :     .fs = {
     476           0 :       .flow_type = UDP_V4_FLOW,
     477           0 :       .h_u = { .udp_ip4_spec = {
     478           0 :         .pdst   = fd_ushort_bswap( dport ),
     479           0 :         .ip4src = src_addr } },
     480           0 :       .m_u = { .udp_ip4_spec = {
     481           0 :         .pdst = 0xFFFF,
     482           0 :         .ip4src = src_addr_mask } },
     483           0 :       .ring_cookie = queue_idx,
     484           0 :       .location = any_location ? RX_CLS_LOC_ANY : rule_idx,
     485           0 :     }
     486           0 :   };
     487           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLINS", &efc );
     488           0 :   return 0;
     489           0 : }
     490             : 
     491             : int
     492             : fd_ethtool_ioctl_ntuple_set_gre( fd_ethtool_ioctl_t * ioc,
     493             :                                  uint                 rule_idx,
     494           0 :                                  uint                 queue_idx ) {
     495             :   /* See above for any_location */
     496           0 :   struct ethtool_rxnfc get = { .cmd = ETHTOOL_GRXCLSRLCNT };
     497           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLCNT", &get );
     498           0 :   int const any_location = !!(get.data & RX_CLS_LOC_SPECIAL);
     499             : 
     500           0 :   FD_LOG_NOTICE(( "%sRUN: `ethtool --config-ntuple %s flow-type ip4 l4proto 47 queue %u`%s", fd_log_style_dim(),
     501           0 :                   ioc->ifr.ifr_name, queue_idx , fd_log_style_normal() ));
     502           0 :   struct ethtool_rxnfc efc = {
     503           0 :     .cmd = ETHTOOL_SRXCLSRLINS,
     504           0 :     .fs = {
     505           0 :       .flow_type = IPV4_USER_FLOW,
     506           0 :       .h_u = { .usr_ip4_spec = {
     507           0 :         .ip_ver = ETH_RX_NFC_IP4,
     508           0 :         .proto  = IPPROTO_GRE } },
     509           0 :       .m_u = { .usr_ip4_spec = {
     510           0 :         .ip_ver = 0xFF,
     511           0 :         .proto  = 0xFF } },
     512           0 :       .ring_cookie = queue_idx,
     513           0 :       .location = any_location ? RX_CLS_LOC_ANY : rule_idx,
     514           0 :     }
     515           0 :   };
     516           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLINS", &efc );
     517           0 :   return 0;
     518           0 : }
     519             : 
     520             : int
     521             : fd_ethtool_ioctl_ntuple_validate( fd_ethtool_ioctl_t * ioc,
     522             :                                   ushort const *       dports,
     523             :                                   uint                 dports_cnt,
     524             :                                   uint                 queue_cnt,
     525             :                                   uint                 gre_queue,
     526           0 :                                   int *                valid ) {
     527           0 :   union {
     528           0 :     struct ethtool_rxnfc m;
     529           0 :     uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxnfc, uint, MAX_NTUPLE_RULES ) ];
     530           0 :   } efc = { 0 };
     531             : 
     532           0 :   int gre_rule = (gre_queue!=UINT_MAX);
     533             : 
     534             :   /* Get count of currently defined rules */
     535           0 :   efc.m.cmd = ETHTOOL_GRXCLSRLCNT;
     536           0 :   int ret = run_ioctl( ioc, "ETHTOOL_GRXCLSRLCNT", &efc, 1, 0 );
     537           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
     538           0 :     if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
     539           0 :       *valid = (dports_cnt==0U) & !gre_rule;
     540           0 :       return 0;
     541           0 :     }
     542           0 :     return ret;
     543           0 :   }
     544           0 :   uint const rule_cnt = efc.m.rule_cnt;
     545           0 :   if( FD_UNLIKELY( rule_cnt>MAX_NTUPLE_RULES ) )
     546           0 :     return EINVAL;
     547           0 :   if( (dports_cnt==0U) & !gre_rule ) {
     548           0 :     *valid = (rule_cnt==0U);
     549           0 :     return 0;
     550           0 :   }
     551           0 :   FD_TEST( queue_cnt>0U );
     552           0 :   uint const rule_group_cnt = fd_uint_pow2_up( queue_cnt );
     553           0 :   if( rule_cnt!=(dports_cnt*rule_group_cnt + (uint)gre_rule) ) {
     554           0 :     *valid = 0;
     555           0 :     return 0;
     556           0 :   }
     557             : 
     558           0 :   ushort expected[ MAX_NTUPLE_RULES ] = { 0 };
     559           0 :   int    found_gre = 0;
     560           0 :   for( uint r=0U; r<rule_group_cnt; r++ ) {
     561           0 :     for( uint p=0U; p<dports_cnt; p++ ) {
     562           0 :       expected[ (r*dports_cnt)+p ] = dports[ p ];
     563           0 :     }
     564           0 :   }
     565             : 
     566             :   /* Get location indices of all rules */
     567           0 :   efc.m.cmd = ETHTOOL_GRXCLSRLALL;
     568           0 :   efc.m.rule_cnt = rule_cnt;
     569           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLALL", &efc );
     570             : 
     571             :   /* Loop over all rules, returning early if any are invalid */
     572           0 :   static const struct ethtool_flow_ext  EXPECTED_EXT_MASK = { 0 };
     573           0 :          const union ethtool_flow_union expected_mask     = { .udp_ip4_spec = {
     574           0 :     .pdst   = 0xFFFF,
     575           0 :     .ip4src = fd_uint_bswap( rule_group_cnt-1U ),
     576           0 :   } };
     577           0 :   static const union ethtool_flow_union expected_gre_mask = { .usr_ip4_spec = {
     578           0 :     .ip_ver = 0xFF,
     579           0 :     .proto  = 0xFF
     580           0 :   } };
     581           0 :   for( uint i=0u; i<efc.m.rule_cnt; i++) {
     582           0 :     struct ethtool_rxnfc get = {
     583           0 :       .cmd = ETHTOOL_GRXCLSRULE,
     584           0 :       .fs = { .location = efc.m.rule_locs[ i ] }
     585           0 :     };
     586           0 :     TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRULE", &get );
     587           0 :     uint   flow_type      = get.fs.flow_type & ~(uint)FLOW_RSS & ~(uint)FLOW_EXT & ~(uint)FLOW_MAC_EXT;
     588           0 :     uint   rule_group_idx = fd_uint_bswap( get.fs.h_u.udp_ip4_spec.ip4src );
     589           0 :     ushort dport          = fd_ushort_bswap( get.fs.h_u.udp_ip4_spec.pdst );
     590             : 
     591             :     /* Is this the GRE rule? */
     592           0 :     if( FD_UNLIKELY( ((!found_gre) & (flow_type==IPV4_USER_FLOW) & (get.fs.ring_cookie==gre_queue) &
     593           0 :                         (get.fs.h_u.usr_ip4_spec.proto==IPPROTO_GRE))&&
     594           0 :                      !memcmp( &get.fs.m_u,   &expected_gre_mask, sizeof(expected_gre_mask) ) &&
     595           0 :                      !memcmp( &get.fs.m_ext, &EXPECTED_EXT_MASK, sizeof(EXPECTED_EXT_MASK)) ) ) {
     596           0 :       found_gre = 1;
     597           0 :       continue;
     598           0 :     } else if( FD_UNLIKELY( ((flow_type!=UDP_V4_FLOW) | (rule_group_idx>=rule_group_cnt) |
     599           0 :                             (get.fs.ring_cookie!=(rule_group_idx%queue_cnt)) | (dport==0)) ||
     600           0 :                             0!=memcmp( &get.fs.m_u,   &expected_mask,     sizeof(expected_mask)    ) ||
     601           0 :                             0!=memcmp( &get.fs.m_ext, &EXPECTED_EXT_MASK, sizeof(EXPECTED_EXT_MASK)) ) ) {
     602           0 :       *valid = 0;
     603           0 :       return 0;
     604           0 :     }
     605             :     /* This is a valid udp rule, find the expected port it matches in
     606             :        the given rule group, or return an error */
     607           0 :     int found = 0;
     608           0 :     for( uint j=0u; j<dports_cnt; j++) {
     609           0 :       if( expected[ (rule_group_idx*dports_cnt)+j ] == dport ) {
     610           0 :         expected[ (rule_group_idx*dports_cnt)+j ] = 0U;
     611           0 :         found = 1;
     612           0 :         break;
     613           0 :       }
     614           0 :     }
     615           0 :     if( !found ) {
     616           0 :       *valid = 0;
     617           0 :       return 0;
     618           0 :     }
     619           0 :   }
     620             : 
     621             :   /* All UDP rules are valid and matched expected ports.  It's fully
     622             :      valid if GRE matched. */
     623           0 :   *valid = found_gre==gre_rule;
     624           0 :   return 0;
     625           0 : }
     626             : 
     627             : int
     628           0 : fd_ethtool_ioctl_rxfh_set_flow_hash_udp4( fd_ethtool_ioctl_t * ioc ) {
     629           0 :   FD_LOG_NOTICE(( "%sRUN: `ethtool --config-nfc %s rx-flow-hash udp4 sdfn`%s", fd_log_style_dim(), ioc->ifr.ifr_name , fd_log_style_normal() ));
     630           0 :   struct ethtool_rxnfc nfc = {
     631           0 :     .cmd       = ETHTOOL_SRXFH,
     632           0 :     .flow_type = UDP_V4_FLOW,
     633           0 :     .data      = RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3,
     634           0 :   };
     635           0 :   TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXFH", &nfc );
     636           0 :   return 0;
     637           0 : }
     638             : 
     639             : static int
     640           0 : is_modified_device( char const * device ) {
     641           0 :   fd_ethtool_ioctl_t ioc __attribute__((cleanup(fd_ethtool_ioctl_fini)));
     642           0 :   if( FD_UNLIKELY( &ioc!=fd_ethtool_ioctl_init( &ioc, device ) ) )
     643           0 :     FD_LOG_ERR(( "error configuring network device (%s), unable to init ethtool ioctl", device ));
     644             : 
     645           0 :   fd_ethtool_ioctl_channels_t channels;
     646           0 :   FD_TEST( 0==fd_ethtool_ioctl_channels_get_num( &ioc, &channels ) );
     647           0 :   if( channels.current!=channels.max ) return 1;
     648             : 
     649           0 :   uint rxfh_queue_cnt;
     650           0 :   FD_TEST( 0==fd_ethtool_ioctl_rxfh_get_queue_cnt( &ioc, &rxfh_queue_cnt ) );
     651             : 
     652           0 :   uint rxfh_table[ FD_ETHTOOL_MAX_RXFH_TABLE_CNT ] = { 0 };
     653           0 :   uint rxfh_table_ele_cnt;
     654           0 :   FD_TEST( 0==fd_ethtool_ioctl_rxfh_get_table( &ioc, rxfh_table, &rxfh_table_ele_cnt ) );
     655           0 :   for( uint j=0U, q=0U; j<rxfh_table_ele_cnt; j++) {
     656           0 :     if( rxfh_table[ j ]!=q++ ) return 1;
     657           0 :     if( q>=rxfh_queue_cnt ) q = 0;
     658           0 :   }
     659             : 
     660           0 :   int ntuple_rules_empty;
     661           0 :   FD_TEST( 0==fd_ethtool_ioctl_ntuple_validate( &ioc, NULL, 0, 0, UINT_MAX, &ntuple_rules_empty ) );
     662           0 :   if( !ntuple_rules_empty ) return 1;
     663             : 
     664           0 :   return 0;
     665           0 : }
     666             : 
     667             : static int
     668           0 : reset_device( char const * device ) {
     669           0 :   if( !is_modified_device( device ) ) return 0;
     670             : 
     671           0 :   int error = 0;
     672             : 
     673           0 :   fd_ethtool_ioctl_t ioc;
     674           0 :   if( FD_UNLIKELY( &ioc != fd_ethtool_ioctl_init( &ioc, device ) ) )
     675           0 :     FD_LOG_ERR(( "error configuring network device (%s), unable to init ethtool ioctl", device ));
     676             : 
     677             :   /* Compare states to report whether the reset changed the device. */
     678           0 :   fd_ethtool_ioctl_channels_t channels_orig;
     679           0 :   error |= (0!=fd_ethtool_ioctl_channels_get_num( &ioc, &channels_orig ));
     680           0 :   uint rxfh_table_orig[ FD_ETHTOOL_MAX_RXFH_TABLE_CNT ] = { 0 };
     681           0 :   uint rxfh_table_orig_ele_cnt;
     682           0 :   error |= (0!=fd_ethtool_ioctl_rxfh_get_table( &ioc, rxfh_table_orig, &rxfh_table_orig_ele_cnt ));
     683           0 :   int ntuple_rules_empty_orig;
     684           0 :   error |= (0!=fd_ethtool_ioctl_ntuple_validate( &ioc, NULL, 0, 0, UINT_MAX, &ntuple_rules_empty_orig ));
     685           0 :   if( FD_UNLIKELY( error ) )
     686           0 :     FD_LOG_ERR(( "error configuring network device (%s), unable to determine initial state", device ));
     687             : 
     688             :   /* Leave the ntuple feature flag unchanged. */
     689           0 :   error |= (0!=fd_ethtool_ioctl_ntuple_clear( &ioc ));
     690             : 
     691             :   /* This should happen first, otherwise changing the number of channels may fail */
     692           0 :   error |= (0!=fd_ethtool_ioctl_rxfh_set_default( &ioc ));
     693             : 
     694           0 :   int channels_error = fd_ethtool_ioctl_channels_set_num( &ioc, 0 /* max */ );
     695           0 :   error |= (0!=channels_error);
     696             : 
     697             :   /* Some drivers (i40e) do not always evenly redistribute the RXFH table
     698             :      when increasing the channel count, so we run this again just in case. */
     699           0 :   error |= (0!=fd_ethtool_ioctl_rxfh_set_default( &ioc ));
     700             : 
     701           0 :   if( FD_UNLIKELY( error ) ) {
     702           0 :     if( FD_LIKELY( channels_error==EBUSY ) )
     703           0 :       FD_LOG_ERR(( "error configuring network device (%s), unable to set to default state. "
     704           0 :                    "This is most commonly caused by an issue with the Intel ice driver on certain versions "
     705           0 :                    "of Ubuntu.  If you are using the ice driver, `sudo dmesg | grep %s` contains "
     706           0 :                    "messages about RDMA, and you do not need RDMA, try running `rmmod irdma` and/or "
     707           0 :                    "blacklisting the irdma kernel module.", device, device ));
     708           0 :     else
     709           0 :       FD_LOG_ERR(( "error configuring network device (%s), unable to set to default state", device ));
     710           0 :   }
     711             : 
     712           0 :   fd_ethtool_ioctl_channels_t channels_new;
     713           0 :   error |= (0!=fd_ethtool_ioctl_channels_get_num( &ioc, &channels_new ));
     714           0 :   uint rxfh_table_new[ FD_ETHTOOL_MAX_RXFH_TABLE_CNT ] = { 0 };
     715           0 :   uint rxfh_table_new_ele_cnt;
     716           0 :   error |= (0!=fd_ethtool_ioctl_rxfh_get_table( &ioc, rxfh_table_new, &rxfh_table_new_ele_cnt ));
     717           0 :   int ntuple_rules_empty_new;
     718           0 :   error |= (0!=fd_ethtool_ioctl_ntuple_validate( &ioc, NULL, 0, 0, UINT_MAX, &ntuple_rules_empty_new ));
     719           0 :   if( FD_UNLIKELY( error ) )
     720           0 :     FD_LOG_ERR(( "error configuring network device (%s), unable to determine final state", device ));
     721             : 
     722           0 :   fd_ethtool_ioctl_fini( &ioc );
     723             : 
     724           0 :   int modified = (0!=memcmp( &channels_orig, &channels_new, sizeof(fd_ethtool_ioctl_channels_t) )) ||
     725           0 :                  (rxfh_table_orig_ele_cnt != rxfh_table_new_ele_cnt) ||
     726           0 :                  (0!=memcmp( rxfh_table_orig, rxfh_table_new, rxfh_table_orig_ele_cnt * sizeof(uint) )) ||
     727           0 :                  (ntuple_rules_empty_orig!=ntuple_rules_empty_new);
     728           0 :   return modified;
     729           0 : }
     730             : 
     731             : int
     732           0 : fd_ethtool_ioctl_is_modified( char const * device ) {
     733           0 :   if( !fd_bonding_is_master( device ) ) return is_modified_device( device );
     734             : 
     735           0 :   fd_bonding_slave_iter_t iter_[1];
     736           0 :   fd_bonding_slave_iter_t * iter = fd_bonding_slave_iter_init( iter_, device );
     737           0 :   for( ; !fd_bonding_slave_iter_done( iter ); fd_bonding_slave_iter_next( iter ) ) {
     738           0 :     if( is_modified_device( fd_bonding_slave_iter_ele( iter ) ) ) return 1;
     739           0 :   }
     740           0 :   return 0;
     741           0 : }
     742             : 
     743             : int
     744           0 : fd_ethtool_ioctl_reset( char const * device ) {
     745           0 :   if( !fd_bonding_is_master( device ) ) return reset_device( device );
     746             : 
     747           0 :   int modified = 0;
     748           0 :   fd_bonding_slave_iter_t iter_[1];
     749           0 :   fd_bonding_slave_iter_t * iter = fd_bonding_slave_iter_init( iter_, device );
     750           0 :   for( ; !fd_bonding_slave_iter_done( iter ); fd_bonding_slave_iter_next( iter ) ) {
     751           0 :     modified |= reset_device( fd_bonding_slave_iter_ele( iter ) );
     752           0 :   }
     753           0 :   return modified;
     754           0 : }

Generated by: LCOV version 1.14