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