diff options
Diffstat (limited to 'drivers/net/netdevsim/dev.c')
-rw-r--r-- | drivers/net/netdevsim/dev.c | 282 |
1 files changed, 281 insertions, 1 deletions
diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index a570da406d1d..c217049552f7 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c | |||
@@ -17,11 +17,20 @@ | |||
17 | 17 | ||
18 | #include <linux/debugfs.h> | 18 | #include <linux/debugfs.h> |
19 | #include <linux/device.h> | 19 | #include <linux/device.h> |
20 | #include <linux/etherdevice.h> | ||
21 | #include <linux/inet.h> | ||
22 | #include <linux/jiffies.h> | ||
23 | #include <linux/kernel.h> | ||
20 | #include <linux/list.h> | 24 | #include <linux/list.h> |
21 | #include <linux/mutex.h> | 25 | #include <linux/mutex.h> |
22 | #include <linux/random.h> | 26 | #include <linux/random.h> |
23 | #include <linux/rtnetlink.h> | 27 | #include <linux/rtnetlink.h> |
28 | #include <linux/workqueue.h> | ||
24 | #include <net/devlink.h> | 29 | #include <net/devlink.h> |
30 | #include <net/ip.h> | ||
31 | #include <uapi/linux/devlink.h> | ||
32 | #include <uapi/linux/ip.h> | ||
33 | #include <uapi/linux/udp.h> | ||
25 | 34 | ||
26 | #include "netdevsim.h" | 35 | #include "netdevsim.h" |
27 | 36 | ||
@@ -302,6 +311,218 @@ static void nsim_dev_dummy_region_exit(struct nsim_dev *nsim_dev) | |||
302 | devlink_region_destroy(nsim_dev->dummy_region); | 311 | devlink_region_destroy(nsim_dev->dummy_region); |
303 | } | 312 | } |
304 | 313 | ||
314 | struct nsim_trap_item { | ||
315 | void *trap_ctx; | ||
316 | enum devlink_trap_action action; | ||
317 | }; | ||
318 | |||
319 | struct nsim_trap_data { | ||
320 | struct delayed_work trap_report_dw; | ||
321 | struct nsim_trap_item *trap_items_arr; | ||
322 | struct nsim_dev *nsim_dev; | ||
323 | spinlock_t trap_lock; /* Protects trap_items_arr */ | ||
324 | }; | ||
325 | |||
326 | /* All driver-specific traps must be documented in | ||
327 | * Documentation/networking/devlink-trap-netdevsim.rst | ||
328 | */ | ||
329 | enum { | ||
330 | NSIM_TRAP_ID_BASE = DEVLINK_TRAP_GENERIC_ID_MAX, | ||
331 | NSIM_TRAP_ID_FID_MISS, | ||
332 | }; | ||
333 | |||
334 | #define NSIM_TRAP_NAME_FID_MISS "fid_miss" | ||
335 | |||
336 | #define NSIM_TRAP_METADATA DEVLINK_TRAP_METADATA_TYPE_F_IN_PORT | ||
337 | |||
338 | #define NSIM_TRAP_DROP(_id, _group_id) \ | ||
339 | DEVLINK_TRAP_GENERIC(DROP, DROP, _id, \ | ||
340 | DEVLINK_TRAP_GROUP_GENERIC(_group_id), \ | ||
341 | NSIM_TRAP_METADATA) | ||
342 | #define NSIM_TRAP_EXCEPTION(_id, _group_id) \ | ||
343 | DEVLINK_TRAP_GENERIC(EXCEPTION, TRAP, _id, \ | ||
344 | DEVLINK_TRAP_GROUP_GENERIC(_group_id), \ | ||
345 | NSIM_TRAP_METADATA) | ||
346 | #define NSIM_TRAP_DRIVER_EXCEPTION(_id, _group_id) \ | ||
347 | DEVLINK_TRAP_DRIVER(EXCEPTION, TRAP, NSIM_TRAP_ID_##_id, \ | ||
348 | NSIM_TRAP_NAME_##_id, \ | ||
349 | DEVLINK_TRAP_GROUP_GENERIC(_group_id), \ | ||
350 | NSIM_TRAP_METADATA) | ||
351 | |||
352 | static const struct devlink_trap nsim_traps_arr[] = { | ||
353 | NSIM_TRAP_DROP(SMAC_MC, L2_DROPS), | ||
354 | NSIM_TRAP_DROP(VLAN_TAG_MISMATCH, L2_DROPS), | ||
355 | NSIM_TRAP_DROP(INGRESS_VLAN_FILTER, L2_DROPS), | ||
356 | NSIM_TRAP_DROP(INGRESS_STP_FILTER, L2_DROPS), | ||
357 | NSIM_TRAP_DROP(EMPTY_TX_LIST, L2_DROPS), | ||
358 | NSIM_TRAP_DROP(PORT_LOOPBACK_FILTER, L2_DROPS), | ||
359 | NSIM_TRAP_DRIVER_EXCEPTION(FID_MISS, L2_DROPS), | ||
360 | NSIM_TRAP_DROP(BLACKHOLE_ROUTE, L3_DROPS), | ||
361 | NSIM_TRAP_EXCEPTION(TTL_ERROR, L3_DROPS), | ||
362 | NSIM_TRAP_DROP(TAIL_DROP, BUFFER_DROPS), | ||
363 | }; | ||
364 | |||
365 | #define NSIM_TRAP_L4_DATA_LEN 100 | ||
366 | |||
367 | static struct sk_buff *nsim_dev_trap_skb_build(void) | ||
368 | { | ||
369 | int tot_len, data_len = NSIM_TRAP_L4_DATA_LEN; | ||
370 | struct sk_buff *skb; | ||
371 | struct udphdr *udph; | ||
372 | struct ethhdr *eth; | ||
373 | struct iphdr *iph; | ||
374 | |||
375 | skb = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC); | ||
376 | if (!skb) | ||
377 | return NULL; | ||
378 | tot_len = sizeof(struct iphdr) + sizeof(struct udphdr) + data_len; | ||
379 | |||
380 | eth = skb_put(skb, sizeof(struct ethhdr)); | ||
381 | eth_random_addr(eth->h_dest); | ||
382 | eth_random_addr(eth->h_source); | ||
383 | eth->h_proto = htons(ETH_P_IP); | ||
384 | skb->protocol = htons(ETH_P_IP); | ||
385 | |||
386 | iph = skb_put(skb, sizeof(struct iphdr)); | ||
387 | iph->protocol = IPPROTO_UDP; | ||
388 | iph->saddr = in_aton("192.0.2.1"); | ||
389 | iph->daddr = in_aton("198.51.100.1"); | ||
390 | iph->version = 0x4; | ||
391 | iph->frag_off = 0; | ||
392 | iph->ihl = 0x5; | ||
393 | iph->tot_len = htons(tot_len); | ||
394 | iph->ttl = 100; | ||
395 | ip_send_check(iph); | ||
396 | |||
397 | udph = skb_put_zero(skb, sizeof(struct udphdr) + data_len); | ||
398 | get_random_bytes(&udph->source, sizeof(u16)); | ||
399 | get_random_bytes(&udph->dest, sizeof(u16)); | ||
400 | udph->len = htons(sizeof(struct udphdr) + data_len); | ||
401 | |||
402 | return skb; | ||
403 | } | ||
404 | |||
405 | static void nsim_dev_trap_report(struct nsim_dev_port *nsim_dev_port) | ||
406 | { | ||
407 | struct nsim_dev *nsim_dev = nsim_dev_port->ns->nsim_dev; | ||
408 | struct devlink *devlink = priv_to_devlink(nsim_dev); | ||
409 | struct nsim_trap_data *nsim_trap_data; | ||
410 | int i; | ||
411 | |||
412 | nsim_trap_data = nsim_dev->trap_data; | ||
413 | |||
414 | spin_lock(&nsim_trap_data->trap_lock); | ||
415 | for (i = 0; i < ARRAY_SIZE(nsim_traps_arr); i++) { | ||
416 | struct nsim_trap_item *nsim_trap_item; | ||
417 | struct sk_buff *skb; | ||
418 | |||
419 | nsim_trap_item = &nsim_trap_data->trap_items_arr[i]; | ||
420 | if (nsim_trap_item->action == DEVLINK_TRAP_ACTION_DROP) | ||
421 | continue; | ||
422 | |||
423 | skb = nsim_dev_trap_skb_build(); | ||
424 | if (!skb) | ||
425 | continue; | ||
426 | skb->dev = nsim_dev_port->ns->netdev; | ||
427 | |||
428 | /* Trapped packets are usually passed to devlink in softIRQ, | ||
429 | * but in this case they are generated in a workqueue. Disable | ||
430 | * softIRQs to prevent lockdep from complaining about | ||
431 | * "incosistent lock state". | ||
432 | */ | ||
433 | local_bh_disable(); | ||
434 | devlink_trap_report(devlink, skb, nsim_trap_item->trap_ctx, | ||
435 | &nsim_dev_port->devlink_port); | ||
436 | local_bh_enable(); | ||
437 | consume_skb(skb); | ||
438 | } | ||
439 | spin_unlock(&nsim_trap_data->trap_lock); | ||
440 | } | ||
441 | |||
442 | #define NSIM_TRAP_REPORT_INTERVAL_MS 100 | ||
443 | |||
444 | static void nsim_dev_trap_report_work(struct work_struct *work) | ||
445 | { | ||
446 | struct nsim_trap_data *nsim_trap_data; | ||
447 | struct nsim_dev_port *nsim_dev_port; | ||
448 | struct nsim_dev *nsim_dev; | ||
449 | |||
450 | nsim_trap_data = container_of(work, struct nsim_trap_data, | ||
451 | trap_report_dw.work); | ||
452 | nsim_dev = nsim_trap_data->nsim_dev; | ||
453 | |||
454 | /* For each running port and enabled packet trap, generate a UDP | ||
455 | * packet with a random 5-tuple and report it. | ||
456 | */ | ||
457 | mutex_lock(&nsim_dev->port_list_lock); | ||
458 | list_for_each_entry(nsim_dev_port, &nsim_dev->port_list, list) { | ||
459 | if (!netif_running(nsim_dev_port->ns->netdev)) | ||
460 | continue; | ||
461 | |||
462 | nsim_dev_trap_report(nsim_dev_port); | ||
463 | } | ||
464 | mutex_unlock(&nsim_dev->port_list_lock); | ||
465 | |||
466 | schedule_delayed_work(&nsim_dev->trap_data->trap_report_dw, | ||
467 | msecs_to_jiffies(NSIM_TRAP_REPORT_INTERVAL_MS)); | ||
468 | } | ||
469 | |||
470 | static int nsim_dev_traps_init(struct devlink *devlink) | ||
471 | { | ||
472 | struct nsim_dev *nsim_dev = devlink_priv(devlink); | ||
473 | struct nsim_trap_data *nsim_trap_data; | ||
474 | int err; | ||
475 | |||
476 | nsim_trap_data = kzalloc(sizeof(*nsim_trap_data), GFP_KERNEL); | ||
477 | if (!nsim_trap_data) | ||
478 | return -ENOMEM; | ||
479 | |||
480 | nsim_trap_data->trap_items_arr = kcalloc(ARRAY_SIZE(nsim_traps_arr), | ||
481 | sizeof(struct nsim_trap_item), | ||
482 | GFP_KERNEL); | ||
483 | if (!nsim_trap_data->trap_items_arr) { | ||
484 | err = -ENOMEM; | ||
485 | goto err_trap_data_free; | ||
486 | } | ||
487 | |||
488 | /* The lock is used to protect the action state of the registered | ||
489 | * traps. The value is written by user and read in delayed work when | ||
490 | * iterating over all the traps. | ||
491 | */ | ||
492 | spin_lock_init(&nsim_trap_data->trap_lock); | ||
493 | nsim_trap_data->nsim_dev = nsim_dev; | ||
494 | nsim_dev->trap_data = nsim_trap_data; | ||
495 | |||
496 | err = devlink_traps_register(devlink, nsim_traps_arr, | ||
497 | ARRAY_SIZE(nsim_traps_arr), NULL); | ||
498 | if (err) | ||
499 | goto err_trap_items_free; | ||
500 | |||
501 | INIT_DELAYED_WORK(&nsim_dev->trap_data->trap_report_dw, | ||
502 | nsim_dev_trap_report_work); | ||
503 | schedule_delayed_work(&nsim_dev->trap_data->trap_report_dw, | ||
504 | msecs_to_jiffies(NSIM_TRAP_REPORT_INTERVAL_MS)); | ||
505 | |||
506 | return 0; | ||
507 | |||
508 | err_trap_items_free: | ||
509 | kfree(nsim_trap_data->trap_items_arr); | ||
510 | err_trap_data_free: | ||
511 | kfree(nsim_trap_data); | ||
512 | return err; | ||
513 | } | ||
514 | |||
515 | static void nsim_dev_traps_exit(struct devlink *devlink) | ||
516 | { | ||
517 | struct nsim_dev *nsim_dev = devlink_priv(devlink); | ||
518 | |||
519 | cancel_delayed_work_sync(&nsim_dev->trap_data->trap_report_dw); | ||
520 | devlink_traps_unregister(devlink, nsim_traps_arr, | ||
521 | ARRAY_SIZE(nsim_traps_arr)); | ||
522 | kfree(nsim_dev->trap_data->trap_items_arr); | ||
523 | kfree(nsim_dev->trap_data); | ||
524 | } | ||
525 | |||
305 | static int nsim_dev_reload(struct devlink *devlink, | 526 | static int nsim_dev_reload(struct devlink *devlink, |
306 | struct netlink_ext_ack *extack) | 527 | struct netlink_ext_ack *extack) |
307 | { | 528 | { |
@@ -369,9 +590,61 @@ static int nsim_dev_flash_update(struct devlink *devlink, const char *file_name, | |||
369 | return 0; | 590 | return 0; |
370 | } | 591 | } |
371 | 592 | ||
593 | static struct nsim_trap_item * | ||
594 | nsim_dev_trap_item_lookup(struct nsim_dev *nsim_dev, u16 trap_id) | ||
595 | { | ||
596 | struct nsim_trap_data *nsim_trap_data = nsim_dev->trap_data; | ||
597 | int i; | ||
598 | |||
599 | for (i = 0; i < ARRAY_SIZE(nsim_traps_arr); i++) { | ||
600 | if (nsim_traps_arr[i].id == trap_id) | ||
601 | return &nsim_trap_data->trap_items_arr[i]; | ||
602 | } | ||
603 | |||
604 | return NULL; | ||
605 | } | ||
606 | |||
607 | static int nsim_dev_devlink_trap_init(struct devlink *devlink, | ||
608 | const struct devlink_trap *trap, | ||
609 | void *trap_ctx) | ||
610 | { | ||
611 | struct nsim_dev *nsim_dev = devlink_priv(devlink); | ||
612 | struct nsim_trap_item *nsim_trap_item; | ||
613 | |||
614 | nsim_trap_item = nsim_dev_trap_item_lookup(nsim_dev, trap->id); | ||
615 | if (WARN_ON(!nsim_trap_item)) | ||
616 | return -ENOENT; | ||
617 | |||
618 | nsim_trap_item->trap_ctx = trap_ctx; | ||
619 | nsim_trap_item->action = trap->init_action; | ||
620 | |||
621 | return 0; | ||
622 | } | ||
623 | |||
624 | static int | ||
625 | nsim_dev_devlink_trap_action_set(struct devlink *devlink, | ||
626 | const struct devlink_trap *trap, | ||
627 | enum devlink_trap_action action) | ||
628 | { | ||
629 | struct nsim_dev *nsim_dev = devlink_priv(devlink); | ||
630 | struct nsim_trap_item *nsim_trap_item; | ||
631 | |||
632 | nsim_trap_item = nsim_dev_trap_item_lookup(nsim_dev, trap->id); | ||
633 | if (WARN_ON(!nsim_trap_item)) | ||
634 | return -ENOENT; | ||
635 | |||
636 | spin_lock(&nsim_dev->trap_data->trap_lock); | ||
637 | nsim_trap_item->action = action; | ||
638 | spin_unlock(&nsim_dev->trap_data->trap_lock); | ||
639 | |||
640 | return 0; | ||
641 | } | ||
642 | |||
372 | static const struct devlink_ops nsim_dev_devlink_ops = { | 643 | static const struct devlink_ops nsim_dev_devlink_ops = { |
373 | .reload = nsim_dev_reload, | 644 | .reload = nsim_dev_reload, |
374 | .flash_update = nsim_dev_flash_update, | 645 | .flash_update = nsim_dev_flash_update, |
646 | .trap_init = nsim_dev_devlink_trap_init, | ||
647 | .trap_action_set = nsim_dev_devlink_trap_action_set, | ||
375 | }; | 648 | }; |
376 | 649 | ||
377 | #define NSIM_DEV_MAX_MACS_DEFAULT 32 | 650 | #define NSIM_DEV_MAX_MACS_DEFAULT 32 |
@@ -421,10 +694,14 @@ nsim_dev_create(struct nsim_bus_dev *nsim_bus_dev, unsigned int port_count) | |||
421 | if (err) | 694 | if (err) |
422 | goto err_params_unregister; | 695 | goto err_params_unregister; |
423 | 696 | ||
424 | err = nsim_dev_debugfs_init(nsim_dev); | 697 | err = nsim_dev_traps_init(devlink); |
425 | if (err) | 698 | if (err) |
426 | goto err_dummy_region_exit; | 699 | goto err_dummy_region_exit; |
427 | 700 | ||
701 | err = nsim_dev_debugfs_init(nsim_dev); | ||
702 | if (err) | ||
703 | goto err_traps_exit; | ||
704 | |||
428 | err = nsim_bpf_dev_init(nsim_dev); | 705 | err = nsim_bpf_dev_init(nsim_dev); |
429 | if (err) | 706 | if (err) |
430 | goto err_debugfs_exit; | 707 | goto err_debugfs_exit; |
@@ -434,6 +711,8 @@ nsim_dev_create(struct nsim_bus_dev *nsim_bus_dev, unsigned int port_count) | |||
434 | 711 | ||
435 | err_debugfs_exit: | 712 | err_debugfs_exit: |
436 | nsim_dev_debugfs_exit(nsim_dev); | 713 | nsim_dev_debugfs_exit(nsim_dev); |
714 | err_traps_exit: | ||
715 | nsim_dev_traps_exit(devlink); | ||
437 | err_dummy_region_exit: | 716 | err_dummy_region_exit: |
438 | nsim_dev_dummy_region_exit(nsim_dev); | 717 | nsim_dev_dummy_region_exit(nsim_dev); |
439 | err_params_unregister: | 718 | err_params_unregister: |
@@ -456,6 +735,7 @@ static void nsim_dev_destroy(struct nsim_dev *nsim_dev) | |||
456 | 735 | ||
457 | nsim_bpf_dev_exit(nsim_dev); | 736 | nsim_bpf_dev_exit(nsim_dev); |
458 | nsim_dev_debugfs_exit(nsim_dev); | 737 | nsim_dev_debugfs_exit(nsim_dev); |
738 | nsim_dev_traps_exit(devlink); | ||
459 | nsim_dev_dummy_region_exit(nsim_dev); | 739 | nsim_dev_dummy_region_exit(nsim_dev); |
460 | devlink_params_unregister(devlink, nsim_devlink_params, | 740 | devlink_params_unregister(devlink, nsim_devlink_params, |
461 | ARRAY_SIZE(nsim_devlink_params)); | 741 | ARRAY_SIZE(nsim_devlink_params)); |