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 :
9 : #include "fd_ethtool_ioctl.h"
10 : #include "../../../../util/fd_util.h"
11 :
12 : #define MAX_RXFH_KEY_SIZE (1024)
13 0 : #define MAX_FEATURES (2048)
14 : #define MAX_NTUPLE_RULES (8192)
15 :
16 : #define ETHTOOL_CMD_SIZE( base_t, data_t, data_len ) ( sizeof(base_t) + (sizeof(data_t)*(data_len)) )
17 :
18 : static int
19 : run_ioctl( fd_ethtool_ioctl_t * ioc,
20 : char const * cmd,
21 : void * data,
22 : int log,
23 0 : int log_notsupp ) {
24 0 : ioc->ifr.ifr_data = data;
25 0 : if( FD_UNLIKELY( ioctl( ioc->fd, SIOCETHTOOL, &ioc->ifr ) ) ) {
26 0 : if( (!!log) & ((errno!=EOPNOTSUPP) | (!!log_notsupp)) )
27 0 : FD_LOG_WARNING(( "error configuring network device (%s), ioctl(SIOCETHTOOL,%s) failed (%i-%s)",
28 0 : ioc->ifr.ifr_name, cmd, errno, fd_io_strerror( errno ) ));
29 0 : return errno;
30 0 : }
31 0 : return 0;
32 0 : }
33 :
34 : #define TRY_RUN_IOCTL( ioc, cmd, data ) \
35 0 : do { int __ret__ = run_ioctl( (ioc), (cmd), (data), 1, 1 ); \
36 0 : if( FD_UNLIKELY( __ret__ != 0 ) ) { return __ret__; } } while(0)
37 :
38 : fd_ethtool_ioctl_t *
39 : fd_ethtool_ioctl_init( fd_ethtool_ioctl_t * ioc,
40 0 : char const * device ) {
41 0 : if( FD_UNLIKELY( strlen( device ) >= IF_NAMESIZE ) ) {
42 0 : FD_LOG_WARNING(( "device name `%s` is too long", device ));
43 0 : return NULL;
44 0 : }
45 0 : if( FD_UNLIKELY( strlen( device ) == 0 ) ) {
46 0 : FD_LOG_WARNING(( "device name `%s` is empty", device ));
47 0 : return NULL;
48 0 : }
49 :
50 0 : ioc->fd = socket( AF_INET, SOCK_DGRAM, 0 );
51 0 : if( FD_UNLIKELY( ioc->fd < 0 ) ) {
52 0 : FD_LOG_WARNING(( "error configuring network device (%s), socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
53 0 : device, errno, fd_io_strerror( errno ) ));
54 0 : return NULL;
55 0 : }
56 :
57 0 : fd_memset( &ioc->ifr, 0, sizeof(struct ifreq) );
58 0 : fd_cstr_fini( fd_cstr_append_cstr( fd_cstr_init( ioc->ifr.ifr_name ), device ) );
59 :
60 0 : return ioc;
61 0 : }
62 :
63 : void
64 0 : fd_ethtool_ioctl_fini( fd_ethtool_ioctl_t * ioc ) {
65 0 : if( FD_UNLIKELY( close( ioc->fd ) ) ) {
66 0 : FD_LOG_WARNING(( "error configuring network device (%s), close() socket failed (%i-%s)",
67 0 : ioc->ifr.ifr_name, errno, fd_io_strerror( errno ) ));
68 0 : }
69 0 : ioc->fd = -1;
70 0 : fd_memset( &ioc->ifr, 0, sizeof(struct ifreq) );
71 0 : }
72 :
73 : int
74 : fd_ethtool_ioctl_channels_set_num( fd_ethtool_ioctl_t * ioc,
75 0 : uint num ) {
76 0 : struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
77 0 : int ret = run_ioctl( ioc, "ETHTOOL_GCHANNELS", &ech, 1, 0 );
78 0 : if( FD_UNLIKELY( ret!=0 ) ) {
79 0 : if( (ret==EOPNOTSUPP) & ((num==0) | (num==1))) return 0;
80 0 : return ret;
81 0 : }
82 :
83 0 : ech.cmd = ETHTOOL_SCHANNELS;
84 0 : if( num == 0 ) {
85 0 : uint max_queue_count = ech.max_combined ? ech.max_combined : ech.max_rx;
86 0 : num = fd_uint_min( max_queue_count, (uint)fd_shmem_cpu_cnt() );
87 0 : }
88 0 : if( ech.max_combined ) {
89 0 : ech.combined_count = num;
90 0 : ech.rx_count = 0;
91 0 : ech.tx_count = 0;
92 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-channels %s combined %u`", ioc->ifr.ifr_name, num ));
93 0 : } else {
94 0 : ech.combined_count = 0;
95 0 : ech.rx_count = num;
96 0 : ech.tx_count = num;
97 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-channels %s rx %u tx %u`", ioc->ifr.ifr_name, num, num ));
98 0 : }
99 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SCHANNELS", &ech );
100 0 : return 0;
101 0 : }
102 :
103 : int
104 : fd_ethtool_ioctl_channels_get_num( fd_ethtool_ioctl_t * ioc,
105 0 : fd_ethtool_ioctl_channels_t * channels ) {
106 0 : struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
107 0 : int ret = run_ioctl( ioc, "ETHTOOL_GCHANNELS", &ech, 1, 0 );
108 0 : if( FD_UNLIKELY( ret!=0 ) ) {
109 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
110 : /* network device doesn't support getting number of channels, so
111 : it must always be 1 */
112 0 : channels->supported = 0;
113 0 : channels->current = 1;
114 0 : channels->max = 1;
115 0 : return 0;
116 0 : }
117 0 : return ret;
118 0 : }
119 0 : channels->supported = 1;
120 :
121 0 : if( FD_LIKELY( ech.combined_count ) ) {
122 0 : channels->current = ech.combined_count;
123 0 : channels->max = fd_uint_min( ech.max_combined, (uint)fd_shmem_cpu_cnt() );
124 0 : return 0;
125 0 : }
126 0 : if( ech.rx_count || ech.tx_count ) {
127 0 : if( FD_UNLIKELY( ech.rx_count != ech.tx_count ) )
128 0 : FD_LOG_WARNING(( "device `%s` has unbalanced channel count: (got %u rx, %u tx)",
129 0 : ioc->ifr.ifr_name, ech.rx_count, ech.tx_count ));
130 0 : channels->current = ech.rx_count;
131 0 : channels->max = fd_uint_min( ech.max_rx, (uint)fd_shmem_cpu_cnt() );
132 0 : return 0;
133 0 : }
134 0 : return EINVAL;
135 0 : }
136 :
137 : int
138 0 : fd_ethtool_ioctl_rxfh_set_default( fd_ethtool_ioctl_t * ioc ) {
139 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-rxfh-indir %s default`", ioc->ifr.ifr_name ));
140 0 : struct ethtool_rxfh_indir rxfh = {
141 0 : .cmd = ETHTOOL_SRXFHINDIR,
142 0 : .size = 0, /* default indirection table */
143 0 : };
144 0 : int ret = run_ioctl( ioc, "ETHTOOL_SRXFHINDIR", &rxfh, 1, 0 );
145 0 : if( FD_UNLIKELY( ret==EOPNOTSUPP ) ) return 0;
146 0 : return ret;
147 0 : }
148 :
149 : int
150 : fd_ethtool_ioctl_rxfh_set_suffix( fd_ethtool_ioctl_t * ioc,
151 0 : uint start_idx ) {
152 : /* Get current channel count */
153 0 : struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
154 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GCHANNELS", &ech );
155 0 : uint const num_channels = ech.combined_count ? ech.combined_count : ech.rx_count;
156 0 : if( FD_UNLIKELY( start_idx >= num_channels ))
157 0 : return EINVAL;
158 :
159 0 : union {
160 0 : struct ethtool_rxfh_indir m;
161 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxfh_indir, uint, FD_ETHTOOL_MAX_RXFH_TABLE_CNT ) ];
162 0 : } rxfh = { 0 };
163 :
164 : /* Get count of rx indirection table */
165 0 : rxfh.m.cmd = ETHTOOL_GRXFHINDIR;
166 0 : rxfh.m.size = 0;
167 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXFHINDIR", &rxfh );
168 0 : uint const table_ele_cnt = rxfh.m.size;
169 0 : if( FD_UNLIKELY( (table_ele_cnt == 0) | (table_ele_cnt > FD_ETHTOOL_MAX_RXFH_TABLE_CNT) ) )
170 0 : return EINVAL;
171 :
172 : /* Set table to round robin over all channels from [start_idx, num_channels) */
173 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-rxfh-indir %s start %u equal %u`",
174 0 : ioc->ifr.ifr_name, start_idx, num_channels - start_idx ));
175 0 : rxfh.m.cmd = ETHTOOL_SRXFHINDIR;
176 0 : rxfh.m.size = table_ele_cnt;
177 0 : uint i = start_idx;
178 0 : for(uint j=0u; j<table_ele_cnt; j++) {
179 0 : rxfh.m.ring_index[ j ] = i++;
180 0 : if( i >= num_channels )
181 0 : i = start_idx;
182 0 : }
183 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXFHINDIR", &rxfh );
184 :
185 0 : return 0;
186 0 : }
187 :
188 : int
189 : fd_ethtool_ioctl_rxfh_get_table( fd_ethtool_ioctl_t * ioc,
190 : uint * table,
191 0 : uint * table_ele_cnt ) {
192 : /* Note: A simpler implementation of this would use ETHTOOL_GRXFHINDIR
193 : as we are only concerned with the indirection table and do not need
194 : the other information. However, it appears that the ice driver has
195 : a bugged implementation of this command. */
196 :
197 0 : union {
198 0 : struct ethtool_rxfh m;
199 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxfh, uint, FD_ETHTOOL_MAX_RXFH_TABLE_CNT ) + MAX_RXFH_KEY_SIZE ];
200 0 : } rxfh = { 0 };
201 :
202 : /* First get the count of the indirection table and hash key */
203 0 : rxfh.m.cmd = ETHTOOL_GRSSH;
204 0 : int ret = run_ioctl( ioc, "ETHTOOL_GRSSH", &rxfh, 1, 0 );
205 0 : if( FD_UNLIKELY( ret!=0 ) ) {
206 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
207 0 : *table_ele_cnt = 0;
208 0 : return 0;
209 0 : }
210 0 : return ret;
211 0 : }
212 0 : if( FD_UNLIKELY( (rxfh.m.indir_size == 0) | (rxfh.m.indir_size > FD_ETHTOOL_MAX_RXFH_TABLE_CNT) |
213 0 : (rxfh.m.key_size == 0) | (rxfh.m.key_size > MAX_RXFH_KEY_SIZE) ) )
214 0 : return EINVAL;
215 0 : *table_ele_cnt = rxfh.m.indir_size;
216 :
217 : /* Now get the table contents itself. We also get the key bytes. */
218 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRSSH", &rxfh );
219 0 : fd_memcpy( table, rxfh.m.rss_config, *table_ele_cnt * sizeof(uint) );
220 0 : return 0;
221 0 : }
222 :
223 : static int
224 : get_feature_idx( fd_ethtool_ioctl_t * ioc,
225 : char const * name,
226 0 : uint * feature_idx ) {
227 : /* Check size of features string set is not too large (prevent overflow) */
228 0 : union {
229 0 : struct ethtool_sset_info m;
230 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_sset_info, uint, 1 ) ];
231 0 : } esi = { .m = {
232 0 : .cmd = ETHTOOL_GSSET_INFO,
233 0 : .sset_mask = fd_ulong_mask_bit( ETH_SS_FEATURES )
234 0 : } };
235 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GSSET_INFO", &esi );
236 0 : if( FD_UNLIKELY( (esi.m.data[0] == 0) | (esi.m.data[0] > MAX_FEATURES) ) )
237 0 : return EINVAL;
238 :
239 : /* Get strings from features string set */
240 0 : union {
241 0 : struct ethtool_gstrings m;
242 0 : uchar _[ sizeof(struct ethtool_gstrings) + (MAX_FEATURES * ETH_GSTRING_LEN) ];
243 0 : } egs = { 0 };
244 0 : egs.m.cmd = ETHTOOL_GSTRINGS;
245 0 : egs.m.string_set = ETH_SS_FEATURES;
246 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GSTRINGS", &egs );
247 :
248 : /* Find index of matching string from the set */
249 0 : for( uint i=0U; i<egs.m.len; i++) {
250 0 : uchar const * gstring = egs.m.data + (i * ETH_GSTRING_LEN);
251 0 : if( 0==strncmp( (char const *)gstring, name, ETH_GSTRING_LEN ) ) {
252 0 : *feature_idx = i;
253 0 : return 0;
254 0 : }
255 0 : }
256 0 : return EINVAL;
257 0 : }
258 :
259 : int
260 : fd_ethtool_ioctl_feature_set( fd_ethtool_ioctl_t * ioc,
261 : char const * name,
262 0 : int enabled ) {
263 0 : uint feature_idx;
264 0 : if( FD_UNLIKELY( 0!=get_feature_idx( ioc, name, &feature_idx ) ) )
265 0 : return EINVAL;
266 :
267 0 : FD_LOG_NOTICE(( "RUN: `ethtool --features %s %s %s`",
268 0 : ioc->ifr.ifr_name, name, enabled ? "on" : "off" ));
269 0 : uint feature_block = feature_idx / 32u;
270 0 : uint feature_offset = feature_idx % 32u;
271 0 : union {
272 0 : struct ethtool_sfeatures m;
273 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_sfeatures, struct ethtool_set_features_block, MAX_FEATURES / 32u ) ];
274 0 : } esf = { 0 };
275 0 : esf.m.cmd = ETHTOOL_SFEATURES;
276 0 : esf.m.size = feature_block + 1;
277 0 : esf.m.features[ feature_block ].valid = fd_uint_mask_bit( (int)feature_offset );
278 0 : esf.m.features[ feature_block ].requested = enabled ? fd_uint_mask_bit( (int)feature_offset ) : 0;
279 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SFEATURES", &esf );
280 0 : return 0;
281 0 : }
282 :
283 : int
284 : fd_ethtool_ioctl_feature_test( fd_ethtool_ioctl_t * ioc,
285 : char const * name,
286 0 : int * enabled ) {
287 0 : uint feature_idx;
288 0 : if( FD_UNLIKELY( 0!=get_feature_idx( ioc, name, &feature_idx ) ) )
289 0 : return EINVAL;
290 :
291 0 : uint feature_block = feature_idx / 32u;
292 0 : uint feature_offset = feature_idx % 32u;
293 0 : union {
294 0 : struct ethtool_gfeatures m;
295 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_gfeatures, struct ethtool_get_features_block, MAX_FEATURES / 32u ) ];
296 0 : } egf = { 0 };
297 0 : egf.m.cmd = ETHTOOL_GFEATURES;
298 0 : egf.m.size = MAX_FEATURES / 32u;
299 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GFEATURES", &egf );
300 :
301 0 : *enabled = !!(egf.m.features[ feature_block ].active & fd_uint_mask_bit( (int)feature_offset ));
302 0 : return 0;
303 0 : }
304 :
305 : int
306 0 : fd_ethtool_ioctl_ntuple_clear( fd_ethtool_ioctl_t * ioc ) {
307 0 : union {
308 0 : struct ethtool_rxnfc m;
309 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxnfc, uint, MAX_NTUPLE_RULES ) ];
310 0 : } efc = { 0 };
311 :
312 : /* Get count of currently defined rules, return if none exist */
313 0 : efc.m.cmd = ETHTOOL_GRXCLSRLCNT;
314 0 : int ret = run_ioctl( ioc, "ETHTOOL_GRXCLSRLCNT", &efc, 1, 0 );
315 0 : if( FD_UNLIKELY( ret!=0 ) ) {
316 0 : if( ret==EOPNOTSUPP ) return 0;
317 0 : return ret;
318 0 : }
319 0 : uint const rule_cnt = efc.m.rule_cnt;
320 0 : if( FD_UNLIKELY( rule_cnt > MAX_NTUPLE_RULES ) )
321 0 : return EINVAL;
322 0 : if( rule_cnt == 0 )
323 0 : return 0;
324 :
325 : /* Get location indices of all rules */
326 0 : efc.m.cmd = ETHTOOL_GRXCLSRLALL;
327 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLALL", &efc );
328 :
329 : /* Delete all rules */
330 0 : for( uint i=0u; i<rule_cnt; i++) {
331 0 : FD_LOG_NOTICE(( "RUN: `ethtool --config-ntuple %s delete %u`", ioc->ifr.ifr_name, efc.m.rule_locs[ i ] ));
332 0 : struct ethtool_rxnfc del = {
333 0 : .cmd = ETHTOOL_SRXCLSRLDEL,
334 0 : .fs = { .location = efc.m.rule_locs[ i ] }
335 0 : };
336 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLDEL", &del );
337 0 : }
338 :
339 0 : return 0;
340 0 : }
341 :
342 : int
343 : fd_ethtool_ioctl_ntuple_set_udp_dport( fd_ethtool_ioctl_t * ioc,
344 : uint rule_idx,
345 : ushort dport,
346 0 : uint queue_idx ) {
347 : /* Note: Some drivers do not support RX_CLS_LOC_ANY (e.g. mlx5), and
348 : some drivers only support it (e.g. bnxt). So first we try with
349 : the explicit rule index and then again with the any location if
350 : the former failed. */
351 0 : FD_LOG_NOTICE(( "RUN: `ethtool --config-ntuple %s flow-type udp4 dst-port %hu queue %u`",
352 0 : ioc->ifr.ifr_name, dport, queue_idx ));
353 0 : struct ethtool_rxnfc efc = {
354 0 : .cmd = ETHTOOL_SRXCLSRLINS,
355 0 : .fs = {
356 0 : .flow_type = UDP_V4_FLOW,
357 0 : .h_u = { .udp_ip4_spec = { .pdst = fd_ushort_bswap( dport ) } },
358 0 : .m_u = { .udp_ip4_spec = { .pdst = 0xFFFF } },
359 0 : .ring_cookie = queue_idx,
360 0 : .location = rule_idx
361 0 : }
362 0 : };
363 0 : if( FD_LIKELY( 0==run_ioctl( ioc, "ETHTOOL_SRXCLSRLINS", &efc, 0, 0 ) ) )
364 0 : return 0;
365 0 : efc.fs.location = RX_CLS_LOC_ANY;
366 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLINS", &efc );
367 0 : return 0;
368 0 : }
369 :
370 : int
371 : fd_ethtool_ioctl_ntuple_validate_udp_dport( fd_ethtool_ioctl_t * ioc,
372 : ushort * dports,
373 : uint num_dports,
374 : uint queue_idx,
375 0 : int * valid ) {
376 0 : union {
377 0 : struct ethtool_rxnfc m;
378 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxnfc, uint, MAX_NTUPLE_RULES ) ];
379 0 : } efc = { 0 };
380 :
381 : /* Get count of currently defined rules */
382 0 : efc.m.cmd = ETHTOOL_GRXCLSRLCNT;
383 0 : int ret = run_ioctl( ioc, "ETHTOOL_GRXCLSRLCNT", &efc, 1, 0 );
384 0 : if( FD_UNLIKELY( ret!=0 ) ) {
385 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
386 0 : *valid = (num_dports == 0);
387 0 : return 0;
388 0 : }
389 0 : return ret;
390 0 : }
391 0 : uint const rule_cnt = efc.m.rule_cnt;
392 0 : if( FD_UNLIKELY( rule_cnt > MAX_NTUPLE_RULES ) )
393 0 : return EINVAL;
394 0 : if( rule_cnt == 0 ) {
395 0 : *valid = (num_dports == 0);
396 0 : return 0;
397 0 : }
398 0 : if( num_dports == 0 ) {
399 0 : *valid = 0;
400 0 : return 0;
401 0 : }
402 :
403 : /* Get location indices of all rules */
404 0 : efc.m.cmd = ETHTOOL_GRXCLSRLALL;
405 0 : efc.m.rule_cnt = rule_cnt;
406 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLALL", &efc );
407 :
408 : /* Loop over all rules, returning early if any are invalid */
409 0 : static const union ethtool_flow_union EXPECTED_MASK = { .udp_ip4_spec = { .pdst = 0xFFFF } };
410 0 : static const struct ethtool_flow_ext EXPECTED_EXT_MASK = { 0 };
411 0 : for( uint i=0u; i<efc.m.rule_cnt; i++) {
412 0 : struct ethtool_rxnfc get = {
413 0 : .cmd = ETHTOOL_GRXCLSRULE,
414 0 : .fs = { .location = efc.m.rule_locs[ i ] }
415 0 : };
416 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRULE", &get );
417 0 : if( FD_UNLIKELY( ((get.fs.flow_type != UDP_V4_FLOW) | (get.fs.ring_cookie != queue_idx)) ||
418 0 : 0!=memcmp( &get.fs.m_u, &EXPECTED_MASK, sizeof(EXPECTED_MASK) ) ||
419 0 : 0!=memcmp( &get.fs.m_ext, &EXPECTED_EXT_MASK, sizeof(EXPECTED_EXT_MASK)) ) ) {
420 0 : *valid = 0;
421 0 : return 0;
422 0 : }
423 : /* This is a valid udp rule, find the expected port(s) it matches or return error */
424 0 : int found = 0;
425 0 : for( uint j=0u; j<num_dports; j++) {
426 0 : if( dports[ j ] == fd_ushort_bswap( get.fs.h_u.udp_ip4_spec.pdst ) ) {
427 0 : dports[ j ] = 0u;
428 0 : found = 1;
429 0 : }
430 0 : }
431 0 : if( !found ) {
432 0 : *valid = 0;
433 0 : return 0;
434 0 : }
435 0 : }
436 :
437 : /* All rules are valid and matched expected ports. Lastly, check that
438 : no expected ports were missing */
439 0 : *valid = 1;
440 0 : for( uint i=0u; i<num_dports; i++)
441 0 : if( dports[ i ] != 0 )
442 0 : *valid = 0;
443 0 : return 0;
444 0 : }
|