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
|