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 : #include <netinet/in.h>
9 :
10 : #include "fd_ethtool_ioctl.h"
11 : #include "../../../../util/fd_util.h"
12 : #include "../../../../util/net/fd_ip4.h"
13 :
14 : #define MAX_RXFH_KEY_SIZE (1024)
15 0 : #define MAX_FEATURES (2048)
16 : #define MAX_NTUPLE_RULES (8192)
17 :
18 : #define ETHTOOL_CMD_SIZE( base_t, data_t, data_len ) ( sizeof(base_t) + (sizeof(data_t)*(data_len)) )
19 :
20 : static int
21 : run_ioctl( fd_ethtool_ioctl_t * ioc,
22 : char const * cmd,
23 : void * data,
24 : int log,
25 0 : int log_notsupp ) {
26 0 : ioc->ifr.ifr_data = data;
27 0 : if( FD_UNLIKELY( ioctl( ioc->fd, SIOCETHTOOL, &ioc->ifr ) ) ) {
28 0 : int error = errno;
29 0 : if( (!!log) & ((errno!=EOPNOTSUPP) | (!!log_notsupp)) )
30 0 : FD_LOG_WARNING(( "error configuring network device (%s), ioctl(SIOCETHTOOL,%s) failed (%i-%s)",
31 0 : ioc->ifr.ifr_name, cmd, errno, fd_io_strerror( errno ) ));
32 0 : return error;
33 0 : }
34 0 : return 0;
35 0 : }
36 :
37 : #define TRY_RUN_IOCTL( ioc, cmd, data ) \
38 0 : do { int __ret__ = run_ioctl( (ioc), (cmd), (data), 1, 1 ); \
39 0 : if( FD_UNLIKELY( __ret__ != 0 ) ) { return __ret__; } } while(0)
40 :
41 : fd_ethtool_ioctl_t *
42 : fd_ethtool_ioctl_init( fd_ethtool_ioctl_t * ioc,
43 0 : char const * device ) {
44 0 : if( FD_UNLIKELY( strlen( device ) >= IF_NAMESIZE ) ) {
45 0 : FD_LOG_WARNING(( "device name `%s` is too long", device ));
46 0 : return NULL;
47 0 : }
48 0 : if( FD_UNLIKELY( strlen( device ) == 0 ) ) {
49 0 : FD_LOG_WARNING(( "device name `%s` is empty", device ));
50 0 : return NULL;
51 0 : }
52 :
53 0 : ioc->fd = socket( AF_INET, SOCK_DGRAM, 0 );
54 0 : if( FD_UNLIKELY( ioc->fd < 0 ) ) {
55 0 : FD_LOG_WARNING(( "error configuring network device (%s), socket(AF_INET,SOCK_DGRAM,0) failed (%i-%s)",
56 0 : device, errno, fd_io_strerror( errno ) ));
57 0 : return NULL;
58 0 : }
59 :
60 0 : fd_memset( &ioc->ifr, 0, sizeof(struct ifreq) );
61 0 : fd_cstr_fini( fd_cstr_append_cstr( fd_cstr_init( ioc->ifr.ifr_name ), device ) );
62 :
63 0 : return ioc;
64 0 : }
65 :
66 : void
67 0 : fd_ethtool_ioctl_fini( fd_ethtool_ioctl_t * ioc ) {
68 0 : if( FD_UNLIKELY( close( ioc->fd ) ) ) {
69 0 : FD_LOG_WARNING(( "error configuring network device (%s), close() socket failed (%i-%s)",
70 0 : ioc->ifr.ifr_name, errno, fd_io_strerror( errno ) ));
71 0 : }
72 0 : ioc->fd = -1;
73 0 : fd_memset( &ioc->ifr, 0, sizeof(struct ifreq) );
74 0 : }
75 :
76 : int
77 : fd_ethtool_ioctl_channels_set_num( fd_ethtool_ioctl_t * ioc,
78 0 : uint num ) {
79 0 : struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
80 0 : int ret = run_ioctl( ioc, "ETHTOOL_GCHANNELS", &ech, 1, 0 );
81 0 : if( FD_UNLIKELY( ret!=0 ) ) {
82 0 : if( (ret==EOPNOTSUPP) & ((num==0) | (num==1))) return 0;
83 0 : return ret;
84 0 : }
85 :
86 0 : ech.cmd = ETHTOOL_SCHANNELS;
87 0 : if( num == 0 ) {
88 0 : uint max_queue_count = ech.max_combined ? ech.max_combined : ech.max_rx;
89 0 : num = fd_uint_min( max_queue_count, (uint)fd_shmem_cpu_cnt() );
90 0 : }
91 0 : if( ech.max_combined ) {
92 0 : ech.combined_count = num;
93 0 : ech.rx_count = 0;
94 0 : ech.tx_count = 0;
95 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-channels %s combined %u`", ioc->ifr.ifr_name, num ));
96 0 : } else {
97 0 : ech.combined_count = 0;
98 0 : ech.rx_count = num;
99 0 : ech.tx_count = num;
100 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-channels %s rx %u tx %u`", ioc->ifr.ifr_name, num, num ));
101 0 : }
102 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SCHANNELS", &ech );
103 0 : return 0;
104 0 : }
105 :
106 : int
107 : fd_ethtool_ioctl_channels_get_num( fd_ethtool_ioctl_t * ioc,
108 0 : fd_ethtool_ioctl_channels_t * channels ) {
109 0 : struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
110 0 : int ret = run_ioctl( ioc, "ETHTOOL_GCHANNELS", &ech, 1, 0 );
111 0 : if( FD_UNLIKELY( ret!=0 ) ) {
112 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
113 : /* network device doesn't support getting number of channels, so
114 : it must always be 1 */
115 0 : channels->supported = 0;
116 0 : channels->current = 1;
117 0 : channels->max = 1;
118 0 : return 0;
119 0 : }
120 0 : return ret;
121 0 : }
122 0 : channels->supported = 1;
123 :
124 0 : if( FD_LIKELY( ech.combined_count ) ) {
125 0 : channels->current = ech.combined_count;
126 0 : channels->max = fd_uint_min( ech.max_combined, (uint)fd_shmem_cpu_cnt() );
127 0 : return 0;
128 0 : }
129 0 : if( ech.rx_count || ech.tx_count ) {
130 0 : if( FD_UNLIKELY( ech.rx_count != ech.tx_count ) )
131 0 : FD_LOG_WARNING(( "device `%s` has unbalanced channel count: (got %u rx, %u tx)",
132 0 : ioc->ifr.ifr_name, ech.rx_count, ech.tx_count ));
133 0 : channels->current = ech.rx_count;
134 0 : channels->max = fd_uint_min( ech.max_rx, (uint)fd_shmem_cpu_cnt() );
135 0 : return 0;
136 0 : }
137 0 : return EINVAL;
138 0 : }
139 :
140 : int
141 0 : fd_ethtool_ioctl_rxfh_set_default( fd_ethtool_ioctl_t * ioc ) {
142 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-rxfh-indir %s default`", ioc->ifr.ifr_name ));
143 0 : struct ethtool_rxfh_indir rxfh = {
144 0 : .cmd = ETHTOOL_SRXFHINDIR,
145 0 : .size = 0, /* default indirection table */
146 0 : };
147 0 : int ret = run_ioctl( ioc, "ETHTOOL_SRXFHINDIR", &rxfh, 1, 0 );
148 0 : if( FD_UNLIKELY( ret==EOPNOTSUPP ) ) return 0;
149 0 : return ret;
150 0 : }
151 :
152 : int
153 : fd_ethtool_ioctl_rxfh_set_suffix( fd_ethtool_ioctl_t * ioc,
154 0 : uint start_idx ) {
155 : /* Get current channel count */
156 0 : struct ethtool_channels ech = { .cmd = ETHTOOL_GCHANNELS };
157 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GCHANNELS", &ech );
158 0 : uint const channels_cnt = ech.combined_count ? ech.combined_count : ech.rx_count;
159 :
160 : /* Get current RXFH queue count
161 :
162 : Note: One would expect that ethtool can always configure the RXFH
163 : indirection table to target all channels / queues supported by the
164 : device. This is not the case. Some drivers limit the max queue
165 : index in the table to less than the current channel count. For
166 : example, see ixgbe_rss_indir_tbl_max(). */
167 0 : struct ethtool_rxnfc nfc = { .cmd = ETHTOOL_GRXRINGS };
168 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXRINGS", &nfc );
169 0 : uint const queue_cnt = (uint)nfc.data;
170 0 : if( FD_UNLIKELY( start_idx>=queue_cnt || queue_cnt>channels_cnt ) ) return EINVAL;
171 :
172 0 : union {
173 0 : struct ethtool_rxfh_indir m;
174 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxfh_indir, uint, FD_ETHTOOL_MAX_RXFH_TABLE_CNT ) ];
175 0 : } rxfh = { 0 };
176 :
177 : /* Get count of rx indirection table */
178 0 : rxfh.m.cmd = ETHTOOL_GRXFHINDIR;
179 0 : rxfh.m.size = 0;
180 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXFHINDIR", &rxfh );
181 0 : uint const table_ele_cnt = rxfh.m.size;
182 0 : if( FD_UNLIKELY( (table_ele_cnt == 0) | (table_ele_cnt > FD_ETHTOOL_MAX_RXFH_TABLE_CNT) ) )
183 0 : return EINVAL;
184 :
185 : /* Set table to round robin over all channels from [start_idx, queue_cnt) */
186 0 : FD_LOG_NOTICE(( "RUN: `ethtool --set-rxfh-indir %s start %u equal %u`",
187 0 : ioc->ifr.ifr_name, start_idx, queue_cnt - start_idx ));
188 0 : rxfh.m.cmd = ETHTOOL_SRXFHINDIR;
189 0 : rxfh.m.size = table_ele_cnt;
190 0 : for( uint j=0u, q=start_idx; j<table_ele_cnt; j++ ) {
191 0 : rxfh.m.ring_index[ j ] = q++;
192 0 : if( FD_UNLIKELY( q>=queue_cnt ) ) q = start_idx;
193 0 : }
194 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXFHINDIR", &rxfh );
195 :
196 0 : return 0;
197 0 : }
198 :
199 : int
200 : fd_ethtool_ioctl_rxfh_get_queue_cnt( fd_ethtool_ioctl_t * ioc,
201 : uint * queue_cnt )
202 0 : {
203 0 : struct ethtool_rxnfc nfc = { .cmd = ETHTOOL_GRXRINGS };
204 0 : int ret = run_ioctl( ioc, "ETHTOOL_GRXRINGS", &nfc, 1, 0 );
205 0 : if( FD_UNLIKELY( ret!=0 ) ) {
206 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
207 0 : *queue_cnt = 1;
208 0 : return 0;
209 0 : }
210 0 : return ret;
211 0 : }
212 0 : *queue_cnt = (uint)nfc.data;
213 0 : FD_TEST( *queue_cnt>0U );
214 0 : return 0;
215 0 : }
216 :
217 : int
218 : fd_ethtool_ioctl_rxfh_get_table( fd_ethtool_ioctl_t * ioc,
219 : uint * table,
220 0 : uint * table_ele_cnt ) {
221 : /* Note: A simpler implementation of this would use ETHTOOL_GRXFHINDIR
222 : as we are only concerned with the indirection table and do not need
223 : the other information. However, it appears that the ice driver has
224 : a bugged implementation of this command. */
225 :
226 0 : union {
227 0 : struct ethtool_rxfh m;
228 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxfh, uint, FD_ETHTOOL_MAX_RXFH_TABLE_CNT ) + MAX_RXFH_KEY_SIZE ];
229 0 : } rxfh = { 0 };
230 :
231 : /* First get the count of the indirection table and hash key */
232 0 : rxfh.m.cmd = ETHTOOL_GRSSH;
233 0 : int ret = run_ioctl( ioc, "ETHTOOL_GRSSH", &rxfh, 1, 0 );
234 0 : if( FD_UNLIKELY( ret!=0 ) ) {
235 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
236 0 : *table_ele_cnt = 0;
237 0 : return 0;
238 0 : }
239 0 : return ret;
240 0 : }
241 0 : if( FD_UNLIKELY( (rxfh.m.indir_size > FD_ETHTOOL_MAX_RXFH_TABLE_CNT) |
242 0 : (rxfh.m.key_size > MAX_RXFH_KEY_SIZE) ) )
243 0 : return EINVAL;
244 0 : *table_ele_cnt = rxfh.m.indir_size;
245 :
246 0 : if( 0!=*table_ele_cnt ) {
247 : /* Now get the table contents itself. We also get the key bytes. */
248 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRSSH", &rxfh );
249 0 : fd_memcpy( table, rxfh.m.rss_config, *table_ele_cnt * sizeof(uint) );
250 0 : }
251 0 : return 0;
252 0 : }
253 :
254 : static int
255 : get_feature_idx( fd_ethtool_ioctl_t * ioc,
256 : char const * name,
257 : uint * feature_idx,
258 0 : uint * feature_cnt ) {
259 : /* Check size of features string set is not too large (prevent overflow) */
260 0 : union {
261 0 : struct ethtool_sset_info m;
262 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_sset_info, uint, 1 ) ];
263 0 : } esi = { .m = {
264 0 : .cmd = ETHTOOL_GSSET_INFO,
265 0 : .sset_mask = fd_ulong_mask_bit( ETH_SS_FEATURES )
266 0 : } };
267 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GSSET_INFO", &esi );
268 0 : if( FD_UNLIKELY( (esi.m.data[0] == 0) | (esi.m.data[0] > MAX_FEATURES) ) )
269 0 : return EINVAL;
270 0 : *feature_cnt = esi.m.data[0];
271 :
272 : /* Get strings from features string set */
273 0 : union {
274 0 : struct ethtool_gstrings m;
275 0 : uchar _[ sizeof(struct ethtool_gstrings) + (MAX_FEATURES * ETH_GSTRING_LEN) ];
276 0 : } egs = { 0 };
277 0 : egs.m.cmd = ETHTOOL_GSTRINGS;
278 0 : egs.m.string_set = ETH_SS_FEATURES;
279 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GSTRINGS", &egs );
280 :
281 : /* Find index of matching string from the set */
282 0 : for( uint i=0U; i<egs.m.len; i++) {
283 0 : uchar const * gstring = egs.m.data + (i * ETH_GSTRING_LEN);
284 0 : if( 0==strncmp( (char const *)gstring, name, ETH_GSTRING_LEN ) ) {
285 0 : *feature_idx = i;
286 0 : return 0;
287 0 : }
288 0 : }
289 0 : return -1;
290 0 : }
291 :
292 : int
293 : fd_ethtool_ioctl_feature_set( fd_ethtool_ioctl_t * ioc,
294 : char const * name,
295 0 : int enabled ) {
296 0 : uint feature_idx;
297 0 : uint feature_cnt;
298 0 : int ret = get_feature_idx( ioc, name, &feature_idx, &feature_cnt );
299 0 : if( FD_UNLIKELY( ret!=0 ) ) {
300 0 : if( (ret==-1) & (!enabled) ) return 0;
301 0 : return EINVAL;
302 0 : }
303 0 : uint feature_block = feature_idx / 32U;
304 0 : uint feature_offset = feature_idx % 32U;
305 :
306 0 : union {
307 0 : struct ethtool_gfeatures m;
308 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_gfeatures, struct ethtool_get_features_block, MAX_FEATURES / 32U ) ];
309 0 : } egf = { 0 };
310 0 : egf.m.cmd = ETHTOOL_GFEATURES;
311 0 : egf.m.size = MAX_FEATURES / 32U;
312 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GFEATURES", &egf );
313 0 : if( enabled == !!(egf.m.features[ feature_block ].active & fd_uint_mask_bit( (int)feature_offset )) )
314 0 : return 0;
315 :
316 0 : FD_LOG_NOTICE(( "RUN: `ethtool --features %s %s %s`",
317 0 : ioc->ifr.ifr_name, name, enabled ? "on" : "off" ));
318 0 : union {
319 0 : struct ethtool_sfeatures m;
320 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_sfeatures, struct ethtool_set_features_block, MAX_FEATURES / 32U ) ];
321 0 : } esf = { 0 };
322 0 : esf.m.cmd = ETHTOOL_SFEATURES;
323 0 : esf.m.size = fd_uint_align_up( feature_cnt, 32U ) / 32U;
324 0 : esf.m.features[ feature_block ].valid = fd_uint_mask_bit( (int)feature_offset );
325 0 : esf.m.features[ feature_block ].requested = enabled ? fd_uint_mask_bit( (int)feature_offset ) : 0;
326 :
327 : /* Note: ETHTOOL_SFEATURES has special behavior where it returns a
328 : positive nonzero number with flags set for specific things.
329 : ETHTOOL_F_UNSUPPORTED is set if the feature is not able to be
330 : changed, i.e. it is forever fixed on or fixed off. */
331 0 : ioc->ifr.ifr_data = &esf;
332 0 : ret = ioctl( ioc->fd, SIOCETHTOOL, &ioc->ifr );
333 0 : if( FD_UNLIKELY( ret < 0 ) ) {
334 0 : int error = errno;
335 0 : FD_LOG_WARNING(( "error configuring network device (%s), ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%i-%s)",
336 0 : ioc->ifr.ifr_name, errno, fd_io_strerror( errno ) ));
337 0 : return error;
338 0 : }
339 0 : if( FD_UNLIKELY( ret==ETHTOOL_F_UNSUPPORTED ) ) {
340 0 : FD_LOG_WARNING(( "error configuring network device (%s), unable to change fixed feature (%s)",
341 0 : ioc->ifr.ifr_name, name ));
342 0 : return EINVAL;
343 0 : }
344 0 : if( FD_UNLIKELY( ret!=0 ) ) {
345 0 : FD_LOG_WARNING(( "error configuring network device (%s), ioctl(SIOCETHTOOL,ETHTOOL_SFEATURES) failed (%d)",
346 0 : ioc->ifr.ifr_name, ret ));
347 0 : return EINVAL;
348 0 : }
349 0 : return 0;
350 0 : }
351 :
352 : int
353 : fd_ethtool_ioctl_feature_test( fd_ethtool_ioctl_t * ioc,
354 : char const * name,
355 0 : int * enabled ) {
356 0 : uint feature_idx;
357 0 : uint feature_cnt;
358 0 : int ret = get_feature_idx( ioc, name, &feature_idx, &feature_cnt );
359 0 : if( FD_UNLIKELY( ret!=0 ) ) {
360 0 : if( ret==-1 ) {
361 0 : *enabled = 0;
362 0 : return 0;
363 0 : }
364 0 : return EINVAL;
365 0 : }
366 0 : uint feature_block = feature_idx / 32U;
367 0 : uint feature_offset = feature_idx % 32U;
368 :
369 0 : union {
370 0 : struct ethtool_gfeatures m;
371 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_gfeatures, struct ethtool_get_features_block, MAX_FEATURES / 32U ) ];
372 0 : } egf = { 0 };
373 0 : egf.m.cmd = ETHTOOL_GFEATURES;
374 0 : egf.m.size = MAX_FEATURES / 32U;
375 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GFEATURES", &egf );
376 :
377 0 : *enabled = !!(egf.m.features[ feature_block ].active & fd_uint_mask_bit( (int)feature_offset ));
378 0 : return 0;
379 0 : }
380 :
381 : int
382 : fd_ethtool_ioctl_feature_gro_set( fd_ethtool_ioctl_t * ioc,
383 0 : int enabled ) {
384 0 : FD_LOG_NOTICE(( "RUN: `ethtool --offload %s generic-receive-offload %s`",
385 0 : ioc->ifr.ifr_name, enabled ? "on" : "off" ));
386 0 : struct ethtool_value gro = {
387 0 : .cmd = ETHTOOL_SGRO,
388 0 : .data = !!enabled
389 0 : };
390 0 : int ret = run_ioctl( ioc, "ETHTOOL_SGRO", &gro, 1, 0 );
391 0 : if( FD_UNLIKELY( (ret==EOPNOTSUPP) & (!enabled) ) ) return 0;
392 0 : return ret;
393 0 : }
394 :
395 : int
396 : fd_ethtool_ioctl_feature_gro_test( fd_ethtool_ioctl_t * ioc,
397 : int * enabled,
398 0 : int * supported ) {
399 0 : struct ethtool_value gro = { .cmd = ETHTOOL_GGRO };
400 0 : int ret = run_ioctl( ioc, "ETHTOOL_GGRO", &gro, 1, 0 );
401 0 : *supported = (ret!=EOPNOTSUPP);
402 0 : if( FD_UNLIKELY( ret!=0 ) ) {
403 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
404 0 : *enabled = 0;
405 0 : return 0;
406 0 : }
407 0 : return ret;
408 0 : }
409 0 : *enabled = !!gro.data;
410 0 : return 0;
411 0 : }
412 :
413 : int
414 0 : fd_ethtool_ioctl_ntuple_clear( fd_ethtool_ioctl_t * ioc ) {
415 0 : union {
416 0 : struct ethtool_rxnfc m;
417 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxnfc, uint, MAX_NTUPLE_RULES ) ];
418 0 : } efc = { 0 };
419 :
420 : /* Get count of currently defined rules, return if none exist */
421 0 : efc.m.cmd = ETHTOOL_GRXCLSRLCNT;
422 0 : int ret = run_ioctl( ioc, "ETHTOOL_GRXCLSRLCNT", &efc, 1, 0 );
423 0 : if( FD_UNLIKELY( ret!=0 ) ) {
424 0 : if( ret==EOPNOTSUPP ) return 0;
425 0 : return ret;
426 0 : }
427 0 : uint const rule_cnt = efc.m.rule_cnt;
428 0 : if( FD_UNLIKELY( rule_cnt > MAX_NTUPLE_RULES ) )
429 0 : return EINVAL;
430 0 : if( rule_cnt == 0 )
431 0 : return 0;
432 :
433 : /* Get location indices of all rules */
434 0 : efc.m.cmd = ETHTOOL_GRXCLSRLALL;
435 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLALL", &efc );
436 :
437 : /* Delete all rules */
438 0 : for( uint i=0u; i<rule_cnt; i++) {
439 0 : FD_LOG_NOTICE(( "RUN: `ethtool --config-ntuple %s delete %u`", ioc->ifr.ifr_name, efc.m.rule_locs[ i ] ));
440 0 : struct ethtool_rxnfc del = {
441 0 : .cmd = ETHTOOL_SRXCLSRLDEL,
442 0 : .fs = { .location = efc.m.rule_locs[ i ] }
443 0 : };
444 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLDEL", &del );
445 0 : }
446 :
447 0 : return 0;
448 0 : }
449 :
450 : int
451 : fd_ethtool_ioctl_ntuple_set_udp_dport( fd_ethtool_ioctl_t * ioc,
452 : uint rule_idx,
453 : ushort dport,
454 : uint rule_group_idx,
455 : uint rule_group_cnt,
456 0 : uint queue_idx ) {
457 : /* Note: Most drivers do not support RX_CLS_LOC_ANY, instead requiring
458 : an explicit location index. However, some drivers require it
459 : (e.g. bnxt). We can tell based on the RX_CLS_LOC_SPECIAL flag. */
460 0 : struct ethtool_rxnfc get = { .cmd = ETHTOOL_GRXCLSRLCNT };
461 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLCNT", &get );
462 0 : int const any_location = !!(get.data & RX_CLS_LOC_SPECIAL);
463 :
464 0 : FD_TEST( rule_group_idx<rule_group_cnt && fd_uint_is_pow2( rule_group_cnt ) && rule_group_cnt>0U );
465 0 : uint const src_addr = fd_uint_bswap( rule_group_idx );
466 0 : uint const src_addr_mask = fd_uint_bswap( rule_group_cnt-1U );
467 0 : FD_LOG_NOTICE(( "RUN: `ethtool --config-ntuple %s flow-type udp4 dst-port %hu src-ip "
468 0 : FD_IP4_ADDR_FMT " m " FD_IP4_ADDR_FMT " queue %u`",
469 0 : ioc->ifr.ifr_name, dport, FD_IP4_ADDR_FMT_ARGS( src_addr ),
470 0 : FD_IP4_ADDR_FMT_ARGS( ~src_addr_mask ), queue_idx ));
471 0 : struct ethtool_rxnfc efc = {
472 0 : .cmd = ETHTOOL_SRXCLSRLINS,
473 0 : .fs = {
474 0 : .flow_type = UDP_V4_FLOW,
475 0 : .h_u = { .udp_ip4_spec = {
476 0 : .pdst = fd_ushort_bswap( dport ),
477 0 : .ip4src = src_addr } },
478 0 : .m_u = { .udp_ip4_spec = {
479 0 : .pdst = 0xFFFF,
480 0 : .ip4src = src_addr_mask } },
481 0 : .ring_cookie = queue_idx,
482 0 : .location = any_location ? RX_CLS_LOC_ANY : rule_idx,
483 0 : }
484 0 : };
485 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLINS", &efc );
486 0 : return 0;
487 0 : }
488 :
489 : int
490 : fd_ethtool_ioctl_ntuple_set_gre( fd_ethtool_ioctl_t * ioc,
491 : uint rule_idx,
492 0 : uint queue_idx ) {
493 : /* See above for any_location */
494 0 : struct ethtool_rxnfc get = { .cmd = ETHTOOL_GRXCLSRLCNT };
495 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLCNT", &get );
496 0 : int const any_location = !!(get.data & RX_CLS_LOC_SPECIAL);
497 :
498 0 : FD_LOG_NOTICE(( "RUN: `ethtool --config-ntuple %s flow-type ip4 l4proto 47 queue %u`",
499 0 : ioc->ifr.ifr_name, queue_idx ));
500 0 : struct ethtool_rxnfc efc = {
501 0 : .cmd = ETHTOOL_SRXCLSRLINS,
502 0 : .fs = {
503 0 : .flow_type = IPV4_USER_FLOW,
504 0 : .h_u = { .usr_ip4_spec = {
505 0 : .ip_ver = ETH_RX_NFC_IP4,
506 0 : .proto = IPPROTO_GRE } },
507 0 : .m_u = { .usr_ip4_spec = {
508 0 : .ip_ver = 0xFF,
509 0 : .proto = 0xFF } },
510 0 : .ring_cookie = queue_idx,
511 0 : .location = any_location ? RX_CLS_LOC_ANY : rule_idx,
512 0 : }
513 0 : };
514 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXCLSRLINS", &efc );
515 0 : return 0;
516 0 : }
517 :
518 : int
519 : fd_ethtool_ioctl_ntuple_validate( fd_ethtool_ioctl_t * ioc,
520 : ushort const * dports,
521 : uint dports_cnt,
522 : uint queue_cnt,
523 : uint gre_queue,
524 0 : int * valid ) {
525 0 : union {
526 0 : struct ethtool_rxnfc m;
527 0 : uchar _[ ETHTOOL_CMD_SIZE( struct ethtool_rxnfc, uint, MAX_NTUPLE_RULES ) ];
528 0 : } efc = { 0 };
529 :
530 0 : int gre_rule = (gre_queue!=UINT_MAX);
531 :
532 : /* Get count of currently defined rules */
533 0 : efc.m.cmd = ETHTOOL_GRXCLSRLCNT;
534 0 : int ret = run_ioctl( ioc, "ETHTOOL_GRXCLSRLCNT", &efc, 1, 0 );
535 0 : if( FD_UNLIKELY( ret!=0 ) ) {
536 0 : if( FD_LIKELY( ret==EOPNOTSUPP ) ) {
537 0 : *valid = (dports_cnt==0U) & !gre_rule;
538 0 : return 0;
539 0 : }
540 0 : return ret;
541 0 : }
542 0 : uint const rule_cnt = efc.m.rule_cnt;
543 0 : if( FD_UNLIKELY( rule_cnt>MAX_NTUPLE_RULES ) )
544 0 : return EINVAL;
545 0 : if( (dports_cnt==0U) & !gre_rule ) {
546 0 : *valid = (rule_cnt==0U);
547 0 : return 0;
548 0 : }
549 0 : FD_TEST( queue_cnt>0U );
550 0 : uint const rule_group_cnt = fd_uint_pow2_up( queue_cnt );
551 0 : if( rule_cnt!=(dports_cnt*rule_group_cnt + (uint)gre_rule) ) {
552 0 : *valid = 0;
553 0 : return 0;
554 0 : }
555 :
556 0 : ushort expected[ MAX_NTUPLE_RULES ] = { 0 };
557 0 : int found_gre = 0;
558 0 : for( uint r=0U; r<rule_group_cnt; r++ ) {
559 0 : for( uint p=0U; p<dports_cnt; p++ ) {
560 0 : expected[ (r*dports_cnt)+p ] = dports[ p ];
561 0 : }
562 0 : }
563 :
564 : /* Get location indices of all rules */
565 0 : efc.m.cmd = ETHTOOL_GRXCLSRLALL;
566 0 : efc.m.rule_cnt = rule_cnt;
567 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRLALL", &efc );
568 :
569 : /* Loop over all rules, returning early if any are invalid */
570 0 : static const struct ethtool_flow_ext EXPECTED_EXT_MASK = { 0 };
571 0 : const union ethtool_flow_union expected_mask = { .udp_ip4_spec = {
572 0 : .pdst = 0xFFFF,
573 0 : .ip4src = fd_uint_bswap( rule_group_cnt-1U ),
574 0 : } };
575 0 : static const union ethtool_flow_union expected_gre_mask = { .usr_ip4_spec = {
576 0 : .ip_ver = 0xFF,
577 0 : .proto = 0xFF
578 0 : } };
579 0 : for( uint i=0u; i<efc.m.rule_cnt; i++) {
580 0 : struct ethtool_rxnfc get = {
581 0 : .cmd = ETHTOOL_GRXCLSRULE,
582 0 : .fs = { .location = efc.m.rule_locs[ i ] }
583 0 : };
584 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_GRXCLSRULE", &get );
585 0 : uint flow_type = get.fs.flow_type & ~(uint)FLOW_RSS & ~(uint)FLOW_EXT & ~(uint)FLOW_MAC_EXT;
586 0 : uint rule_group_idx = fd_uint_bswap( get.fs.h_u.udp_ip4_spec.ip4src );
587 0 : ushort dport = fd_ushort_bswap( get.fs.h_u.udp_ip4_spec.pdst );
588 :
589 : /* Is this the GRE rule? */
590 0 : if( FD_UNLIKELY( ((!found_gre) & (flow_type==IPV4_USER_FLOW) & (get.fs.ring_cookie==gre_queue) &
591 0 : (get.fs.h_u.usr_ip4_spec.proto==IPPROTO_GRE))&&
592 0 : !memcmp( &get.fs.m_u, &expected_gre_mask, sizeof(expected_gre_mask) ) &&
593 0 : !memcmp( &get.fs.m_ext, &EXPECTED_EXT_MASK, sizeof(EXPECTED_EXT_MASK)) ) ) {
594 0 : found_gre = 1;
595 0 : continue;
596 0 : } else if( FD_UNLIKELY( ((flow_type!=UDP_V4_FLOW) | (rule_group_idx>=rule_group_cnt) |
597 0 : (get.fs.ring_cookie!=(rule_group_idx%queue_cnt)) | (dport==0)) ||
598 0 : 0!=memcmp( &get.fs.m_u, &expected_mask, sizeof(expected_mask) ) ||
599 0 : 0!=memcmp( &get.fs.m_ext, &EXPECTED_EXT_MASK, sizeof(EXPECTED_EXT_MASK)) ) ) {
600 0 : *valid = 0;
601 0 : return 0;
602 0 : }
603 : /* This is a valid udp rule, find the expected port it matches in
604 : the given rule group, or return an error */
605 0 : int found = 0;
606 0 : for( uint j=0u; j<dports_cnt; j++) {
607 0 : if( expected[ (rule_group_idx*dports_cnt)+j ] == dport ) {
608 0 : expected[ (rule_group_idx*dports_cnt)+j ] = 0U;
609 0 : found = 1;
610 0 : break;
611 0 : }
612 0 : }
613 0 : if( !found ) {
614 0 : *valid = 0;
615 0 : return 0;
616 0 : }
617 0 : }
618 :
619 : /* All UDP rules are valid and matched expected ports. It's fully
620 : valid if GRE matched. */
621 0 : *valid = found_gre==gre_rule;
622 0 : return 0;
623 0 : }
624 :
625 : int
626 0 : fd_ethtool_ioctl_rxfh_set_flow_hash_udp4( fd_ethtool_ioctl_t * ioc ) {
627 0 : FD_LOG_NOTICE(( "RUN: `ethtool --config-nfc %s rx-flow-hash udp4 sdfn`", ioc->ifr.ifr_name ));
628 0 : struct ethtool_rxnfc nfc = {
629 0 : .cmd = ETHTOOL_SRXFH,
630 0 : .flow_type = UDP_V4_FLOW,
631 0 : .data = RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3,
632 0 : };
633 0 : TRY_RUN_IOCTL( ioc, "ETHTOOL_SRXFH", &nfc );
634 0 : return 0;
635 0 : }
|