diff options
Diffstat (limited to 'net/core/drop_monitor.c')
| -rw-r--r-- | net/core/drop_monitor.c | 200 | 
1 files changed, 123 insertions, 77 deletions
diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 36e603c78ce..e70301eb7a4 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -4,6 +4,8 @@   * 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> @@ -22,6 +24,7 @@  #include <linux/timer.h>  #include <linux/bitops.h>  #include <linux/slab.h> +#include <linux/module.h>  #include <net/genetlink.h>  #include <net/netevent.h> @@ -33,22 +36,19 @@  #define TRACE_ON 1  #define TRACE_OFF 0 -static void send_dm_alert(struct work_struct *unused); - -  /*   * Globals, our netlink socket pointer   * and the work handle that will send up   * netlink alerts   */  static int trace_state = TRACE_OFF; -static DEFINE_SPINLOCK(trace_state_lock); +static DEFINE_MUTEX(trace_state_mutex);  struct per_cpu_dm_data { -	struct work_struct dm_alert_work; -	struct sk_buff *skb; -	atomic_t dm_hit_count; -	struct timer_list send_timer; +	spinlock_t		lock; +	struct sk_buff		*skb; +	struct work_struct	dm_alert_work; +	struct timer_list	send_timer;  };  struct dm_hw_stat_delta { @@ -64,7 +64,6 @@ static struct genl_family net_drop_monitor_family = {  	.hdrsize        = 0,  	.name           = "NET_DM",  	.version        = 2, -	.maxattr        = NET_DM_CMD_MAX,  };  static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_cpu_data); @@ -74,56 +73,64 @@ static int dm_delay = 1;  static unsigned long dm_hw_check_delta = 2*HZ;  static LIST_HEAD(hw_stats_list); -static void reset_per_cpu_data(struct per_cpu_dm_data *data) +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;  	al = sizeof(struct net_dm_alert_msg);  	al += dm_hit_limit * sizeof(struct net_dm_drop_point);  	al += sizeof(struct nlattr); -	data->skb = genlmsg_new(al, GFP_KERNEL); -	genlmsg_put(data->skb, 0, 0, &net_drop_monitor_family, -			0, NET_DM_CMD_ALERT); -	nla = nla_reserve(data->skb, NLA_UNSPEC, sizeof(struct net_dm_alert_msg)); -	msg = nla_data(nla); -	memset(msg, 0, al); -	atomic_set(&data->dm_hit_count, dm_hit_limit); +	skb = genlmsg_new(al, GFP_KERNEL); + +	if (skb) { +		genlmsg_put(skb, 0, 0, &net_drop_monitor_family, +				0, NET_DM_CMD_ALERT); +		nla = nla_reserve(skb, NLA_UNSPEC, +				  sizeof(struct net_dm_alert_msg)); +		msg = nla_data(nla); +		memset(msg, 0, al); +	} else { +		mod_timer(&data->send_timer, jiffies + HZ / 10); +	} + +	spin_lock_irqsave(&data->lock, flags); +	swap(data->skb, skb); +	spin_unlock_irqrestore(&data->lock, flags); + +	return skb;  } -static void send_dm_alert(struct work_struct *unused) +static struct genl_multicast_group dropmon_mcgrps[] = { +	{ .name = "events", }, +}; + +static void send_dm_alert(struct work_struct *work)  {  	struct sk_buff *skb; -	struct per_cpu_dm_data *data = &__get_cpu_var(dm_cpu_data); - -	/* -	 * Grab the skb we're about to send -	 */ -	skb = data->skb; +	struct per_cpu_dm_data *data; -	/* -	 * Replace it with a new one -	 */ -	reset_per_cpu_data(data); +	data = container_of(work, struct per_cpu_dm_data, dm_alert_work); -	/* -	 * Ship it! -	 */ -	genlmsg_multicast(skb, 0, NET_DM_GRP_ALERT, GFP_KERNEL); +	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.  Note that it operates under the timer interrupt - * so we don't need to disable preemption here + * hysteresis period.   */ -static void sched_send_work(unsigned long unused) +static void sched_send_work(unsigned long _data)  { -	struct per_cpu_dm_data *data =  &__get_cpu_var(dm_cpu_data); +	struct per_cpu_dm_data *data = (struct per_cpu_dm_data *)_data;  	schedule_work(&data->dm_alert_work);  } @@ -134,17 +141,19 @@ static void trace_drop_common(struct sk_buff *skb, void *location)  	struct nlmsghdr *nlh;  	struct nlattr *nla;  	int i; -	struct per_cpu_dm_data *data = &__get_cpu_var(dm_cpu_data); +	struct sk_buff *dskb; +	struct per_cpu_dm_data *data; +	unsigned long flags; +	local_irq_save(flags); +	data = &__get_cpu_var(dm_cpu_data); +	spin_lock(&data->lock); +	dskb = data->skb; -	if (!atomic_add_unless(&data->dm_hit_count, -1, 0)) { -		/* -		 * we're already at zero, discard this hit -		 */ +	if (!dskb)  		goto out; -	} -	nlh = (struct nlmsghdr *)data->skb->data; +	nlh = (struct nlmsghdr *)dskb->data;  	nla = genlmsg_data(nlmsg_data(nlh));  	msg = nla_data(nla);  	for (i = 0; i < msg->entries; i++) { @@ -153,11 +162,12 @@ static void trace_drop_common(struct sk_buff *skb, void *location)  			goto out;  		}  	} - +	if (msg->entries == dm_hit_limit) +		goto out;  	/*  	 * We need to create a new entry  	 */ -	__nla_reserve_nohdr(data->skb, sizeof(struct net_dm_drop_point)); +	__nla_reserve_nohdr(dskb, sizeof(struct net_dm_drop_point));  	nla->nla_len += NLA_ALIGN(sizeof(struct net_dm_drop_point));  	memcpy(msg->points[msg->entries].pc, &location, sizeof(void *));  	msg->points[msg->entries].count = 1; @@ -165,11 +175,11 @@ static void trace_drop_common(struct sk_buff *skb, void *location)  	if (!timer_pending(&data->send_timer)) {  		data->send_timer.expires = jiffies + dm_delay * HZ; -		add_timer_on(&data->send_timer, smp_processor_id()); +		add_timer(&data->send_timer);  	}  out: -	return; +	spin_unlock_irqrestore(&data->lock, flags);  }  static void trace_kfree_skb_hit(void *ignore, struct sk_buff *skb, void *location) @@ -207,21 +217,13 @@ static void trace_napi_poll_hit(void *ignore, struct napi_struct *napi)  	rcu_read_unlock();  } - -static void free_dm_hw_stat(struct rcu_head *head) -{ -	struct dm_hw_stat_delta *n; -	n = container_of(head, struct dm_hw_stat_delta, rcu); -	kfree(n); -} -  static int set_all_monitor_traces(int state)  {  	int rc = 0;  	struct dm_hw_stat_delta *new_stat = NULL;  	struct dm_hw_stat_delta *temp; -	spin_lock(&trace_state_lock); +	mutex_lock(&trace_state_mutex);  	if (state == trace_state) {  		rc = -EAGAIN; @@ -230,9 +232,15 @@ static int set_all_monitor_traces(int state)  	switch (state) {  	case TRACE_ON: +		if (!try_module_get(THIS_MODULE)) { +			rc = -ENODEV; +			break; +		} +  		rc |= register_trace_kfree_skb(trace_kfree_skb_hit, NULL);  		rc |= register_trace_napi_poll(trace_napi_poll_hit, NULL);  		break; +  	case TRACE_OFF:  		rc |= unregister_trace_kfree_skb(trace_kfree_skb_hit, NULL);  		rc |= unregister_trace_napi_poll(trace_napi_poll_hit, NULL); @@ -245,9 +253,12 @@ static int set_all_monitor_traces(int state)  		list_for_each_entry_safe(new_stat, temp, &hw_stats_list, list) {  			if (new_stat->dev == NULL) {  				list_del_rcu(&new_stat->list); -				call_rcu(&new_stat->rcu, free_dm_hw_stat); +				kfree_rcu(new_stat, rcu);  			}  		} + +		module_put(THIS_MODULE); +  		break;  	default:  		rc = 1; @@ -260,7 +271,7 @@ static int set_all_monitor_traces(int state)  		rc = -EINPROGRESS;  out_unlock: -	spin_unlock(&trace_state_lock); +	mutex_unlock(&trace_state_mutex);  	return rc;  } @@ -288,9 +299,9 @@ static int net_dm_cmd_trace(struct sk_buff *skb,  }  static int dropmon_net_event(struct notifier_block *ev_block, -			unsigned long event, void *ptr) +			     unsigned long event, void *ptr)  { -	struct net_device *dev = ptr; +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);  	struct dm_hw_stat_delta *new_stat = NULL;  	struct dm_hw_stat_delta *tmp; @@ -303,30 +314,30 @@ static int dropmon_net_event(struct notifier_block *ev_block,  		new_stat->dev = dev;  		new_stat->last_rx = jiffies; -		spin_lock(&trace_state_lock); +		mutex_lock(&trace_state_mutex);  		list_add_rcu(&new_stat->list, &hw_stats_list); -		spin_unlock(&trace_state_lock); +		mutex_unlock(&trace_state_mutex);  		break;  	case NETDEV_UNREGISTER: -		spin_lock(&trace_state_lock); +		mutex_lock(&trace_state_mutex);  		list_for_each_entry_safe(new_stat, tmp, &hw_stats_list, list) {  			if (new_stat->dev == dev) {  				new_stat->dev = NULL;  				if (trace_state == TRACE_OFF) {  					list_del_rcu(&new_stat->list); -					call_rcu(&new_stat->rcu, free_dm_hw_stat); +					kfree_rcu(new_stat, rcu);  					break;  				}  			}  		} -		spin_unlock(&trace_state_lock); +		mutex_unlock(&trace_state_mutex);  		break;  	}  out:  	return NOTIFY_DONE;  } -static struct genl_ops dropmon_ops[] = { +static const struct genl_ops dropmon_ops[] = {  	{  		.cmd = NET_DM_CMD_CONFIG,  		.doit = net_dm_cmd_config, @@ -350,38 +361,40 @@ static int __init init_net_drop_monitor(void)  	struct per_cpu_dm_data *data;  	int cpu, rc; -	printk(KERN_INFO "Initalizing network drop monitor service\n"); +	pr_info("Initializing network drop monitor service\n");  	if (sizeof(void *) > 8) { -		printk(KERN_ERR "Unable to store program counters on this arch, Drop monitor failed\n"); +		pr_err("Unable to store program counters on this arch, Drop monitor failed\n");  		return -ENOSPC;  	} -	rc = genl_register_family_with_ops(&net_drop_monitor_family, -					   dropmon_ops, -					   ARRAY_SIZE(dropmon_ops)); +	rc = genl_register_family_with_ops_groups(&net_drop_monitor_family, +						  dropmon_ops, dropmon_mcgrps);  	if (rc) { -		printk(KERN_ERR "Could not create drop monitor netlink family\n"); +		pr_err("Could not create drop monitor netlink family\n");  		return rc;  	} +	WARN_ON(net_drop_monitor_family.mcgrp_offset != NET_DM_GRP_ALERT);  	rc = register_netdevice_notifier(&dropmon_net_notifier);  	if (rc < 0) { -		printk(KERN_CRIT "Failed to register netdevice notifier\n"); +		pr_crit("Failed to register netdevice notifier\n");  		goto out_unreg;  	}  	rc = 0; -	for_each_present_cpu(cpu) { +	for_each_possible_cpu(cpu) {  		data = &per_cpu(dm_cpu_data, cpu); -		reset_per_cpu_data(data);  		INIT_WORK(&data->dm_alert_work, send_dm_alert);  		init_timer(&data->send_timer); -		data->send_timer.data = cpu; +		data->send_timer.data = (unsigned long)data;  		data->send_timer.function = sched_send_work; +		spin_lock_init(&data->lock); +		reset_per_cpu_data(data);  	} +  	goto out;  out_unreg: @@ -390,4 +403,37 @@ out:  	return rc;  } -late_initcall(init_net_drop_monitor); +static void exit_net_drop_monitor(void) +{ +	struct per_cpu_dm_data *data; +	int cpu; + +	BUG_ON(unregister_netdevice_notifier(&dropmon_net_notifier)); + +	/* +	 * Because of the module_get/put we do in the trace state change path +	 * we are guarnateed not to have any current users when we get here +	 * all we need to do is make sure that we don't have any running timers +	 * or pending schedule calls +	 */ + +	for_each_possible_cpu(cpu) { +		data = &per_cpu(dm_cpu_data, cpu); +		del_timer_sync(&data->send_timer); +		cancel_work_sync(&data->dm_alert_work); +		/* +		 * At this point, we should have exclusive access +		 * to this struct and can free the skb inside it +		 */ +		kfree_skb(data->skb); +	} + +	BUG_ON(genl_unregister_family(&net_drop_monitor_family)); +} + +module_init(init_net_drop_monitor); +module_exit(exit_net_drop_monitor); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Neil Horman <nhorman@tuxdriver.com>"); +MODULE_ALIAS_GENL_FAMILY("NET_DM");  | 
