1784 lines · 41.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Monitoring code for network dropped packet alerts * * Copyright (C) 2009 Neil Horman <nhorman@tuxdriver.com> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/string.h>#include <linux/if_arp.h>#include <linux/inetdevice.h>#include <linux/inet.h>#include <linux/interrupt.h>#include <linux/netpoll.h>#include <linux/sched.h>#include <linux/delay.h>#include <linux/types.h>#include <linux/workqueue.h>#include <linux/netlink.h>#include <linux/net_dropmon.h>#include <linux/bitfield.h>#include <linux/percpu.h>#include <linux/timer.h>#include <linux/bitops.h>#include <linux/slab.h>#include <linux/module.h>#include <net/genetlink.h>#include <net/netevent.h>#include <net/flow_offload.h>#include <net/dropreason.h>#include <net/devlink.h> #include <trace/events/skb.h>#include <trace/events/napi.h>#include <trace/events/devlink.h> #include <linux/unaligned.h> #define TRACE_ON 1#define TRACE_OFF 0 /* * Globals, our netlink socket pointer * and the work handle that will send up * netlink alerts */static int trace_state = TRACE_OFF;static bool monitor_hw; /* net_dm_mutex * * An overall lock guarding every operation coming from userspace. */static DEFINE_MUTEX(net_dm_mutex); struct net_dm_stats {	u64_stats_t dropped;	struct u64_stats_sync syncp;}; #define NET_DM_MAX_HW_TRAP_NAME_LEN 40 struct net_dm_hw_entry {	char trap_name[NET_DM_MAX_HW_TRAP_NAME_LEN];	u32 count;}; struct net_dm_hw_entries {	u32 num_entries;	struct net_dm_hw_entry entries[];}; struct per_cpu_dm_data {	raw_spinlock_t		lock;	/* Protects 'skb', 'hw_entries' and					 * 'send_timer'					 */	union {		struct sk_buff			*skb;		struct net_dm_hw_entries	*hw_entries;	};	struct sk_buff_head	drop_queue;	struct work_struct	dm_alert_work;	struct timer_list	send_timer;	struct net_dm_stats	stats;}; struct dm_hw_stat_delta {	unsigned long last_rx;	unsigned long last_drop_val;	struct rcu_head rcu;}; static struct genl_family net_drop_monitor_family; static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_cpu_data);static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_hw_cpu_data); static int dm_hit_limit = 64;static int dm_delay = 1;static unsigned long dm_hw_check_delta = 2*HZ; static enum net_dm_alert_mode net_dm_alert_mode = NET_DM_ALERT_MODE_SUMMARY;static u32 net_dm_trunc_len;static u32 net_dm_queue_len = 1000; struct net_dm_alert_ops {	void (*kfree_skb_probe)(void *ignore, struct sk_buff *skb,				void *location,				enum skb_drop_reason reason,				const struct sock *rx_sk);	void (*napi_poll_probe)(void *ignore, struct napi_struct *napi,				int work, int budget);	void (*work_item_func)(struct work_struct *work);	void (*hw_work_item_func)(struct work_struct *work);	void (*hw_trap_probe)(void *ignore, const struct devlink *devlink,			      struct sk_buff *skb,			      const struct devlink_trap_metadata *metadata);}; struct net_dm_skb_cb {	union {		struct devlink_trap_metadata *hw_metadata;		void *pc;	};	enum skb_drop_reason reason;}; #define NET_DM_SKB_CB(__skb) ((struct net_dm_skb_cb *)&((__skb)->cb[0])) static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data){	size_t al;	struct net_dm_alert_msg *msg;	struct nlattr *nla;	struct sk_buff *skb;	unsigned long flags;	void *msg_header; 	al = sizeof(struct net_dm_alert_msg);	al += dm_hit_limit * sizeof(struct net_dm_drop_point);	al += sizeof(struct nlattr); 	skb = genlmsg_new(al, GFP_KERNEL); 	if (!skb)		goto err; 	msg_header = genlmsg_put(skb, 0, 0, &net_drop_monitor_family,				 0, NET_DM_CMD_ALERT);	if (!msg_header) {		nlmsg_free(skb);		skb = NULL;		goto err;	}	nla = nla_reserve(skb, NLA_UNSPEC,			  sizeof(struct net_dm_alert_msg));	if (!nla) {		nlmsg_free(skb);		skb = NULL;		goto err;	}	msg = nla_data(nla);	memset(msg, 0, al);	goto out; err:	mod_timer(&data->send_timer, jiffies + HZ / 10);out:	raw_spin_lock_irqsave(&data->lock, flags);	swap(data->skb, skb);	raw_spin_unlock_irqrestore(&data->lock, flags); 	if (skb) {		struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;		struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlh); 		genlmsg_end(skb, genlmsg_data(gnlh));	} 	return skb;} static const struct genl_multicast_group dropmon_mcgrps[] = {	{ .name = "events", .flags = GENL_MCAST_CAP_SYS_ADMIN, },}; static void send_dm_alert(struct work_struct *work){	struct sk_buff *skb;	struct per_cpu_dm_data *data; 	data = container_of(work, struct per_cpu_dm_data, dm_alert_work); 	skb = reset_per_cpu_data(data); 	if (skb)		genlmsg_multicast(&net_drop_monitor_family, skb, 0,				  0, GFP_KERNEL);} /* * This is the timer function to delay the sending of an alert * in the event that more drops will arrive during the * hysteresis period. */static void sched_send_work(struct timer_list *t){	struct per_cpu_dm_data *data = timer_container_of(data, t, send_timer); 	schedule_work(&data->dm_alert_work);} static void trace_drop_common(struct sk_buff *skb, void *location){	struct net_dm_alert_msg *msg;	struct net_dm_drop_point *point;	struct nlmsghdr *nlh;	struct nlattr *nla;	int i;	struct sk_buff *dskb;	struct per_cpu_dm_data *data;	unsigned long flags; 	local_irq_save(flags);	data = this_cpu_ptr(&dm_cpu_data);	raw_spin_lock(&data->lock);	dskb = data->skb; 	if (!dskb)		goto out; 	nlh = (struct nlmsghdr *)dskb->data;	nla = genlmsg_data(nlmsg_data(nlh));	msg = nla_data(nla);	point = msg->points;	for (i = 0; i < msg->entries; i++) {		if (!memcmp(&location, &point->pc, sizeof(void *))) {			point->count++;			goto out;		}		point++;	}	if (msg->entries == dm_hit_limit)		goto out;	/*	 * We need to create a new entry	 */	__nla_reserve_nohdr(dskb, sizeof(struct net_dm_drop_point));	nla->nla_len += NLA_ALIGN(sizeof(struct net_dm_drop_point));	memcpy(point->pc, &location, sizeof(void *));	point->count = 1;	msg->entries++; 	if (!timer_pending(&data->send_timer)) {		data->send_timer.expires = jiffies + dm_delay * HZ;		add_timer(&data->send_timer);	} out:	raw_spin_unlock_irqrestore(&data->lock, flags);} static void trace_kfree_skb_hit(void *ignore, struct sk_buff *skb,				void *location,				enum skb_drop_reason reason,				const struct sock *rx_sk){	trace_drop_common(skb, location);} static void trace_napi_poll_hit(void *ignore, struct napi_struct *napi,				int work, int budget){	struct net_device *dev = napi->dev;	struct dm_hw_stat_delta *stat;	/*	 * Don't check napi structures with no associated device	 */	if (!dev)		return; 	rcu_read_lock();	stat = rcu_dereference(dev->dm_private);	if (stat) {		/*		 * only add a note to our monitor buffer if:		 * 1) its after the last_rx delta		 * 2) our rx_dropped count has gone up		 */		if (time_after(jiffies, stat->last_rx + dm_hw_check_delta) &&		    (dev->stats.rx_dropped != stat->last_drop_val)) {			trace_drop_common(NULL, NULL);			stat->last_drop_val = dev->stats.rx_dropped;			stat->last_rx = jiffies;		}	}	rcu_read_unlock();} static struct net_dm_hw_entries *net_dm_hw_reset_per_cpu_data(struct per_cpu_dm_data *hw_data){	struct net_dm_hw_entries *hw_entries;	unsigned long flags; 	hw_entries = kzalloc_flex(*hw_entries, entries, dm_hit_limit);	if (!hw_entries) {		/* If the memory allocation failed, we try to perform another		 * allocation in 1/10 second. Otherwise, the probe function		 * will constantly bail out.		 */		mod_timer(&hw_data->send_timer, jiffies + HZ / 10);	} 	raw_spin_lock_irqsave(&hw_data->lock, flags);	swap(hw_data->hw_entries, hw_entries);	raw_spin_unlock_irqrestore(&hw_data->lock, flags); 	return hw_entries;} static int net_dm_hw_entry_put(struct sk_buff *msg,			       const struct net_dm_hw_entry *hw_entry){	struct nlattr *attr; 	attr = nla_nest_start(msg, NET_DM_ATTR_HW_ENTRY);	if (!attr)		return -EMSGSIZE; 	if (nla_put_string(msg, NET_DM_ATTR_HW_TRAP_NAME, hw_entry->trap_name))		goto nla_put_failure; 	if (nla_put_u32(msg, NET_DM_ATTR_HW_TRAP_COUNT, hw_entry->count))		goto nla_put_failure; 	nla_nest_end(msg, attr); 	return 0; nla_put_failure:	nla_nest_cancel(msg, attr);	return -EMSGSIZE;} static int net_dm_hw_entries_put(struct sk_buff *msg,				 const struct net_dm_hw_entries *hw_entries){	struct nlattr *attr;	int i; 	attr = nla_nest_start(msg, NET_DM_ATTR_HW_ENTRIES);	if (!attr)		return -EMSGSIZE; 	for (i = 0; i < hw_entries->num_entries; i++) {		int rc; 		rc = net_dm_hw_entry_put(msg, &hw_entries->entries[i]);		if (rc)			goto nla_put_failure;	} 	nla_nest_end(msg, attr); 	return 0; nla_put_failure:	nla_nest_cancel(msg, attr);	return -EMSGSIZE;} static intnet_dm_hw_summary_report_fill(struct sk_buff *msg,			      const struct net_dm_hw_entries *hw_entries){	struct net_dm_alert_msg anc_hdr = { 0 };	void *hdr;	int rc; 	hdr = genlmsg_put(msg, 0, 0, &net_drop_monitor_family, 0,			  NET_DM_CMD_ALERT);	if (!hdr)		return -EMSGSIZE; 	/* We need to put the ancillary header in order not to break user	 * space.	 */	if (nla_put(msg, NLA_UNSPEC, sizeof(anc_hdr), &anc_hdr))		goto nla_put_failure; 	rc = net_dm_hw_entries_put(msg, hw_entries);	if (rc)		goto nla_put_failure; 	genlmsg_end(msg, hdr); 	return 0; nla_put_failure:	genlmsg_cancel(msg, hdr);	return -EMSGSIZE;} static void net_dm_hw_summary_work(struct work_struct *work){	struct net_dm_hw_entries *hw_entries;	struct per_cpu_dm_data *hw_data;	struct sk_buff *msg;	int rc; 	hw_data = container_of(work, struct per_cpu_dm_data, dm_alert_work); 	hw_entries = net_dm_hw_reset_per_cpu_data(hw_data);	if (!hw_entries)		return; 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);	if (!msg)		goto out; 	rc = net_dm_hw_summary_report_fill(msg, hw_entries);	if (rc) {		nlmsg_free(msg);		goto out;	} 	genlmsg_multicast(&net_drop_monitor_family, msg, 0, 0, GFP_KERNEL); out:	kfree(hw_entries);} static voidnet_dm_hw_trap_summary_probe(void *ignore, const struct devlink *devlink,			     struct sk_buff *skb,			     const struct devlink_trap_metadata *metadata){	struct net_dm_hw_entries *hw_entries;	struct net_dm_hw_entry *hw_entry;	struct per_cpu_dm_data *hw_data;	unsigned long flags;	int i; 	if (metadata->trap_type == DEVLINK_TRAP_TYPE_CONTROL)		return; 	hw_data = this_cpu_ptr(&dm_hw_cpu_data);	raw_spin_lock_irqsave(&hw_data->lock, flags);	hw_entries = hw_data->hw_entries; 	if (!hw_entries)		goto out; 	for (i = 0; i < hw_entries->num_entries; i++) {		hw_entry = &hw_entries->entries[i];		if (!strncmp(hw_entry->trap_name, metadata->trap_name,			     NET_DM_MAX_HW_TRAP_NAME_LEN - 1)) {			hw_entry->count++;			goto out;		}	}	if (WARN_ON_ONCE(hw_entries->num_entries == dm_hit_limit))		goto out; 	hw_entry = &hw_entries->entries[hw_entries->num_entries];	strscpy(hw_entry->trap_name, metadata->trap_name,		NET_DM_MAX_HW_TRAP_NAME_LEN - 1);	hw_entry->count = 1;	hw_entries->num_entries++; 	if (!timer_pending(&hw_data->send_timer)) {		hw_data->send_timer.expires = jiffies + dm_delay * HZ;		add_timer(&hw_data->send_timer);	} out:	raw_spin_unlock_irqrestore(&hw_data->lock, flags);} static const struct net_dm_alert_ops net_dm_alert_summary_ops = {	.kfree_skb_probe	= trace_kfree_skb_hit,	.napi_poll_probe	= trace_napi_poll_hit,	.work_item_func		= send_dm_alert,	.hw_work_item_func	= net_dm_hw_summary_work,	.hw_trap_probe		= net_dm_hw_trap_summary_probe,}; static void net_dm_packet_trace_kfree_skb_hit(void *ignore,					      struct sk_buff *skb,					      void *location,					      enum skb_drop_reason reason,					      const struct sock *rx_sk){	ktime_t tstamp = ktime_get_real();	struct per_cpu_dm_data *data;	struct net_dm_skb_cb *cb;

Lines 1–500 of 1784.