LCOV - code coverage report
Current view: top level - app/shared/commands/configure - ethtool-offloads.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 90 0.0 %
Date: 2025-10-14 04:31:44 Functions: 0 8 0.0 %

          Line data    Source code
       1             : /* This stage disables various ethtool features on the main and
       2             :    loopback interfaces that are known to be incompatible with AF_XDP.
       3             : 
       4             :      - "Generic Receive Offload": If left enabled, GRO will mangle UDP
       5             :      packets in a way that causes AF_XDP packets to get corrupted.
       6             : 
       7             :      - "GRE Segmentation Offload": This feature has been known to cause
       8             :      corruption of packets sent via normal sockets while XDP is in use
       9             :      on the same system. */
      10             : 
      11             : #include "configure.h"
      12             : 
      13             : #include <errno.h>
      14             : #include <stdio.h>
      15             : #include <sys/stat.h>
      16             : 
      17             : #include "fd_ethtool_ioctl.h"
      18             : 
      19           0 : #define NAME "ethtool-offloads"
      20             : 
      21             : static int
      22           0 : enabled( fd_config_t const * config ) {
      23             : 
      24             :   /* if we're running in a network namespace, we configure ethtool on
      25             :      the virtual device as part of netns setup, not here */
      26           0 :   if( config->development.netns.enabled ) return 0;
      27             : 
      28             :   /* only enable if network stack is XDP */
      29           0 :   if( 0!=strcmp( config->net.provider, "xdp" ) ) return 0;
      30             : 
      31           0 :   return 1;
      32           0 : }
      33             : 
      34             : static void
      35             : init_perm( fd_cap_chk_t *      chk,
      36           0 :            fd_config_t const * config FD_PARAM_UNUSED ) {
      37           0 :   fd_cap_chk_root( chk, NAME, "disable network device features with `ethtool --offload INTF FEATURE off`" );
      38           0 : }
      39             : 
      40             : static int
      41           0 : device_is_bonded( char const * device ) {
      42           0 :   char path[ PATH_MAX ];
      43           0 :   FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "/sys/class/net/%s/bonding", device ) );
      44           0 :   struct stat st;
      45           0 :   int err = stat( path, &st );
      46           0 :   if( FD_UNLIKELY( err && errno != ENOENT ) )
      47           0 :     FD_LOG_ERR(( "error checking if device `%s` is bonded, stat(%s) failed (%i-%s)",
      48           0 :                  device, path, errno, fd_io_strerror( errno ) ));
      49           0 :   return !err;
      50           0 : }
      51             : 
      52             : static void
      53             : device_read_slaves( char const * device,
      54           0 :                     char         output[ 4096 ] ) {
      55           0 :   char path[ PATH_MAX ];
      56           0 :   FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "/sys/class/net/%s/bonding/slaves", device ) );
      57             : 
      58           0 :   FILE * fp = fopen( path, "r" );
      59           0 :   if( FD_UNLIKELY( !fp ) )
      60           0 :     FD_LOG_ERR(( "error configuring network device, fopen(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      61           0 :   if( FD_UNLIKELY( !fgets( output, 4096, fp ) ) )
      62           0 :     FD_LOG_ERR(( "error configuring network device, fgets(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      63           0 :   if( FD_UNLIKELY( feof( fp ) ) ) FD_LOG_ERR(( "error configuring network device, fgets(%s) failed (EOF)", path ));
      64           0 :   if( FD_UNLIKELY( ferror( fp ) ) ) FD_LOG_ERR(( "error configuring network device, fgets(%s) failed (error)", path ));
      65           0 :   if( FD_UNLIKELY( strlen( output ) == 4095 ) ) FD_LOG_ERR(( "line too long in `%s`", path ));
      66           0 :   if( FD_UNLIKELY( strlen( output ) == 0 ) ) FD_LOG_ERR(( "line empty in `%s`", path ));
      67           0 :   if( FD_UNLIKELY( fclose( fp ) ) )
      68           0 :     FD_LOG_ERR(( "error configuring network device, fclose(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      69           0 :   output[ strlen( output ) - 1 ] = '\0';
      70           0 : }
      71             : 
      72             : static void
      73           0 : init_device( char const * device ) {
      74           0 :   fd_ethtool_ioctl_t ioc;
      75           0 :   if( FD_UNLIKELY( &ioc != fd_ethtool_ioctl_init( &ioc, device ) ) )
      76           0 :     FD_LOG_ERR(( "error configuring network device (%s), unable to init ethtool ioctl", device ));
      77             : 
      78             :   /* turn off generic-receive-offload, which is entirely incompatible with
      79             :    * AF_XDP and QUIC
      80             :    * It results in multiple UDP payloads being merged into a single UDP packet,
      81             :    * with IP and UDP headers rewritten, combining the lengths and updating the
      82             :    * checksums. QUIC short packets cannot be processed reliably in this case. */
      83           0 :   FD_TEST( 0==fd_ethtool_ioctl_feature_gro_set( &ioc, 0 ) );
      84             : 
      85             :   /* turn off tx-gre-segmentation and tx-gre-csum-segmentation.  When
      86             :      enabled, some packets sent via normal sockets can be corrupted
      87             :      while XDP is in use on the same system. */
      88           0 :   FD_TEST( 0==fd_ethtool_ioctl_feature_set( &ioc, FD_ETHTOOL_FEATURE_TXGRESEG,     0 ) );
      89           0 :   FD_TEST( 0==fd_ethtool_ioctl_feature_set( &ioc, FD_ETHTOOL_FEATURE_TXGRECSUMSEG, 0 ) );
      90             : 
      91           0 :   fd_ethtool_ioctl_fini( &ioc );
      92           0 : }
      93             : 
      94             : static void
      95           0 : init( fd_config_t const * config ) {
      96           0 :   if( FD_UNLIKELY( device_is_bonded( config->net.interface ) ) ) {
      97             :     /* if using a bonded device, we need to disable gro on the
      98             :        underlying devices.
      99             : 
     100             :        we don't need to disable gro on the bonded device, as the packets are
     101             :        redirected by XDP before any of the kernel bonding logic */
     102           0 :     char line[ 4096 ];
     103           0 :     device_read_slaves( config->net.interface, line );
     104           0 :     char * saveptr;
     105           0 :     for( char * token=strtok_r( line , " \t", &saveptr ); token!=NULL; token=strtok_r( NULL, " \t", &saveptr ) ) {
     106           0 :       init_device( token );
     107           0 :     }
     108           0 :   } else {
     109           0 :     init_device( config->net.interface );
     110           0 :   }
     111           0 :   init_device( "lo" );
     112           0 : }
     113             : 
     114             : static configure_result_t
     115           0 : check_device( char const * device ) {
     116           0 :   fd_ethtool_ioctl_t ioc;
     117           0 :   if( FD_UNLIKELY( &ioc != fd_ethtool_ioctl_init( &ioc, device ) ) )
     118           0 :     FD_LOG_ERR(( "error configuring network device (%s), unable to init ethtool ioctl", device ));
     119             : 
     120           0 :   int gro_active;
     121           0 :   FD_TEST( 0==fd_ethtool_ioctl_feature_gro_test( &ioc, &gro_active ) );
     122             : 
     123           0 :   int greseg_active;
     124           0 :   int grecsumseg_active;
     125           0 :   FD_TEST( 0==fd_ethtool_ioctl_feature_test( &ioc, FD_ETHTOOL_FEATURE_TXGRESEG,     &greseg_active     ) );
     126           0 :   FD_TEST( 0==fd_ethtool_ioctl_feature_test( &ioc, FD_ETHTOOL_FEATURE_TXGRECSUMSEG, &grecsumseg_active ) );
     127             : 
     128           0 :   fd_ethtool_ioctl_fini( &ioc );
     129             : 
     130           0 :   if( FD_UNLIKELY( gro_active ) )
     131           0 :     NOT_CONFIGURED( "device `%s` has generic-receive-offload enabled. Should be disabled", device );
     132             : 
     133           0 :   if( FD_UNLIKELY( greseg_active ) )
     134           0 :     NOT_CONFIGURED( "device `%s` has tx-gre-segmentation enabled. Should be disabled", device );
     135           0 :   if( FD_UNLIKELY( grecsumseg_active ) )
     136           0 :     NOT_CONFIGURED( "device `%s` has tx-gre-csum-segmentation enabled. Should be disabled", device );
     137             : 
     138           0 :   CONFIGURE_OK();
     139           0 : }
     140             : 
     141             : static configure_result_t
     142           0 : check( fd_config_t const * config ) {
     143           0 :   if( FD_UNLIKELY( device_is_bonded( config->net.interface ) ) ) {
     144           0 :     char line[ 4096 ];
     145           0 :     device_read_slaves( config->net.interface, line );
     146           0 :     char * saveptr;
     147           0 :     for( char * token=strtok_r( line, " \t", &saveptr ); token!=NULL; token=strtok_r( NULL, " \t", &saveptr ) ) {
     148           0 :       CHECK( check_device( token ) );
     149           0 :     }
     150           0 :   } else {
     151           0 :     CHECK( check_device( config->net.interface ) );
     152           0 :   }
     153           0 :   CHECK( check_device( "lo" ) );
     154             : 
     155           0 :   CONFIGURE_OK();
     156           0 : }
     157             : 
     158             : configure_stage_t fd_cfg_stage_ethtool_offloads = {
     159             :   .name            = NAME,
     160             :   .always_recreate = 0,
     161             :   .enabled         = enabled,
     162             :   .init_perm       = init_perm,
     163             :   .fini_perm       = NULL,
     164             :   .init            = init,
     165             :   .fini            = NULL,
     166             :   .check           = check,
     167             : };
     168             : 
     169             : #undef NAME

Generated by: LCOV version 1.14