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