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

          Line data    Source code
       1             : /* This stage disables the "Generic Receive Offload" ethtool feature on the
       2             :    main and loopback interfaces.  If left enabled, GRO will mangle UDP
       3             :    packets in a way that causes AF_XDP packets to get corrupted.
       4             : 
       5             :    TLDR GRO and AF_XDP are incompatible. */
       6             : 
       7             : #include "configure.h"
       8             : 
       9             : #include <errno.h>
      10             : #include <stdio.h>
      11             : #include <unistd.h>
      12             : #include <sys/ioctl.h>
      13             : #include <sys/stat.h>
      14             : #include <linux/if.h>
      15             : #include <linux/ethtool.h>
      16             : #include <linux/sockios.h>
      17             : 
      18           0 : #define NAME "ethtool-gro"
      19             : 
      20             : static int
      21           0 : enabled( config_t const * config ) {
      22             : 
      23             :   /* if we're running in a network namespace, we configure ethtool on
      24             :      the virtual device as part of netns setup, not here */
      25           0 :   if( config->development.netns.enabled ) return 0;
      26             : 
      27             :   /* only enable if network stack is XDP */
      28           0 :   if( 0!=strcmp( config->development.net.provider, "xdp" ) ) return 0;
      29             : 
      30           0 :   return 1;
      31           0 : }
      32             : 
      33             : static void
      34             : init_perm( fd_cap_chk_t *   chk,
      35           0 :            config_t const * config FD_PARAM_UNUSED ) {
      36           0 :   fd_cap_chk_root( chk, NAME, "disable network device generic-receive-offload (gro) with `ethtool --offload generic-receive-offload off`" );
      37           0 : }
      38             : 
      39             : static int
      40           0 : device_is_bonded( const char * device ) {
      41           0 :   char path[ PATH_MAX ];
      42           0 :   FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "/sys/class/net/%s/bonding", device ) );
      43           0 :   struct stat st;
      44           0 :   int err = stat( path, &st );
      45           0 :   if( FD_UNLIKELY( err && errno != ENOENT ) )
      46           0 :     FD_LOG_ERR(( "error checking if device `%s` is bonded, stat(%s) failed (%i-%s)",
      47           0 :                  device, path, errno, fd_io_strerror( errno ) ));
      48           0 :   return !err;
      49           0 : }
      50             : 
      51             : static void
      52             : device_read_slaves( const char * device,
      53           0 :                     char         output[ 4096 ] ) {
      54           0 :   char path[ PATH_MAX ];
      55           0 :   FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "/sys/class/net/%s/bonding/slaves", device ) );
      56             : 
      57           0 :   FILE * fp = fopen( path, "r" );
      58           0 :   if( FD_UNLIKELY( !fp ) )
      59           0 :     FD_LOG_ERR(( "error configuring network device, fopen(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      60           0 :   if( FD_UNLIKELY( !fgets( output, 4096, fp ) ) )
      61           0 :     FD_LOG_ERR(( "error configuring network device, fgets(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      62           0 :   if( FD_UNLIKELY( feof( fp ) ) ) FD_LOG_ERR(( "error configuring network device, fgets(%s) failed (EOF)", path ));
      63           0 :   if( FD_UNLIKELY( ferror( fp ) ) ) FD_LOG_ERR(( "error configuring network device, fgets(%s) failed (error)", path ));
      64           0 :   if( FD_UNLIKELY( strlen( output ) == 4095 ) ) FD_LOG_ERR(( "line too long in `%s`", path ));
      65           0 :   if( FD_UNLIKELY( strlen( output ) == 0 ) ) FD_LOG_ERR(( "line empty in `%s`", path ));
      66           0 :   if( FD_UNLIKELY( fclose( fp ) ) )
      67           0 :     FD_LOG_ERR(( "error configuring network device, fclose(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      68           0 :   output[ strlen( output ) - 1 ] = '\0';
      69           0 : }
      70             : 
      71             : static void
      72           0 : init_device( const char * device ) {
      73           0 :   if( FD_UNLIKELY( strlen( device ) >= IF_NAMESIZE ) ) FD_LOG_ERR(( "device name `%s` is too long", device ));
      74           0 :   if( FD_UNLIKELY( strlen( device ) == 0 ) ) FD_LOG_ERR(( "device name `%s` is empty", device ));
      75             : 
      76           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
      77           0 :   if( FD_UNLIKELY( sock < 0 ) )
      78           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
      79           0 :                  errno, fd_io_strerror( errno ) ));
      80             : 
      81           0 :   struct ifreq ifr = {0};
      82           0 :   strncpy( ifr.ifr_name, device, IF_NAMESIZE-1 );
      83             : 
      84             :   /* turn off generic-receive-offload, which is entirely incompatible with
      85             :    * AF_XDP and QUIC
      86             :    * It results in multiple UDP payloads being merged into a single UDP packet,
      87             :    * with IP and UDP headers rewritten, combining the lengths and updating the
      88             :    * checksums. QUIC short packets cannot be processed reliably in this case. */
      89             : 
      90             :   /* command for generic-receive-offload = off */
      91           0 :   struct ethtool_value gro = { .cmd = ETHTOOL_SGRO, .data = 0 };
      92             : 
      93             :   /* attach command to ifr */
      94           0 :   ifr.ifr_data = (void *)&gro;
      95             : 
      96             :   /* log command */
      97           0 :   FD_LOG_NOTICE(( "RUN: `ethtool --offload %s generic-receive-offload off`",
      98           0 :                   device ));
      99             : 
     100             :   /* execute command */
     101           0 :   if( FD_UNLIKELY( ioctl( sock, SIOCETHTOOL, &ifr ) ) ) {
     102           0 :     FD_LOG_ERR(( "configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_SGRO) failed (%i-%s)",
     103           0 :                  errno, fd_io_strerror( errno ) ));
     104           0 :   }
     105             : 
     106           0 :   if( FD_UNLIKELY( close( sock ) ) )
     107           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     108           0 : }
     109             : 
     110             : static void
     111           0 : init( config_t const * config ) {
     112             :   /* we need one channel for both TX and RX on the NIC for each QUIC
     113             :      tile, but the interface probably defaults to one channel total */
     114           0 :   if( FD_UNLIKELY( device_is_bonded( config->tiles.net.interface ) ) ) {
     115             :     /* if using a bonded device, we need to disable gro on the
     116             :        underlying devices.
     117             : 
     118             :        we don't need to disable gro on the bonded device, as the packets are
     119             :        redirected by XDP before any of the kernel bonding logic */
     120           0 :     char line[ 4096 ];
     121           0 :     device_read_slaves( config->tiles.net.interface, line );
     122           0 :     char * saveptr;
     123           0 :     for( char * token=strtok_r( line , " \t", &saveptr ); token!=NULL; token=strtok_r( NULL, " \t", &saveptr ) ) {
     124           0 :       init_device( token );
     125           0 :     }
     126           0 :   } else {
     127           0 :     init_device( config->tiles.net.interface );
     128           0 :   }
     129           0 :   init_device( "lo" );
     130           0 : }
     131             : 
     132             : static configure_result_t
     133           0 : check_device( const char * device ) {
     134           0 :   if( FD_UNLIKELY( strlen( device ) >= IF_NAMESIZE ) ) FD_LOG_ERR(( "device name `%s` is too long", device ));
     135           0 :   if( FD_UNLIKELY( strlen( device ) == 0 ) ) FD_LOG_ERR(( "device name `%s` is empty", device ));
     136             : 
     137           0 :   int sock = socket( AF_INET, SOCK_DGRAM, 0 );
     138           0 :   if( FD_UNLIKELY( sock < 0 ) )
     139           0 :     FD_LOG_ERR(( "error configuring network device, socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
     140           0 :                  errno, fd_io_strerror( errno ) ));
     141             : 
     142           0 :   struct ifreq ifr = {0};
     143           0 :   strncpy( ifr.ifr_name, device, IF_NAMESIZE );
     144           0 :   ifr.ifr_name[ IF_NAMESIZE - 1 ] = '\0'; // silence linter, not needed for correctness
     145             : 
     146             :   /* check generic-receive-offload, which is entirely incompatible with
     147             :    * AF_XDP and QUIC */
     148             : 
     149             :   /* command for getting generic-receive-offload */
     150           0 :   struct ethtool_value gro = { .cmd = ETHTOOL_GGRO, .data = 0 };
     151             : 
     152             :   /* attach command to ifr */
     153           0 :   ifr.ifr_data = (void *)&gro;
     154             : 
     155             :   /* execute command */
     156           0 :   if( FD_UNLIKELY( ioctl( sock, SIOCETHTOOL, &ifr ) ) ) {
     157           0 :     if( FD_LIKELY( errno != EOPNOTSUPP ) ) {
     158           0 :       FD_LOG_ERR(( "configuring network device, ioctl(SIOCETHTOOL,ETHTOOL_GGRO) failed (%i-%s)",
     159           0 :                    errno, fd_io_strerror( errno ) ));
     160           0 :     }
     161           0 :   }
     162             : 
     163           0 :   if( FD_UNLIKELY( close( sock ) ) )
     164           0 :     FD_LOG_ERR(( "error configuring network device, close() socket failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     165             : 
     166             :   /* if generic-receive-offload enabled, set NOT_CONFIGURED */
     167           0 :   if( FD_UNLIKELY( gro.data ) ) {
     168           0 :     NOT_CONFIGURED( "device `%s` has generic-receive-offload enabled. Should be disabled",
     169           0 :                     device );
     170           0 :   }
     171             : 
     172           0 :   CONFIGURE_OK();
     173           0 : }
     174             : 
     175             : static configure_result_t
     176           0 : check( config_t const * config ) {
     177           0 :   if( FD_UNLIKELY( device_is_bonded( config->tiles.net.interface ) ) ) {
     178           0 :     char line[ 4096 ];
     179           0 :     device_read_slaves( config->tiles.net.interface, line );
     180           0 :     char * saveptr;
     181           0 :     for( char * token=strtok_r( line, " \t", &saveptr ); token!=NULL; token=strtok_r( NULL, " \t", &saveptr ) ) {
     182           0 :       CHECK( check_device( token ) );
     183           0 :     }
     184           0 :   } else {
     185           0 :     CHECK( check_device( config->tiles.net.interface ) );
     186           0 :   }
     187             : 
     188           0 :   CONFIGURE_OK();
     189           0 : }
     190             : 
     191             : configure_stage_t fd_cfg_stage_ethtool_gro = {
     192             :   .name            = NAME,
     193             :   .always_recreate = 0,
     194             :   .enabled         = enabled,
     195             :   .init_perm       = init_perm,
     196             :   .fini_perm       = NULL,
     197             :   .init            = init,
     198             :   .fini            = NULL,
     199             :   .check           = check,
     200             : };
     201             : 
     202             : #undef NAME

Generated by: LCOV version 1.14