History

drop_monitor: Add packet alert mode

So far drop monitor supported only one alert mode in which a summary of locations in which packets were recently dropped was sent to user space. This alert mode is sufficient in order to understand that packets were dropped, but lacks information to perform a more detailed analysis. Add a new alert mode in which the dropped packet itself is passed to user space along with metadata: The drop location (as program counter and resolved symbol), ingress netdevice and drop timestamp. More metadata can be added in the future. To avoid performing expensive operations in the context in which kfree_skb() is invoked (can be hard IRQ), the dropped skb is cloned and queued on per-CPU skb drop list. Then, in process context the netlink message is allocated, prepared and finally sent to user space. The per-CPU skb drop list is limited to 1000 skbs to prevent exhausting the system's memory. Subsequent patches will make this limit configurable and also add a counter that indicates how many skbs were tail dropped.

Ido Schimmel committed 7 years ago

Commit ca307071 parent 28315f72 files · +305 −2Browse files

Unified · Split

2 files
  1. include/uapi/linux/net_dropmon.h +27 −0
  2. net/core/drop_monitor.c +278 −2
include/uapi/linux/net_dropmon.h+27−0
@@ -53,6 +53,7 @@ enum { · 52 unmodified lines
5353 NET_DM_CMD_CONFIG,
5454 NET_DM_CMD_START,
5555 NET_DM_CMD_STOP,
56+ NET_DM_CMD_PACKET_ALERT,
5657 _NET_DM_CMD_MAX,
5758 };
5859
@@ -63,12 +64,38 @@ enum { · 4 unmodified lines
6364 */
6465 #define NET_DM_GRP_ALERT 1
6566
67+enum net_dm_attr {
68+ NET_DM_ATTR_UNSPEC,
69+
70+ NET_DM_ATTR_ALERT_MODE, /* u8 */
71+ NET_DM_ATTR_PC, /* u64 */
72+ NET_DM_ATTR_SYMBOL, /* string */
73+ NET_DM_ATTR_IN_PORT, /* nested */
74+ NET_DM_ATTR_TIMESTAMP, /* struct timespec */
75+ NET_DM_ATTR_PROTO, /* u16 */
76+ NET_DM_ATTR_PAYLOAD, /* binary */
77+ NET_DM_ATTR_PAD,
78+
79+ __NET_DM_ATTR_MAX,
80+ NET_DM_ATTR_MAX = __NET_DM_ATTR_MAX - 1
81+};
82+
6683 /**
6784 * enum net_dm_alert_mode - Alert mode.
6885 * @NET_DM_ALERT_MODE_SUMMARY: A summary of recent drops is sent to user space.
86+ * @NET_DM_ALERT_MODE_PACKET: Each dropped packet is sent to user space along
87+ * with metadata.
6988 */
7089 enum net_dm_alert_mode {
7190 NET_DM_ALERT_MODE_SUMMARY,
91+ NET_DM_ALERT_MODE_PACKET,
92+};
93+
94+enum {
95+ NET_DM_ATTR_PORT_NETDEV_IFINDEX, /* u32 */
96+
97+ __NET_DM_ATTR_PORT_MAX,
98+ NET_DM_ATTR_PORT_MAX = __NET_DM_ATTR_PORT_MAX - 1
7299 };
73100
74101 #endif
net/core/drop_monitor.c+278−2
@@ -54,6 +54,7 @@ static DEFINE_MUTEX(net_dm_mutex); · 53 unmodified lines
5454 struct per_cpu_dm_data {
5555 spinlock_t lock; /* Protects 'skb' and 'send_timer' */
5656 struct sk_buff *skb;
57+ struct sk_buff_head drop_queue;
5758 struct work_struct dm_alert_work;
5859 struct timer_list send_timer;
5960 };
@@ -85,6 +86,14 @@ struct net_dm_alert_ops { · 25 unmodified lines
8586 void (*work_item_func)(struct work_struct *work);
8687 };
8788
89+struct net_dm_skb_cb {
90+ void *pc;
91+};
92+
93+#define NET_DM_SKB_CB(__skb) ((struct net_dm_skb_cb *)&((__skb)->cb[0]))
94+
95+#define NET_DM_QUEUE_LEN 1000
96+
8897 static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data)
8998 {
9099 size_t al;
@@ -257,8 +266,214 @@ static const struct net_dm_alert_ops net_dm_alert_summary_ops = { · 166 unmodified lines
257266 .work_item_func = send_dm_alert,
258267 };
259268
269+static void net_dm_packet_trace_kfree_skb_hit(void *ignore,
270+ struct sk_buff *skb,
271+ void *location)
272+{
273+ ktime_t tstamp = ktime_get_real();
274+ struct per_cpu_dm_data *data;
275+ struct sk_buff *nskb;
276+ unsigned long flags;
277+
278+ nskb = skb_clone(skb, GFP_ATOMIC);
279+ if (!nskb)
280+ return;
281+
282+ NET_DM_SKB_CB(nskb)->pc = location;
283+ /* Override the timestamp because we care about the time when the
284+ * packet was dropped.
285+ */
286+ nskb->tstamp = tstamp;
287+
288+ data = this_cpu_ptr(&dm_cpu_data);
289+
290+ spin_lock_irqsave(&data->drop_queue.lock, flags);
291+ if (skb_queue_len(&data->drop_queue) < NET_DM_QUEUE_LEN)
292+ __skb_queue_tail(&data->drop_queue, nskb);
293+ else
294+ goto unlock_free;
295+ spin_unlock_irqrestore(&data->drop_queue.lock, flags);
296+
297+ schedule_work(&data->dm_alert_work);
298+
299+ return;
300+
301+unlock_free:
302+ spin_unlock_irqrestore(&data->drop_queue.lock, flags);
303+ consume_skb(nskb);
304+}
305+
306+static void net_dm_packet_trace_napi_poll_hit(void *ignore,
307+ struct napi_struct *napi,
308+ int work, int budget)
309+{
310+}
311+
312+static size_t net_dm_in_port_size(void)
313+{
314+ /* NET_DM_ATTR_IN_PORT nest */
315+ return nla_total_size(0) +
316+ /* NET_DM_ATTR_PORT_NETDEV_IFINDEX */
317+ nla_total_size(sizeof(u32));
318+}
319+
320+#define NET_DM_MAX_SYMBOL_LEN 40
321+
322+static size_t net_dm_packet_report_size(size_t payload_len)
323+{
324+ size_t size;
325+
326+ size = nlmsg_msg_size(GENL_HDRLEN + net_drop_monitor_family.hdrsize);
327+
328+ return NLMSG_ALIGN(size) +
329+ /* NET_DM_ATTR_PC */
330+ nla_total_size(sizeof(u64)) +
331+ /* NET_DM_ATTR_SYMBOL */
332+ nla_total_size(NET_DM_MAX_SYMBOL_LEN + 1) +
333+ /* NET_DM_ATTR_IN_PORT */
334+ net_dm_in_port_size() +
335+ /* NET_DM_ATTR_TIMESTAMP */
336+ nla_total_size(sizeof(struct timespec)) +
337+ /* NET_DM_ATTR_PROTO */
338+ nla_total_size(sizeof(u16)) +
339+ /* NET_DM_ATTR_PAYLOAD */
340+ nla_total_size(payload_len);
341+}
342+
343+static int net_dm_packet_report_in_port_put(struct sk_buff *msg, int ifindex)
344+{
345+ struct nlattr *attr;
346+
347+ attr = nla_nest_start(msg, NET_DM_ATTR_IN_PORT);
348+ if (!attr)
349+ return -EMSGSIZE;
350+
351+ if (ifindex &&
352+ nla_put_u32(msg, NET_DM_ATTR_PORT_NETDEV_IFINDEX, ifindex))
353+ goto nla_put_failure;
354+
355+ nla_nest_end(msg, attr);
356+
357+ return 0;
358+
359+nla_put_failure:
360+ nla_nest_cancel(msg, attr);
361+ return -EMSGSIZE;
362+}
363+
364+static int net_dm_packet_report_fill(struct sk_buff *msg, struct sk_buff *skb,
365+ size_t payload_len)
366+{
367+ u64 pc = (u64)(uintptr_t) NET_DM_SKB_CB(skb)->pc;
368+ char buf[NET_DM_MAX_SYMBOL_LEN];
369+ struct nlattr *attr;
370+ struct timespec ts;
371+ void *hdr;
372+ int rc;
373+
374+ hdr = genlmsg_put(msg, 0, 0, &net_drop_monitor_family, 0,
375+ NET_DM_CMD_PACKET_ALERT);
376+ if (!hdr)
377+ return -EMSGSIZE;
378+
379+ if (nla_put_u64_64bit(msg, NET_DM_ATTR_PC, pc, NET_DM_ATTR_PAD))
380+ goto nla_put_failure;
381+
382+ snprintf(buf, sizeof(buf), "%pS", NET_DM_SKB_CB(skb)->pc);
383+ if (nla_put_string(msg, NET_DM_ATTR_SYMBOL, buf))
384+ goto nla_put_failure;
385+
386+ rc = net_dm_packet_report_in_port_put(msg, skb->skb_iif);
387+ if (rc)
388+ goto nla_put_failure;
389+
390+ if (ktime_to_timespec_cond(skb->tstamp, &ts) &&
391+ nla_put(msg, NET_DM_ATTR_TIMESTAMP, sizeof(ts), &ts))
392+ goto nla_put_failure;
393+
394+ if (!payload_len)
395+ goto out;
396+
397+ if (nla_put_u16(msg, NET_DM_ATTR_PROTO, be16_to_cpu(skb->protocol)))
398+ goto nla_put_failure;
399+
400+ attr = skb_put(msg, nla_total_size(payload_len));
401+ attr->nla_type = NET_DM_ATTR_PAYLOAD;
402+ attr->nla_len = nla_attr_size(payload_len);
403+ if (skb_copy_bits(skb, 0, nla_data(attr), payload_len))
404+ goto nla_put_failure;
405+
406+out:
407+ genlmsg_end(msg, hdr);
408+
409+ return 0;
410+
411+nla_put_failure:
412+ genlmsg_cancel(msg, hdr);
413+ return -EMSGSIZE;
414+}
415+
416+#define NET_DM_MAX_PACKET_SIZE (0xffff - NLA_HDRLEN - NLA_ALIGNTO)
417+
418+static void net_dm_packet_report(struct sk_buff *skb)
419+{
420+ struct sk_buff *msg;
421+ size_t payload_len;
422+ int rc;
423+
424+ /* Make sure we start copying the packet from the MAC header */
425+ if (skb->data > skb_mac_header(skb))
426+ skb_push(skb, skb->data - skb_mac_header(skb));
427+ else
428+ skb_pull(skb, skb_mac_header(skb) - skb->data);
429+
430+ /* Ensure packet fits inside a single netlink attribute */
431+ payload_len = min_t(size_t, skb->len, NET_DM_MAX_PACKET_SIZE);
432+
433+ msg = nlmsg_new(net_dm_packet_report_size(payload_len), GFP_KERNEL);
434+ if (!msg)
435+ goto out;
436+
437+ rc = net_dm_packet_report_fill(msg, skb, payload_len);
438+ if (rc) {
439+ nlmsg_free(msg);
440+ goto out;
441+ }
442+
443+ genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL);
444+
445+out:
446+ consume_skb(skb);
447+}
448+
449+static void net_dm_packet_work(struct work_struct *work)
450+{
451+ struct per_cpu_dm_data *data;
452+ struct sk_buff_head list;
453+ struct sk_buff *skb;
454+ unsigned long flags;
455+
456+ data = container_of(work, struct per_cpu_dm_data, dm_alert_work);
457+
458+ __skb_queue_head_init(&list);
459+
460+ spin_lock_irqsave(&data->drop_queue.lock, flags);
461+ skb_queue_splice_tail_init(&data->drop_queue, &list);
462+ spin_unlock_irqrestore(&data->drop_queue.lock, flags);
463+
464+ while ((skb = __skb_dequeue(&list)))
465+ net_dm_packet_report(skb);
466+}
467+
468+static const struct net_dm_alert_ops net_dm_alert_packet_ops = {
469+ .kfree_skb_probe = net_dm_packet_trace_kfree_skb_hit,
470+ .napi_poll_probe = net_dm_packet_trace_napi_poll_hit,
471+ .work_item_func = net_dm_packet_work,
472+};
473+
260474 static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = {
261475 [NET_DM_ALERT_MODE_SUMMARY] = &net_dm_alert_summary_ops,
476+ [NET_DM_ALERT_MODE_PACKET] = &net_dm_alert_packet_ops,
262477 };
263478
264479 static int net_dm_trace_on_set(struct netlink_ext_ack *extack)
@@ -326,9 +541,12 @@ static void net_dm_trace_off_set(void) · 61 unmodified lines
326541 */
327542 for_each_possible_cpu(cpu) {
328543 struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu);
544+ struct sk_buff *skb;
329545
330546 del_timer_sync(&data->send_timer);
331547 cancel_work_sync(&data->dm_alert_work);
548+ while ((skb = __skb_dequeue(&data->drop_queue)))
549+ consume_skb(skb);
332550 }
333551
334552 list_for_each_entry_safe(new_stat, temp, &hw_stats_list, list) {
@@ -370,12 +588,61 @@ static int set_all_monitor_traces(int state, struct netlink_ext_ack *extack) · 35 unmodified lines
370588 return rc;
371589 }
372590
591+static int net_dm_alert_mode_get_from_info(struct genl_info *info,
592+ enum net_dm_alert_mode *p_alert_mode)
593+{
594+ u8 val;
595+
596+ val = nla_get_u8(info->attrs[NET_DM_ATTR_ALERT_MODE]);
597+
598+ switch (val) {
599+ case NET_DM_ALERT_MODE_SUMMARY: /* fall-through */
600+ case NET_DM_ALERT_MODE_PACKET:
601+ *p_alert_mode = val;
602+ break;
603+ default:
604+ return -EINVAL;
605+ }
606+
607+ return 0;
608+}
609+
610+static int net_dm_alert_mode_set(struct genl_info *info)
611+{
612+ struct netlink_ext_ack *extack = info->extack;
613+ enum net_dm_alert_mode alert_mode;
614+ int rc;
615+
616+ if (!info->attrs[NET_DM_ATTR_ALERT_MODE])
617+ return 0;
618+
619+ rc = net_dm_alert_mode_get_from_info(info, &alert_mode);
620+ if (rc) {
621+ NL_SET_ERR_MSG_MOD(extack, "Invalid alert mode");
622+ return -EINVAL;
623+ }
624+
625+ net_dm_alert_mode = alert_mode;
626+
627+ return 0;
628+}
629+
373630 static int net_dm_cmd_config(struct sk_buff *skb,
374631 struct genl_info *info)
375632 {
376− NL_SET_ERR_MSG_MOD(info->extack, "Command not supported");
633+ struct netlink_ext_ack *extack = info->extack;
634+ int rc;
377635
378− return -EOPNOTSUPP;
636+ if (trace_state == TRACE_ON) {
637+ NL_SET_ERR_MSG_MOD(extack, "Cannot configure drop monitor while tracing is on");
638+ return -EBUSY;
639+ }
640+
641+ rc = net_dm_alert_mode_set(info);
642+ if (rc)
643+ return rc;
644+
645+ return 0;
379646 }
380647
381648 static int net_dm_cmd_trace(struct sk_buff *skb,
@@ -430,6 +697,11 @@ out: · 48 unmodified lines
430697 return NOTIFY_DONE;
431698 }
432699
700+static const struct nla_policy net_dm_nl_policy[NET_DM_ATTR_MAX + 1] = {
701+ [NET_DM_ATTR_UNSPEC] = { .strict_start_type = NET_DM_ATTR_UNSPEC + 1 },
702+ [NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 },
703+};
704+
433705 static const struct genl_ops dropmon_ops[] = {
434706 {
435707 .cmd = NET_DM_CMD_CONFIG,
@@ -467,6 +739,8 @@ static struct genl_family net_drop_monitor_family __ro_after_init = { · 31 unmodified lines
467739 .hdrsize = 0,
468740 .name = "NET_DM",
469741 .version = 2,
742+ .maxattr = NET_DM_ATTR_MAX,
743+ .policy = net_dm_nl_policy,
470744 .pre_doit = net_dm_nl_pre_doit,
471745 .post_doit = net_dm_nl_post_doit,
472746 .module = THIS_MODULE,
@@ -510,6 +784,7 @@ static int __init init_net_drop_monitor(void) · 37 unmodified lines
510784 for_each_possible_cpu(cpu) {
511785 data = &per_cpu(dm_cpu_data, cpu);
512786 spin_lock_init(&data->lock);
787+ skb_queue_head_init(&data->drop_queue);
513788 }
514789
515790 goto out;
@@ -539,6 +814,7 @@ static void exit_net_drop_monitor(void) · 23 unmodified lines
539814 * to this struct and can free the skb inside it
540815 */
541816 kfree_skb(data->skb);
817+ WARN_ON(!skb_queue_empty(&data->drop_queue));
542818 }
543819
544820 BUG_ON(genl_unregister_family(&net_drop_monitor_family));