diff options
-rw-r--r-- | Documentation/networking/devlink-trap-netdevsim.rst | 20 | ||||
-rw-r--r-- | Documentation/networking/devlink-trap.rst | 208 | ||||
-rw-r--r-- | Documentation/networking/index.rst | 2 | ||||
-rw-r--r-- | MAINTAINERS | 1 | ||||
-rw-r--r-- | drivers/net/netdevsim/dev.c | 282 | ||||
-rw-r--r-- | drivers/net/netdevsim/netdevsim.h | 1 | ||||
-rw-r--r-- | include/net/devlink.h | 175 | ||||
-rw-r--r-- | include/net/drop_monitor.h | 33 | ||||
-rw-r--r-- | include/uapi/linux/devlink.h | 62 | ||||
-rw-r--r-- | include/uapi/linux/net_dropmon.h | 15 | ||||
-rw-r--r-- | net/Kconfig | 1 | ||||
-rw-r--r-- | net/core/devlink.c | 1080 | ||||
-rw-r--r-- | net/core/drop_monitor.c | 724 | ||||
-rwxr-xr-x | tools/testing/selftests/drivers/net/netdevsim/devlink_trap.sh | 364 | ||||
-rw-r--r-- | tools/testing/selftests/net/forwarding/devlink_lib.sh | 189 |
15 files changed, 3116 insertions, 41 deletions
diff --git a/Documentation/networking/devlink-trap-netdevsim.rst b/Documentation/networking/devlink-trap-netdevsim.rst new file mode 100644 index 000000000000..b721c9415473 --- /dev/null +++ b/Documentation/networking/devlink-trap-netdevsim.rst | |||
@@ -0,0 +1,20 @@ | |||
1 | .. SPDX-License-Identifier: GPL-2.0 | ||
2 | |||
3 | ====================== | ||
4 | Devlink Trap netdevsim | ||
5 | ====================== | ||
6 | |||
7 | Driver-specific Traps | ||
8 | ===================== | ||
9 | |||
10 | .. list-table:: List of Driver-specific Traps Registered by ``netdevsim`` | ||
11 | :widths: 5 5 90 | ||
12 | |||
13 | * - Name | ||
14 | - Type | ||
15 | - Description | ||
16 | * - ``fid_miss`` | ||
17 | - ``exception`` | ||
18 | - When a packet enters the device it is classified to a filtering | ||
19 | indentifier (FID) based on the ingress port and VLAN. This trap is used | ||
20 | to trap packets for which a FID could not be found | ||
diff --git a/Documentation/networking/devlink-trap.rst b/Documentation/networking/devlink-trap.rst new file mode 100644 index 000000000000..c20c7c483664 --- /dev/null +++ b/Documentation/networking/devlink-trap.rst | |||
@@ -0,0 +1,208 @@ | |||
1 | .. SPDX-License-Identifier: GPL-2.0 | ||
2 | |||
3 | ============ | ||
4 | Devlink Trap | ||
5 | ============ | ||
6 | |||
7 | Background | ||
8 | ========== | ||
9 | |||
10 | Devices capable of offloading the kernel's datapath and perform functions such | ||
11 | as bridging and routing must also be able to send specific packets to the | ||
12 | kernel (i.e., the CPU) for processing. | ||
13 | |||
14 | For example, a device acting as a multicast-aware bridge must be able to send | ||
15 | IGMP membership reports to the kernel for processing by the bridge module. | ||
16 | Without processing such packets, the bridge module could never populate its | ||
17 | MDB. | ||
18 | |||
19 | As another example, consider a device acting as router which has received an IP | ||
20 | packet with a TTL of 1. Upon routing the packet the device must send it to the | ||
21 | kernel so that it will route it as well and generate an ICMP Time Exceeded | ||
22 | error datagram. Without letting the kernel route such packets itself, utilities | ||
23 | such as ``traceroute`` could never work. | ||
24 | |||
25 | The fundamental ability of sending certain packets to the kernel for processing | ||
26 | is called "packet trapping". | ||
27 | |||
28 | Overview | ||
29 | ======== | ||
30 | |||
31 | The ``devlink-trap`` mechanism allows capable device drivers to register their | ||
32 | supported packet traps with ``devlink`` and report trapped packets to | ||
33 | ``devlink`` for further analysis. | ||
34 | |||
35 | Upon receiving trapped packets, ``devlink`` will perform a per-trap packets and | ||
36 | bytes accounting and potentially report the packet to user space via a netlink | ||
37 | event along with all the provided metadata (e.g., trap reason, timestamp, input | ||
38 | port). This is especially useful for drop traps (see :ref:`Trap-Types`) | ||
39 | as it allows users to obtain further visibility into packet drops that would | ||
40 | otherwise be invisible. | ||
41 | |||
42 | The following diagram provides a general overview of ``devlink-trap``:: | ||
43 | |||
44 | Netlink event: Packet w/ metadata | ||
45 | Or a summary of recent drops | ||
46 | ^ | ||
47 | | | ||
48 | Userspace | | ||
49 | +---------------------------------------------------+ | ||
50 | Kernel | | ||
51 | | | ||
52 | +-------+--------+ | ||
53 | | | | ||
54 | | drop_monitor | | ||
55 | | | | ||
56 | +-------^--------+ | ||
57 | | | ||
58 | | | ||
59 | | | ||
60 | +----+----+ | ||
61 | | | Kernel's Rx path | ||
62 | | devlink | (non-drop traps) | ||
63 | | | | ||
64 | +----^----+ ^ | ||
65 | | | | ||
66 | +-----------+ | ||
67 | | | ||
68 | +-------+-------+ | ||
69 | | | | ||
70 | | Device driver | | ||
71 | | | | ||
72 | +-------^-------+ | ||
73 | Kernel | | ||
74 | +---------------------------------------------------+ | ||
75 | Hardware | | ||
76 | | Trapped packet | ||
77 | | | ||
78 | +--+---+ | ||
79 | | | | ||
80 | | ASIC | | ||
81 | | | | ||
82 | +------+ | ||
83 | |||
84 | .. _Trap-Types: | ||
85 | |||
86 | Trap Types | ||
87 | ========== | ||
88 | |||
89 | The ``devlink-trap`` mechanism supports the following packet trap types: | ||
90 | |||
91 | * ``drop``: Trapped packets were dropped by the underlying device. Packets | ||
92 | are only processed by ``devlink`` and not injected to the kernel's Rx path. | ||
93 | The trap action (see :ref:`Trap-Actions`) can be changed. | ||
94 | * ``exception``: Trapped packets were not forwarded as intended by the | ||
95 | underlying device due to an exception (e.g., TTL error, missing neighbour | ||
96 | entry) and trapped to the control plane for resolution. Packets are | ||
97 | processed by ``devlink`` and injected to the kernel's Rx path. Changing the | ||
98 | action of such traps is not allowed, as it can easily break the control | ||
99 | plane. | ||
100 | |||
101 | .. _Trap-Actions: | ||
102 | |||
103 | Trap Actions | ||
104 | ============ | ||
105 | |||
106 | The ``devlink-trap`` mechanism supports the following packet trap actions: | ||
107 | |||
108 | * ``trap``: The sole copy of the packet is sent to the CPU. | ||
109 | * ``drop``: The packet is dropped by the underlying device and a copy is not | ||
110 | sent to the CPU. | ||
111 | |||
112 | Generic Packet Traps | ||
113 | ==================== | ||
114 | |||
115 | Generic packet traps are used to describe traps that trap well-defined packets | ||
116 | or packets that are trapped due to well-defined conditions (e.g., TTL error). | ||
117 | Such traps can be shared by multiple device drivers and their description must | ||
118 | be added to the following table: | ||
119 | |||
120 | .. list-table:: List of Generic Packet Traps | ||
121 | :widths: 5 5 90 | ||
122 | |||
123 | * - Name | ||
124 | - Type | ||
125 | - Description | ||
126 | * - ``source_mac_is_multicast`` | ||
127 | - ``drop`` | ||
128 | - Traps incoming packets that the device decided to drop because of a | ||
129 | multicast source MAC | ||
130 | * - ``vlan_tag_mismatch`` | ||
131 | - ``drop`` | ||
132 | - Traps incoming packets that the device decided to drop in case of VLAN | ||
133 | tag mismatch: The ingress bridge port is not configured with a PVID and | ||
134 | the packet is untagged or prio-tagged | ||
135 | * - ``ingress_vlan_filter`` | ||
136 | - ``drop`` | ||
137 | - Traps incoming packets that the device decided to drop in case they are | ||
138 | tagged with a VLAN that is not configured on the ingress bridge port | ||
139 | * - ``ingress_spanning_tree_filter`` | ||
140 | - ``drop`` | ||
141 | - Traps incoming packets that the device decided to drop in case the STP | ||
142 | state of the ingress bridge port is not "forwarding" | ||
143 | * - ``port_list_is_empty`` | ||
144 | - ``drop`` | ||
145 | - Traps packets that the device decided to drop in case they need to be | ||
146 | flooded and the flood list is empty | ||
147 | * - ``port_loopback_filter`` | ||
148 | - ``drop`` | ||
149 | - Traps packets that the device decided to drop in case after layer 2 | ||
150 | forwarding the only port from which they should be transmitted through | ||
151 | is the port from which they were received | ||
152 | * - ``blackhole_route`` | ||
153 | - ``drop`` | ||
154 | - Traps packets that the device decided to drop in case they hit a | ||
155 | blackhole route | ||
156 | * - ``ttl_value_is_too_small`` | ||
157 | - ``exception`` | ||
158 | - Traps unicast packets that should be forwarded by the device whose TTL | ||
159 | was decremented to 0 or less | ||
160 | * - ``tail_drop`` | ||
161 | - ``drop`` | ||
162 | - Traps packets that the device decided to drop because they could not be | ||
163 | enqueued to a transmission queue which is full | ||
164 | |||
165 | Driver-specific Packet Traps | ||
166 | ============================ | ||
167 | |||
168 | Device drivers can register driver-specific packet traps, but these must be | ||
169 | clearly documented. Such traps can correspond to device-specific exceptions and | ||
170 | help debug packet drops caused by these exceptions. The following list includes | ||
171 | links to the description of driver-specific traps registered by various device | ||
172 | drivers: | ||
173 | |||
174 | * :doc:`/devlink-trap-netdevsim` | ||
175 | |||
176 | Generic Packet Trap Groups | ||
177 | ========================== | ||
178 | |||
179 | Generic packet trap groups are used to aggregate logically related packet | ||
180 | traps. These groups allow the user to batch operations such as setting the trap | ||
181 | action of all member traps. In addition, ``devlink-trap`` can report aggregated | ||
182 | per-group packets and bytes statistics, in case per-trap statistics are too | ||
183 | narrow. The description of these groups must be added to the following table: | ||
184 | |||
185 | .. list-table:: List of Generic Packet Trap Groups | ||
186 | :widths: 10 90 | ||
187 | |||
188 | * - Name | ||
189 | - Description | ||
190 | * - ``l2_drops`` | ||
191 | - Contains packet traps for packets that were dropped by the device during | ||
192 | layer 2 forwarding (i.e., bridge) | ||
193 | * - ``l3_drops`` | ||
194 | - Contains packet traps for packets that were dropped by the device or hit | ||
195 | an exception (e.g., TTL error) during layer 3 forwarding | ||
196 | * - ``buffer_drops`` | ||
197 | - Contains packet traps for packets that were dropped by the device due to | ||
198 | an enqueue decision | ||
199 | |||
200 | Testing | ||
201 | ======= | ||
202 | |||
203 | See ``tools/testing/selftests/drivers/net/netdevsim/devlink_trap.sh`` for a | ||
204 | test covering the core infrastructure. Test cases should be added for any new | ||
205 | functionality. | ||
206 | |||
207 | Device drivers should focus their tests on device-specific functionality, such | ||
208 | as the triggering of supported packet traps. | ||
diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst index a46fca264bee..37eabc17894c 100644 --- a/Documentation/networking/index.rst +++ b/Documentation/networking/index.rst | |||
@@ -14,6 +14,8 @@ Contents: | |||
14 | device_drivers/index | 14 | device_drivers/index |
15 | dsa/index | 15 | dsa/index |
16 | devlink-info-versions | 16 | devlink-info-versions |
17 | devlink-trap | ||
18 | devlink-trap-netdevsim | ||
17 | ieee802154 | 19 | ieee802154 |
18 | kapi | 20 | kapi |
19 | z8530book | 21 | z8530book |
diff --git a/MAINTAINERS b/MAINTAINERS index fd9ab61c2670..96d3e60697f5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -11156,6 +11156,7 @@ S: Maintained | |||
11156 | W: https://fedorahosted.org/dropwatch/ | 11156 | W: https://fedorahosted.org/dropwatch/ |
11157 | F: net/core/drop_monitor.c | 11157 | F: net/core/drop_monitor.c |
11158 | F: include/uapi/linux/net_dropmon.h | 11158 | F: include/uapi/linux/net_dropmon.h |
11159 | F: include/net/drop_monitor.h | ||
11159 | 11160 | ||
11160 | NETWORKING DRIVERS | 11161 | NETWORKING DRIVERS |
11161 | M: "David S. Miller" <davem@davemloft.net> | 11162 | M: "David S. Miller" <davem@davemloft.net> |
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)); |
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h index 4c758c6919f5..262a6978bbca 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h | |||
@@ -145,6 +145,7 @@ struct nsim_dev_port { | |||
145 | struct nsim_dev { | 145 | struct nsim_dev { |
146 | struct nsim_bus_dev *nsim_bus_dev; | 146 | struct nsim_bus_dev *nsim_bus_dev; |
147 | struct nsim_fib_data *fib_data; | 147 | struct nsim_fib_data *fib_data; |
148 | struct nsim_trap_data *trap_data; | ||
148 | struct dentry *ddir; | 149 | struct dentry *ddir; |
149 | struct dentry *ports_ddir; | 150 | struct dentry *ports_ddir; |
150 | struct bpf_offload_dev *bpf_dev; | 151 | struct bpf_offload_dev *bpf_dev; |
diff --git a/include/net/devlink.h b/include/net/devlink.h index 451268f64880..7f43c48f54cd 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h | |||
@@ -14,6 +14,7 @@ | |||
14 | #include <linux/netdevice.h> | 14 | #include <linux/netdevice.h> |
15 | #include <linux/spinlock.h> | 15 | #include <linux/spinlock.h> |
16 | #include <linux/workqueue.h> | 16 | #include <linux/workqueue.h> |
17 | #include <linux/refcount.h> | ||
17 | #include <net/net_namespace.h> | 18 | #include <net/net_namespace.h> |
18 | #include <uapi/linux/devlink.h> | 19 | #include <uapi/linux/devlink.h> |
19 | 20 | ||
@@ -31,6 +32,8 @@ struct devlink { | |||
31 | struct list_head reporter_list; | 32 | struct list_head reporter_list; |
32 | struct mutex reporters_lock; /* protects reporter_list */ | 33 | struct mutex reporters_lock; /* protects reporter_list */ |
33 | struct devlink_dpipe_headers *dpipe_headers; | 34 | struct devlink_dpipe_headers *dpipe_headers; |
35 | struct list_head trap_list; | ||
36 | struct list_head trap_group_list; | ||
34 | const struct devlink_ops *ops; | 37 | const struct devlink_ops *ops; |
35 | struct device *dev; | 38 | struct device *dev; |
36 | possible_net_t _net; | 39 | possible_net_t _net; |
@@ -497,6 +500,135 @@ struct devlink_health_reporter_ops { | |||
497 | struct devlink_fmsg *fmsg); | 500 | struct devlink_fmsg *fmsg); |
498 | }; | 501 | }; |
499 | 502 | ||
503 | /** | ||
504 | * struct devlink_trap_group - Immutable packet trap group attributes. | ||
505 | * @name: Trap group name. | ||
506 | * @id: Trap group identifier. | ||
507 | * @generic: Whether the trap group is generic or not. | ||
508 | * | ||
509 | * Describes immutable attributes of packet trap groups that drivers register | ||
510 | * with devlink. | ||
511 | */ | ||
512 | struct devlink_trap_group { | ||
513 | const char *name; | ||
514 | u16 id; | ||
515 | bool generic; | ||
516 | }; | ||
517 | |||
518 | #define DEVLINK_TRAP_METADATA_TYPE_F_IN_PORT BIT(0) | ||
519 | |||
520 | /** | ||
521 | * struct devlink_trap - Immutable packet trap attributes. | ||
522 | * @type: Trap type. | ||
523 | * @init_action: Initial trap action. | ||
524 | * @generic: Whether the trap is generic or not. | ||
525 | * @id: Trap identifier. | ||
526 | * @name: Trap name. | ||
527 | * @group: Immutable packet trap group attributes. | ||
528 | * @metadata_cap: Metadata types that can be provided by the trap. | ||
529 | * | ||
530 | * Describes immutable attributes of packet traps that drivers register with | ||
531 | * devlink. | ||
532 | */ | ||
533 | struct devlink_trap { | ||
534 | enum devlink_trap_type type; | ||
535 | enum devlink_trap_action init_action; | ||
536 | bool generic; | ||
537 | u16 id; | ||
538 | const char *name; | ||
539 | struct devlink_trap_group group; | ||
540 | u32 metadata_cap; | ||
541 | }; | ||
542 | |||
543 | /* All traps must be documented in | ||
544 | * Documentation/networking/devlink-trap.rst | ||
545 | */ | ||
546 | enum devlink_trap_generic_id { | ||
547 | DEVLINK_TRAP_GENERIC_ID_SMAC_MC, | ||
548 | DEVLINK_TRAP_GENERIC_ID_VLAN_TAG_MISMATCH, | ||
549 | DEVLINK_TRAP_GENERIC_ID_INGRESS_VLAN_FILTER, | ||
550 | DEVLINK_TRAP_GENERIC_ID_INGRESS_STP_FILTER, | ||
551 | DEVLINK_TRAP_GENERIC_ID_EMPTY_TX_LIST, | ||
552 | DEVLINK_TRAP_GENERIC_ID_PORT_LOOPBACK_FILTER, | ||
553 | DEVLINK_TRAP_GENERIC_ID_BLACKHOLE_ROUTE, | ||
554 | DEVLINK_TRAP_GENERIC_ID_TTL_ERROR, | ||
555 | DEVLINK_TRAP_GENERIC_ID_TAIL_DROP, | ||
556 | |||
557 | /* Add new generic trap IDs above */ | ||
558 | __DEVLINK_TRAP_GENERIC_ID_MAX, | ||
559 | DEVLINK_TRAP_GENERIC_ID_MAX = __DEVLINK_TRAP_GENERIC_ID_MAX - 1, | ||
560 | }; | ||
561 | |||
562 | /* All trap groups must be documented in | ||
563 | * Documentation/networking/devlink-trap.rst | ||
564 | */ | ||
565 | enum devlink_trap_group_generic_id { | ||
566 | DEVLINK_TRAP_GROUP_GENERIC_ID_L2_DROPS, | ||
567 | DEVLINK_TRAP_GROUP_GENERIC_ID_L3_DROPS, | ||
568 | DEVLINK_TRAP_GROUP_GENERIC_ID_BUFFER_DROPS, | ||
569 | |||
570 | /* Add new generic trap group IDs above */ | ||
571 | __DEVLINK_TRAP_GROUP_GENERIC_ID_MAX, | ||
572 | DEVLINK_TRAP_GROUP_GENERIC_ID_MAX = | ||
573 | __DEVLINK_TRAP_GROUP_GENERIC_ID_MAX - 1, | ||
574 | }; | ||
575 | |||
576 | #define DEVLINK_TRAP_GENERIC_NAME_SMAC_MC \ | ||
577 | "source_mac_is_multicast" | ||
578 | #define DEVLINK_TRAP_GENERIC_NAME_VLAN_TAG_MISMATCH \ | ||
579 | "vlan_tag_mismatch" | ||
580 | #define DEVLINK_TRAP_GENERIC_NAME_INGRESS_VLAN_FILTER \ | ||
581 | "ingress_vlan_filter" | ||
582 | #define DEVLINK_TRAP_GENERIC_NAME_INGRESS_STP_FILTER \ | ||
583 | "ingress_spanning_tree_filter" | ||
584 | #define DEVLINK_TRAP_GENERIC_NAME_EMPTY_TX_LIST \ | ||
585 | "port_list_is_empty" | ||
586 | #define DEVLINK_TRAP_GENERIC_NAME_PORT_LOOPBACK_FILTER \ | ||
587 | "port_loopback_filter" | ||
588 | #define DEVLINK_TRAP_GENERIC_NAME_BLACKHOLE_ROUTE \ | ||
589 | "blackhole_route" | ||
590 | #define DEVLINK_TRAP_GENERIC_NAME_TTL_ERROR \ | ||
591 | "ttl_value_is_too_small" | ||
592 | #define DEVLINK_TRAP_GENERIC_NAME_TAIL_DROP \ | ||
593 | "tail_drop" | ||
594 | |||
595 | #define DEVLINK_TRAP_GROUP_GENERIC_NAME_L2_DROPS \ | ||
596 | "l2_drops" | ||
597 | #define DEVLINK_TRAP_GROUP_GENERIC_NAME_L3_DROPS \ | ||
598 | "l3_drops" | ||
599 | #define DEVLINK_TRAP_GROUP_GENERIC_NAME_BUFFER_DROPS \ | ||
600 | "buffer_drops" | ||
601 | |||
602 | #define DEVLINK_TRAP_GENERIC(_type, _init_action, _id, _group, _metadata_cap) \ | ||
603 | { \ | ||
604 | .type = DEVLINK_TRAP_TYPE_##_type, \ | ||
605 | .init_action = DEVLINK_TRAP_ACTION_##_init_action, \ | ||
606 | .generic = true, \ | ||
607 | .id = DEVLINK_TRAP_GENERIC_ID_##_id, \ | ||
608 | .name = DEVLINK_TRAP_GENERIC_NAME_##_id, \ | ||
609 | .group = _group, \ | ||
610 | .metadata_cap = _metadata_cap, \ | ||
611 | } | ||
612 | |||
613 | #define DEVLINK_TRAP_DRIVER(_type, _init_action, _id, _name, _group, \ | ||
614 | _metadata_cap) \ | ||
615 | { \ | ||
616 | .type = DEVLINK_TRAP_TYPE_##_type, \ | ||
617 | .init_action = DEVLINK_TRAP_ACTION_##_init_action, \ | ||
618 | .generic = false, \ | ||
619 | .id = _id, \ | ||
620 | .name = _name, \ | ||
621 | .group = _group, \ | ||
622 | .metadata_cap = _metadata_cap, \ | ||
623 | } | ||
624 | |||
625 | #define DEVLINK_TRAP_GROUP_GENERIC(_id) \ | ||
626 | { \ | ||
627 | .name = DEVLINK_TRAP_GROUP_GENERIC_NAME_##_id, \ | ||
628 | .id = DEVLINK_TRAP_GROUP_GENERIC_ID_##_id, \ | ||
629 | .generic = true, \ | ||
630 | } | ||
631 | |||
500 | struct devlink_ops { | 632 | struct devlink_ops { |
501 | int (*reload)(struct devlink *devlink, struct netlink_ext_ack *extack); | 633 | int (*reload)(struct devlink *devlink, struct netlink_ext_ack *extack); |
502 | int (*port_type_set)(struct devlink_port *devlink_port, | 634 | int (*port_type_set)(struct devlink_port *devlink_port, |
@@ -558,6 +690,38 @@ struct devlink_ops { | |||
558 | int (*flash_update)(struct devlink *devlink, const char *file_name, | 690 | int (*flash_update)(struct devlink *devlink, const char *file_name, |
559 | const char *component, | 691 | const char *component, |
560 | struct netlink_ext_ack *extack); | 692 | struct netlink_ext_ack *extack); |
693 | /** | ||
694 | * @trap_init: Trap initialization function. | ||
695 | * | ||
696 | * Should be used by device drivers to initialize the trap in the | ||
697 | * underlying device. Drivers should also store the provided trap | ||
698 | * context, so that they could efficiently pass it to | ||
699 | * devlink_trap_report() when the trap is triggered. | ||
700 | */ | ||
701 | int (*trap_init)(struct devlink *devlink, | ||
702 | const struct devlink_trap *trap, void *trap_ctx); | ||
703 | /** | ||
704 | * @trap_fini: Trap de-initialization function. | ||
705 | * | ||
706 | * Should be used by device drivers to de-initialize the trap in the | ||
707 | * underlying device. | ||
708 | */ | ||
709 | void (*trap_fini)(struct devlink *devlink, | ||
710 | const struct devlink_trap *trap, void *trap_ctx); | ||
711 | /** | ||
712 | * @trap_action_set: Trap action set function. | ||
713 | */ | ||
714 | int (*trap_action_set)(struct devlink *devlink, | ||
715 | const struct devlink_trap *trap, | ||
716 | enum devlink_trap_action action); | ||
717 | /** | ||
718 | * @trap_group_init: Trap group initialization function. | ||
719 | * | ||
720 | * Should be used by device drivers to initialize the trap group in the | ||
721 | * underlying device. | ||
722 | */ | ||
723 | int (*trap_group_init)(struct devlink *devlink, | ||
724 | const struct devlink_trap_group *group); | ||
561 | }; | 725 | }; |
562 | 726 | ||
563 | static inline void *devlink_priv(struct devlink *devlink) | 727 | static inline void *devlink_priv(struct devlink *devlink) |
@@ -774,6 +938,17 @@ void devlink_flash_update_status_notify(struct devlink *devlink, | |||
774 | unsigned long done, | 938 | unsigned long done, |
775 | unsigned long total); | 939 | unsigned long total); |
776 | 940 | ||
941 | int devlink_traps_register(struct devlink *devlink, | ||
942 | const struct devlink_trap *traps, | ||
943 | size_t traps_count, void *priv); | ||
944 | void devlink_traps_unregister(struct devlink *devlink, | ||
945 | const struct devlink_trap *traps, | ||
946 | size_t traps_count); | ||
947 | void devlink_trap_report(struct devlink *devlink, | ||
948 | struct sk_buff *skb, void *trap_ctx, | ||
949 | struct devlink_port *in_devlink_port); | ||
950 | void *devlink_trap_ctx_priv(void *trap_ctx); | ||
951 | |||
777 | #if IS_ENABLED(CONFIG_NET_DEVLINK) | 952 | #if IS_ENABLED(CONFIG_NET_DEVLINK) |
778 | 953 | ||
779 | void devlink_compat_running_version(struct net_device *dev, | 954 | void devlink_compat_running_version(struct net_device *dev, |
diff --git a/include/net/drop_monitor.h b/include/net/drop_monitor.h new file mode 100644 index 000000000000..2ab668461463 --- /dev/null +++ b/include/net/drop_monitor.h | |||
@@ -0,0 +1,33 @@ | |||
1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
2 | |||
3 | #ifndef _NET_DROP_MONITOR_H_ | ||
4 | #define _NET_DROP_MONITOR_H_ | ||
5 | |||
6 | #include <linux/ktime.h> | ||
7 | #include <linux/netdevice.h> | ||
8 | #include <linux/skbuff.h> | ||
9 | |||
10 | /** | ||
11 | * struct net_dm_hw_metadata - Hardware-supplied packet metadata. | ||
12 | * @trap_group_name: Hardware trap group name. | ||
13 | * @trap_name: Hardware trap name. | ||
14 | * @input_dev: Input netdevice. | ||
15 | */ | ||
16 | struct net_dm_hw_metadata { | ||
17 | const char *trap_group_name; | ||
18 | const char *trap_name; | ||
19 | struct net_device *input_dev; | ||
20 | }; | ||
21 | |||
22 | #if IS_ENABLED(CONFIG_NET_DROP_MONITOR) | ||
23 | void net_dm_hw_report(struct sk_buff *skb, | ||
24 | const struct net_dm_hw_metadata *hw_metadata); | ||
25 | #else | ||
26 | static inline void | ||
27 | net_dm_hw_report(struct sk_buff *skb, | ||
28 | const struct net_dm_hw_metadata *hw_metadata) | ||
29 | { | ||
30 | } | ||
31 | #endif | ||
32 | |||
33 | #endif /* _NET_DROP_MONITOR_H_ */ | ||
diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index ffc993256527..546e75dd74ac 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h | |||
@@ -107,6 +107,16 @@ enum devlink_command { | |||
107 | DEVLINK_CMD_FLASH_UPDATE_END, /* notification only */ | 107 | DEVLINK_CMD_FLASH_UPDATE_END, /* notification only */ |
108 | DEVLINK_CMD_FLASH_UPDATE_STATUS, /* notification only */ | 108 | DEVLINK_CMD_FLASH_UPDATE_STATUS, /* notification only */ |
109 | 109 | ||
110 | DEVLINK_CMD_TRAP_GET, /* can dump */ | ||
111 | DEVLINK_CMD_TRAP_SET, | ||
112 | DEVLINK_CMD_TRAP_NEW, | ||
113 | DEVLINK_CMD_TRAP_DEL, | ||
114 | |||
115 | DEVLINK_CMD_TRAP_GROUP_GET, /* can dump */ | ||
116 | DEVLINK_CMD_TRAP_GROUP_SET, | ||
117 | DEVLINK_CMD_TRAP_GROUP_NEW, | ||
118 | DEVLINK_CMD_TRAP_GROUP_DEL, | ||
119 | |||
110 | /* add new commands above here */ | 120 | /* add new commands above here */ |
111 | __DEVLINK_CMD_MAX, | 121 | __DEVLINK_CMD_MAX, |
112 | DEVLINK_CMD_MAX = __DEVLINK_CMD_MAX - 1 | 122 | DEVLINK_CMD_MAX = __DEVLINK_CMD_MAX - 1 |
@@ -194,6 +204,47 @@ enum devlink_param_fw_load_policy_value { | |||
194 | DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH, | 204 | DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH, |
195 | }; | 205 | }; |
196 | 206 | ||
207 | enum { | ||
208 | DEVLINK_ATTR_STATS_RX_PACKETS, /* u64 */ | ||
209 | DEVLINK_ATTR_STATS_RX_BYTES, /* u64 */ | ||
210 | |||
211 | __DEVLINK_ATTR_STATS_MAX, | ||
212 | DEVLINK_ATTR_STATS_MAX = __DEVLINK_ATTR_STATS_MAX - 1 | ||
213 | }; | ||
214 | |||
215 | /** | ||
216 | * enum devlink_trap_action - Packet trap action. | ||
217 | * @DEVLINK_TRAP_ACTION_DROP: Packet is dropped by the device and a copy is not | ||
218 | * sent to the CPU. | ||
219 | * @DEVLINK_TRAP_ACTION_TRAP: The sole copy of the packet is sent to the CPU. | ||
220 | */ | ||
221 | enum devlink_trap_action { | ||
222 | DEVLINK_TRAP_ACTION_DROP, | ||
223 | DEVLINK_TRAP_ACTION_TRAP, | ||
224 | }; | ||
225 | |||
226 | /** | ||
227 | * enum devlink_trap_type - Packet trap type. | ||
228 | * @DEVLINK_TRAP_TYPE_DROP: Trap reason is a drop. Trapped packets are only | ||
229 | * processed by devlink and not injected to the | ||
230 | * kernel's Rx path. | ||
231 | * @DEVLINK_TRAP_TYPE_EXCEPTION: Trap reason is an exception. Packet was not | ||
232 | * forwarded as intended due to an exception | ||
233 | * (e.g., missing neighbour entry) and trapped to | ||
234 | * control plane for resolution. Trapped packets | ||
235 | * are processed by devlink and injected to | ||
236 | * the kernel's Rx path. | ||
237 | */ | ||
238 | enum devlink_trap_type { | ||
239 | DEVLINK_TRAP_TYPE_DROP, | ||
240 | DEVLINK_TRAP_TYPE_EXCEPTION, | ||
241 | }; | ||
242 | |||
243 | enum { | ||
244 | /* Trap can report input port as metadata */ | ||
245 | DEVLINK_ATTR_TRAP_METADATA_TYPE_IN_PORT, | ||
246 | }; | ||
247 | |||
197 | enum devlink_attr { | 248 | enum devlink_attr { |
198 | /* don't change the order or add anything between, this is ABI! */ | 249 | /* don't change the order or add anything between, this is ABI! */ |
199 | DEVLINK_ATTR_UNSPEC, | 250 | DEVLINK_ATTR_UNSPEC, |
@@ -348,6 +399,17 @@ enum devlink_attr { | |||
348 | DEVLINK_ATTR_PORT_PCI_PF_NUMBER, /* u16 */ | 399 | DEVLINK_ATTR_PORT_PCI_PF_NUMBER, /* u16 */ |
349 | DEVLINK_ATTR_PORT_PCI_VF_NUMBER, /* u16 */ | 400 | DEVLINK_ATTR_PORT_PCI_VF_NUMBER, /* u16 */ |
350 | 401 | ||
402 | DEVLINK_ATTR_STATS, /* nested */ | ||
403 | |||
404 | DEVLINK_ATTR_TRAP_NAME, /* string */ | ||
405 | /* enum devlink_trap_action */ | ||
406 | DEVLINK_ATTR_TRAP_ACTION, /* u8 */ | ||
407 | /* enum devlink_trap_type */ | ||
408 | DEVLINK_ATTR_TRAP_TYPE, /* u8 */ | ||
409 | DEVLINK_ATTR_TRAP_GENERIC, /* flag */ | ||
410 | DEVLINK_ATTR_TRAP_METADATA, /* nested */ | ||
411 | DEVLINK_ATTR_TRAP_GROUP_NAME, /* string */ | ||
412 | |||
351 | /* add new attributes above here, update the policy in devlink.c */ | 413 | /* add new attributes above here, update the policy in devlink.c */ |
352 | 414 | ||
353 | __DEVLINK_ATTR_MAX, | 415 | __DEVLINK_ATTR_MAX, |
diff --git a/include/uapi/linux/net_dropmon.h b/include/uapi/linux/net_dropmon.h index 405b31cbf723..75a35dccb675 100644 --- a/include/uapi/linux/net_dropmon.h +++ b/include/uapi/linux/net_dropmon.h | |||
@@ -83,6 +83,15 @@ enum net_dm_attr { | |||
83 | NET_DM_ATTR_ORIG_LEN, /* u32 */ | 83 | NET_DM_ATTR_ORIG_LEN, /* u32 */ |
84 | NET_DM_ATTR_QUEUE_LEN, /* u32 */ | 84 | NET_DM_ATTR_QUEUE_LEN, /* u32 */ |
85 | NET_DM_ATTR_STATS, /* nested */ | 85 | NET_DM_ATTR_STATS, /* nested */ |
86 | NET_DM_ATTR_HW_STATS, /* nested */ | ||
87 | NET_DM_ATTR_ORIGIN, /* u16 */ | ||
88 | NET_DM_ATTR_HW_TRAP_GROUP_NAME, /* string */ | ||
89 | NET_DM_ATTR_HW_TRAP_NAME, /* string */ | ||
90 | NET_DM_ATTR_HW_ENTRIES, /* nested */ | ||
91 | NET_DM_ATTR_HW_ENTRY, /* nested */ | ||
92 | NET_DM_ATTR_HW_TRAP_COUNT, /* u32 */ | ||
93 | NET_DM_ATTR_SW_DROPS, /* flag */ | ||
94 | NET_DM_ATTR_HW_DROPS, /* flag */ | ||
86 | 95 | ||
87 | __NET_DM_ATTR_MAX, | 96 | __NET_DM_ATTR_MAX, |
88 | NET_DM_ATTR_MAX = __NET_DM_ATTR_MAX - 1 | 97 | NET_DM_ATTR_MAX = __NET_DM_ATTR_MAX - 1 |
@@ -101,6 +110,7 @@ enum net_dm_alert_mode { | |||
101 | 110 | ||
102 | enum { | 111 | enum { |
103 | NET_DM_ATTR_PORT_NETDEV_IFINDEX, /* u32 */ | 112 | NET_DM_ATTR_PORT_NETDEV_IFINDEX, /* u32 */ |
113 | NET_DM_ATTR_PORT_NETDEV_NAME, /* string */ | ||
104 | 114 | ||
105 | __NET_DM_ATTR_PORT_MAX, | 115 | __NET_DM_ATTR_PORT_MAX, |
106 | NET_DM_ATTR_PORT_MAX = __NET_DM_ATTR_PORT_MAX - 1 | 116 | NET_DM_ATTR_PORT_MAX = __NET_DM_ATTR_PORT_MAX - 1 |
@@ -113,4 +123,9 @@ enum { | |||
113 | NET_DM_ATTR_STATS_MAX = __NET_DM_ATTR_STATS_MAX - 1 | 123 | NET_DM_ATTR_STATS_MAX = __NET_DM_ATTR_STATS_MAX - 1 |
114 | }; | 124 | }; |
115 | 125 | ||
126 | enum net_dm_origin { | ||
127 | NET_DM_ORIGIN_SW, | ||
128 | NET_DM_ORIGIN_HW, | ||
129 | }; | ||
130 | |||
116 | #endif | 131 | #endif |
diff --git a/net/Kconfig b/net/Kconfig index 57f51a279ad6..3101bfcbdd7a 100644 --- a/net/Kconfig +++ b/net/Kconfig | |||
@@ -430,6 +430,7 @@ config NET_SOCK_MSG | |||
430 | config NET_DEVLINK | 430 | config NET_DEVLINK |
431 | bool | 431 | bool |
432 | default n | 432 | default n |
433 | imply NET_DROP_MONITOR | ||
433 | 434 | ||
434 | config PAGE_POOL | 435 | config PAGE_POOL |
435 | bool | 436 | bool |
diff --git a/net/core/devlink.c b/net/core/devlink.c index d3dbb904bf3b..650f36379203 100644 --- a/net/core/devlink.c +++ b/net/core/devlink.c | |||
@@ -18,6 +18,8 @@ | |||
18 | #include <linux/spinlock.h> | 18 | #include <linux/spinlock.h> |
19 | #include <linux/refcount.h> | 19 | #include <linux/refcount.h> |
20 | #include <linux/workqueue.h> | 20 | #include <linux/workqueue.h> |
21 | #include <linux/u64_stats_sync.h> | ||
22 | #include <linux/timekeeping.h> | ||
21 | #include <rdma/ib_verbs.h> | 23 | #include <rdma/ib_verbs.h> |
22 | #include <net/netlink.h> | 24 | #include <net/netlink.h> |
23 | #include <net/genetlink.h> | 25 | #include <net/genetlink.h> |
@@ -25,6 +27,7 @@ | |||
25 | #include <net/net_namespace.h> | 27 | #include <net/net_namespace.h> |
26 | #include <net/sock.h> | 28 | #include <net/sock.h> |
27 | #include <net/devlink.h> | 29 | #include <net/devlink.h> |
30 | #include <net/drop_monitor.h> | ||
28 | #define CREATE_TRACE_POINTS | 31 | #define CREATE_TRACE_POINTS |
29 | #include <trace/events/devlink.h> | 32 | #include <trace/events/devlink.h> |
30 | 33 | ||
@@ -551,7 +554,7 @@ static int devlink_nl_port_fill(struct sk_buff *msg, struct devlink *devlink, | |||
551 | if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) | 554 | if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) |
552 | goto nla_put_failure; | 555 | goto nla_put_failure; |
553 | 556 | ||
554 | spin_lock(&devlink_port->type_lock); | 557 | spin_lock_bh(&devlink_port->type_lock); |
555 | if (nla_put_u16(msg, DEVLINK_ATTR_PORT_TYPE, devlink_port->type)) | 558 | if (nla_put_u16(msg, DEVLINK_ATTR_PORT_TYPE, devlink_port->type)) |
556 | goto nla_put_failure_type_locked; | 559 | goto nla_put_failure_type_locked; |
557 | if (devlink_port->desired_type != DEVLINK_PORT_TYPE_NOTSET && | 560 | if (devlink_port->desired_type != DEVLINK_PORT_TYPE_NOTSET && |
@@ -576,7 +579,7 @@ static int devlink_nl_port_fill(struct sk_buff *msg, struct devlink *devlink, | |||
576 | ibdev->name)) | 579 | ibdev->name)) |
577 | goto nla_put_failure_type_locked; | 580 | goto nla_put_failure_type_locked; |
578 | } | 581 | } |
579 | spin_unlock(&devlink_port->type_lock); | 582 | spin_unlock_bh(&devlink_port->type_lock); |
580 | if (devlink_nl_port_attrs_put(msg, devlink_port)) | 583 | if (devlink_nl_port_attrs_put(msg, devlink_port)) |
581 | goto nla_put_failure; | 584 | goto nla_put_failure; |
582 | 585 | ||
@@ -584,7 +587,7 @@ static int devlink_nl_port_fill(struct sk_buff *msg, struct devlink *devlink, | |||
584 | return 0; | 587 | return 0; |
585 | 588 | ||
586 | nla_put_failure_type_locked: | 589 | nla_put_failure_type_locked: |
587 | spin_unlock(&devlink_port->type_lock); | 590 | spin_unlock_bh(&devlink_port->type_lock); |
588 | nla_put_failure: | 591 | nla_put_failure: |
589 | genlmsg_cancel(msg, hdr); | 592 | genlmsg_cancel(msg, hdr); |
590 | return -EMSGSIZE; | 593 | return -EMSGSIZE; |
@@ -5154,6 +5157,571 @@ devlink_nl_cmd_health_reporter_dump_clear_doit(struct sk_buff *skb, | |||
5154 | return 0; | 5157 | return 0; |
5155 | } | 5158 | } |
5156 | 5159 | ||
5160 | struct devlink_stats { | ||
5161 | u64 rx_bytes; | ||
5162 | u64 rx_packets; | ||
5163 | struct u64_stats_sync syncp; | ||
5164 | }; | ||
5165 | |||
5166 | /** | ||
5167 | * struct devlink_trap_group_item - Packet trap group attributes. | ||
5168 | * @group: Immutable packet trap group attributes. | ||
5169 | * @refcount: Number of trap items using the group. | ||
5170 | * @list: trap_group_list member. | ||
5171 | * @stats: Trap group statistics. | ||
5172 | * | ||
5173 | * Describes packet trap group attributes. Created by devlink during trap | ||
5174 | * registration. | ||
5175 | */ | ||
5176 | struct devlink_trap_group_item { | ||
5177 | const struct devlink_trap_group *group; | ||
5178 | refcount_t refcount; | ||
5179 | struct list_head list; | ||
5180 | struct devlink_stats __percpu *stats; | ||
5181 | }; | ||
5182 | |||
5183 | /** | ||
5184 | * struct devlink_trap_item - Packet trap attributes. | ||
5185 | * @trap: Immutable packet trap attributes. | ||
5186 | * @group_item: Associated group item. | ||
5187 | * @list: trap_list member. | ||
5188 | * @action: Trap action. | ||
5189 | * @stats: Trap statistics. | ||
5190 | * @priv: Driver private information. | ||
5191 | * | ||
5192 | * Describes both mutable and immutable packet trap attributes. Created by | ||
5193 | * devlink during trap registration and used for all trap related operations. | ||
5194 | */ | ||
5195 | struct devlink_trap_item { | ||
5196 | const struct devlink_trap *trap; | ||
5197 | struct devlink_trap_group_item *group_item; | ||
5198 | struct list_head list; | ||
5199 | enum devlink_trap_action action; | ||
5200 | struct devlink_stats __percpu *stats; | ||
5201 | void *priv; | ||
5202 | }; | ||
5203 | |||
5204 | static struct devlink_trap_item * | ||
5205 | devlink_trap_item_lookup(struct devlink *devlink, const char *name) | ||
5206 | { | ||
5207 | struct devlink_trap_item *trap_item; | ||
5208 | |||
5209 | list_for_each_entry(trap_item, &devlink->trap_list, list) { | ||
5210 | if (!strcmp(trap_item->trap->name, name)) | ||
5211 | return trap_item; | ||
5212 | } | ||
5213 | |||
5214 | return NULL; | ||
5215 | } | ||
5216 | |||
5217 | static struct devlink_trap_item * | ||
5218 | devlink_trap_item_get_from_info(struct devlink *devlink, | ||
5219 | struct genl_info *info) | ||
5220 | { | ||
5221 | struct nlattr *attr; | ||
5222 | |||
5223 | if (!info->attrs[DEVLINK_ATTR_TRAP_NAME]) | ||
5224 | return NULL; | ||
5225 | attr = info->attrs[DEVLINK_ATTR_TRAP_NAME]; | ||
5226 | |||
5227 | return devlink_trap_item_lookup(devlink, nla_data(attr)); | ||
5228 | } | ||
5229 | |||
5230 | static int | ||
5231 | devlink_trap_action_get_from_info(struct genl_info *info, | ||
5232 | enum devlink_trap_action *p_trap_action) | ||
5233 | { | ||
5234 | u8 val; | ||
5235 | |||
5236 | val = nla_get_u8(info->attrs[DEVLINK_ATTR_TRAP_ACTION]); | ||
5237 | switch (val) { | ||
5238 | case DEVLINK_TRAP_ACTION_DROP: /* fall-through */ | ||
5239 | case DEVLINK_TRAP_ACTION_TRAP: | ||
5240 | *p_trap_action = val; | ||
5241 | break; | ||
5242 | default: | ||
5243 | return -EINVAL; | ||
5244 | } | ||
5245 | |||
5246 | return 0; | ||
5247 | } | ||
5248 | |||
5249 | static int devlink_trap_metadata_put(struct sk_buff *msg, | ||
5250 | const struct devlink_trap *trap) | ||
5251 | { | ||
5252 | struct nlattr *attr; | ||
5253 | |||
5254 | attr = nla_nest_start(msg, DEVLINK_ATTR_TRAP_METADATA); | ||
5255 | if (!attr) | ||
5256 | return -EMSGSIZE; | ||
5257 | |||
5258 | if ((trap->metadata_cap & DEVLINK_TRAP_METADATA_TYPE_F_IN_PORT) && | ||
5259 | nla_put_flag(msg, DEVLINK_ATTR_TRAP_METADATA_TYPE_IN_PORT)) | ||
5260 | goto nla_put_failure; | ||
5261 | |||
5262 | nla_nest_end(msg, attr); | ||
5263 | |||
5264 | return 0; | ||
5265 | |||
5266 | nla_put_failure: | ||
5267 | nla_nest_cancel(msg, attr); | ||
5268 | return -EMSGSIZE; | ||
5269 | } | ||
5270 | |||
5271 | static void devlink_trap_stats_read(struct devlink_stats __percpu *trap_stats, | ||
5272 | struct devlink_stats *stats) | ||
5273 | { | ||
5274 | int i; | ||
5275 | |||
5276 | memset(stats, 0, sizeof(*stats)); | ||
5277 | for_each_possible_cpu(i) { | ||
5278 | struct devlink_stats *cpu_stats; | ||
5279 | u64 rx_packets, rx_bytes; | ||
5280 | unsigned int start; | ||
5281 | |||
5282 | cpu_stats = per_cpu_ptr(trap_stats, i); | ||
5283 | do { | ||
5284 | start = u64_stats_fetch_begin_irq(&cpu_stats->syncp); | ||
5285 | rx_packets = cpu_stats->rx_packets; | ||
5286 | rx_bytes = cpu_stats->rx_bytes; | ||
5287 | } while (u64_stats_fetch_retry_irq(&cpu_stats->syncp, start)); | ||
5288 | |||
5289 | stats->rx_packets += rx_packets; | ||
5290 | stats->rx_bytes += rx_bytes; | ||
5291 | } | ||
5292 | } | ||
5293 | |||
5294 | static int devlink_trap_stats_put(struct sk_buff *msg, | ||
5295 | struct devlink_stats __percpu *trap_stats) | ||
5296 | { | ||
5297 | struct devlink_stats stats; | ||
5298 | struct nlattr *attr; | ||
5299 | |||
5300 | devlink_trap_stats_read(trap_stats, &stats); | ||
5301 | |||
5302 | attr = nla_nest_start(msg, DEVLINK_ATTR_STATS); | ||
5303 | if (!attr) | ||
5304 | return -EMSGSIZE; | ||
5305 | |||
5306 | if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_PACKETS, | ||
5307 | stats.rx_packets, DEVLINK_ATTR_PAD)) | ||
5308 | goto nla_put_failure; | ||
5309 | |||
5310 | if (nla_put_u64_64bit(msg, DEVLINK_ATTR_STATS_RX_BYTES, | ||
5311 | stats.rx_bytes, DEVLINK_ATTR_PAD)) | ||
5312 | goto nla_put_failure; | ||
5313 | |||
5314 | nla_nest_end(msg, attr); | ||
5315 | |||
5316 | return 0; | ||
5317 | |||
5318 | nla_put_failure: | ||
5319 | nla_nest_cancel(msg, attr); | ||
5320 | return -EMSGSIZE; | ||
5321 | } | ||
5322 | |||
5323 | static int devlink_nl_trap_fill(struct sk_buff *msg, struct devlink *devlink, | ||
5324 | const struct devlink_trap_item *trap_item, | ||
5325 | enum devlink_command cmd, u32 portid, u32 seq, | ||
5326 | int flags) | ||
5327 | { | ||
5328 | struct devlink_trap_group_item *group_item = trap_item->group_item; | ||
5329 | void *hdr; | ||
5330 | int err; | ||
5331 | |||
5332 | hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); | ||
5333 | if (!hdr) | ||
5334 | return -EMSGSIZE; | ||
5335 | |||
5336 | if (devlink_nl_put_handle(msg, devlink)) | ||
5337 | goto nla_put_failure; | ||
5338 | |||
5339 | if (nla_put_string(msg, DEVLINK_ATTR_TRAP_GROUP_NAME, | ||
5340 | group_item->group->name)) | ||
5341 | goto nla_put_failure; | ||
5342 | |||
5343 | if (nla_put_string(msg, DEVLINK_ATTR_TRAP_NAME, trap_item->trap->name)) | ||
5344 | goto nla_put_failure; | ||
5345 | |||
5346 | if (nla_put_u8(msg, DEVLINK_ATTR_TRAP_TYPE, trap_item->trap->type)) | ||
5347 | goto nla_put_failure; | ||
5348 | |||
5349 | if (trap_item->trap->generic && | ||
5350 | nla_put_flag(msg, DEVLINK_ATTR_TRAP_GENERIC)) | ||
5351 | goto nla_put_failure; | ||
5352 | |||
5353 | if (nla_put_u8(msg, DEVLINK_ATTR_TRAP_ACTION, trap_item->action)) | ||
5354 | goto nla_put_failure; | ||
5355 | |||
5356 | err = devlink_trap_metadata_put(msg, trap_item->trap); | ||
5357 | if (err) | ||
5358 | goto nla_put_failure; | ||
5359 | |||
5360 | err = devlink_trap_stats_put(msg, trap_item->stats); | ||
5361 | if (err) | ||
5362 | goto nla_put_failure; | ||
5363 | |||
5364 | genlmsg_end(msg, hdr); | ||
5365 | |||
5366 | return 0; | ||
5367 | |||
5368 | nla_put_failure: | ||
5369 | genlmsg_cancel(msg, hdr); | ||
5370 | return -EMSGSIZE; | ||
5371 | } | ||
5372 | |||
5373 | static int devlink_nl_cmd_trap_get_doit(struct sk_buff *skb, | ||
5374 | struct genl_info *info) | ||
5375 | { | ||
5376 | struct netlink_ext_ack *extack = info->extack; | ||
5377 | struct devlink *devlink = info->user_ptr[0]; | ||
5378 | struct devlink_trap_item *trap_item; | ||
5379 | struct sk_buff *msg; | ||
5380 | int err; | ||
5381 | |||
5382 | if (list_empty(&devlink->trap_list)) | ||
5383 | return -EOPNOTSUPP; | ||
5384 | |||
5385 | trap_item = devlink_trap_item_get_from_info(devlink, info); | ||
5386 | if (!trap_item) { | ||
5387 | NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap"); | ||
5388 | return -ENOENT; | ||
5389 | } | ||
5390 | |||
5391 | msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); | ||
5392 | if (!msg) | ||
5393 | return -ENOMEM; | ||
5394 | |||
5395 | err = devlink_nl_trap_fill(msg, devlink, trap_item, | ||
5396 | DEVLINK_CMD_TRAP_NEW, info->snd_portid, | ||
5397 | info->snd_seq, 0); | ||
5398 | if (err) | ||
5399 | goto err_trap_fill; | ||
5400 | |||
5401 | return genlmsg_reply(msg, info); | ||
5402 | |||
5403 | err_trap_fill: | ||
5404 | nlmsg_free(msg); | ||
5405 | return err; | ||
5406 | } | ||
5407 | |||
5408 | static int devlink_nl_cmd_trap_get_dumpit(struct sk_buff *msg, | ||
5409 | struct netlink_callback *cb) | ||
5410 | { | ||
5411 | struct devlink_trap_item *trap_item; | ||
5412 | struct devlink *devlink; | ||
5413 | int start = cb->args[0]; | ||
5414 | int idx = 0; | ||
5415 | int err; | ||
5416 | |||
5417 | mutex_lock(&devlink_mutex); | ||
5418 | list_for_each_entry(devlink, &devlink_list, list) { | ||
5419 | if (!net_eq(devlink_net(devlink), sock_net(msg->sk))) | ||
5420 | continue; | ||
5421 | mutex_lock(&devlink->lock); | ||
5422 | list_for_each_entry(trap_item, &devlink->trap_list, list) { | ||
5423 | if (idx < start) { | ||
5424 | idx++; | ||
5425 | continue; | ||
5426 | } | ||
5427 | err = devlink_nl_trap_fill(msg, devlink, trap_item, | ||
5428 | DEVLINK_CMD_TRAP_NEW, | ||
5429 | NETLINK_CB(cb->skb).portid, | ||
5430 | cb->nlh->nlmsg_seq, | ||
5431 | NLM_F_MULTI); | ||
5432 | if (err) { | ||
5433 | mutex_unlock(&devlink->lock); | ||
5434 | goto out; | ||
5435 | } | ||
5436 | idx++; | ||
5437 | } | ||
5438 | mutex_unlock(&devlink->lock); | ||
5439 | } | ||
5440 | out: | ||
5441 | mutex_unlock(&devlink_mutex); | ||
5442 | |||
5443 | cb->args[0] = idx; | ||
5444 | return msg->len; | ||
5445 | } | ||
5446 | |||
5447 | static int __devlink_trap_action_set(struct devlink *devlink, | ||
5448 | struct devlink_trap_item *trap_item, | ||
5449 | enum devlink_trap_action trap_action, | ||
5450 | struct netlink_ext_ack *extack) | ||
5451 | { | ||
5452 | int err; | ||
5453 | |||
5454 | if (trap_item->action != trap_action && | ||
5455 | trap_item->trap->type != DEVLINK_TRAP_TYPE_DROP) { | ||
5456 | NL_SET_ERR_MSG_MOD(extack, "Cannot change action of non-drop traps. Skipping"); | ||
5457 | return 0; | ||
5458 | } | ||
5459 | |||
5460 | err = devlink->ops->trap_action_set(devlink, trap_item->trap, | ||
5461 | trap_action); | ||
5462 | if (err) | ||
5463 | return err; | ||
5464 | |||
5465 | trap_item->action = trap_action; | ||
5466 | |||
5467 | return 0; | ||
5468 | } | ||
5469 | |||
5470 | static int devlink_trap_action_set(struct devlink *devlink, | ||
5471 | struct devlink_trap_item *trap_item, | ||
5472 | struct genl_info *info) | ||
5473 | { | ||
5474 | enum devlink_trap_action trap_action; | ||
5475 | int err; | ||
5476 | |||
5477 | if (!info->attrs[DEVLINK_ATTR_TRAP_ACTION]) | ||
5478 | return 0; | ||
5479 | |||
5480 | err = devlink_trap_action_get_from_info(info, &trap_action); | ||
5481 | if (err) { | ||
5482 | NL_SET_ERR_MSG_MOD(info->extack, "Invalid trap action"); | ||
5483 | return -EINVAL; | ||
5484 | } | ||
5485 | |||
5486 | return __devlink_trap_action_set(devlink, trap_item, trap_action, | ||
5487 | info->extack); | ||
5488 | } | ||
5489 | |||
5490 | static int devlink_nl_cmd_trap_set_doit(struct sk_buff *skb, | ||
5491 | struct genl_info *info) | ||
5492 | { | ||
5493 | struct netlink_ext_ack *extack = info->extack; | ||
5494 | struct devlink *devlink = info->user_ptr[0]; | ||
5495 | struct devlink_trap_item *trap_item; | ||
5496 | int err; | ||
5497 | |||
5498 | if (list_empty(&devlink->trap_list)) | ||
5499 | return -EOPNOTSUPP; | ||
5500 | |||
5501 | trap_item = devlink_trap_item_get_from_info(devlink, info); | ||
5502 | if (!trap_item) { | ||
5503 | NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap"); | ||
5504 | return -ENOENT; | ||
5505 | } | ||
5506 | |||
5507 | err = devlink_trap_action_set(devlink, trap_item, info); | ||
5508 | if (err) | ||
5509 | return err; | ||
5510 | |||
5511 | return 0; | ||
5512 | } | ||
5513 | |||
5514 | static struct devlink_trap_group_item * | ||
5515 | devlink_trap_group_item_lookup(struct devlink *devlink, const char *name) | ||
5516 | { | ||
5517 | struct devlink_trap_group_item *group_item; | ||
5518 | |||
5519 | list_for_each_entry(group_item, &devlink->trap_group_list, list) { | ||
5520 | if (!strcmp(group_item->group->name, name)) | ||
5521 | return group_item; | ||
5522 | } | ||
5523 | |||
5524 | return NULL; | ||
5525 | } | ||
5526 | |||
5527 | static struct devlink_trap_group_item * | ||
5528 | devlink_trap_group_item_get_from_info(struct devlink *devlink, | ||
5529 | struct genl_info *info) | ||
5530 | { | ||
5531 | char *name; | ||
5532 | |||
5533 | if (!info->attrs[DEVLINK_ATTR_TRAP_GROUP_NAME]) | ||
5534 | return NULL; | ||
5535 | name = nla_data(info->attrs[DEVLINK_ATTR_TRAP_GROUP_NAME]); | ||
5536 | |||
5537 | return devlink_trap_group_item_lookup(devlink, name); | ||
5538 | } | ||
5539 | |||
5540 | static int | ||
5541 | devlink_nl_trap_group_fill(struct sk_buff *msg, struct devlink *devlink, | ||
5542 | const struct devlink_trap_group_item *group_item, | ||
5543 | enum devlink_command cmd, u32 portid, u32 seq, | ||
5544 | int flags) | ||
5545 | { | ||
5546 | void *hdr; | ||
5547 | int err; | ||
5548 | |||
5549 | hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); | ||
5550 | if (!hdr) | ||
5551 | return -EMSGSIZE; | ||
5552 | |||
5553 | if (devlink_nl_put_handle(msg, devlink)) | ||
5554 | goto nla_put_failure; | ||
5555 | |||
5556 | if (nla_put_string(msg, DEVLINK_ATTR_TRAP_GROUP_NAME, | ||
5557 | group_item->group->name)) | ||
5558 | goto nla_put_failure; | ||
5559 | |||
5560 | if (group_item->group->generic && | ||
5561 | nla_put_flag(msg, DEVLINK_ATTR_TRAP_GENERIC)) | ||
5562 | goto nla_put_failure; | ||
5563 | |||
5564 | err = devlink_trap_stats_put(msg, group_item->stats); | ||
5565 | if (err) | ||
5566 | goto nla_put_failure; | ||
5567 | |||
5568 | genlmsg_end(msg, hdr); | ||
5569 | |||
5570 | return 0; | ||
5571 | |||
5572 | nla_put_failure: | ||
5573 | genlmsg_cancel(msg, hdr); | ||
5574 | return -EMSGSIZE; | ||
5575 | } | ||
5576 | |||
5577 | static int devlink_nl_cmd_trap_group_get_doit(struct sk_buff *skb, | ||
5578 | struct genl_info *info) | ||
5579 | { | ||
5580 | struct netlink_ext_ack *extack = info->extack; | ||
5581 | struct devlink *devlink = info->user_ptr[0]; | ||
5582 | struct devlink_trap_group_item *group_item; | ||
5583 | struct sk_buff *msg; | ||
5584 | int err; | ||
5585 | |||
5586 | if (list_empty(&devlink->trap_group_list)) | ||
5587 | return -EOPNOTSUPP; | ||
5588 | |||
5589 | group_item = devlink_trap_group_item_get_from_info(devlink, info); | ||
5590 | if (!group_item) { | ||
5591 | NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap group"); | ||
5592 | return -ENOENT; | ||
5593 | } | ||
5594 | |||
5595 | msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); | ||
5596 | if (!msg) | ||
5597 | return -ENOMEM; | ||
5598 | |||
5599 | err = devlink_nl_trap_group_fill(msg, devlink, group_item, | ||
5600 | DEVLINK_CMD_TRAP_GROUP_NEW, | ||
5601 | info->snd_portid, info->snd_seq, 0); | ||
5602 | if (err) | ||
5603 | goto err_trap_group_fill; | ||
5604 | |||
5605 | return genlmsg_reply(msg, info); | ||
5606 | |||
5607 | err_trap_group_fill: | ||
5608 | nlmsg_free(msg); | ||
5609 | return err; | ||
5610 | } | ||
5611 | |||
5612 | static int devlink_nl_cmd_trap_group_get_dumpit(struct sk_buff *msg, | ||
5613 | struct netlink_callback *cb) | ||
5614 | { | ||
5615 | enum devlink_command cmd = DEVLINK_CMD_TRAP_GROUP_NEW; | ||
5616 | struct devlink_trap_group_item *group_item; | ||
5617 | u32 portid = NETLINK_CB(cb->skb).portid; | ||
5618 | struct devlink *devlink; | ||
5619 | int start = cb->args[0]; | ||
5620 | int idx = 0; | ||
5621 | int err; | ||
5622 | |||
5623 | mutex_lock(&devlink_mutex); | ||
5624 | list_for_each_entry(devlink, &devlink_list, list) { | ||
5625 | if (!net_eq(devlink_net(devlink), sock_net(msg->sk))) | ||
5626 | continue; | ||
5627 | mutex_lock(&devlink->lock); | ||
5628 | list_for_each_entry(group_item, &devlink->trap_group_list, | ||
5629 | list) { | ||
5630 | if (idx < start) { | ||
5631 | idx++; | ||
5632 | continue; | ||
5633 | } | ||
5634 | err = devlink_nl_trap_group_fill(msg, devlink, | ||
5635 | group_item, cmd, | ||
5636 | portid, | ||
5637 | cb->nlh->nlmsg_seq, | ||
5638 | NLM_F_MULTI); | ||
5639 | if (err) { | ||
5640 | mutex_unlock(&devlink->lock); | ||
5641 | goto out; | ||
5642 | } | ||
5643 | idx++; | ||
5644 | } | ||
5645 | mutex_unlock(&devlink->lock); | ||
5646 | } | ||
5647 | out: | ||
5648 | mutex_unlock(&devlink_mutex); | ||
5649 | |||
5650 | cb->args[0] = idx; | ||
5651 | return msg->len; | ||
5652 | } | ||
5653 | |||
5654 | static int | ||
5655 | __devlink_trap_group_action_set(struct devlink *devlink, | ||
5656 | struct devlink_trap_group_item *group_item, | ||
5657 | enum devlink_trap_action trap_action, | ||
5658 | struct netlink_ext_ack *extack) | ||
5659 | { | ||
5660 | const char *group_name = group_item->group->name; | ||
5661 | struct devlink_trap_item *trap_item; | ||
5662 | int err; | ||
5663 | |||
5664 | list_for_each_entry(trap_item, &devlink->trap_list, list) { | ||
5665 | if (strcmp(trap_item->trap->group.name, group_name)) | ||
5666 | continue; | ||
5667 | err = __devlink_trap_action_set(devlink, trap_item, | ||
5668 | trap_action, extack); | ||
5669 | if (err) | ||
5670 | return err; | ||
5671 | } | ||
5672 | |||
5673 | return 0; | ||
5674 | } | ||
5675 | |||
5676 | static int | ||
5677 | devlink_trap_group_action_set(struct devlink *devlink, | ||
5678 | struct devlink_trap_group_item *group_item, | ||
5679 | struct genl_info *info) | ||
5680 | { | ||
5681 | enum devlink_trap_action trap_action; | ||
5682 | int err; | ||
5683 | |||
5684 | if (!info->attrs[DEVLINK_ATTR_TRAP_ACTION]) | ||
5685 | return 0; | ||
5686 | |||
5687 | err = devlink_trap_action_get_from_info(info, &trap_action); | ||
5688 | if (err) { | ||
5689 | NL_SET_ERR_MSG_MOD(info->extack, "Invalid trap action"); | ||
5690 | return -EINVAL; | ||
5691 | } | ||
5692 | |||
5693 | err = __devlink_trap_group_action_set(devlink, group_item, trap_action, | ||
5694 | info->extack); | ||
5695 | if (err) | ||
5696 | return err; | ||
5697 | |||
5698 | return 0; | ||
5699 | } | ||
5700 | |||
5701 | static int devlink_nl_cmd_trap_group_set_doit(struct sk_buff *skb, | ||
5702 | struct genl_info *info) | ||
5703 | { | ||
5704 | struct netlink_ext_ack *extack = info->extack; | ||
5705 | struct devlink *devlink = info->user_ptr[0]; | ||
5706 | struct devlink_trap_group_item *group_item; | ||
5707 | int err; | ||
5708 | |||
5709 | if (list_empty(&devlink->trap_group_list)) | ||
5710 | return -EOPNOTSUPP; | ||
5711 | |||
5712 | group_item = devlink_trap_group_item_get_from_info(devlink, info); | ||
5713 | if (!group_item) { | ||
5714 | NL_SET_ERR_MSG_MOD(extack, "Device did not register this trap group"); | ||
5715 | return -ENOENT; | ||
5716 | } | ||
5717 | |||
5718 | err = devlink_trap_group_action_set(devlink, group_item, info); | ||
5719 | if (err) | ||
5720 | return err; | ||
5721 | |||
5722 | return 0; | ||
5723 | } | ||
5724 | |||
5157 | static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = { | 5725 | static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = { |
5158 | [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING }, | 5726 | [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING }, |
5159 | [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING }, | 5727 | [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING }, |
@@ -5184,6 +5752,9 @@ static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = { | |||
5184 | [DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER] = { .type = NLA_U8 }, | 5752 | [DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER] = { .type = NLA_U8 }, |
5185 | [DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME] = { .type = NLA_NUL_STRING }, | 5753 | [DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME] = { .type = NLA_NUL_STRING }, |
5186 | [DEVLINK_ATTR_FLASH_UPDATE_COMPONENT] = { .type = NLA_NUL_STRING }, | 5754 | [DEVLINK_ATTR_FLASH_UPDATE_COMPONENT] = { .type = NLA_NUL_STRING }, |
5755 | [DEVLINK_ATTR_TRAP_NAME] = { .type = NLA_NUL_STRING }, | ||
5756 | [DEVLINK_ATTR_TRAP_ACTION] = { .type = NLA_U8 }, | ||
5757 | [DEVLINK_ATTR_TRAP_GROUP_NAME] = { .type = NLA_NUL_STRING }, | ||
5187 | }; | 5758 | }; |
5188 | 5759 | ||
5189 | static const struct genl_ops devlink_nl_ops[] = { | 5760 | static const struct genl_ops devlink_nl_ops[] = { |
@@ -5483,6 +6054,32 @@ static const struct genl_ops devlink_nl_ops[] = { | |||
5483 | .flags = GENL_ADMIN_PERM, | 6054 | .flags = GENL_ADMIN_PERM, |
5484 | .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, | 6055 | .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, |
5485 | }, | 6056 | }, |
6057 | { | ||
6058 | .cmd = DEVLINK_CMD_TRAP_GET, | ||
6059 | .doit = devlink_nl_cmd_trap_get_doit, | ||
6060 | .dumpit = devlink_nl_cmd_trap_get_dumpit, | ||
6061 | .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, | ||
6062 | /* can be retrieved by unprivileged users */ | ||
6063 | }, | ||
6064 | { | ||
6065 | .cmd = DEVLINK_CMD_TRAP_SET, | ||
6066 | .doit = devlink_nl_cmd_trap_set_doit, | ||
6067 | .flags = GENL_ADMIN_PERM, | ||
6068 | .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, | ||
6069 | }, | ||
6070 | { | ||
6071 | .cmd = DEVLINK_CMD_TRAP_GROUP_GET, | ||
6072 | .doit = devlink_nl_cmd_trap_group_get_doit, | ||
6073 | .dumpit = devlink_nl_cmd_trap_group_get_dumpit, | ||
6074 | .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, | ||
6075 | /* can be retrieved by unprivileged users */ | ||
6076 | }, | ||
6077 | { | ||
6078 | .cmd = DEVLINK_CMD_TRAP_GROUP_SET, | ||
6079 | .doit = devlink_nl_cmd_trap_group_set_doit, | ||
6080 | .flags = GENL_ADMIN_PERM, | ||
6081 | .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, | ||
6082 | }, | ||
5486 | }; | 6083 | }; |
5487 | 6084 | ||
5488 | static struct genl_family devlink_nl_family __ro_after_init = { | 6085 | static struct genl_family devlink_nl_family __ro_after_init = { |
@@ -5528,6 +6125,8 @@ struct devlink *devlink_alloc(const struct devlink_ops *ops, size_t priv_size) | |||
5528 | INIT_LIST_HEAD(&devlink->param_list); | 6125 | INIT_LIST_HEAD(&devlink->param_list); |
5529 | INIT_LIST_HEAD(&devlink->region_list); | 6126 | INIT_LIST_HEAD(&devlink->region_list); |
5530 | INIT_LIST_HEAD(&devlink->reporter_list); | 6127 | INIT_LIST_HEAD(&devlink->reporter_list); |
6128 | INIT_LIST_HEAD(&devlink->trap_list); | ||
6129 | INIT_LIST_HEAD(&devlink->trap_group_list); | ||
5531 | mutex_init(&devlink->lock); | 6130 | mutex_init(&devlink->lock); |
5532 | mutex_init(&devlink->reporters_lock); | 6131 | mutex_init(&devlink->reporters_lock); |
5533 | return devlink; | 6132 | return devlink; |
@@ -5574,6 +6173,8 @@ void devlink_free(struct devlink *devlink) | |||
5574 | { | 6173 | { |
5575 | mutex_destroy(&devlink->reporters_lock); | 6174 | mutex_destroy(&devlink->reporters_lock); |
5576 | mutex_destroy(&devlink->lock); | 6175 | mutex_destroy(&devlink->lock); |
6176 | WARN_ON(!list_empty(&devlink->trap_group_list)); | ||
6177 | WARN_ON(!list_empty(&devlink->trap_list)); | ||
5577 | WARN_ON(!list_empty(&devlink->reporter_list)); | 6178 | WARN_ON(!list_empty(&devlink->reporter_list)); |
5578 | WARN_ON(!list_empty(&devlink->region_list)); | 6179 | WARN_ON(!list_empty(&devlink->region_list)); |
5579 | WARN_ON(!list_empty(&devlink->param_list)); | 6180 | WARN_ON(!list_empty(&devlink->param_list)); |
@@ -5678,10 +6279,10 @@ static void __devlink_port_type_set(struct devlink_port *devlink_port, | |||
5678 | if (WARN_ON(!devlink_port->registered)) | 6279 | if (WARN_ON(!devlink_port->registered)) |
5679 | return; | 6280 | return; |
5680 | devlink_port_type_warn_cancel(devlink_port); | 6281 | devlink_port_type_warn_cancel(devlink_port); |
5681 | spin_lock(&devlink_port->type_lock); | 6282 | spin_lock_bh(&devlink_port->type_lock); |
5682 | devlink_port->type = type; | 6283 | devlink_port->type = type; |
5683 | devlink_port->type_dev = type_dev; | 6284 | devlink_port->type_dev = type_dev; |
5684 | spin_unlock(&devlink_port->type_lock); | 6285 | spin_unlock_bh(&devlink_port->type_lock); |
5685 | devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); | 6286 | devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW); |
5686 | } | 6287 | } |
5687 | 6288 | ||
@@ -6834,6 +7435,475 @@ unlock: | |||
6834 | } | 7435 | } |
6835 | EXPORT_SYMBOL_GPL(devlink_region_snapshot_create); | 7436 | EXPORT_SYMBOL_GPL(devlink_region_snapshot_create); |
6836 | 7437 | ||
7438 | #define DEVLINK_TRAP(_id, _type) \ | ||
7439 | { \ | ||
7440 | .type = DEVLINK_TRAP_TYPE_##_type, \ | ||
7441 | .id = DEVLINK_TRAP_GENERIC_ID_##_id, \ | ||
7442 | .name = DEVLINK_TRAP_GENERIC_NAME_##_id, \ | ||
7443 | } | ||
7444 | |||
7445 | static const struct devlink_trap devlink_trap_generic[] = { | ||
7446 | DEVLINK_TRAP(SMAC_MC, DROP), | ||
7447 | DEVLINK_TRAP(VLAN_TAG_MISMATCH, DROP), | ||
7448 | DEVLINK_TRAP(INGRESS_VLAN_FILTER, DROP), | ||
7449 | DEVLINK_TRAP(INGRESS_STP_FILTER, DROP), | ||
7450 | DEVLINK_TRAP(EMPTY_TX_LIST, DROP), | ||
7451 | DEVLINK_TRAP(PORT_LOOPBACK_FILTER, DROP), | ||
7452 | DEVLINK_TRAP(BLACKHOLE_ROUTE, DROP), | ||
7453 | DEVLINK_TRAP(TTL_ERROR, EXCEPTION), | ||
7454 | DEVLINK_TRAP(TAIL_DROP, DROP), | ||
7455 | }; | ||
7456 | |||
7457 | #define DEVLINK_TRAP_GROUP(_id) \ | ||
7458 | { \ | ||
7459 | .id = DEVLINK_TRAP_GROUP_GENERIC_ID_##_id, \ | ||
7460 | .name = DEVLINK_TRAP_GROUP_GENERIC_NAME_##_id, \ | ||
7461 | } | ||
7462 | |||
7463 | static const struct devlink_trap_group devlink_trap_group_generic[] = { | ||
7464 | DEVLINK_TRAP_GROUP(L2_DROPS), | ||
7465 | DEVLINK_TRAP_GROUP(L3_DROPS), | ||
7466 | DEVLINK_TRAP_GROUP(BUFFER_DROPS), | ||
7467 | }; | ||
7468 | |||
7469 | static int devlink_trap_generic_verify(const struct devlink_trap *trap) | ||
7470 | { | ||
7471 | if (trap->id > DEVLINK_TRAP_GENERIC_ID_MAX) | ||
7472 | return -EINVAL; | ||
7473 | |||
7474 | if (strcmp(trap->name, devlink_trap_generic[trap->id].name)) | ||
7475 | return -EINVAL; | ||
7476 | |||
7477 | if (trap->type != devlink_trap_generic[trap->id].type) | ||
7478 | return -EINVAL; | ||
7479 | |||
7480 | return 0; | ||
7481 | } | ||
7482 | |||
7483 | static int devlink_trap_driver_verify(const struct devlink_trap *trap) | ||
7484 | { | ||
7485 | int i; | ||
7486 | |||
7487 | if (trap->id <= DEVLINK_TRAP_GENERIC_ID_MAX) | ||
7488 | return -EINVAL; | ||
7489 | |||
7490 | for (i = 0; i < ARRAY_SIZE(devlink_trap_generic); i++) { | ||
7491 | if (!strcmp(trap->name, devlink_trap_generic[i].name)) | ||
7492 | return -EEXIST; | ||
7493 | } | ||
7494 | |||
7495 | return 0; | ||
7496 | } | ||
7497 | |||
7498 | static int devlink_trap_verify(const struct devlink_trap *trap) | ||
7499 | { | ||
7500 | if (!trap || !trap->name || !trap->group.name) | ||
7501 | return -EINVAL; | ||
7502 | |||
7503 | if (trap->generic) | ||
7504 | return devlink_trap_generic_verify(trap); | ||
7505 | else | ||
7506 | return devlink_trap_driver_verify(trap); | ||
7507 | } | ||
7508 | |||
7509 | static int | ||
7510 | devlink_trap_group_generic_verify(const struct devlink_trap_group *group) | ||
7511 | { | ||
7512 | if (group->id > DEVLINK_TRAP_GROUP_GENERIC_ID_MAX) | ||
7513 | return -EINVAL; | ||
7514 | |||
7515 | if (strcmp(group->name, devlink_trap_group_generic[group->id].name)) | ||
7516 | return -EINVAL; | ||
7517 | |||
7518 | return 0; | ||
7519 | } | ||
7520 | |||
7521 | static int | ||
7522 | devlink_trap_group_driver_verify(const struct devlink_trap_group *group) | ||
7523 | { | ||
7524 | int i; | ||
7525 | |||
7526 | if (group->id <= DEVLINK_TRAP_GROUP_GENERIC_ID_MAX) | ||
7527 | return -EINVAL; | ||
7528 | |||
7529 | for (i = 0; i < ARRAY_SIZE(devlink_trap_group_generic); i++) { | ||
7530 | if (!strcmp(group->name, devlink_trap_group_generic[i].name)) | ||
7531 | return -EEXIST; | ||
7532 | } | ||
7533 | |||
7534 | return 0; | ||
7535 | } | ||
7536 | |||
7537 | static int devlink_trap_group_verify(const struct devlink_trap_group *group) | ||
7538 | { | ||
7539 | if (group->generic) | ||
7540 | return devlink_trap_group_generic_verify(group); | ||
7541 | else | ||
7542 | return devlink_trap_group_driver_verify(group); | ||
7543 | } | ||
7544 | |||
7545 | static void | ||
7546 | devlink_trap_group_notify(struct devlink *devlink, | ||
7547 | const struct devlink_trap_group_item *group_item, | ||
7548 | enum devlink_command cmd) | ||
7549 | { | ||
7550 | struct sk_buff *msg; | ||
7551 | int err; | ||
7552 | |||
7553 | WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_GROUP_NEW && | ||
7554 | cmd != DEVLINK_CMD_TRAP_GROUP_DEL); | ||
7555 | |||
7556 | msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); | ||
7557 | if (!msg) | ||
7558 | return; | ||
7559 | |||
7560 | err = devlink_nl_trap_group_fill(msg, devlink, group_item, cmd, 0, 0, | ||
7561 | 0); | ||
7562 | if (err) { | ||
7563 | nlmsg_free(msg); | ||
7564 | return; | ||
7565 | } | ||
7566 | |||
7567 | genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), | ||
7568 | msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); | ||
7569 | } | ||
7570 | |||
7571 | static struct devlink_trap_group_item * | ||
7572 | devlink_trap_group_item_create(struct devlink *devlink, | ||
7573 | const struct devlink_trap_group *group) | ||
7574 | { | ||
7575 | struct devlink_trap_group_item *group_item; | ||
7576 | int err; | ||
7577 | |||
7578 | err = devlink_trap_group_verify(group); | ||
7579 | if (err) | ||
7580 | return ERR_PTR(err); | ||
7581 | |||
7582 | group_item = kzalloc(sizeof(*group_item), GFP_KERNEL); | ||
7583 | if (!group_item) | ||
7584 | return ERR_PTR(-ENOMEM); | ||
7585 | |||
7586 | group_item->stats = netdev_alloc_pcpu_stats(struct devlink_stats); | ||
7587 | if (!group_item->stats) { | ||
7588 | err = -ENOMEM; | ||
7589 | goto err_stats_alloc; | ||
7590 | } | ||
7591 | |||
7592 | group_item->group = group; | ||
7593 | refcount_set(&group_item->refcount, 1); | ||
7594 | |||
7595 | if (devlink->ops->trap_group_init) { | ||
7596 | err = devlink->ops->trap_group_init(devlink, group); | ||
7597 | if (err) | ||
7598 | goto err_group_init; | ||
7599 | } | ||
7600 | |||
7601 | list_add_tail(&group_item->list, &devlink->trap_group_list); | ||
7602 | devlink_trap_group_notify(devlink, group_item, | ||
7603 | DEVLINK_CMD_TRAP_GROUP_NEW); | ||
7604 | |||
7605 | return group_item; | ||
7606 | |||
7607 | err_group_init: | ||
7608 | free_percpu(group_item->stats); | ||
7609 | err_stats_alloc: | ||
7610 | kfree(group_item); | ||
7611 | return ERR_PTR(err); | ||
7612 | } | ||
7613 | |||
7614 | static void | ||
7615 | devlink_trap_group_item_destroy(struct devlink *devlink, | ||
7616 | struct devlink_trap_group_item *group_item) | ||
7617 | { | ||
7618 | devlink_trap_group_notify(devlink, group_item, | ||
7619 | DEVLINK_CMD_TRAP_GROUP_DEL); | ||
7620 | list_del(&group_item->list); | ||
7621 | free_percpu(group_item->stats); | ||
7622 | kfree(group_item); | ||
7623 | } | ||
7624 | |||
7625 | static struct devlink_trap_group_item * | ||
7626 | devlink_trap_group_item_get(struct devlink *devlink, | ||
7627 | const struct devlink_trap_group *group) | ||
7628 | { | ||
7629 | struct devlink_trap_group_item *group_item; | ||
7630 | |||
7631 | group_item = devlink_trap_group_item_lookup(devlink, group->name); | ||
7632 | if (group_item) { | ||
7633 | refcount_inc(&group_item->refcount); | ||
7634 | return group_item; | ||
7635 | } | ||
7636 | |||
7637 | return devlink_trap_group_item_create(devlink, group); | ||
7638 | } | ||
7639 | |||
7640 | static void | ||
7641 | devlink_trap_group_item_put(struct devlink *devlink, | ||
7642 | struct devlink_trap_group_item *group_item) | ||
7643 | { | ||
7644 | if (!refcount_dec_and_test(&group_item->refcount)) | ||
7645 | return; | ||
7646 | |||
7647 | devlink_trap_group_item_destroy(devlink, group_item); | ||
7648 | } | ||
7649 | |||
7650 | static int | ||
7651 | devlink_trap_item_group_link(struct devlink *devlink, | ||
7652 | struct devlink_trap_item *trap_item) | ||
7653 | { | ||
7654 | struct devlink_trap_group_item *group_item; | ||
7655 | |||
7656 | group_item = devlink_trap_group_item_get(devlink, | ||
7657 | &trap_item->trap->group); | ||
7658 | if (IS_ERR(group_item)) | ||
7659 | return PTR_ERR(group_item); | ||
7660 | |||
7661 | trap_item->group_item = group_item; | ||
7662 | |||
7663 | return 0; | ||
7664 | } | ||
7665 | |||
7666 | static void | ||
7667 | devlink_trap_item_group_unlink(struct devlink *devlink, | ||
7668 | struct devlink_trap_item *trap_item) | ||
7669 | { | ||
7670 | devlink_trap_group_item_put(devlink, trap_item->group_item); | ||
7671 | } | ||
7672 | |||
7673 | static void devlink_trap_notify(struct devlink *devlink, | ||
7674 | const struct devlink_trap_item *trap_item, | ||
7675 | enum devlink_command cmd) | ||
7676 | { | ||
7677 | struct sk_buff *msg; | ||
7678 | int err; | ||
7679 | |||
7680 | WARN_ON_ONCE(cmd != DEVLINK_CMD_TRAP_NEW && | ||
7681 | cmd != DEVLINK_CMD_TRAP_DEL); | ||
7682 | |||
7683 | msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); | ||
7684 | if (!msg) | ||
7685 | return; | ||
7686 | |||
7687 | err = devlink_nl_trap_fill(msg, devlink, trap_item, cmd, 0, 0, 0); | ||
7688 | if (err) { | ||
7689 | nlmsg_free(msg); | ||
7690 | return; | ||
7691 | } | ||
7692 | |||
7693 | genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), | ||
7694 | msg, 0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL); | ||
7695 | } | ||
7696 | |||
7697 | static int | ||
7698 | devlink_trap_register(struct devlink *devlink, | ||
7699 | const struct devlink_trap *trap, void *priv) | ||
7700 | { | ||
7701 | struct devlink_trap_item *trap_item; | ||
7702 | int err; | ||
7703 | |||
7704 | if (devlink_trap_item_lookup(devlink, trap->name)) | ||
7705 | return -EEXIST; | ||
7706 | |||
7707 | trap_item = kzalloc(sizeof(*trap_item), GFP_KERNEL); | ||
7708 | if (!trap_item) | ||
7709 | return -ENOMEM; | ||
7710 | |||
7711 | trap_item->stats = netdev_alloc_pcpu_stats(struct devlink_stats); | ||
7712 | if (!trap_item->stats) { | ||
7713 | err = -ENOMEM; | ||
7714 | goto err_stats_alloc; | ||
7715 | } | ||
7716 | |||
7717 | trap_item->trap = trap; | ||
7718 | trap_item->action = trap->init_action; | ||
7719 | trap_item->priv = priv; | ||
7720 | |||
7721 | err = devlink_trap_item_group_link(devlink, trap_item); | ||
7722 | if (err) | ||
7723 | goto err_group_link; | ||
7724 | |||
7725 | err = devlink->ops->trap_init(devlink, trap, trap_item); | ||
7726 | if (err) | ||
7727 | goto err_trap_init; | ||
7728 | |||
7729 | list_add_tail(&trap_item->list, &devlink->trap_list); | ||
7730 | devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_NEW); | ||
7731 | |||
7732 | return 0; | ||
7733 | |||
7734 | err_trap_init: | ||
7735 | devlink_trap_item_group_unlink(devlink, trap_item); | ||
7736 | err_group_link: | ||
7737 | free_percpu(trap_item->stats); | ||
7738 | err_stats_alloc: | ||
7739 | kfree(trap_item); | ||
7740 | return err; | ||
7741 | } | ||
7742 | |||
7743 | static void devlink_trap_unregister(struct devlink *devlink, | ||
7744 | const struct devlink_trap *trap) | ||
7745 | { | ||
7746 | struct devlink_trap_item *trap_item; | ||
7747 | |||
7748 | trap_item = devlink_trap_item_lookup(devlink, trap->name); | ||
7749 | if (WARN_ON_ONCE(!trap_item)) | ||
7750 | return; | ||
7751 | |||
7752 | devlink_trap_notify(devlink, trap_item, DEVLINK_CMD_TRAP_DEL); | ||
7753 | list_del(&trap_item->list); | ||
7754 | if (devlink->ops->trap_fini) | ||
7755 | devlink->ops->trap_fini(devlink, trap, trap_item); | ||
7756 | devlink_trap_item_group_unlink(devlink, trap_item); | ||
7757 | free_percpu(trap_item->stats); | ||
7758 | kfree(trap_item); | ||
7759 | } | ||
7760 | |||
7761 | static void devlink_trap_disable(struct devlink *devlink, | ||
7762 | const struct devlink_trap *trap) | ||
7763 | { | ||
7764 | struct devlink_trap_item *trap_item; | ||
7765 | |||
7766 | trap_item = devlink_trap_item_lookup(devlink, trap->name); | ||
7767 | if (WARN_ON_ONCE(!trap_item)) | ||
7768 | return; | ||
7769 | |||
7770 | devlink->ops->trap_action_set(devlink, trap, DEVLINK_TRAP_ACTION_DROP); | ||
7771 | trap_item->action = DEVLINK_TRAP_ACTION_DROP; | ||
7772 | } | ||
7773 | |||
7774 | /** | ||
7775 | * devlink_traps_register - Register packet traps with devlink. | ||
7776 | * @devlink: devlink. | ||
7777 | * @traps: Packet traps. | ||
7778 | * @traps_count: Count of provided packet traps. | ||
7779 | * @priv: Driver private information. | ||
7780 | * | ||
7781 | * Return: Non-zero value on failure. | ||
7782 | */ | ||
7783 | int devlink_traps_register(struct devlink *devlink, | ||
7784 | const struct devlink_trap *traps, | ||
7785 | size_t traps_count, void *priv) | ||
7786 | { | ||
7787 | int i, err; | ||
7788 | |||
7789 | if (!devlink->ops->trap_init || !devlink->ops->trap_action_set) | ||
7790 | return -EINVAL; | ||
7791 | |||
7792 | mutex_lock(&devlink->lock); | ||
7793 | for (i = 0; i < traps_count; i++) { | ||
7794 | const struct devlink_trap *trap = &traps[i]; | ||
7795 | |||
7796 | err = devlink_trap_verify(trap); | ||
7797 | if (err) | ||
7798 | goto err_trap_verify; | ||
7799 | |||
7800 | err = devlink_trap_register(devlink, trap, priv); | ||
7801 | if (err) | ||
7802 | goto err_trap_register; | ||
7803 | } | ||
7804 | mutex_unlock(&devlink->lock); | ||
7805 | |||
7806 | return 0; | ||
7807 | |||
7808 | err_trap_register: | ||
7809 | err_trap_verify: | ||
7810 | for (i--; i >= 0; i--) | ||
7811 | devlink_trap_unregister(devlink, &traps[i]); | ||
7812 | mutex_unlock(&devlink->lock); | ||
7813 | return err; | ||
7814 | } | ||
7815 | EXPORT_SYMBOL_GPL(devlink_traps_register); | ||
7816 | |||
7817 | /** | ||
7818 | * devlink_traps_unregister - Unregister packet traps from devlink. | ||
7819 | * @devlink: devlink. | ||
7820 | * @traps: Packet traps. | ||
7821 | * @traps_count: Count of provided packet traps. | ||
7822 | */ | ||
7823 | void devlink_traps_unregister(struct devlink *devlink, | ||
7824 | const struct devlink_trap *traps, | ||
7825 | size_t traps_count) | ||
7826 | { | ||
7827 | int i; | ||
7828 | |||
7829 | mutex_lock(&devlink->lock); | ||
7830 | /* Make sure we do not have any packets in-flight while unregistering | ||
7831 | * traps by disabling all of them and waiting for a grace period. | ||
7832 | */ | ||
7833 | for (i = traps_count - 1; i >= 0; i--) | ||
7834 | devlink_trap_disable(devlink, &traps[i]); | ||
7835 | synchronize_rcu(); | ||
7836 | for (i = traps_count - 1; i >= 0; i--) | ||
7837 | devlink_trap_unregister(devlink, &traps[i]); | ||
7838 | mutex_unlock(&devlink->lock); | ||
7839 | } | ||
7840 | EXPORT_SYMBOL_GPL(devlink_traps_unregister); | ||
7841 | |||
7842 | static void | ||
7843 | devlink_trap_stats_update(struct devlink_stats __percpu *trap_stats, | ||
7844 | size_t skb_len) | ||
7845 | { | ||
7846 | struct devlink_stats *stats; | ||
7847 | |||
7848 | stats = this_cpu_ptr(trap_stats); | ||
7849 | u64_stats_update_begin(&stats->syncp); | ||
7850 | stats->rx_bytes += skb_len; | ||
7851 | stats->rx_packets++; | ||
7852 | u64_stats_update_end(&stats->syncp); | ||
7853 | } | ||
7854 | |||
7855 | static void | ||
7856 | devlink_trap_report_metadata_fill(struct net_dm_hw_metadata *hw_metadata, | ||
7857 | const struct devlink_trap_item *trap_item, | ||
7858 | struct devlink_port *in_devlink_port) | ||
7859 | { | ||
7860 | struct devlink_trap_group_item *group_item = trap_item->group_item; | ||
7861 | |||
7862 | hw_metadata->trap_group_name = group_item->group->name; | ||
7863 | hw_metadata->trap_name = trap_item->trap->name; | ||
7864 | |||
7865 | spin_lock(&in_devlink_port->type_lock); | ||
7866 | if (in_devlink_port->type == DEVLINK_PORT_TYPE_ETH) | ||
7867 | hw_metadata->input_dev = in_devlink_port->type_dev; | ||
7868 | spin_unlock(&in_devlink_port->type_lock); | ||
7869 | } | ||
7870 | |||
7871 | /** | ||
7872 | * devlink_trap_report - Report trapped packet to drop monitor. | ||
7873 | * @devlink: devlink. | ||
7874 | * @skb: Trapped packet. | ||
7875 | * @trap_ctx: Trap context. | ||
7876 | * @in_devlink_port: Input devlink port. | ||
7877 | */ | ||
7878 | void devlink_trap_report(struct devlink *devlink, struct sk_buff *skb, | ||
7879 | void *trap_ctx, struct devlink_port *in_devlink_port) | ||
7880 | { | ||
7881 | struct devlink_trap_item *trap_item = trap_ctx; | ||
7882 | struct net_dm_hw_metadata hw_metadata = {}; | ||
7883 | |||
7884 | devlink_trap_stats_update(trap_item->stats, skb->len); | ||
7885 | devlink_trap_stats_update(trap_item->group_item->stats, skb->len); | ||
7886 | |||
7887 | devlink_trap_report_metadata_fill(&hw_metadata, trap_item, | ||
7888 | in_devlink_port); | ||
7889 | net_dm_hw_report(skb, &hw_metadata); | ||
7890 | } | ||
7891 | EXPORT_SYMBOL_GPL(devlink_trap_report); | ||
7892 | |||
7893 | /** | ||
7894 | * devlink_trap_ctx_priv - Trap context to driver private information. | ||
7895 | * @trap_ctx: Trap context. | ||
7896 | * | ||
7897 | * Return: Driver private information passed during registration. | ||
7898 | */ | ||
7899 | void *devlink_trap_ctx_priv(void *trap_ctx) | ||
7900 | { | ||
7901 | struct devlink_trap_item *trap_item = trap_ctx; | ||
7902 | |||
7903 | return trap_item->priv; | ||
7904 | } | ||
7905 | EXPORT_SYMBOL_GPL(devlink_trap_ctx_priv); | ||
7906 | |||
6837 | static void __devlink_compat_running_version(struct devlink *devlink, | 7907 | static void __devlink_compat_running_version(struct devlink *devlink, |
6838 | char *buf, size_t len) | 7908 | char *buf, size_t len) |
6839 | { | 7909 | { |
diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 39e094907391..bfc024024aa3 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c | |||
@@ -26,6 +26,7 @@ | |||
26 | #include <linux/bitops.h> | 26 | #include <linux/bitops.h> |
27 | #include <linux/slab.h> | 27 | #include <linux/slab.h> |
28 | #include <linux/module.h> | 28 | #include <linux/module.h> |
29 | #include <net/drop_monitor.h> | ||
29 | #include <net/genetlink.h> | 30 | #include <net/genetlink.h> |
30 | #include <net/netevent.h> | 31 | #include <net/netevent.h> |
31 | 32 | ||
@@ -43,6 +44,7 @@ | |||
43 | * netlink alerts | 44 | * netlink alerts |
44 | */ | 45 | */ |
45 | static int trace_state = TRACE_OFF; | 46 | static int trace_state = TRACE_OFF; |
47 | static bool monitor_hw; | ||
46 | 48 | ||
47 | /* net_dm_mutex | 49 | /* net_dm_mutex |
48 | * | 50 | * |
@@ -56,9 +58,26 @@ struct net_dm_stats { | |||
56 | struct u64_stats_sync syncp; | 58 | struct u64_stats_sync syncp; |
57 | }; | 59 | }; |
58 | 60 | ||
61 | #define NET_DM_MAX_HW_TRAP_NAME_LEN 40 | ||
62 | |||
63 | struct net_dm_hw_entry { | ||
64 | char trap_name[NET_DM_MAX_HW_TRAP_NAME_LEN]; | ||
65 | u32 count; | ||
66 | }; | ||
67 | |||
68 | struct net_dm_hw_entries { | ||
69 | u32 num_entries; | ||
70 | struct net_dm_hw_entry entries[0]; | ||
71 | }; | ||
72 | |||
59 | struct per_cpu_dm_data { | 73 | struct per_cpu_dm_data { |
60 | spinlock_t lock; /* Protects 'skb' and 'send_timer' */ | 74 | spinlock_t lock; /* Protects 'skb', 'hw_entries' and |
61 | struct sk_buff *skb; | 75 | * 'send_timer' |
76 | */ | ||
77 | union { | ||
78 | struct sk_buff *skb; | ||
79 | struct net_dm_hw_entries *hw_entries; | ||
80 | }; | ||
62 | struct sk_buff_head drop_queue; | 81 | struct sk_buff_head drop_queue; |
63 | struct work_struct dm_alert_work; | 82 | struct work_struct dm_alert_work; |
64 | struct timer_list send_timer; | 83 | struct timer_list send_timer; |
@@ -76,6 +95,7 @@ struct dm_hw_stat_delta { | |||
76 | static struct genl_family net_drop_monitor_family; | 95 | static struct genl_family net_drop_monitor_family; |
77 | 96 | ||
78 | static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_cpu_data); | 97 | static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_cpu_data); |
98 | static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_hw_cpu_data); | ||
79 | 99 | ||
80 | static int dm_hit_limit = 64; | 100 | static int dm_hit_limit = 64; |
81 | static int dm_delay = 1; | 101 | static int dm_delay = 1; |
@@ -92,10 +112,16 @@ struct net_dm_alert_ops { | |||
92 | void (*napi_poll_probe)(void *ignore, struct napi_struct *napi, | 112 | void (*napi_poll_probe)(void *ignore, struct napi_struct *napi, |
93 | int work, int budget); | 113 | int work, int budget); |
94 | void (*work_item_func)(struct work_struct *work); | 114 | void (*work_item_func)(struct work_struct *work); |
115 | void (*hw_work_item_func)(struct work_struct *work); | ||
116 | void (*hw_probe)(struct sk_buff *skb, | ||
117 | const struct net_dm_hw_metadata *hw_metadata); | ||
95 | }; | 118 | }; |
96 | 119 | ||
97 | struct net_dm_skb_cb { | 120 | struct net_dm_skb_cb { |
98 | void *pc; | 121 | union { |
122 | struct net_dm_hw_metadata *hw_metadata; | ||
123 | void *pc; | ||
124 | }; | ||
99 | }; | 125 | }; |
100 | 126 | ||
101 | #define NET_DM_SKB_CB(__skb) ((struct net_dm_skb_cb *)&((__skb)->cb[0])) | 127 | #define NET_DM_SKB_CB(__skb) ((struct net_dm_skb_cb *)&((__skb)->cb[0])) |
@@ -266,10 +292,190 @@ static void trace_napi_poll_hit(void *ignore, struct napi_struct *napi, | |||
266 | rcu_read_unlock(); | 292 | rcu_read_unlock(); |
267 | } | 293 | } |
268 | 294 | ||
295 | static struct net_dm_hw_entries * | ||
296 | net_dm_hw_reset_per_cpu_data(struct per_cpu_dm_data *hw_data) | ||
297 | { | ||
298 | struct net_dm_hw_entries *hw_entries; | ||
299 | unsigned long flags; | ||
300 | |||
301 | hw_entries = kzalloc(struct_size(hw_entries, entries, dm_hit_limit), | ||
302 | GFP_KERNEL); | ||
303 | if (!hw_entries) { | ||
304 | /* If the memory allocation failed, we try to perform another | ||
305 | * allocation in 1/10 second. Otherwise, the probe function | ||
306 | * will constantly bail out. | ||
307 | */ | ||
308 | mod_timer(&hw_data->send_timer, jiffies + HZ / 10); | ||
309 | } | ||
310 | |||
311 | spin_lock_irqsave(&hw_data->lock, flags); | ||
312 | swap(hw_data->hw_entries, hw_entries); | ||
313 | spin_unlock_irqrestore(&hw_data->lock, flags); | ||
314 | |||
315 | return hw_entries; | ||
316 | } | ||
317 | |||
318 | static int net_dm_hw_entry_put(struct sk_buff *msg, | ||
319 | const struct net_dm_hw_entry *hw_entry) | ||
320 | { | ||
321 | struct nlattr *attr; | ||
322 | |||
323 | attr = nla_nest_start(msg, NET_DM_ATTR_HW_ENTRY); | ||
324 | if (!attr) | ||
325 | return -EMSGSIZE; | ||
326 | |||
327 | if (nla_put_string(msg, NET_DM_ATTR_HW_TRAP_NAME, hw_entry->trap_name)) | ||
328 | goto nla_put_failure; | ||
329 | |||
330 | if (nla_put_u32(msg, NET_DM_ATTR_HW_TRAP_COUNT, hw_entry->count)) | ||
331 | goto nla_put_failure; | ||
332 | |||
333 | nla_nest_end(msg, attr); | ||
334 | |||
335 | return 0; | ||
336 | |||
337 | nla_put_failure: | ||
338 | nla_nest_cancel(msg, attr); | ||
339 | return -EMSGSIZE; | ||
340 | } | ||
341 | |||
342 | static int net_dm_hw_entries_put(struct sk_buff *msg, | ||
343 | const struct net_dm_hw_entries *hw_entries) | ||
344 | { | ||
345 | struct nlattr *attr; | ||
346 | int i; | ||
347 | |||
348 | attr = nla_nest_start(msg, NET_DM_ATTR_HW_ENTRIES); | ||
349 | if (!attr) | ||
350 | return -EMSGSIZE; | ||
351 | |||
352 | for (i = 0; i < hw_entries->num_entries; i++) { | ||
353 | int rc; | ||
354 | |||
355 | rc = net_dm_hw_entry_put(msg, &hw_entries->entries[i]); | ||
356 | if (rc) | ||
357 | goto nla_put_failure; | ||
358 | } | ||
359 | |||
360 | nla_nest_end(msg, attr); | ||
361 | |||
362 | return 0; | ||
363 | |||
364 | nla_put_failure: | ||
365 | nla_nest_cancel(msg, attr); | ||
366 | return -EMSGSIZE; | ||
367 | } | ||
368 | |||
369 | static int | ||
370 | net_dm_hw_summary_report_fill(struct sk_buff *msg, | ||
371 | const struct net_dm_hw_entries *hw_entries) | ||
372 | { | ||
373 | struct net_dm_alert_msg anc_hdr = { 0 }; | ||
374 | void *hdr; | ||
375 | int rc; | ||
376 | |||
377 | hdr = genlmsg_put(msg, 0, 0, &net_drop_monitor_family, 0, | ||
378 | NET_DM_CMD_ALERT); | ||
379 | if (!hdr) | ||
380 | return -EMSGSIZE; | ||
381 | |||
382 | /* We need to put the ancillary header in order not to break user | ||
383 | * space. | ||
384 | */ | ||
385 | if (nla_put(msg, NLA_UNSPEC, sizeof(anc_hdr), &anc_hdr)) | ||
386 | goto nla_put_failure; | ||
387 | |||
388 | rc = net_dm_hw_entries_put(msg, hw_entries); | ||
389 | if (rc) | ||
390 | goto nla_put_failure; | ||
391 | |||
392 | genlmsg_end(msg, hdr); | ||
393 | |||
394 | return 0; | ||
395 | |||
396 | nla_put_failure: | ||
397 | genlmsg_cancel(msg, hdr); | ||
398 | return -EMSGSIZE; | ||
399 | } | ||
400 | |||
401 | static void net_dm_hw_summary_work(struct work_struct *work) | ||
402 | { | ||
403 | struct net_dm_hw_entries *hw_entries; | ||
404 | struct per_cpu_dm_data *hw_data; | ||
405 | struct sk_buff *msg; | ||
406 | int rc; | ||
407 | |||
408 | hw_data = container_of(work, struct per_cpu_dm_data, dm_alert_work); | ||
409 | |||
410 | hw_entries = net_dm_hw_reset_per_cpu_data(hw_data); | ||
411 | if (!hw_entries) | ||
412 | return; | ||
413 | |||
414 | msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); | ||
415 | if (!msg) | ||
416 | goto out; | ||
417 | |||
418 | rc = net_dm_hw_summary_report_fill(msg, hw_entries); | ||
419 | if (rc) { | ||
420 | nlmsg_free(msg); | ||
421 | goto out; | ||
422 | } | ||
423 | |||
424 | genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL); | ||
425 | |||
426 | out: | ||
427 | kfree(hw_entries); | ||
428 | } | ||
429 | |||
430 | static void | ||
431 | net_dm_hw_summary_probe(struct sk_buff *skb, | ||
432 | const struct net_dm_hw_metadata *hw_metadata) | ||
433 | { | ||
434 | struct net_dm_hw_entries *hw_entries; | ||
435 | struct net_dm_hw_entry *hw_entry; | ||
436 | struct per_cpu_dm_data *hw_data; | ||
437 | unsigned long flags; | ||
438 | int i; | ||
439 | |||
440 | hw_data = this_cpu_ptr(&dm_hw_cpu_data); | ||
441 | spin_lock_irqsave(&hw_data->lock, flags); | ||
442 | hw_entries = hw_data->hw_entries; | ||
443 | |||
444 | if (!hw_entries) | ||
445 | goto out; | ||
446 | |||
447 | for (i = 0; i < hw_entries->num_entries; i++) { | ||
448 | hw_entry = &hw_entries->entries[i]; | ||
449 | if (!strncmp(hw_entry->trap_name, hw_metadata->trap_name, | ||
450 | NET_DM_MAX_HW_TRAP_NAME_LEN - 1)) { | ||
451 | hw_entry->count++; | ||
452 | goto out; | ||
453 | } | ||
454 | } | ||
455 | if (WARN_ON_ONCE(hw_entries->num_entries == dm_hit_limit)) | ||
456 | goto out; | ||
457 | |||
458 | hw_entry = &hw_entries->entries[hw_entries->num_entries]; | ||
459 | strlcpy(hw_entry->trap_name, hw_metadata->trap_name, | ||
460 | NET_DM_MAX_HW_TRAP_NAME_LEN - 1); | ||
461 | hw_entry->count = 1; | ||
462 | hw_entries->num_entries++; | ||
463 | |||
464 | if (!timer_pending(&hw_data->send_timer)) { | ||
465 | hw_data->send_timer.expires = jiffies + dm_delay * HZ; | ||
466 | add_timer(&hw_data->send_timer); | ||
467 | } | ||
468 | |||
469 | out: | ||
470 | spin_unlock_irqrestore(&hw_data->lock, flags); | ||
471 | } | ||
472 | |||
269 | static const struct net_dm_alert_ops net_dm_alert_summary_ops = { | 473 | static const struct net_dm_alert_ops net_dm_alert_summary_ops = { |
270 | .kfree_skb_probe = trace_kfree_skb_hit, | 474 | .kfree_skb_probe = trace_kfree_skb_hit, |
271 | .napi_poll_probe = trace_napi_poll_hit, | 475 | .napi_poll_probe = trace_napi_poll_hit, |
272 | .work_item_func = send_dm_alert, | 476 | .work_item_func = send_dm_alert, |
477 | .hw_work_item_func = net_dm_hw_summary_work, | ||
478 | .hw_probe = net_dm_hw_summary_probe, | ||
273 | }; | 479 | }; |
274 | 480 | ||
275 | static void net_dm_packet_trace_kfree_skb_hit(void *ignore, | 481 | static void net_dm_packet_trace_kfree_skb_hit(void *ignore, |
@@ -323,7 +529,9 @@ static size_t net_dm_in_port_size(void) | |||
323 | /* NET_DM_ATTR_IN_PORT nest */ | 529 | /* NET_DM_ATTR_IN_PORT nest */ |
324 | return nla_total_size(0) + | 530 | return nla_total_size(0) + |
325 | /* NET_DM_ATTR_PORT_NETDEV_IFINDEX */ | 531 | /* NET_DM_ATTR_PORT_NETDEV_IFINDEX */ |
326 | nla_total_size(sizeof(u32)); | 532 | nla_total_size(sizeof(u32)) + |
533 | /* NET_DM_ATTR_PORT_NETDEV_NAME */ | ||
534 | nla_total_size(IFNAMSIZ + 1); | ||
327 | } | 535 | } |
328 | 536 | ||
329 | #define NET_DM_MAX_SYMBOL_LEN 40 | 537 | #define NET_DM_MAX_SYMBOL_LEN 40 |
@@ -335,6 +543,8 @@ static size_t net_dm_packet_report_size(size_t payload_len) | |||
335 | size = nlmsg_msg_size(GENL_HDRLEN + net_drop_monitor_family.hdrsize); | 543 | size = nlmsg_msg_size(GENL_HDRLEN + net_drop_monitor_family.hdrsize); |
336 | 544 | ||
337 | return NLMSG_ALIGN(size) + | 545 | return NLMSG_ALIGN(size) + |
546 | /* NET_DM_ATTR_ORIGIN */ | ||
547 | nla_total_size(sizeof(u16)) + | ||
338 | /* NET_DM_ATTR_PC */ | 548 | /* NET_DM_ATTR_PC */ |
339 | nla_total_size(sizeof(u64)) + | 549 | nla_total_size(sizeof(u64)) + |
340 | /* NET_DM_ATTR_SYMBOL */ | 550 | /* NET_DM_ATTR_SYMBOL */ |
@@ -351,7 +561,8 @@ static size_t net_dm_packet_report_size(size_t payload_len) | |||
351 | nla_total_size(payload_len); | 561 | nla_total_size(payload_len); |
352 | } | 562 | } |
353 | 563 | ||
354 | static int net_dm_packet_report_in_port_put(struct sk_buff *msg, int ifindex) | 564 | static int net_dm_packet_report_in_port_put(struct sk_buff *msg, int ifindex, |
565 | const char *name) | ||
355 | { | 566 | { |
356 | struct nlattr *attr; | 567 | struct nlattr *attr; |
357 | 568 | ||
@@ -363,6 +574,9 @@ static int net_dm_packet_report_in_port_put(struct sk_buff *msg, int ifindex) | |||
363 | nla_put_u32(msg, NET_DM_ATTR_PORT_NETDEV_IFINDEX, ifindex)) | 574 | nla_put_u32(msg, NET_DM_ATTR_PORT_NETDEV_IFINDEX, ifindex)) |
364 | goto nla_put_failure; | 575 | goto nla_put_failure; |
365 | 576 | ||
577 | if (name && nla_put_string(msg, NET_DM_ATTR_PORT_NETDEV_NAME, name)) | ||
578 | goto nla_put_failure; | ||
579 | |||
366 | nla_nest_end(msg, attr); | 580 | nla_nest_end(msg, attr); |
367 | 581 | ||
368 | return 0; | 582 | return 0; |
@@ -387,6 +601,9 @@ static int net_dm_packet_report_fill(struct sk_buff *msg, struct sk_buff *skb, | |||
387 | if (!hdr) | 601 | if (!hdr) |
388 | return -EMSGSIZE; | 602 | return -EMSGSIZE; |
389 | 603 | ||
604 | if (nla_put_u16(msg, NET_DM_ATTR_ORIGIN, NET_DM_ORIGIN_SW)) | ||
605 | goto nla_put_failure; | ||
606 | |||
390 | if (nla_put_u64_64bit(msg, NET_DM_ATTR_PC, pc, NET_DM_ATTR_PAD)) | 607 | if (nla_put_u64_64bit(msg, NET_DM_ATTR_PC, pc, NET_DM_ATTR_PAD)) |
391 | goto nla_put_failure; | 608 | goto nla_put_failure; |
392 | 609 | ||
@@ -394,7 +611,7 @@ static int net_dm_packet_report_fill(struct sk_buff *msg, struct sk_buff *skb, | |||
394 | if (nla_put_string(msg, NET_DM_ATTR_SYMBOL, buf)) | 611 | if (nla_put_string(msg, NET_DM_ATTR_SYMBOL, buf)) |
395 | goto nla_put_failure; | 612 | goto nla_put_failure; |
396 | 613 | ||
397 | rc = net_dm_packet_report_in_port_put(msg, skb->skb_iif); | 614 | rc = net_dm_packet_report_in_port_put(msg, skb->skb_iif, NULL); |
398 | if (rc) | 615 | if (rc) |
399 | goto nla_put_failure; | 616 | goto nla_put_failure; |
400 | 617 | ||
@@ -481,10 +698,250 @@ static void net_dm_packet_work(struct work_struct *work) | |||
481 | net_dm_packet_report(skb); | 698 | net_dm_packet_report(skb); |
482 | } | 699 | } |
483 | 700 | ||
701 | static size_t | ||
702 | net_dm_hw_packet_report_size(size_t payload_len, | ||
703 | const struct net_dm_hw_metadata *hw_metadata) | ||
704 | { | ||
705 | size_t size; | ||
706 | |||
707 | size = nlmsg_msg_size(GENL_HDRLEN + net_drop_monitor_family.hdrsize); | ||
708 | |||
709 | return NLMSG_ALIGN(size) + | ||
710 | /* NET_DM_ATTR_ORIGIN */ | ||
711 | nla_total_size(sizeof(u16)) + | ||
712 | /* NET_DM_ATTR_HW_TRAP_GROUP_NAME */ | ||
713 | nla_total_size(strlen(hw_metadata->trap_group_name) + 1) + | ||
714 | /* NET_DM_ATTR_HW_TRAP_NAME */ | ||
715 | nla_total_size(strlen(hw_metadata->trap_name) + 1) + | ||
716 | /* NET_DM_ATTR_IN_PORT */ | ||
717 | net_dm_in_port_size() + | ||
718 | /* NET_DM_ATTR_TIMESTAMP */ | ||
719 | nla_total_size(sizeof(struct timespec)) + | ||
720 | /* NET_DM_ATTR_ORIG_LEN */ | ||
721 | nla_total_size(sizeof(u32)) + | ||
722 | /* NET_DM_ATTR_PROTO */ | ||
723 | nla_total_size(sizeof(u16)) + | ||
724 | /* NET_DM_ATTR_PAYLOAD */ | ||
725 | nla_total_size(payload_len); | ||
726 | } | ||
727 | |||
728 | static int net_dm_hw_packet_report_fill(struct sk_buff *msg, | ||
729 | struct sk_buff *skb, size_t payload_len) | ||
730 | { | ||
731 | struct net_dm_hw_metadata *hw_metadata; | ||
732 | struct nlattr *attr; | ||
733 | struct timespec ts; | ||
734 | void *hdr; | ||
735 | |||
736 | hw_metadata = NET_DM_SKB_CB(skb)->hw_metadata; | ||
737 | |||
738 | hdr = genlmsg_put(msg, 0, 0, &net_drop_monitor_family, 0, | ||
739 | NET_DM_CMD_PACKET_ALERT); | ||
740 | if (!hdr) | ||
741 | return -EMSGSIZE; | ||
742 | |||
743 | if (nla_put_u16(msg, NET_DM_ATTR_ORIGIN, NET_DM_ORIGIN_HW)) | ||
744 | goto nla_put_failure; | ||
745 | |||
746 | if (nla_put_string(msg, NET_DM_ATTR_HW_TRAP_GROUP_NAME, | ||
747 | hw_metadata->trap_group_name)) | ||
748 | goto nla_put_failure; | ||
749 | |||
750 | if (nla_put_string(msg, NET_DM_ATTR_HW_TRAP_NAME, | ||
751 | hw_metadata->trap_name)) | ||
752 | goto nla_put_failure; | ||
753 | |||
754 | if (hw_metadata->input_dev) { | ||
755 | struct net_device *dev = hw_metadata->input_dev; | ||
756 | int rc; | ||
757 | |||
758 | rc = net_dm_packet_report_in_port_put(msg, dev->ifindex, | ||
759 | dev->name); | ||
760 | if (rc) | ||
761 | goto nla_put_failure; | ||
762 | } | ||
763 | |||
764 | if (ktime_to_timespec_cond(skb->tstamp, &ts) && | ||
765 | nla_put(msg, NET_DM_ATTR_TIMESTAMP, sizeof(ts), &ts)) | ||
766 | goto nla_put_failure; | ||
767 | |||
768 | if (nla_put_u32(msg, NET_DM_ATTR_ORIG_LEN, skb->len)) | ||
769 | goto nla_put_failure; | ||
770 | |||
771 | if (!payload_len) | ||
772 | goto out; | ||
773 | |||
774 | if (nla_put_u16(msg, NET_DM_ATTR_PROTO, be16_to_cpu(skb->protocol))) | ||
775 | goto nla_put_failure; | ||
776 | |||
777 | attr = skb_put(msg, nla_total_size(payload_len)); | ||
778 | attr->nla_type = NET_DM_ATTR_PAYLOAD; | ||
779 | attr->nla_len = nla_attr_size(payload_len); | ||
780 | if (skb_copy_bits(skb, 0, nla_data(attr), payload_len)) | ||
781 | goto nla_put_failure; | ||
782 | |||
783 | out: | ||
784 | genlmsg_end(msg, hdr); | ||
785 | |||
786 | return 0; | ||
787 | |||
788 | nla_put_failure: | ||
789 | genlmsg_cancel(msg, hdr); | ||
790 | return -EMSGSIZE; | ||
791 | } | ||
792 | |||
793 | static struct net_dm_hw_metadata * | ||
794 | net_dm_hw_metadata_clone(const struct net_dm_hw_metadata *hw_metadata) | ||
795 | { | ||
796 | struct net_dm_hw_metadata *n_hw_metadata; | ||
797 | const char *trap_group_name; | ||
798 | const char *trap_name; | ||
799 | |||
800 | n_hw_metadata = kmalloc(sizeof(*hw_metadata), GFP_ATOMIC); | ||
801 | if (!n_hw_metadata) | ||
802 | return NULL; | ||
803 | |||
804 | trap_group_name = kmemdup(hw_metadata->trap_group_name, | ||
805 | strlen(hw_metadata->trap_group_name) + 1, | ||
806 | GFP_ATOMIC | __GFP_ZERO); | ||
807 | if (!trap_group_name) | ||
808 | goto free_hw_metadata; | ||
809 | n_hw_metadata->trap_group_name = trap_group_name; | ||
810 | |||
811 | trap_name = kmemdup(hw_metadata->trap_name, | ||
812 | strlen(hw_metadata->trap_name) + 1, | ||
813 | GFP_ATOMIC | __GFP_ZERO); | ||
814 | if (!trap_name) | ||
815 | goto free_trap_group; | ||
816 | n_hw_metadata->trap_name = trap_name; | ||
817 | |||
818 | n_hw_metadata->input_dev = hw_metadata->input_dev; | ||
819 | if (n_hw_metadata->input_dev) | ||
820 | dev_hold(n_hw_metadata->input_dev); | ||
821 | |||
822 | return n_hw_metadata; | ||
823 | |||
824 | free_trap_group: | ||
825 | kfree(trap_group_name); | ||
826 | free_hw_metadata: | ||
827 | kfree(n_hw_metadata); | ||
828 | return NULL; | ||
829 | } | ||
830 | |||
831 | static void | ||
832 | net_dm_hw_metadata_free(const struct net_dm_hw_metadata *hw_metadata) | ||
833 | { | ||
834 | if (hw_metadata->input_dev) | ||
835 | dev_put(hw_metadata->input_dev); | ||
836 | kfree(hw_metadata->trap_name); | ||
837 | kfree(hw_metadata->trap_group_name); | ||
838 | kfree(hw_metadata); | ||
839 | } | ||
840 | |||
841 | static void net_dm_hw_packet_report(struct sk_buff *skb) | ||
842 | { | ||
843 | struct net_dm_hw_metadata *hw_metadata; | ||
844 | struct sk_buff *msg; | ||
845 | size_t payload_len; | ||
846 | int rc; | ||
847 | |||
848 | if (skb->data > skb_mac_header(skb)) | ||
849 | skb_push(skb, skb->data - skb_mac_header(skb)); | ||
850 | else | ||
851 | skb_pull(skb, skb_mac_header(skb) - skb->data); | ||
852 | |||
853 | payload_len = min_t(size_t, skb->len, NET_DM_MAX_PACKET_SIZE); | ||
854 | if (net_dm_trunc_len) | ||
855 | payload_len = min_t(size_t, net_dm_trunc_len, payload_len); | ||
856 | |||
857 | hw_metadata = NET_DM_SKB_CB(skb)->hw_metadata; | ||
858 | msg = nlmsg_new(net_dm_hw_packet_report_size(payload_len, hw_metadata), | ||
859 | GFP_KERNEL); | ||
860 | if (!msg) | ||
861 | goto out; | ||
862 | |||
863 | rc = net_dm_hw_packet_report_fill(msg, skb, payload_len); | ||
864 | if (rc) { | ||
865 | nlmsg_free(msg); | ||
866 | goto out; | ||
867 | } | ||
868 | |||
869 | genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL); | ||
870 | |||
871 | out: | ||
872 | net_dm_hw_metadata_free(NET_DM_SKB_CB(skb)->hw_metadata); | ||
873 | consume_skb(skb); | ||
874 | } | ||
875 | |||
876 | static void net_dm_hw_packet_work(struct work_struct *work) | ||
877 | { | ||
878 | struct per_cpu_dm_data *hw_data; | ||
879 | struct sk_buff_head list; | ||
880 | struct sk_buff *skb; | ||
881 | unsigned long flags; | ||
882 | |||
883 | hw_data = container_of(work, struct per_cpu_dm_data, dm_alert_work); | ||
884 | |||
885 | __skb_queue_head_init(&list); | ||
886 | |||
887 | spin_lock_irqsave(&hw_data->drop_queue.lock, flags); | ||
888 | skb_queue_splice_tail_init(&hw_data->drop_queue, &list); | ||
889 | spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags); | ||
890 | |||
891 | while ((skb = __skb_dequeue(&list))) | ||
892 | net_dm_hw_packet_report(skb); | ||
893 | } | ||
894 | |||
895 | static void | ||
896 | net_dm_hw_packet_probe(struct sk_buff *skb, | ||
897 | const struct net_dm_hw_metadata *hw_metadata) | ||
898 | { | ||
899 | struct net_dm_hw_metadata *n_hw_metadata; | ||
900 | ktime_t tstamp = ktime_get_real(); | ||
901 | struct per_cpu_dm_data *hw_data; | ||
902 | struct sk_buff *nskb; | ||
903 | unsigned long flags; | ||
904 | |||
905 | nskb = skb_clone(skb, GFP_ATOMIC); | ||
906 | if (!nskb) | ||
907 | return; | ||
908 | |||
909 | n_hw_metadata = net_dm_hw_metadata_clone(hw_metadata); | ||
910 | if (!n_hw_metadata) | ||
911 | goto free; | ||
912 | |||
913 | NET_DM_SKB_CB(nskb)->hw_metadata = n_hw_metadata; | ||
914 | nskb->tstamp = tstamp; | ||
915 | |||
916 | hw_data = this_cpu_ptr(&dm_hw_cpu_data); | ||
917 | |||
918 | spin_lock_irqsave(&hw_data->drop_queue.lock, flags); | ||
919 | if (skb_queue_len(&hw_data->drop_queue) < net_dm_queue_len) | ||
920 | __skb_queue_tail(&hw_data->drop_queue, nskb); | ||
921 | else | ||
922 | goto unlock_free; | ||
923 | spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags); | ||
924 | |||
925 | schedule_work(&hw_data->dm_alert_work); | ||
926 | |||
927 | return; | ||
928 | |||
929 | unlock_free: | ||
930 | spin_unlock_irqrestore(&hw_data->drop_queue.lock, flags); | ||
931 | u64_stats_update_begin(&hw_data->stats.syncp); | ||
932 | hw_data->stats.dropped++; | ||
933 | u64_stats_update_end(&hw_data->stats.syncp); | ||
934 | net_dm_hw_metadata_free(n_hw_metadata); | ||
935 | free: | ||
936 | consume_skb(nskb); | ||
937 | } | ||
938 | |||
484 | static const struct net_dm_alert_ops net_dm_alert_packet_ops = { | 939 | static const struct net_dm_alert_ops net_dm_alert_packet_ops = { |
485 | .kfree_skb_probe = net_dm_packet_trace_kfree_skb_hit, | 940 | .kfree_skb_probe = net_dm_packet_trace_kfree_skb_hit, |
486 | .napi_poll_probe = net_dm_packet_trace_napi_poll_hit, | 941 | .napi_poll_probe = net_dm_packet_trace_napi_poll_hit, |
487 | .work_item_func = net_dm_packet_work, | 942 | .work_item_func = net_dm_packet_work, |
943 | .hw_work_item_func = net_dm_hw_packet_work, | ||
944 | .hw_probe = net_dm_hw_packet_probe, | ||
488 | }; | 945 | }; |
489 | 946 | ||
490 | static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = { | 947 | static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = { |
@@ -492,6 +949,85 @@ static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = { | |||
492 | [NET_DM_ALERT_MODE_PACKET] = &net_dm_alert_packet_ops, | 949 | [NET_DM_ALERT_MODE_PACKET] = &net_dm_alert_packet_ops, |
493 | }; | 950 | }; |
494 | 951 | ||
952 | void net_dm_hw_report(struct sk_buff *skb, | ||
953 | const struct net_dm_hw_metadata *hw_metadata) | ||
954 | { | ||
955 | rcu_read_lock(); | ||
956 | |||
957 | if (!monitor_hw) | ||
958 | goto out; | ||
959 | |||
960 | net_dm_alert_ops_arr[net_dm_alert_mode]->hw_probe(skb, hw_metadata); | ||
961 | |||
962 | out: | ||
963 | rcu_read_unlock(); | ||
964 | } | ||
965 | EXPORT_SYMBOL_GPL(net_dm_hw_report); | ||
966 | |||
967 | static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) | ||
968 | { | ||
969 | const struct net_dm_alert_ops *ops; | ||
970 | int cpu; | ||
971 | |||
972 | if (monitor_hw) { | ||
973 | NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already enabled"); | ||
974 | return -EAGAIN; | ||
975 | } | ||
976 | |||
977 | ops = net_dm_alert_ops_arr[net_dm_alert_mode]; | ||
978 | |||
979 | if (!try_module_get(THIS_MODULE)) { | ||
980 | NL_SET_ERR_MSG_MOD(extack, "Failed to take reference on module"); | ||
981 | return -ENODEV; | ||
982 | } | ||
983 | |||
984 | for_each_possible_cpu(cpu) { | ||
985 | struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); | ||
986 | struct net_dm_hw_entries *hw_entries; | ||
987 | |||
988 | INIT_WORK(&hw_data->dm_alert_work, ops->hw_work_item_func); | ||
989 | timer_setup(&hw_data->send_timer, sched_send_work, 0); | ||
990 | hw_entries = net_dm_hw_reset_per_cpu_data(hw_data); | ||
991 | kfree(hw_entries); | ||
992 | } | ||
993 | |||
994 | monitor_hw = true; | ||
995 | |||
996 | return 0; | ||
997 | } | ||
998 | |||
999 | static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack) | ||
1000 | { | ||
1001 | int cpu; | ||
1002 | |||
1003 | if (!monitor_hw) | ||
1004 | NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already disabled"); | ||
1005 | |||
1006 | monitor_hw = false; | ||
1007 | |||
1008 | /* After this call returns we are guaranteed that no CPU is processing | ||
1009 | * any hardware drops. | ||
1010 | */ | ||
1011 | synchronize_rcu(); | ||
1012 | |||
1013 | for_each_possible_cpu(cpu) { | ||
1014 | struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); | ||
1015 | struct sk_buff *skb; | ||
1016 | |||
1017 | del_timer_sync(&hw_data->send_timer); | ||
1018 | cancel_work_sync(&hw_data->dm_alert_work); | ||
1019 | while ((skb = __skb_dequeue(&hw_data->drop_queue))) { | ||
1020 | struct net_dm_hw_metadata *hw_metadata; | ||
1021 | |||
1022 | hw_metadata = NET_DM_SKB_CB(skb)->hw_metadata; | ||
1023 | net_dm_hw_metadata_free(hw_metadata); | ||
1024 | consume_skb(skb); | ||
1025 | } | ||
1026 | } | ||
1027 | |||
1028 | module_put(THIS_MODULE); | ||
1029 | } | ||
1030 | |||
495 | static int net_dm_trace_on_set(struct netlink_ext_ack *extack) | 1031 | static int net_dm_trace_on_set(struct netlink_ext_ack *extack) |
496 | { | 1032 | { |
497 | const struct net_dm_alert_ops *ops; | 1033 | const struct net_dm_alert_ops *ops; |
@@ -604,6 +1140,11 @@ static int set_all_monitor_traces(int state, struct netlink_ext_ack *extack) | |||
604 | return rc; | 1140 | return rc; |
605 | } | 1141 | } |
606 | 1142 | ||
1143 | static bool net_dm_is_monitoring(void) | ||
1144 | { | ||
1145 | return trace_state == TRACE_ON || monitor_hw; | ||
1146 | } | ||
1147 | |||
607 | static int net_dm_alert_mode_get_from_info(struct genl_info *info, | 1148 | static int net_dm_alert_mode_get_from_info(struct genl_info *info, |
608 | enum net_dm_alert_mode *p_alert_mode) | 1149 | enum net_dm_alert_mode *p_alert_mode) |
609 | { | 1150 | { |
@@ -665,8 +1206,8 @@ static int net_dm_cmd_config(struct sk_buff *skb, | |||
665 | struct netlink_ext_ack *extack = info->extack; | 1206 | struct netlink_ext_ack *extack = info->extack; |
666 | int rc; | 1207 | int rc; |
667 | 1208 | ||
668 | if (trace_state == TRACE_ON) { | 1209 | if (net_dm_is_monitoring()) { |
669 | NL_SET_ERR_MSG_MOD(extack, "Cannot configure drop monitor while tracing is on"); | 1210 | NL_SET_ERR_MSG_MOD(extack, "Cannot configure drop monitor during monitoring"); |
670 | return -EBUSY; | 1211 | return -EBUSY; |
671 | } | 1212 | } |
672 | 1213 | ||
@@ -681,14 +1222,61 @@ static int net_dm_cmd_config(struct sk_buff *skb, | |||
681 | return 0; | 1222 | return 0; |
682 | } | 1223 | } |
683 | 1224 | ||
1225 | static int net_dm_monitor_start(bool set_sw, bool set_hw, | ||
1226 | struct netlink_ext_ack *extack) | ||
1227 | { | ||
1228 | bool sw_set = false; | ||
1229 | int rc; | ||
1230 | |||
1231 | if (set_sw) { | ||
1232 | rc = set_all_monitor_traces(TRACE_ON, extack); | ||
1233 | if (rc) | ||
1234 | return rc; | ||
1235 | sw_set = true; | ||
1236 | } | ||
1237 | |||
1238 | if (set_hw) { | ||
1239 | rc = net_dm_hw_monitor_start(extack); | ||
1240 | if (rc) | ||
1241 | goto err_monitor_hw; | ||
1242 | } | ||
1243 | |||
1244 | return 0; | ||
1245 | |||
1246 | err_monitor_hw: | ||
1247 | if (sw_set) | ||
1248 | set_all_monitor_traces(TRACE_OFF, extack); | ||
1249 | return rc; | ||
1250 | } | ||
1251 | |||
1252 | static void net_dm_monitor_stop(bool set_sw, bool set_hw, | ||
1253 | struct netlink_ext_ack *extack) | ||
1254 | { | ||
1255 | if (set_hw) | ||
1256 | net_dm_hw_monitor_stop(extack); | ||
1257 | if (set_sw) | ||
1258 | set_all_monitor_traces(TRACE_OFF, extack); | ||
1259 | } | ||
1260 | |||
684 | static int net_dm_cmd_trace(struct sk_buff *skb, | 1261 | static int net_dm_cmd_trace(struct sk_buff *skb, |
685 | struct genl_info *info) | 1262 | struct genl_info *info) |
686 | { | 1263 | { |
1264 | bool set_sw = !!info->attrs[NET_DM_ATTR_SW_DROPS]; | ||
1265 | bool set_hw = !!info->attrs[NET_DM_ATTR_HW_DROPS]; | ||
1266 | struct netlink_ext_ack *extack = info->extack; | ||
1267 | |||
1268 | /* To maintain backward compatibility, we start / stop monitoring of | ||
1269 | * software drops if no flag is specified. | ||
1270 | */ | ||
1271 | if (!set_sw && !set_hw) | ||
1272 | set_sw = true; | ||
1273 | |||
687 | switch (info->genlhdr->cmd) { | 1274 | switch (info->genlhdr->cmd) { |
688 | case NET_DM_CMD_START: | 1275 | case NET_DM_CMD_START: |
689 | return set_all_monitor_traces(TRACE_ON, info->extack); | 1276 | return net_dm_monitor_start(set_sw, set_hw, extack); |
690 | case NET_DM_CMD_STOP: | 1277 | case NET_DM_CMD_STOP: |
691 | return set_all_monitor_traces(TRACE_OFF, info->extack); | 1278 | net_dm_monitor_stop(set_sw, set_hw, extack); |
1279 | return 0; | ||
692 | } | 1280 | } |
693 | 1281 | ||
694 | return -EOPNOTSUPP; | 1282 | return -EOPNOTSUPP; |
@@ -785,6 +1373,50 @@ nla_put_failure: | |||
785 | return -EMSGSIZE; | 1373 | return -EMSGSIZE; |
786 | } | 1374 | } |
787 | 1375 | ||
1376 | static void net_dm_hw_stats_read(struct net_dm_stats *stats) | ||
1377 | { | ||
1378 | int cpu; | ||
1379 | |||
1380 | memset(stats, 0, sizeof(*stats)); | ||
1381 | for_each_possible_cpu(cpu) { | ||
1382 | struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); | ||
1383 | struct net_dm_stats *cpu_stats = &hw_data->stats; | ||
1384 | unsigned int start; | ||
1385 | u64 dropped; | ||
1386 | |||
1387 | do { | ||
1388 | start = u64_stats_fetch_begin_irq(&cpu_stats->syncp); | ||
1389 | dropped = cpu_stats->dropped; | ||
1390 | } while (u64_stats_fetch_retry_irq(&cpu_stats->syncp, start)); | ||
1391 | |||
1392 | stats->dropped += dropped; | ||
1393 | } | ||
1394 | } | ||
1395 | |||
1396 | static int net_dm_hw_stats_put(struct sk_buff *msg) | ||
1397 | { | ||
1398 | struct net_dm_stats stats; | ||
1399 | struct nlattr *attr; | ||
1400 | |||
1401 | net_dm_hw_stats_read(&stats); | ||
1402 | |||
1403 | attr = nla_nest_start(msg, NET_DM_ATTR_HW_STATS); | ||
1404 | if (!attr) | ||
1405 | return -EMSGSIZE; | ||
1406 | |||
1407 | if (nla_put_u64_64bit(msg, NET_DM_ATTR_STATS_DROPPED, | ||
1408 | stats.dropped, NET_DM_ATTR_PAD)) | ||
1409 | goto nla_put_failure; | ||
1410 | |||
1411 | nla_nest_end(msg, attr); | ||
1412 | |||
1413 | return 0; | ||
1414 | |||
1415 | nla_put_failure: | ||
1416 | nla_nest_cancel(msg, attr); | ||
1417 | return -EMSGSIZE; | ||
1418 | } | ||
1419 | |||
788 | static int net_dm_stats_fill(struct sk_buff *msg, struct genl_info *info) | 1420 | static int net_dm_stats_fill(struct sk_buff *msg, struct genl_info *info) |
789 | { | 1421 | { |
790 | void *hdr; | 1422 | void *hdr; |
@@ -799,6 +1431,10 @@ static int net_dm_stats_fill(struct sk_buff *msg, struct genl_info *info) | |||
799 | if (rc) | 1431 | if (rc) |
800 | goto nla_put_failure; | 1432 | goto nla_put_failure; |
801 | 1433 | ||
1434 | rc = net_dm_hw_stats_put(msg); | ||
1435 | if (rc) | ||
1436 | goto nla_put_failure; | ||
1437 | |||
802 | genlmsg_end(msg, hdr); | 1438 | genlmsg_end(msg, hdr); |
803 | 1439 | ||
804 | return 0; | 1440 | return 0; |
@@ -872,6 +1508,8 @@ static const struct nla_policy net_dm_nl_policy[NET_DM_ATTR_MAX + 1] = { | |||
872 | [NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 }, | 1508 | [NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 }, |
873 | [NET_DM_ATTR_TRUNC_LEN] = { .type = NLA_U32 }, | 1509 | [NET_DM_ATTR_TRUNC_LEN] = { .type = NLA_U32 }, |
874 | [NET_DM_ATTR_QUEUE_LEN] = { .type = NLA_U32 }, | 1510 | [NET_DM_ATTR_QUEUE_LEN] = { .type = NLA_U32 }, |
1511 | [NET_DM_ATTR_SW_DROPS] = {. type = NLA_FLAG }, | ||
1512 | [NET_DM_ATTR_HW_DROPS] = {. type = NLA_FLAG }, | ||
875 | }; | 1513 | }; |
876 | 1514 | ||
877 | static const struct genl_ops dropmon_ops[] = { | 1515 | static const struct genl_ops dropmon_ops[] = { |
@@ -934,9 +1572,57 @@ static struct notifier_block dropmon_net_notifier = { | |||
934 | .notifier_call = dropmon_net_event | 1572 | .notifier_call = dropmon_net_event |
935 | }; | 1573 | }; |
936 | 1574 | ||
937 | static int __init init_net_drop_monitor(void) | 1575 | static void __net_dm_cpu_data_init(struct per_cpu_dm_data *data) |
1576 | { | ||
1577 | spin_lock_init(&data->lock); | ||
1578 | skb_queue_head_init(&data->drop_queue); | ||
1579 | u64_stats_init(&data->stats.syncp); | ||
1580 | } | ||
1581 | |||
1582 | static void __net_dm_cpu_data_fini(struct per_cpu_dm_data *data) | ||
1583 | { | ||
1584 | WARN_ON(!skb_queue_empty(&data->drop_queue)); | ||
1585 | } | ||
1586 | |||
1587 | static void net_dm_cpu_data_init(int cpu) | ||
938 | { | 1588 | { |
939 | struct per_cpu_dm_data *data; | 1589 | struct per_cpu_dm_data *data; |
1590 | |||
1591 | data = &per_cpu(dm_cpu_data, cpu); | ||
1592 | __net_dm_cpu_data_init(data); | ||
1593 | } | ||
1594 | |||
1595 | static void net_dm_cpu_data_fini(int cpu) | ||
1596 | { | ||
1597 | struct per_cpu_dm_data *data; | ||
1598 | |||
1599 | data = &per_cpu(dm_cpu_data, cpu); | ||
1600 | /* At this point, we should have exclusive access | ||
1601 | * to this struct and can free the skb inside it. | ||
1602 | */ | ||
1603 | consume_skb(data->skb); | ||
1604 | __net_dm_cpu_data_fini(data); | ||
1605 | } | ||
1606 | |||
1607 | static void net_dm_hw_cpu_data_init(int cpu) | ||
1608 | { | ||
1609 | struct per_cpu_dm_data *hw_data; | ||
1610 | |||
1611 | hw_data = &per_cpu(dm_hw_cpu_data, cpu); | ||
1612 | __net_dm_cpu_data_init(hw_data); | ||
1613 | } | ||
1614 | |||
1615 | static void net_dm_hw_cpu_data_fini(int cpu) | ||
1616 | { | ||
1617 | struct per_cpu_dm_data *hw_data; | ||
1618 | |||
1619 | hw_data = &per_cpu(dm_hw_cpu_data, cpu); | ||
1620 | kfree(hw_data->hw_entries); | ||
1621 | __net_dm_cpu_data_fini(hw_data); | ||
1622 | } | ||
1623 | |||
1624 | static int __init init_net_drop_monitor(void) | ||
1625 | { | ||
940 | int cpu, rc; | 1626 | int cpu, rc; |
941 | 1627 | ||
942 | pr_info("Initializing network drop monitor service\n"); | 1628 | pr_info("Initializing network drop monitor service\n"); |
@@ -962,10 +1648,8 @@ static int __init init_net_drop_monitor(void) | |||
962 | rc = 0; | 1648 | rc = 0; |
963 | 1649 | ||
964 | for_each_possible_cpu(cpu) { | 1650 | for_each_possible_cpu(cpu) { |
965 | data = &per_cpu(dm_cpu_data, cpu); | 1651 | net_dm_cpu_data_init(cpu); |
966 | spin_lock_init(&data->lock); | 1652 | net_dm_hw_cpu_data_init(cpu); |
967 | skb_queue_head_init(&data->drop_queue); | ||
968 | u64_stats_init(&data->stats.syncp); | ||
969 | } | 1653 | } |
970 | 1654 | ||
971 | goto out; | 1655 | goto out; |
@@ -978,7 +1662,6 @@ out: | |||
978 | 1662 | ||
979 | static void exit_net_drop_monitor(void) | 1663 | static void exit_net_drop_monitor(void) |
980 | { | 1664 | { |
981 | struct per_cpu_dm_data *data; | ||
982 | int cpu; | 1665 | int cpu; |
983 | 1666 | ||
984 | BUG_ON(unregister_netdevice_notifier(&dropmon_net_notifier)); | 1667 | BUG_ON(unregister_netdevice_notifier(&dropmon_net_notifier)); |
@@ -989,13 +1672,8 @@ static void exit_net_drop_monitor(void) | |||
989 | */ | 1672 | */ |
990 | 1673 | ||
991 | for_each_possible_cpu(cpu) { | 1674 | for_each_possible_cpu(cpu) { |
992 | data = &per_cpu(dm_cpu_data, cpu); | 1675 | net_dm_hw_cpu_data_fini(cpu); |
993 | /* | 1676 | net_dm_cpu_data_fini(cpu); |
994 | * At this point, we should have exclusive access | ||
995 | * to this struct and can free the skb inside it | ||
996 | */ | ||
997 | kfree_skb(data->skb); | ||
998 | WARN_ON(!skb_queue_empty(&data->drop_queue)); | ||
999 | } | 1677 | } |
1000 | 1678 | ||
1001 | BUG_ON(genl_unregister_family(&net_drop_monitor_family)); | 1679 | BUG_ON(genl_unregister_family(&net_drop_monitor_family)); |
diff --git a/tools/testing/selftests/drivers/net/netdevsim/devlink_trap.sh b/tools/testing/selftests/drivers/net/netdevsim/devlink_trap.sh new file mode 100755 index 000000000000..f101ab9441e2 --- /dev/null +++ b/tools/testing/selftests/drivers/net/netdevsim/devlink_trap.sh | |||
@@ -0,0 +1,364 @@ | |||
1 | #!/bin/bash | ||
2 | # SPDX-License-Identifier: GPL-2.0 | ||
3 | # | ||
4 | # This test is for checking devlink-trap functionality. It makes use of | ||
5 | # netdevsim which implements the required callbacks. | ||
6 | |||
7 | lib_dir=$(dirname $0)/../../../net/forwarding | ||
8 | |||
9 | ALL_TESTS=" | ||
10 | init_test | ||
11 | trap_action_test | ||
12 | trap_metadata_test | ||
13 | bad_trap_test | ||
14 | bad_trap_action_test | ||
15 | trap_stats_test | ||
16 | trap_group_action_test | ||
17 | bad_trap_group_test | ||
18 | trap_group_stats_test | ||
19 | port_del_test | ||
20 | dev_del_test | ||
21 | " | ||
22 | NETDEVSIM_PATH=/sys/bus/netdevsim/ | ||
23 | DEV_ADDR=1337 | ||
24 | DEV=netdevsim${DEV_ADDR} | ||
25 | DEVLINK_DEV=netdevsim/${DEV} | ||
26 | SLEEP_TIME=1 | ||
27 | NETDEV="" | ||
28 | NUM_NETIFS=0 | ||
29 | source $lib_dir/lib.sh | ||
30 | source $lib_dir/devlink_lib.sh | ||
31 | |||
32 | require_command udevadm | ||
33 | |||
34 | modprobe netdevsim &> /dev/null | ||
35 | if [ ! -d "$NETDEVSIM_PATH" ]; then | ||
36 | echo "SKIP: No netdevsim support" | ||
37 | exit 1 | ||
38 | fi | ||
39 | |||
40 | if [ -d "${NETDEVSIM_PATH}/devices/netdevsim${DEV_ADDR}" ]; then | ||
41 | echo "SKIP: Device netdevsim${DEV_ADDR} already exists" | ||
42 | exit 1 | ||
43 | fi | ||
44 | |||
45 | init_test() | ||
46 | { | ||
47 | RET=0 | ||
48 | |||
49 | test $(devlink_traps_num_get) -ne 0 | ||
50 | check_err $? "No traps were registered" | ||
51 | |||
52 | log_test "Initialization" | ||
53 | } | ||
54 | |||
55 | trap_action_test() | ||
56 | { | ||
57 | local orig_action | ||
58 | local trap_name | ||
59 | local action | ||
60 | |||
61 | RET=0 | ||
62 | |||
63 | for trap_name in $(devlink_traps_get); do | ||
64 | # The action of non-drop traps cannot be changed. | ||
65 | if [ $(devlink_trap_type_get $trap_name) = "drop" ]; then | ||
66 | devlink_trap_action_set $trap_name "trap" | ||
67 | action=$(devlink_trap_action_get $trap_name) | ||
68 | if [ $action != "trap" ]; then | ||
69 | check_err 1 "Trap $trap_name did not change action to trap" | ||
70 | fi | ||
71 | |||
72 | devlink_trap_action_set $trap_name "drop" | ||
73 | action=$(devlink_trap_action_get $trap_name) | ||
74 | if [ $action != "drop" ]; then | ||
75 | check_err 1 "Trap $trap_name did not change action to drop" | ||
76 | fi | ||
77 | else | ||
78 | orig_action=$(devlink_trap_action_get $trap_name) | ||
79 | |||
80 | devlink_trap_action_set $trap_name "trap" | ||
81 | action=$(devlink_trap_action_get $trap_name) | ||
82 | if [ $action != $orig_action ]; then | ||
83 | check_err 1 "Trap $trap_name changed action when should not" | ||
84 | fi | ||
85 | |||
86 | devlink_trap_action_set $trap_name "drop" | ||
87 | action=$(devlink_trap_action_get $trap_name) | ||
88 | if [ $action != $orig_action ]; then | ||
89 | check_err 1 "Trap $trap_name changed action when should not" | ||
90 | fi | ||
91 | fi | ||
92 | done | ||
93 | |||
94 | log_test "Trap action" | ||
95 | } | ||
96 | |||
97 | trap_metadata_test() | ||
98 | { | ||
99 | local trap_name | ||
100 | |||
101 | RET=0 | ||
102 | |||
103 | for trap_name in $(devlink_traps_get); do | ||
104 | devlink_trap_metadata_test $trap_name "input_port" | ||
105 | check_err $? "Input port not reported as metadata of trap $trap_name" | ||
106 | done | ||
107 | |||
108 | log_test "Trap metadata" | ||
109 | } | ||
110 | |||
111 | bad_trap_test() | ||
112 | { | ||
113 | RET=0 | ||
114 | |||
115 | devlink_trap_action_set "made_up_trap" "drop" | ||
116 | check_fail $? "Did not get an error for non-existing trap" | ||
117 | |||
118 | log_test "Non-existing trap" | ||
119 | } | ||
120 | |||
121 | bad_trap_action_test() | ||
122 | { | ||
123 | local traps_arr | ||
124 | local trap_name | ||
125 | |||
126 | RET=0 | ||
127 | |||
128 | # Pick first trap. | ||
129 | traps_arr=($(devlink_traps_get)) | ||
130 | trap_name=${traps_arr[0]} | ||
131 | |||
132 | devlink_trap_action_set $trap_name "made_up_action" | ||
133 | check_fail $? "Did not get an error for non-existing trap action" | ||
134 | |||
135 | log_test "Non-existing trap action" | ||
136 | } | ||
137 | |||
138 | trap_stats_test() | ||
139 | { | ||
140 | local trap_name | ||
141 | |||
142 | RET=0 | ||
143 | |||
144 | for trap_name in $(devlink_traps_get); do | ||
145 | devlink_trap_stats_idle_test $trap_name | ||
146 | check_err $? "Stats of trap $trap_name not idle when netdev down" | ||
147 | |||
148 | ip link set dev $NETDEV up | ||
149 | |||
150 | if [ $(devlink_trap_type_get $trap_name) = "drop" ]; then | ||
151 | devlink_trap_action_set $trap_name "trap" | ||
152 | devlink_trap_stats_idle_test $trap_name | ||
153 | check_fail $? "Stats of trap $trap_name idle when action is trap" | ||
154 | |||
155 | devlink_trap_action_set $trap_name "drop" | ||
156 | devlink_trap_stats_idle_test $trap_name | ||
157 | check_err $? "Stats of trap $trap_name not idle when action is drop" | ||
158 | else | ||
159 | devlink_trap_stats_idle_test $trap_name | ||
160 | check_fail $? "Stats of non-drop trap $trap_name idle when should not" | ||
161 | fi | ||
162 | |||
163 | ip link set dev $NETDEV down | ||
164 | done | ||
165 | |||
166 | log_test "Trap statistics" | ||
167 | } | ||
168 | |||
169 | trap_group_action_test() | ||
170 | { | ||
171 | local curr_group group_name | ||
172 | local trap_name | ||
173 | local trap_type | ||
174 | local action | ||
175 | |||
176 | RET=0 | ||
177 | |||
178 | for group_name in $(devlink_trap_groups_get); do | ||
179 | devlink_trap_group_action_set $group_name "trap" | ||
180 | |||
181 | for trap_name in $(devlink_traps_get); do | ||
182 | curr_group=$(devlink_trap_group_get $trap_name) | ||
183 | if [ $curr_group != $group_name ]; then | ||
184 | continue | ||
185 | fi | ||
186 | |||
187 | trap_type=$(devlink_trap_type_get $trap_name) | ||
188 | if [ $trap_type != "drop" ]; then | ||
189 | continue | ||
190 | fi | ||
191 | |||
192 | action=$(devlink_trap_action_get $trap_name) | ||
193 | if [ $action != "trap" ]; then | ||
194 | check_err 1 "Trap $trap_name did not change action to trap" | ||
195 | fi | ||
196 | done | ||
197 | |||
198 | devlink_trap_group_action_set $group_name "drop" | ||
199 | |||
200 | for trap_name in $(devlink_traps_get); do | ||
201 | curr_group=$(devlink_trap_group_get $trap_name) | ||
202 | if [ $curr_group != $group_name ]; then | ||
203 | continue | ||
204 | fi | ||
205 | |||
206 | trap_type=$(devlink_trap_type_get $trap_name) | ||
207 | if [ $trap_type != "drop" ]; then | ||
208 | continue | ||
209 | fi | ||
210 | |||
211 | action=$(devlink_trap_action_get $trap_name) | ||
212 | if [ $action != "drop" ]; then | ||
213 | check_err 1 "Trap $trap_name did not change action to drop" | ||
214 | fi | ||
215 | done | ||
216 | done | ||
217 | |||
218 | log_test "Trap group action" | ||
219 | } | ||
220 | |||
221 | bad_trap_group_test() | ||
222 | { | ||
223 | RET=0 | ||
224 | |||
225 | devlink_trap_group_action_set "made_up_trap_group" "drop" | ||
226 | check_fail $? "Did not get an error for non-existing trap group" | ||
227 | |||
228 | log_test "Non-existing trap group" | ||
229 | } | ||
230 | |||
231 | trap_group_stats_test() | ||
232 | { | ||
233 | local group_name | ||
234 | |||
235 | RET=0 | ||
236 | |||
237 | for group_name in $(devlink_trap_groups_get); do | ||
238 | devlink_trap_group_stats_idle_test $group_name | ||
239 | check_err $? "Stats of trap group $group_name not idle when netdev down" | ||
240 | |||
241 | ip link set dev $NETDEV up | ||
242 | |||
243 | devlink_trap_group_action_set $group_name "trap" | ||
244 | devlink_trap_group_stats_idle_test $group_name | ||
245 | check_fail $? "Stats of trap group $group_name idle when action is trap" | ||
246 | |||
247 | devlink_trap_group_action_set $group_name "drop" | ||
248 | ip link set dev $NETDEV down | ||
249 | done | ||
250 | |||
251 | log_test "Trap group statistics" | ||
252 | } | ||
253 | |||
254 | port_del_test() | ||
255 | { | ||
256 | local group_name | ||
257 | local i | ||
258 | |||
259 | # The test never fails. It is meant to exercise different code paths | ||
260 | # and make sure we properly dismantle a port while packets are | ||
261 | # in-flight. | ||
262 | RET=0 | ||
263 | |||
264 | devlink_traps_enable_all | ||
265 | |||
266 | for i in $(seq 1 10); do | ||
267 | ip link set dev $NETDEV up | ||
268 | |||
269 | sleep $SLEEP_TIME | ||
270 | |||
271 | netdevsim_port_destroy | ||
272 | netdevsim_port_create | ||
273 | udevadm settle | ||
274 | done | ||
275 | |||
276 | devlink_traps_disable_all | ||
277 | |||
278 | log_test "Port delete" | ||
279 | } | ||
280 | |||
281 | dev_del_test() | ||
282 | { | ||
283 | local group_name | ||
284 | local i | ||
285 | |||
286 | # The test never fails. It is meant to exercise different code paths | ||
287 | # and make sure we properly unregister traps while packets are | ||
288 | # in-flight. | ||
289 | RET=0 | ||
290 | |||
291 | devlink_traps_enable_all | ||
292 | |||
293 | for i in $(seq 1 10); do | ||
294 | ip link set dev $NETDEV up | ||
295 | |||
296 | sleep $SLEEP_TIME | ||
297 | |||
298 | cleanup | ||
299 | setup_prepare | ||
300 | done | ||
301 | |||
302 | devlink_traps_disable_all | ||
303 | |||
304 | log_test "Device delete" | ||
305 | } | ||
306 | |||
307 | netdevsim_dev_create() | ||
308 | { | ||
309 | echo "$DEV_ADDR 0" > ${NETDEVSIM_PATH}/new_device | ||
310 | } | ||
311 | |||
312 | netdevsim_dev_destroy() | ||
313 | { | ||
314 | echo "$DEV_ADDR" > ${NETDEVSIM_PATH}/del_device | ||
315 | } | ||
316 | |||
317 | netdevsim_port_create() | ||
318 | { | ||
319 | echo 1 > ${NETDEVSIM_PATH}/devices/${DEV}/new_port | ||
320 | } | ||
321 | |||
322 | netdevsim_port_destroy() | ||
323 | { | ||
324 | echo 1 > ${NETDEVSIM_PATH}/devices/${DEV}/del_port | ||
325 | } | ||
326 | |||
327 | setup_prepare() | ||
328 | { | ||
329 | local netdev | ||
330 | |||
331 | netdevsim_dev_create | ||
332 | |||
333 | if [ ! -d "${NETDEVSIM_PATH}/devices/${DEV}" ]; then | ||
334 | echo "Failed to create netdevsim device" | ||
335 | exit 1 | ||
336 | fi | ||
337 | |||
338 | netdevsim_port_create | ||
339 | |||
340 | if [ ! -d "${NETDEVSIM_PATH}/devices/${DEV}/net/" ]; then | ||
341 | echo "Failed to create netdevsim port" | ||
342 | exit 1 | ||
343 | fi | ||
344 | |||
345 | # Wait for udev to rename newly created netdev. | ||
346 | udevadm settle | ||
347 | |||
348 | NETDEV=$(ls ${NETDEVSIM_PATH}/devices/${DEV}/net/) | ||
349 | } | ||
350 | |||
351 | cleanup() | ||
352 | { | ||
353 | pre_cleanup | ||
354 | netdevsim_port_destroy | ||
355 | netdevsim_dev_destroy | ||
356 | } | ||
357 | |||
358 | trap cleanup EXIT | ||
359 | |||
360 | setup_prepare | ||
361 | |||
362 | tests_run | ||
363 | |||
364 | exit $EXIT_STATUS | ||
diff --git a/tools/testing/selftests/net/forwarding/devlink_lib.sh b/tools/testing/selftests/net/forwarding/devlink_lib.sh index 8553a67a2322..13d03a6d85ba 100644 --- a/tools/testing/selftests/net/forwarding/devlink_lib.sh +++ b/tools/testing/selftests/net/forwarding/devlink_lib.sh | |||
@@ -4,19 +4,21 @@ | |||
4 | ############################################################################## | 4 | ############################################################################## |
5 | # Defines | 5 | # Defines |
6 | 6 | ||
7 | DEVLINK_DEV=$(devlink port show "${NETIFS[p1]}" -j \ | 7 | if [[ ! -v DEVLINK_DEV ]]; then |
8 | | jq -r '.port | keys[]' | cut -d/ -f-2) | 8 | DEVLINK_DEV=$(devlink port show "${NETIFS[p1]}" -j \ |
9 | if [ -z "$DEVLINK_DEV" ]; then | 9 | | jq -r '.port | keys[]' | cut -d/ -f-2) |
10 | echo "SKIP: ${NETIFS[p1]} has no devlink device registered for it" | 10 | if [ -z "$DEVLINK_DEV" ]; then |
11 | exit 1 | 11 | echo "SKIP: ${NETIFS[p1]} has no devlink device registered for it" |
12 | fi | 12 | exit 1 |
13 | if [[ "$(echo $DEVLINK_DEV | grep -c pci)" -eq 0 ]]; then | 13 | fi |
14 | echo "SKIP: devlink device's bus is not PCI" | 14 | if [[ "$(echo $DEVLINK_DEV | grep -c pci)" -eq 0 ]]; then |
15 | exit 1 | 15 | echo "SKIP: devlink device's bus is not PCI" |
16 | fi | 16 | exit 1 |
17 | fi | ||
17 | 18 | ||
18 | DEVLINK_VIDDID=$(lspci -s $(echo $DEVLINK_DEV | cut -d"/" -f2) \ | 19 | DEVLINK_VIDDID=$(lspci -s $(echo $DEVLINK_DEV | cut -d"/" -f2) \ |
19 | -n | cut -d" " -f3) | 20 | -n | cut -d" " -f3) |
21 | fi | ||
20 | 22 | ||
21 | ############################################################################## | 23 | ############################################################################## |
22 | # Sanity checks | 24 | # Sanity checks |
@@ -27,6 +29,12 @@ if [ $? -ne 0 ]; then | |||
27 | exit 1 | 29 | exit 1 |
28 | fi | 30 | fi |
29 | 31 | ||
32 | devlink help 2>&1 | grep trap &> /dev/null | ||
33 | if [ $? -ne 0 ]; then | ||
34 | echo "SKIP: iproute2 too old, missing devlink trap support" | ||
35 | exit 1 | ||
36 | fi | ||
37 | |||
30 | ############################################################################## | 38 | ############################################################################## |
31 | # Devlink helpers | 39 | # Devlink helpers |
32 | 40 | ||
@@ -190,3 +198,160 @@ devlink_tc_bind_pool_th_restore() | |||
190 | devlink sb tc bind set $port tc $tc type $dir \ | 198 | devlink sb tc bind set $port tc $tc type $dir \ |
191 | pool ${orig[0]} th ${orig[1]} | 199 | pool ${orig[0]} th ${orig[1]} |
192 | } | 200 | } |
201 | |||
202 | devlink_traps_num_get() | ||
203 | { | ||
204 | devlink -j trap | jq '.[]["'$DEVLINK_DEV'"] | length' | ||
205 | } | ||
206 | |||
207 | devlink_traps_get() | ||
208 | { | ||
209 | devlink -j trap | jq -r '.[]["'$DEVLINK_DEV'"][].name' | ||
210 | } | ||
211 | |||
212 | devlink_trap_type_get() | ||
213 | { | ||
214 | local trap_name=$1; shift | ||
215 | |||
216 | devlink -j trap show $DEVLINK_DEV trap $trap_name \ | ||
217 | | jq -r '.[][][].type' | ||
218 | } | ||
219 | |||
220 | devlink_trap_action_set() | ||
221 | { | ||
222 | local trap_name=$1; shift | ||
223 | local action=$1; shift | ||
224 | |||
225 | # Pipe output to /dev/null to avoid expected warnings. | ||
226 | devlink trap set $DEVLINK_DEV trap $trap_name \ | ||
227 | action $action &> /dev/null | ||
228 | } | ||
229 | |||
230 | devlink_trap_action_get() | ||
231 | { | ||
232 | local trap_name=$1; shift | ||
233 | |||
234 | devlink -j trap show $DEVLINK_DEV trap $trap_name \ | ||
235 | | jq -r '.[][][].action' | ||
236 | } | ||
237 | |||
238 | devlink_trap_group_get() | ||
239 | { | ||
240 | devlink -j trap show $DEVLINK_DEV trap $trap_name \ | ||
241 | | jq -r '.[][][].group' | ||
242 | } | ||
243 | |||
244 | devlink_trap_metadata_test() | ||
245 | { | ||
246 | local trap_name=$1; shift | ||
247 | local metadata=$1; shift | ||
248 | |||
249 | devlink -jv trap show $DEVLINK_DEV trap $trap_name \ | ||
250 | | jq -e '.[][][].metadata | contains(["'$metadata'"])' \ | ||
251 | &> /dev/null | ||
252 | } | ||
253 | |||
254 | devlink_trap_rx_packets_get() | ||
255 | { | ||
256 | local trap_name=$1; shift | ||
257 | |||
258 | devlink -js trap show $DEVLINK_DEV trap $trap_name \ | ||
259 | | jq '.[][][]["stats"]["rx"]["packets"]' | ||
260 | } | ||
261 | |||
262 | devlink_trap_rx_bytes_get() | ||
263 | { | ||
264 | local trap_name=$1; shift | ||
265 | |||
266 | devlink -js trap show $DEVLINK_DEV trap $trap_name \ | ||
267 | | jq '.[][][]["stats"]["rx"]["bytes"]' | ||
268 | } | ||
269 | |||
270 | devlink_trap_stats_idle_test() | ||
271 | { | ||
272 | local trap_name=$1; shift | ||
273 | local t0_packets t0_bytes | ||
274 | local t1_packets t1_bytes | ||
275 | |||
276 | t0_packets=$(devlink_trap_rx_packets_get $trap_name) | ||
277 | t0_bytes=$(devlink_trap_rx_bytes_get $trap_name) | ||
278 | |||
279 | sleep 1 | ||
280 | |||
281 | t1_packets=$(devlink_trap_rx_packets_get $trap_name) | ||
282 | t1_bytes=$(devlink_trap_rx_bytes_get $trap_name) | ||
283 | |||
284 | if [[ $t0_packets -eq $t1_packets && $t0_bytes -eq $t1_bytes ]]; then | ||
285 | return 0 | ||
286 | else | ||
287 | return 1 | ||
288 | fi | ||
289 | } | ||
290 | |||
291 | devlink_traps_enable_all() | ||
292 | { | ||
293 | local trap_name | ||
294 | |||
295 | for trap_name in $(devlink_traps_get); do | ||
296 | devlink_trap_action_set $trap_name "trap" | ||
297 | done | ||
298 | } | ||
299 | |||
300 | devlink_traps_disable_all() | ||
301 | { | ||
302 | for trap_name in $(devlink_traps_get); do | ||
303 | devlink_trap_action_set $trap_name "drop" | ||
304 | done | ||
305 | } | ||
306 | |||
307 | devlink_trap_groups_get() | ||
308 | { | ||
309 | devlink -j trap group | jq -r '.[]["'$DEVLINK_DEV'"][].name' | ||
310 | } | ||
311 | |||
312 | devlink_trap_group_action_set() | ||
313 | { | ||
314 | local group_name=$1; shift | ||
315 | local action=$1; shift | ||
316 | |||
317 | # Pipe output to /dev/null to avoid expected warnings. | ||
318 | devlink trap group set $DEVLINK_DEV group $group_name action $action \ | ||
319 | &> /dev/null | ||
320 | } | ||
321 | |||
322 | devlink_trap_group_rx_packets_get() | ||
323 | { | ||
324 | local group_name=$1; shift | ||
325 | |||
326 | devlink -js trap group show $DEVLINK_DEV group $group_name \ | ||
327 | | jq '.[][][]["stats"]["rx"]["packets"]' | ||
328 | } | ||
329 | |||
330 | devlink_trap_group_rx_bytes_get() | ||
331 | { | ||
332 | local group_name=$1; shift | ||
333 | |||
334 | devlink -js trap group show $DEVLINK_DEV group $group_name \ | ||
335 | | jq '.[][][]["stats"]["rx"]["bytes"]' | ||
336 | } | ||
337 | |||
338 | devlink_trap_group_stats_idle_test() | ||
339 | { | ||
340 | local group_name=$1; shift | ||
341 | local t0_packets t0_bytes | ||
342 | local t1_packets t1_bytes | ||
343 | |||
344 | t0_packets=$(devlink_trap_group_rx_packets_get $group_name) | ||
345 | t0_bytes=$(devlink_trap_group_rx_bytes_get $group_name) | ||
346 | |||
347 | sleep 1 | ||
348 | |||
349 | t1_packets=$(devlink_trap_group_rx_packets_get $group_name) | ||
350 | t1_bytes=$(devlink_trap_group_rx_bytes_get $group_name) | ||
351 | |||
352 | if [[ $t0_packets -eq $t1_packets && $t0_bytes -eq $t1_bytes ]]; then | ||
353 | return 0 | ||
354 | else | ||
355 | return 1 | ||
356 | fi | ||
357 | } | ||