Line data Source code
1 : #include "fd_auto_net.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 0 : #define PROVIDER_CNT ( 2UL)
17 0 : #define FEAT_CNT_MAX (16UL)
18 0 : #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 : int has_uverbs_module;
35 : };
36 : typedef struct fd_auto_info fd_auto_info_t;
37 :
38 : struct fd_auto_driver_rq {
39 : char const * name;
40 : ulong min_linux_major;
41 : ulong min_linux_minor;
42 :
43 : /* 0 = no max linux version */
44 : ulong max_linux_major;
45 : ulong max_linux_minor;
46 : };
47 : typedef struct fd_auto_driver_rq fd_auto_driver_rq_t;
48 :
49 : struct fd_auto_feat {
50 : char const * name;
51 : /* supported_drivers is for unconditional driver requirements.
52 : For conditional driver requirements leave this empty and call
53 : fd_auto_check_driver() inside the check() callback
54 : (like xdp_rss_queue_mode_check() does). */
55 : fd_auto_driver_rq_t supported_drivers[ NET_DRIVER_CNT_MAX ];
56 :
57 : /* is_feat_auto() checks if feature is auto and sets the default */
58 : int (*is_feat_auto)( fd_config_t * config );
59 : int (*check )( fd_config_t const * config,
60 : fd_auto_info_t const * info );
61 : void (*apply )( fd_config_t * config );
62 : };
63 : typedef struct fd_auto_feat fd_auto_feat_t;
64 :
65 : struct fd_auto_provider {
66 : char const * name;
67 : /* Later features observe earlier features updates to config,
68 : so the order is important. e.g. ZC checks xdp_mode is DRV
69 : so DRV needs to come before ZC in the feats ordering. */
70 : fd_auto_feat_t feats [ FEAT_CNT_MAX ];
71 : fd_auto_driver_rq_t supported_drivers[ NET_DRIVER_CNT_MAX ];
72 :
73 : int (*check)( fd_config_t const * config,
74 : fd_auto_info_t const * info );
75 : void (*apply)( fd_config_t * config );
76 : };
77 : typedef struct fd_auto_provider fd_auto_provider_t;
78 :
79 : static int
80 : fd_auto_check_driver( fd_auto_info_t const * info,
81 0 : fd_auto_driver_rq_t const * supported_drivers ) {
82 : /* Check for driver requirements. */
83 0 : if( !supported_drivers[0].name ) return 1;
84 :
85 0 : for( ulong i=0UL; i<NET_DRIVER_CNT_MAX; i++ ) {
86 0 : fd_auto_driver_rq_t const * req = &supported_drivers[ i ];
87 0 : if( !req->name ) return 0;
88 :
89 0 : if( 0==strcmp( info->driver, req->name ) ) {
90 : /* Check system linux version is within bounds of min and max
91 : of what this driver req requires. */
92 0 : if( req->max_linux_major > 0UL ) {
93 0 : if( info->linux_major>req->max_linux_major ||
94 0 : ( info->linux_major==req->max_linux_major &&
95 0 : info->linux_minor>req->max_linux_minor ) ) continue;
96 0 : }
97 :
98 0 : if( info->linux_major>req->min_linux_major ||
99 0 : ( info->linux_major==req->min_linux_major &&
100 0 : info->linux_minor>=req->min_linux_minor ) ) return 1;
101 0 : }
102 0 : }
103 :
104 0 : return 0;
105 0 : }
106 :
107 : /* XDP checks/apply */
108 :
109 : static int
110 : xdp_check( fd_config_t const * config,
111 0 : fd_auto_info_t const * info FD_PARAM_UNUSED ) {
112 0 : if( strcmp( config->net.provider, "auto" ) ) return 0;
113 0 : return 1;
114 0 : }
115 :
116 : static void
117 0 : xdp_apply( fd_config_t * config ) {
118 0 : fd_memcpy( config->net.provider, "xdp", 4 );
119 0 : }
120 :
121 : static int
122 0 : is_xdp_listen_gre_auto( fd_config_t * config ) {
123 0 : if( 2!=config->net.xdp.listen_gre ) return 0;
124 : /* Set to default */
125 0 : config->net.xdp.listen_gre = 0;
126 0 : return 1;
127 0 : }
128 :
129 : static int
130 : xdp_listen_gre_check( fd_config_t const * config FD_PARAM_UNUSED,
131 0 : fd_auto_info_t const * info ) {
132 0 : return info->is_using_gre;
133 0 : }
134 :
135 : static void
136 0 : xdp_listen_gre_apply( fd_config_t * config ) {
137 0 : config->net.xdp.listen_gre = 1;
138 0 : }
139 :
140 : static int
141 0 : is_xdp_rss_queue_mode_auto( fd_config_t * config ) {
142 0 : if( strcmp( config->net.xdp.rss_queue_mode, "auto" ) ) return 0;
143 : /* Set to default */
144 0 : fd_memcpy( config->net.xdp.rss_queue_mode, "simple", 7 );
145 0 : return 1;
146 0 : }
147 :
148 : static int
149 : xdp_rss_queue_mode_check( fd_config_t const * config,
150 0 : fd_auto_info_t const * info ) {
151 0 : if( 1==config->net.xdp.listen_gre ) {
152 0 : static fd_auto_driver_rq_t const gre_rss_drivers[ NET_DRIVER_CNT_MAX ] = {
153 0 : { "mlx5_core", 0, 0, 0, 0 }
154 0 : };
155 :
156 0 : if( 0==fd_auto_check_driver( info, gre_rss_drivers ) ) return 0;
157 0 : }
158 0 : return 1;
159 0 : }
160 :
161 : static void
162 0 : xdp_rss_queue_mode_apply( fd_config_t * config ) {
163 0 : fd_memcpy( config->net.xdp.rss_queue_mode, "auto", 5 );
164 0 : }
165 :
166 : static int
167 0 : is_xdp_native_bond_auto( fd_config_t * config ) {
168 0 : if( 2!=config->net.xdp.native_bond ) return 0;
169 : /* Set to default */
170 0 : config->net.xdp.native_bond = 0;
171 0 : return 1;
172 0 : }
173 :
174 : static int
175 : xdp_native_bond_check( fd_config_t const * config,
176 0 : fd_auto_info_t const * info ) {
177 0 : if( !info->is_bonded_if ) return 0;
178 0 : if( !info->is_lacp_if ) return 0;
179 :
180 0 : if( 0==info->bonded_if_slave_count || info->bonded_if_slave_count>FD_NET_BOND_SLAVE_MAX ) return 0;
181 :
182 : /* net_tile_count must be a multiple of the bonded interface's slave count */
183 0 : if( 0!=config->layout.net_tile_count%info->bonded_if_slave_count ) return 0;
184 :
185 0 : return 1;
186 0 : }
187 :
188 : static void
189 0 : xdp_native_bond_apply( fd_config_t * config ) {
190 0 : config->net.xdp.native_bond = 1;
191 0 : }
192 :
193 : static int
194 0 : is_xdp_mode_auto( fd_config_t * config ) {
195 0 : if( strcmp( config->net.xdp.xdp_mode, "auto" ) ) return 0;
196 : /* Set to default */
197 0 : fd_memcpy( config->net.xdp.xdp_mode, "skb", 4 );
198 0 : return 1;
199 0 : }
200 :
201 : static int
202 : xdp_drv_check( fd_config_t const * config,
203 0 : fd_auto_info_t const * info ) {
204 0 : if( info->is_virtual_if &&
205 0 : !config->net.xdp.native_bond ) return 0;
206 0 : return 1;
207 0 : }
208 :
209 : static void
210 0 : xdp_drv_apply( fd_config_t * config ) {
211 0 : fd_memcpy( config->net.xdp.xdp_mode, "drv", 4 );
212 0 : }
213 :
214 : static int
215 0 : is_xdp_prefbusy_auto( fd_config_t * config ) {
216 0 : if( strcmp( config->net.xdp.poll_mode, "auto" ) ) return 0;
217 : /* Set to default */
218 0 : fd_memcpy( config->net.xdp.poll_mode, "softirq", 8 );
219 0 : return 1;
220 0 : }
221 :
222 : static int
223 : xdp_prefbusy_check( fd_config_t const * config,
224 0 : fd_auto_info_t const * info FD_PARAM_UNUSED ) {
225 0 : if( strcmp( config->net.xdp.xdp_mode, "drv") ) return 0;
226 0 : return 1;
227 0 : }
228 :
229 : static void
230 0 : xdp_prefbusy_apply( fd_config_t * config ) {
231 0 : fd_memcpy( config->net.xdp.poll_mode, "prefbusy", 9 );
232 0 : }
233 :
234 : static int
235 0 : is_xdp_zc_auto( fd_config_t * config ) {
236 0 : if( 2!=config->net.xdp.xdp_zero_copy ) return 0;
237 : /* Set to default */
238 0 : config->net.xdp.xdp_zero_copy = 0;
239 0 : return 1;
240 0 : }
241 :
242 : static int
243 : xdp_zc_check( fd_config_t const * config,
244 0 : fd_auto_info_t const * info FD_PARAM_UNUSED ) {
245 0 : if( strcmp( config->net.xdp.xdp_mode, "drv") ) return 0;
246 0 : return 1;
247 0 : }
248 :
249 : static void
250 0 : xdp_zc_apply( fd_config_t * config ) {
251 0 : config->net.xdp.xdp_zero_copy = 1;
252 0 : }
253 :
254 : /* mlx5 tile checks/apply */
255 : static int
256 : mlx5_check( fd_config_t const * config,
257 0 : fd_auto_info_t const * info ) {
258 0 : if( strcmp( config->net.provider, "auto" ) ) return 0;
259 0 : if( !info->has_mlx5_rdma_port ) return 0;
260 0 : if( !info->has_uverbs && !info->has_uverbs_module ) return 0;
261 0 : return 1;
262 0 : }
263 :
264 : static void
265 0 : mlx5_apply( fd_config_t * config ) {
266 0 : fd_memcpy( config->net.provider, "mlx5", 5 );
267 0 : }
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 : },
331 : .is_feat_auto = is_xdp_zc_auto,
332 : .check = xdp_zc_check,
333 : .apply = xdp_zc_apply
334 : }
335 : }
336 : }
337 : };
338 :
339 : static void
340 0 : scrape_system( fd_auto_info_t * info ) {
341 0 : struct utsname utsname;
342 0 : if( FD_UNLIKELY( -1==uname( &utsname ) ) ) {
343 0 : FD_LOG_WARNING(( "uname() failed (%i-%s), auto config skipping kernel version detection",
344 0 : errno, fd_io_strerror( errno ) ));
345 0 : return;
346 0 : }
347 :
348 : /* utsname.release is typically "major.minor.patch-distrojunk",
349 : e.g. "6.8.0-45-generic" or "5.14.0-362.8.1.el9_3.x86_64". We
350 : only care about major.minor. A missing ".minor" is treated as
351 : minor 0. An unparseable release leaves {0,0}, which fails all
352 : version requirements (fail-soft). */
353 :
354 0 : char const * cur = utsname.release;
355 0 : char * end = NULL;
356 :
357 0 : ulong major = strtoul( cur, &end, 10 );
358 0 : if( FD_UNLIKELY( end==cur ) ) {
359 0 : FD_LOG_WARNING(( "unrecognized kernel release `%s`, auto config skipping kernel version detection",
360 0 : utsname.release ));
361 0 : return;
362 0 : }
363 :
364 0 : ulong minor = 0UL;
365 0 : if( FD_LIKELY( end[0]=='.' ) ) {
366 0 : cur = end+1;
367 0 : minor = strtoul( cur, &end, 10 );
368 0 : if( FD_UNLIKELY( end==cur ) ) minor = 0UL; /* tolerate "6." */
369 0 : }
370 :
371 0 : info->linux_major = major;
372 0 : info->linux_minor = minor;
373 0 : }
374 :
375 : static int
376 0 : scrape_is_using_gre( void ) {
377 0 : fd_netlink_t netlink[1];
378 0 : if( FD_UNLIKELY( !fd_netlink_init( netlink, 42U ) ) ) return 1;
379 :
380 0 : ulong netdev_tbl_footprint = fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX );
381 0 : void * netdev_tbl_mem = aligned_alloc( FD_NETDEV_TBL_ALIGN, netdev_tbl_footprint );
382 0 : if( FD_UNLIKELY( !netdev_tbl_mem ) ) {
383 0 : fd_netlink_fini( netlink );
384 0 : FD_LOG_WARNING(( "failed to allocate network interface table for GRE auto detection" ));
385 0 : return 1;
386 0 : }
387 :
388 0 : fd_netdev_tbl_join_t netdev_tbl[1];
389 0 : FD_TEST( fd_netdev_tbl_new( netdev_tbl_mem, NETDEV_MAX, BOND_MASTER_MAX ) );
390 0 : FD_TEST( fd_netdev_tbl_join( netdev_tbl, netdev_tbl_mem ) );
391 :
392 0 : int err = fd_netdev_netlink_load_table( netdev_tbl, netlink );
393 0 : fd_netlink_fini( netlink );
394 0 : if( FD_UNLIKELY( err ) ) {
395 0 : FD_LOG_WARNING(( "failed to load network interfaces for GRE auto detection (%i-%s)",
396 0 : err, fd_io_strerror( err ) ));
397 0 : free( netdev_tbl_mem );
398 0 : return 1;
399 0 : }
400 :
401 0 : int is_using_gre = 0;
402 0 : for( ushort i=0U; i<netdev_tbl->hdr->dev_cnt; i++ ) {
403 0 : if( netdev_tbl->dev_tbl[ i ].dev_type==ARPHRD_IPGRE ) {
404 0 : is_using_gre = 1;
405 0 : break;
406 0 : }
407 0 : }
408 0 : free( netdev_tbl_mem );
409 0 : return is_using_gre;
410 0 : }
411 :
412 : static void
413 : scrape_networking( fd_auto_info_t * info,
414 0 : char const * if_name ) {
415 0 : info->is_using_gre = scrape_is_using_gre();
416 :
417 : /* Check for virtual interface */
418 0 : char path[ PATH_MAX ];
419 0 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "/sys/class/net/%s/device", if_name ) );
420 0 : info->is_virtual_if = ( -1==access( path, F_OK ) );
421 :
422 : /* Check for bond */
423 0 : info->is_bonded_if = !!fd_bonding_is_master( if_name );
424 0 : if( info->is_bonded_if ) {
425 0 : info->bonded_if_slave_count = (uint)fd_bonding_slave_cnt( if_name );
426 0 : info->is_lacp_if = !!fd_bonding_is_lacp( if_name );
427 0 : }
428 :
429 0 : fd_net_get_driver( info->driver, sizeof(info->driver), if_name );
430 :
431 0 : if( !strcmp( info->driver, "mlx5_core" ) ) {
432 0 : char rdma_name[ FD_MLX5_RDMA_NAME_MAX ];
433 0 : uint rdma_port;
434 0 : info->has_mlx5_rdma_port = fd_mlx5_rdma_dev_find( rdma_name, &rdma_port, if_name );
435 0 : info->has_uverbs = fd_mlx5_uverbs_avail();
436 0 : }
437 0 : }
438 :
439 : static fd_auto_info_t
440 0 : fd_auto_scrape_info( fd_config_t const * config ) {
441 0 : fd_auto_info_t info = {0};
442 :
443 0 : scrape_system ( &info );
444 0 : scrape_networking( &info, config->net.interface );
445 :
446 0 : if( !strcmp( config->net.provider, "auto" ) &&
447 0 : info.has_mlx5_rdma_port &&
448 0 : !info.has_uverbs ) {
449 0 : info.has_uverbs_module = !fd_mlx5_uverbs_modprobe( 1 );
450 0 : }
451 :
452 0 : return info;
453 0 : }
454 :
455 : /* fd_auto_net_apply resolves any "auto" fields in the networking
456 : configuration. The resulting configuration is optimized for the system
457 : but won't always maximally optimize, this is to reduce risk of failures. */
458 : static void
459 : fd_auto_net_apply( fd_config_t * config,
460 0 : fd_auto_info_t const * info ) {
461 : /* Providers are in order of priority with first being the highest */
462 0 : int const is_provider_auto = !strcmp( config->net.provider, "auto" );
463 0 : int is_provider_set = 0;
464 0 : for( ulong p=0UL; p<PROVIDER_CNT; p++ ) {
465 0 : fd_auto_provider_t const * provider = &NET_PROVIDERS[p];
466 :
467 : /* Check provider requirements */
468 0 : int const is_explicit_provider = !is_provider_auto && !strcmp( config->net.provider, provider->name );
469 0 : int is_chosen_provider = !is_provider_set &&
470 0 : ( is_explicit_provider ||
471 0 : ( is_provider_auto &&
472 0 : ( !provider->check || provider->check( config, info ) ) &&
473 0 : fd_auto_check_driver( info, provider->supported_drivers ) ) );
474 0 : if( is_chosen_provider ) {
475 0 : is_provider_set = 1;
476 0 : if( is_provider_auto ) {
477 0 : if( FD_UNLIKELY( !provider->apply ) ) {
478 0 : FD_LOG_ERR(( "Applying auto configure net provider %s failed since no apply function was found.", provider->name ));
479 0 : }
480 0 : provider->apply( config );
481 0 : }
482 0 : }
483 :
484 0 : for( ulong f=0UL; f<FEAT_CNT_MAX; f++ ) {
485 0 : fd_auto_feat_t const * feat = &provider->feats[f];
486 0 : if( !feat->name ) break;
487 :
488 : /* Checks feature is set to "auto", if so replaces "auto" with
489 : default and goes onto the next checks */
490 0 : if( !feat->is_feat_auto( config ) ) continue;
491 :
492 0 : if( !is_chosen_provider ) continue;
493 :
494 : /* Feature's driver/Linux version requirements */
495 0 : if( !fd_auto_check_driver( info, feat->supported_drivers ) ) continue;
496 :
497 : /* Feature's other requirements */
498 0 : if( feat->check && !feat->check( config, info ) ) continue;
499 :
500 0 : if( FD_UNLIKELY( !feat->apply ) ) {
501 0 : FD_LOG_ERR(( "Applying auto configure net feature %s failed since no apply function was found.", feat->name ));
502 0 : }
503 0 : feat->apply( config );
504 0 : }
505 0 : }
506 0 : }
507 :
508 : void
509 0 : fd_auto_net( fd_config_t * config ) {
510 0 : fd_auto_info_t info = fd_auto_scrape_info( config );
511 0 : fd_auto_net_apply( config, &info );
512 :
513 0 : fd_cstr_printf( config->auto_config_log, sizeof(config->auto_config_log), NULL,
514 0 : "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)",
515 0 : config->net.provider,
516 0 : config->net.xdp.xdp_mode,
517 0 : config->net.xdp.poll_mode,
518 0 : config->net.xdp.xdp_zero_copy,
519 0 : config->net.xdp.native_bond,
520 0 : config->net.xdp.listen_gre,
521 0 : config->net.xdp.rss_queue_mode,
522 0 : info.driver[0] ? info.driver : "unknown",
523 0 : info.linux_major, info.linux_minor,
524 0 : info.is_using_gre,
525 0 : info.is_virtual_if,
526 0 : info.is_bonded_if,
527 0 : info.is_lacp_if,
528 0 : info.bonded_if_slave_count,
529 0 : config->layout.net_tile_count );
530 0 : }
|