Line data Source code
1 : #include "fd_config_auto.h"
2 : #include "../../disco/net/fd_linux_bond.h"
3 : #include "../../disco/net/fd_net_tile.h"
4 : #include "../../disco/net/mlx5/fd_mlx5.h"
5 : #include "../../disco/netlink/fd_netlink_tile.h"
6 : #include "../../waltz/mib/fd_netdev_netlink.h"
7 :
8 : #include <stdlib.h> /* strtoul */
9 : #include <limits.h>
10 : #include <errno.h>
11 : #include <string.h>
12 : #include <unistd.h>
13 : #include <sys/utsname.h>
14 : #include <linux/if_arp.h>
15 :
16 162 : #define PROVIDER_CNT ( 2UL)
17 432 : #define FEAT_CNT_MAX (16UL)
18 333 : #define NET_DRIVER_CNT_MAX (16UL)
19 :
20 : struct fd_auto_info {
21 : /* System */
22 : ulong linux_major;
23 : ulong linux_minor;
24 :
25 : /* Networking */
26 : char driver[ NAME_SZ ];
27 : uint is_virtual_if:1;
28 : uint is_bonded_if:1;
29 : uint is_lacp_if:1;
30 : uint bonded_if_slave_count;
31 : int is_using_gre;
32 : int has_mlx5_rdma_port;
33 : int has_uverbs;
34 : };
35 : typedef struct fd_auto_info fd_auto_info_t;
36 :
37 : struct fd_auto_driver_rq {
38 : char const * name;
39 : ulong min_linux_major;
40 : ulong min_linux_minor;
41 :
42 : /* 0 = no max linux version */
43 : ulong max_linux_major;
44 : ulong max_linux_minor;
45 : };
46 : typedef struct fd_auto_driver_rq fd_auto_driver_rq_t;
47 :
48 : struct fd_auto_feat {
49 : char const * name;
50 : /* supported_drivers is for unconditional driver requirements.
51 : For conditional driver requirements leave this empty and call
52 : fd_auto_check_driver() inside the check() callback
53 : (like xdp_rss_queue_mode_check() does). */
54 : fd_auto_driver_rq_t supported_drivers[ NET_DRIVER_CNT_MAX ];
55 :
56 : /* is_feat_auto() checks if feature is auto and sets the default */
57 : int (*is_feat_auto)( fd_config_t * config );
58 : int (*check )( fd_config_t const * config,
59 : fd_auto_info_t const * info );
60 : void (*apply )( fd_config_t * config );
61 : };
62 : typedef struct fd_auto_feat fd_auto_feat_t;
63 :
64 : struct fd_auto_provider {
65 : char const * name;
66 : /* Later features observe earlier features updates to config,
67 : so the order is important. e.g. ZC checks xdp_mode is DRV
68 : so DRV needs to come before ZC in the feats ordering. */
69 : fd_auto_feat_t feats [ FEAT_CNT_MAX ];
70 : fd_auto_driver_rq_t supported_drivers[ NET_DRIVER_CNT_MAX ];
71 :
72 : int (*check)( fd_config_t const * config,
73 : fd_auto_info_t const * info );
74 : void (*apply)( fd_config_t * config );
75 : };
76 : typedef struct fd_auto_provider fd_auto_provider_t;
77 :
78 : static int
79 : fd_auto_check_driver( fd_auto_info_t const * info,
80 273 : fd_auto_driver_rq_t const * supported_drivers ) {
81 : /* Check for driver requirements. */
82 273 : if( !supported_drivers[0].name ) return 1;
83 :
84 333 : for( ulong i=0UL; i<NET_DRIVER_CNT_MAX; i++ ) {
85 333 : fd_auto_driver_rq_t const * req = &supported_drivers[ i ];
86 333 : if( !req->name ) return 0;
87 :
88 282 : if( 0==strcmp( info->driver, req->name ) ) {
89 : /* Check system linux version is within bounds of min and max
90 : of what this driver req requires. */
91 186 : if( req->max_linux_major > 0UL ) {
92 33 : if( info->linux_major>req->max_linux_major ||
93 33 : ( info->linux_major==req->max_linux_major &&
94 27 : info->linux_minor>req->max_linux_minor ) ) continue;
95 33 : }
96 :
97 159 : if( info->linux_major>req->min_linux_major ||
98 159 : ( info->linux_major==req->min_linux_major &&
99 129 : info->linux_minor>=req->min_linux_minor ) ) return 1;
100 159 : }
101 282 : }
102 :
103 0 : return 0;
104 180 : }
105 :
106 : /* XDP checks/apply */
107 :
108 : static int
109 : xdp_check( fd_config_t const * config,
110 15 : fd_auto_info_t const * info FD_PARAM_UNUSED ) {
111 15 : if( strcmp( config->net.provider, "auto" ) ) return 0;
112 15 : return 1;
113 15 : }
114 :
115 : static void
116 15 : xdp_apply( fd_config_t * config ) {
117 15 : fd_memcpy( config->net.provider, "xdp", 4 );
118 15 : }
119 :
120 : static int
121 54 : is_xdp_listen_gre_auto( fd_config_t * config ) {
122 54 : if( 2!=config->net.xdp.listen_gre ) return 0;
123 : /* Set to default */
124 51 : config->net.xdp.listen_gre = 0;
125 51 : return 1;
126 54 : }
127 :
128 : static int
129 : xdp_listen_gre_check( fd_config_t const * config FD_PARAM_UNUSED,
130 39 : fd_auto_info_t const * info ) {
131 39 : return info->is_using_gre;
132 39 : }
133 :
134 : static void
135 6 : xdp_listen_gre_apply( fd_config_t * config ) {
136 6 : config->net.xdp.listen_gre = 1;
137 6 : }
138 :
139 : static int
140 54 : is_xdp_rss_queue_mode_auto( fd_config_t * config ) {
141 54 : if( strcmp( config->net.xdp.rss_queue_mode, "auto" ) ) return 0;
142 : /* Set to default */
143 51 : fd_memcpy( config->net.xdp.rss_queue_mode, "simple", 7 );
144 51 : return 1;
145 54 : }
146 :
147 : static int
148 : xdp_rss_queue_mode_check( fd_config_t const * config,
149 39 : fd_auto_info_t const * info ) {
150 39 : if( 1==config->net.xdp.listen_gre ) {
151 6 : static fd_auto_driver_rq_t const gre_rss_drivers[ NET_DRIVER_CNT_MAX ] = {
152 6 : { "mlx5_core", 0, 0, 0, 0 }
153 6 : };
154 :
155 6 : if( 0==fd_auto_check_driver( info, gre_rss_drivers ) ) return 0;
156 6 : }
157 36 : return 1;
158 39 : }
159 :
160 : static void
161 36 : xdp_rss_queue_mode_apply( fd_config_t * config ) {
162 36 : fd_memcpy( config->net.xdp.rss_queue_mode, "auto", 5 );
163 36 : }
164 :
165 : static int
166 54 : is_xdp_native_bond_auto( fd_config_t * config ) {
167 54 : if( 2!=config->net.xdp.native_bond ) return 0;
168 : /* Set to default */
169 54 : config->net.xdp.native_bond = 0;
170 54 : return 1;
171 54 : }
172 :
173 : static int
174 : xdp_native_bond_check( fd_config_t const * config,
175 30 : fd_auto_info_t const * info ) {
176 30 : if( !info->is_bonded_if ) return 0;
177 6 : if( !info->is_lacp_if ) return 0;
178 :
179 3 : if( 0==info->bonded_if_slave_count || info->bonded_if_slave_count>FD_NET_BOND_SLAVE_MAX ) return 0;
180 :
181 : /* net_tile_count must be a multiple of the bonded interface's slave count */
182 3 : if( 0!=config->layout.net_tile_count%info->bonded_if_slave_count ) return 0;
183 :
184 3 : return 1;
185 3 : }
186 :
187 : static void
188 3 : xdp_native_bond_apply( fd_config_t * config ) {
189 3 : config->net.xdp.native_bond = 1;
190 3 : }
191 :
192 : static int
193 54 : is_xdp_mode_auto( fd_config_t * config ) {
194 54 : if( strcmp( config->net.xdp.xdp_mode, "auto" ) ) return 0;
195 : /* Set to default */
196 54 : fd_memcpy( config->net.xdp.xdp_mode, "skb", 4 );
197 54 : return 1;
198 54 : }
199 :
200 : static int
201 : xdp_drv_check( fd_config_t const * config,
202 30 : fd_auto_info_t const * info ) {
203 30 : if( info->is_virtual_if &&
204 30 : !config->net.xdp.native_bond ) return 0;
205 27 : return 1;
206 30 : }
207 :
208 : static void
209 27 : xdp_drv_apply( fd_config_t * config ) {
210 27 : fd_memcpy( config->net.xdp.xdp_mode, "drv", 4 );
211 27 : }
212 :
213 : static int
214 54 : is_xdp_prefbusy_auto( fd_config_t * config ) {
215 54 : if( strcmp( config->net.xdp.poll_mode, "auto" ) ) return 0;
216 : /* Set to default */
217 54 : fd_memcpy( config->net.xdp.poll_mode, "softirq", 8 );
218 54 : return 1;
219 54 : }
220 :
221 : static int
222 : xdp_prefbusy_check( fd_config_t const * config,
223 30 : fd_auto_info_t const * info FD_PARAM_UNUSED ) {
224 30 : if( strcmp( config->net.xdp.xdp_mode, "drv") ) return 0;
225 27 : return 1;
226 30 : }
227 :
228 : static void
229 27 : xdp_prefbusy_apply( fd_config_t * config ) {
230 27 : fd_memcpy( config->net.xdp.poll_mode, "prefbusy", 9 );
231 27 : }
232 :
233 : static int
234 54 : is_xdp_zc_auto( fd_config_t * config ) {
235 54 : if( 2!=config->net.xdp.xdp_zero_copy ) return 0;
236 : /* Set to default */
237 54 : config->net.xdp.xdp_zero_copy = 0;
238 54 : return 1;
239 54 : }
240 :
241 : static int
242 : xdp_zc_check( fd_config_t const * config,
243 30 : fd_auto_info_t const * info FD_PARAM_UNUSED ) {
244 30 : if( strcmp( config->net.xdp.xdp_mode, "drv") ) return 0;
245 27 : return 1;
246 30 : }
247 :
248 : static void
249 27 : xdp_zc_apply( fd_config_t * config ) {
250 27 : config->net.xdp.xdp_zero_copy = 1;
251 27 : }
252 :
253 : /* mlx5 tile checks/apply */
254 : static int
255 : mlx5_check( fd_config_t const * config,
256 21 : fd_auto_info_t const * info ) {
257 21 : if( strcmp( config->net.provider, "auto" ) ) return 0;
258 21 : if( !info->has_mlx5_rdma_port ) return 0;
259 12 : if( !info->has_uverbs ) return 0;
260 9 : if( !fd_ulong_is_pow2( config->layout.net_tile_count ) ) return 0;
261 6 : return 1;
262 9 : }
263 :
264 : static void
265 6 : mlx5_apply( fd_config_t * config ) {
266 6 : fd_memcpy( config->net.provider, "mlx5", 5 );
267 6 : }
268 :
269 : /* Each feature's supported Linux version matrix per driver decided
270 : based on fd-linux-version-testbed test results. */
271 : static const fd_auto_provider_t NET_PROVIDERS[] = {
272 : {
273 : .name = "mlx5",
274 : .supported_drivers = { { "mlx5_core", 5, 14, 0, 0 } },
275 : .check = mlx5_check,
276 : .apply = mlx5_apply
277 : },
278 : {
279 : .name = "xdp",
280 : .check = xdp_check,
281 : .apply = xdp_apply,
282 : .feats = {
283 : {
284 : .name = "Listen GRE",
285 : .is_feat_auto = is_xdp_listen_gre_auto,
286 : .check = xdp_listen_gre_check,
287 : .apply = xdp_listen_gre_apply
288 : },
289 : {
290 : .name = "RSS Queue Mode",
291 : .is_feat_auto = is_xdp_rss_queue_mode_auto,
292 : .check = xdp_rss_queue_mode_check,
293 : .apply = xdp_rss_queue_mode_apply
294 : },
295 : {
296 : .name = "Native Bond",
297 : .supported_drivers = {
298 : { "mlx5_core", 5, 15, 0, 0 },
299 : { "i40e", 5, 15, 0, 0 },
300 : },
301 : .is_feat_auto = is_xdp_native_bond_auto,
302 : .check = xdp_native_bond_check,
303 : .apply = xdp_native_bond_apply
304 : },
305 : {
306 : .name = "DRV",
307 : .supported_drivers = {
308 : { "mlx5_core", 5, 15, 6, 3 },
309 : { "mlx5_core", 6, 5, 0, 0 },
310 : { "i40e", 5, 19, 0, 0 }
311 : },
312 : .is_feat_auto = is_xdp_mode_auto,
313 : .check = xdp_drv_check,
314 : .apply = xdp_drv_apply,
315 : },
316 : {
317 : .name = "Prefbusy",
318 : .supported_drivers = {
319 : { "mlx5_core", 5, 15, 0, 0 },
320 : { "i40e", 5, 15, 0, 0 }
321 : },
322 : .is_feat_auto = is_xdp_prefbusy_auto,
323 : .check = xdp_prefbusy_check,
324 : .apply = xdp_prefbusy_apply
325 : },
326 : {
327 : .name = "Zero Copy",
328 : .supported_drivers = {
329 : { "mlx5_core", 6, 1, 0, 0 },
330 : { "i40e", 5, 19, 0, 0 }
331 : },
332 : .is_feat_auto = is_xdp_zc_auto,
333 : .check = xdp_zc_check,
334 : .apply = xdp_zc_apply
335 : }
336 : }
337 : }
338 : };
339 :
340 : static void
341 3 : scrape_system( fd_auto_info_t * info ) {
342 3 : struct utsname utsname;
343 3 : if( FD_UNLIKELY( -1==uname( &utsname ) ) ) {
344 0 : FD_LOG_WARNING(( "uname() failed (%i-%s), auto config skipping kernel version detection",
345 0 : errno, fd_io_strerror( errno ) ));
346 0 : return;
347 0 : }
348 :
349 : /* utsname.release is typically "major.minor.patch-distrojunk",
350 : e.g. "6.8.0-45-generic" or "5.14.0-362.8.1.el9_3.x86_64". We
351 : only care about major.minor. A missing ".minor" is treated as
352 : minor 0. An unparseable release leaves {0,0}, which fails all
353 : version requirements (fail-soft). */
354 :
355 3 : char const * cur = utsname.release;
356 3 : char * end = NULL;
357 :
358 3 : ulong major = strtoul( cur, &end, 10 );
359 3 : if( FD_UNLIKELY( end==cur ) ) {
360 0 : FD_LOG_WARNING(( "unrecognized kernel release `%s`, auto config skipping kernel version detection",
361 0 : utsname.release ));
362 0 : return;
363 0 : }
364 :
365 3 : ulong minor = 0UL;
366 3 : if( FD_LIKELY( end[0]=='.' ) ) {
367 3 : cur = end+1;
368 3 : minor = strtoul( cur, &end, 10 );
369 3 : if( FD_UNLIKELY( end==cur ) ) minor = 0UL; /* tolerate "6." */
370 3 : }
371 :
372 3 : info->linux_major = major;
373 3 : info->linux_minor = minor;
374 3 : }
375 :
376 : static void
377 : scrape_driver( char * driver,
378 6 : char const * if_name ) {
379 6 : char path[ PATH_MAX ], target[ PATH_MAX ];
380 6 : driver[0] = '\0';
381 6 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "/sys/class/net/%s/device/driver", if_name ) );
382 :
383 6 : long n = readlink( path, target, PATH_MAX-1 );
384 6 : if( FD_UNLIKELY( n<0L ) ) return;
385 6 : target[ n ] = '\0'; /* readlink does not NUL-terminate */
386 :
387 6 : char const * base = strrchr( target, '/' );
388 6 : fd_cstr_ncpy( driver, base ? base+1 : target, NAME_SZ );
389 6 : }
390 :
391 : static int
392 3 : scrape_is_using_gre( void ) {
393 3 : fd_netlink_t netlink[1];
394 3 : if( FD_UNLIKELY( !fd_netlink_init( netlink, 42U ) ) ) return 1;
395 :
396 3 : ulong netdev_tbl_footprint = fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX );
397 3 : void * netdev_tbl_mem = aligned_alloc( FD_NETDEV_TBL_ALIGN, netdev_tbl_footprint );
398 3 : if( FD_UNLIKELY( !netdev_tbl_mem ) ) {
399 0 : fd_netlink_fini( netlink );
400 0 : FD_LOG_WARNING(( "failed to allocate network interface table for GRE auto detection" ));
401 0 : return 1;
402 0 : }
403 :
404 3 : fd_netdev_tbl_join_t netdev_tbl[1];
405 3 : FD_TEST( fd_netdev_tbl_new( netdev_tbl_mem, NETDEV_MAX, BOND_MASTER_MAX ) );
406 3 : FD_TEST( fd_netdev_tbl_join( netdev_tbl, netdev_tbl_mem ) );
407 :
408 3 : int err = fd_netdev_netlink_load_table( netdev_tbl, netlink );
409 3 : fd_netlink_fini( netlink );
410 3 : if( FD_UNLIKELY( err ) ) {
411 0 : FD_LOG_WARNING(( "failed to load network interfaces for GRE auto detection (%i-%s)",
412 0 : err, fd_io_strerror( err ) ));
413 0 : free( netdev_tbl_mem );
414 0 : return 1;
415 0 : }
416 :
417 3 : int is_using_gre = 0;
418 24 : for( ushort i=0U; i<netdev_tbl->hdr->dev_cnt; i++ ) {
419 21 : if( netdev_tbl->dev_tbl[ i ].dev_type==ARPHRD_IPGRE ) {
420 0 : is_using_gre = 1;
421 0 : break;
422 0 : }
423 21 : }
424 3 : free( netdev_tbl_mem );
425 3 : return is_using_gre;
426 3 : }
427 :
428 : static void
429 : scrape_networking( fd_auto_info_t * info,
430 3 : char const * if_name ) {
431 3 : info->is_using_gre = scrape_is_using_gre();
432 :
433 : /* Check for virtual interface */
434 3 : char path[ PATH_MAX ];
435 3 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "/sys/class/net/%s/device", if_name ) );
436 3 : info->is_virtual_if = ( -1==access( path, F_OK ) );
437 :
438 : /* Check for bond */
439 3 : info->is_bonded_if = !!fd_bonding_is_master( if_name );
440 3 : if( info->is_bonded_if ) {
441 3 : info->bonded_if_slave_count = (uint)fd_bonding_slave_cnt( if_name );
442 3 : info->is_lacp_if = !!fd_bonding_is_lacp( if_name );
443 3 : }
444 :
445 : /* Get driver name. A bond master reports "n/a" so no driver specific
446 : config is applied. If all slaves share a driver name then reports
447 : that instead. */
448 3 : if( info->is_bonded_if ) {
449 3 : fd_bonding_slave_iter_t iter_[1];
450 3 : for( fd_bonding_slave_iter_t * iter = fd_bonding_slave_iter_init( iter_, if_name );
451 9 : !fd_bonding_slave_iter_done( iter );
452 6 : fd_bonding_slave_iter_next ( iter ) ) {
453 :
454 6 : char slave_driver[ NAME_SZ ];
455 6 : scrape_driver( slave_driver, fd_bonding_slave_iter_ele( iter ) );
456 6 : if( FD_UNLIKELY( !slave_driver[0] ||
457 6 : ( info->driver[0] && strcmp( info->driver, slave_driver ) ) ) ) {
458 0 : fd_memcpy( info->driver, "n/a", 4 );
459 0 : break;
460 0 : }
461 6 : fd_cstr_ncpy( info->driver, slave_driver, NAME_SZ );
462 6 : }
463 3 : } else {
464 0 : scrape_driver( info->driver, if_name );
465 0 : }
466 :
467 3 : if( !strcmp( info->driver, "mlx5_core" ) ) {
468 3 : char rdma_name[ FD_MLX5_RDMA_NAME_MAX ];
469 3 : uint rdma_port;
470 3 : info->has_mlx5_rdma_port = fd_mlx5_rdma_dev_find( rdma_name, &rdma_port, if_name );
471 3 : info->has_uverbs = fd_mlx5_uverbs_avail();
472 3 : }
473 3 : }
474 :
475 : static fd_auto_info_t
476 3 : fd_auto_scrape_info( fd_config_t const * config ) {
477 3 : fd_auto_info_t info = {0};
478 :
479 3 : scrape_system ( &info );
480 3 : scrape_networking( &info, config->net.interface );
481 :
482 3 : return info;
483 3 : }
484 :
485 : /* fd_auto_net resolves any "auto" fields in the networking
486 : configuration. The resulting configuration is optimized for the system
487 : but won't always maximally optimize, this is to reduce risk of failures. */
488 : static void
489 : fd_auto_net( fd_config_t * config,
490 54 : fd_auto_info_t const * info ) {
491 : /* Providers are in order of priority with first being the highest */
492 54 : int const is_provider_auto = !strcmp( config->net.provider, "auto" );
493 54 : int is_provider_set = 0;
494 162 : for( ulong p=0UL; p<PROVIDER_CNT; p++ ) {
495 108 : fd_auto_provider_t const * provider = &NET_PROVIDERS[p];
496 :
497 : /* Check provider requirements */
498 108 : int const is_explicit_provider = !is_provider_auto && !strcmp( config->net.provider, provider->name );
499 108 : int is_chosen_provider = !is_provider_set &&
500 108 : ( is_explicit_provider ||
501 99 : ( is_provider_auto &&
502 69 : ( !provider->check || provider->check( config, info ) ) &&
503 69 : fd_auto_check_driver( info, provider->supported_drivers ) ) );
504 108 : if( is_chosen_provider ) {
505 51 : is_provider_set = 1;
506 51 : if( is_provider_auto ) {
507 21 : if( FD_UNLIKELY( !provider->apply ) ) {
508 0 : FD_LOG_ERR(( "Applying auto configure net provider %s failed since no apply function was found.", provider->name ));
509 0 : }
510 21 : provider->apply( config );
511 21 : }
512 51 : }
513 :
514 432 : for( ulong f=0UL; f<FEAT_CNT_MAX; f++ ) {
515 432 : fd_auto_feat_t const * feat = &provider->feats[f];
516 432 : if( !feat->name ) break;
517 :
518 : /* Checks feature is set to "auto", if so replaces "auto" with
519 : default and goes onto the next checks */
520 324 : if( !feat->is_feat_auto( config ) ) continue;
521 :
522 318 : if( !is_chosen_provider ) continue;
523 :
524 : /* Feature's driver/Linux version requirements */
525 246 : if( !fd_auto_check_driver( info, feat->supported_drivers ) ) continue;
526 :
527 : /* Feature's other requirements */
528 198 : if( feat->check && !feat->check( config, info ) ) continue;
529 :
530 126 : if( FD_UNLIKELY( !feat->apply ) ) {
531 0 : FD_LOG_ERR(( "Applying auto configure net feature %s failed since no apply function was found.", feat->name ));
532 0 : }
533 126 : feat->apply( config );
534 126 : }
535 108 : }
536 54 : }
537 :
538 : void
539 3 : fd_config_auto( fd_config_t * config ) {
540 3 : fd_auto_info_t info = fd_auto_scrape_info( config );
541 3 : fd_auto_net( config, &info );
542 :
543 3 : fd_cstr_printf( config->auto_config_log, sizeof(config->auto_config_log), NULL,
544 3 : "network auto configure system info: provider=%s xdp_mode=%s poll_mode=%s zero_copy=%d native_bond=%d listen_gre=%d rss_queue_mode=%s (driver=%s kernel=%lu.%lu gre=%d virtual_if=%d bonded_if=%d lacp_if=%d slaves=%u, net_tile_cnt=%u)",
545 3 : config->net.provider,
546 3 : config->net.xdp.xdp_mode,
547 3 : config->net.xdp.poll_mode,
548 3 : config->net.xdp.xdp_zero_copy,
549 3 : config->net.xdp.native_bond,
550 3 : config->net.xdp.listen_gre,
551 3 : config->net.xdp.rss_queue_mode,
552 3 : info.driver[0] ? info.driver : "unknown",
553 3 : info.linux_major, info.linux_minor,
554 3 : info.is_using_gre,
555 3 : info.is_virtual_if,
556 3 : info.is_bonded_if,
557 3 : info.is_lacp_if,
558 3 : info.bonded_if_slave_count,
559 3 : config->layout.net_tile_count );
560 3 : }
|