Blame view

net/core/drop_monitor.c 8.75 KB
9a8afc8d3   Neil Horman   Network Drop Moni...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  /*
   * Monitoring code for network dropped packet alerts
   *
   * Copyright (C) 2009 Neil Horman <nhorman@tuxdriver.com>
   */
  
  #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/percpu.h>
  #include <linux/timer.h>
  #include <linux/bitops.h>
  #include <net/genetlink.h>
4ea7e3869   Neil Horman   dropmon: add abil...
25
  #include <net/netevent.h>
9a8afc8d3   Neil Horman   Network Drop Moni...
26

ad8d75fff   Steven Rostedt   tracing/events: m...
27
  #include <trace/events/skb.h>
9cbc1cb8c   David S. Miller   Merge branch 'mas...
28
  #include <trace/events/napi.h>
9a8afc8d3   Neil Horman   Network Drop Moni...
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  
  #include <asm/unaligned.h>
  
  #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
   */
4ea7e3869   Neil Horman   dropmon: add abil...
43
44
  static int trace_state = TRACE_OFF;
  static spinlock_t trace_state_lock = SPIN_LOCK_UNLOCKED;
9a8afc8d3   Neil Horman   Network Drop Moni...
45
46
47
48
49
50
51
  
  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;
  };
4ea7e3869   Neil Horman   dropmon: add abil...
52
53
  struct dm_hw_stat_delta {
  	struct net_device *dev;
5848cc096   Neil Horman   net: drop_monitor...
54
  	unsigned long last_rx;
4ea7e3869   Neil Horman   dropmon: add abil...
55
56
57
58
  	struct list_head list;
  	struct rcu_head rcu;
  	unsigned long last_drop_val;
  };
9a8afc8d3   Neil Horman   Network Drop Moni...
59
60
61
62
  static struct genl_family net_drop_monitor_family = {
  	.id             = GENL_ID_GENERATE,
  	.hdrsize        = 0,
  	.name           = "NET_DM",
683703a26   Neil Horman   drop_monitor: Upd...
63
  	.version        = 2,
9a8afc8d3   Neil Horman   Network Drop Moni...
64
65
66
67
68
69
70
  	.maxattr        = NET_DM_CMD_MAX,
  };
  
  static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_cpu_data);
  
  static int dm_hit_limit = 64;
  static int dm_delay = 1;
4ea7e3869   Neil Horman   dropmon: add abil...
71
72
  static unsigned long dm_hw_check_delta = 2*HZ;
  static LIST_HEAD(hw_stats_list);
9a8afc8d3   Neil Horman   Network Drop Moni...
73
74
75
76
77
  
  static void reset_per_cpu_data(struct per_cpu_dm_data *data)
  {
  	size_t al;
  	struct net_dm_alert_msg *msg;
683703a26   Neil Horman   drop_monitor: Upd...
78
  	struct nlattr *nla;
9a8afc8d3   Neil Horman   Network Drop Moni...
79
80
81
  
  	al = sizeof(struct net_dm_alert_msg);
  	al += dm_hit_limit * sizeof(struct net_dm_drop_point);
683703a26   Neil Horman   drop_monitor: Upd...
82
  	al += sizeof(struct nlattr);
9a8afc8d3   Neil Horman   Network Drop Moni...
83
84
85
  	data->skb = genlmsg_new(al, GFP_KERNEL);
  	genlmsg_put(data->skb, 0, 0, &net_drop_monitor_family,
  			0, NET_DM_CMD_ALERT);
683703a26   Neil Horman   drop_monitor: Upd...
86
87
  	nla = nla_reserve(data->skb, NLA_UNSPEC, sizeof(struct net_dm_alert_msg));
  	msg = nla_data(nla);
9a8afc8d3   Neil Horman   Network Drop Moni...
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
  	memset(msg, 0, al);
  	atomic_set(&data->dm_hit_count, dm_hit_limit);
  }
  
  static void send_dm_alert(struct work_struct *unused)
  {
  	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;
  
  	/*
  	 * Replace it with a new one
  	 */
  	reset_per_cpu_data(data);
  
  	/*
  	 * Ship it!
  	 */
  	genlmsg_multicast(skb, 0, NET_DM_GRP_ALERT, 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
   */
  static void sched_send_work(unsigned long unused)
  {
  	struct per_cpu_dm_data *data =  &__get_cpu_var(dm_cpu_data);
  
  	schedule_work(&data->dm_alert_work);
  }
4ea7e3869   Neil Horman   dropmon: add abil...
126
  static void trace_drop_common(struct sk_buff *skb, void *location)
9a8afc8d3   Neil Horman   Network Drop Moni...
127
128
129
  {
  	struct net_dm_alert_msg *msg;
  	struct nlmsghdr *nlh;
683703a26   Neil Horman   drop_monitor: Upd...
130
  	struct nlattr *nla;
9a8afc8d3   Neil Horman   Network Drop Moni...
131
132
133
134
135
136
137
138
139
140
141
142
  	int i;
  	struct per_cpu_dm_data *data = &__get_cpu_var(dm_cpu_data);
  
  
  	if (!atomic_add_unless(&data->dm_hit_count, -1, 0)) {
  		/*
  		 * we're already at zero, discard this hit
  		 */
  		goto out;
  	}
  
  	nlh = (struct nlmsghdr *)data->skb->data;
683703a26   Neil Horman   drop_monitor: Upd...
143
144
  	nla = genlmsg_data(nlmsg_data(nlh));
  	msg = nla_data(nla);
9a8afc8d3   Neil Horman   Network Drop Moni...
145
146
147
148
149
150
151
152
153
154
155
  	for (i = 0; i < msg->entries; i++) {
  		if (!memcmp(&location, msg->points[i].pc, sizeof(void *))) {
  			msg->points[i].count++;
  			goto out;
  		}
  	}
  
  	/*
  	 * We need to create a new entry
  	 */
  	__nla_reserve_nohdr(data->skb, sizeof(struct net_dm_drop_point));
683703a26   Neil Horman   drop_monitor: Upd...
156
  	nla->nla_len += NLA_ALIGN(sizeof(struct net_dm_drop_point));
9a8afc8d3   Neil Horman   Network Drop Moni...
157
158
159
160
161
162
163
164
165
166
167
168
  	memcpy(msg->points[msg->entries].pc, &location, sizeof(void *));
  	msg->points[msg->entries].count = 1;
  	msg->entries++;
  
  	if (!timer_pending(&data->send_timer)) {
  		data->send_timer.expires = jiffies + dm_delay * HZ;
  		add_timer_on(&data->send_timer, smp_processor_id());
  	}
  
  out:
  	return;
  }
4ea7e3869   Neil Horman   dropmon: add abil...
169
170
171
172
173
174
175
176
177
178
  static void trace_kfree_skb_hit(struct sk_buff *skb, void *location)
  {
  	trace_drop_common(skb, location);
  }
  
  static void trace_napi_poll_hit(struct napi_struct *napi)
  {
  	struct dm_hw_stat_delta *new_stat;
  
  	/*
5848cc096   Neil Horman   net: drop_monitor...
179
  	 * Don't check napi structures with no associated device
4ea7e3869   Neil Horman   dropmon: add abil...
180
  	 */
5848cc096   Neil Horman   net: drop_monitor...
181
  	if (!napi->dev)
4ea7e3869   Neil Horman   dropmon: add abil...
182
183
184
185
  		return;
  
  	rcu_read_lock();
  	list_for_each_entry_rcu(new_stat, &hw_stats_list, list) {
5848cc096   Neil Horman   net: drop_monitor...
186
187
188
189
190
191
  		/*
  		 * only add a note to our monitor buffer if:
  		 * 1) this is the dev we received on
  		 * 2) its after the last_rx delta
  		 * 3) our rx_dropped count has gone up
  		 */
4ea7e3869   Neil Horman   dropmon: add abil...
192
  		if ((new_stat->dev == napi->dev)  &&
5848cc096   Neil Horman   net: drop_monitor...
193
  		    (time_after(jiffies, new_stat->last_rx + dm_hw_check_delta)) &&
4ea7e3869   Neil Horman   dropmon: add abil...
194
195
196
  		    (napi->dev->stats.rx_dropped != new_stat->last_drop_val)) {
  			trace_drop_common(NULL, NULL);
  			new_stat->last_drop_val = napi->dev->stats.rx_dropped;
5848cc096   Neil Horman   net: drop_monitor...
197
  			new_stat->last_rx = jiffies;
4ea7e3869   Neil Horman   dropmon: add abil...
198
199
200
201
202
203
204
205
206
207
208
209
210
  			break;
  		}
  	}
  	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);
  }
9a8afc8d3   Neil Horman   Network Drop Moni...
211
212
213
  static int set_all_monitor_traces(int state)
  {
  	int rc = 0;
4ea7e3869   Neil Horman   dropmon: add abil...
214
215
216
217
  	struct dm_hw_stat_delta *new_stat = NULL;
  	struct dm_hw_stat_delta *temp;
  
  	spin_lock(&trace_state_lock);
9a8afc8d3   Neil Horman   Network Drop Moni...
218
219
220
221
  
  	switch (state) {
  	case TRACE_ON:
  		rc |= register_trace_kfree_skb(trace_kfree_skb_hit);
4ea7e3869   Neil Horman   dropmon: add abil...
222
  		rc |= register_trace_napi_poll(trace_napi_poll_hit);
9a8afc8d3   Neil Horman   Network Drop Moni...
223
224
225
  		break;
  	case TRACE_OFF:
  		rc |= unregister_trace_kfree_skb(trace_kfree_skb_hit);
4ea7e3869   Neil Horman   dropmon: add abil...
226
  		rc |= unregister_trace_napi_poll(trace_napi_poll_hit);
9a8afc8d3   Neil Horman   Network Drop Moni...
227
228
  
  		tracepoint_synchronize_unregister();
4ea7e3869   Neil Horman   dropmon: add abil...
229
230
231
232
233
234
235
236
237
238
  
  		/*
  		 * Clean the device list
  		 */
  		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);
  			}
  		}
9a8afc8d3   Neil Horman   Network Drop Moni...
239
240
241
242
243
  		break;
  	default:
  		rc = 1;
  		break;
  	}
4ea7e3869   Neil Horman   dropmon: add abil...
244
245
246
247
  	if (!rc)
  		trace_state = state;
  
  	spin_unlock(&trace_state_lock);
9a8afc8d3   Neil Horman   Network Drop Moni...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
  	if (rc)
  		return -EINPROGRESS;
  	return rc;
  }
  
  
  static int net_dm_cmd_config(struct sk_buff *skb,
  			struct genl_info *info)
  {
  	return -ENOTSUPP;
  }
  
  static int net_dm_cmd_trace(struct sk_buff *skb,
  			struct genl_info *info)
  {
  	switch (info->genlhdr->cmd) {
  	case NET_DM_CMD_START:
  		return set_all_monitor_traces(TRACE_ON);
  		break;
  	case NET_DM_CMD_STOP:
  		return set_all_monitor_traces(TRACE_OFF);
  		break;
  	}
  
  	return -ENOTSUPP;
  }
4ea7e3869   Neil Horman   dropmon: add abil...
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
  static int dropmon_net_event(struct notifier_block *ev_block,
  			unsigned long event, void *ptr)
  {
  	struct net_device *dev = ptr;
  	struct dm_hw_stat_delta *new_stat = NULL;
  	struct dm_hw_stat_delta *tmp;
  
  	switch (event) {
  	case NETDEV_REGISTER:
  		new_stat = kzalloc(sizeof(struct dm_hw_stat_delta), GFP_KERNEL);
  
  		if (!new_stat)
  			goto out;
  
  		new_stat->dev = dev;
5848cc096   Neil Horman   net: drop_monitor...
289
  		new_stat->last_rx = jiffies;
4ea7e3869   Neil Horman   dropmon: add abil...
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
  		INIT_RCU_HEAD(&new_stat->rcu);
  		spin_lock(&trace_state_lock);
  		list_add_rcu(&new_stat->list, &hw_stats_list);
  		spin_unlock(&trace_state_lock);
  		break;
  	case NETDEV_UNREGISTER:
  		spin_lock(&trace_state_lock);
  		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);
  					break;
  				}
  			}
  		}
  		spin_unlock(&trace_state_lock);
  		break;
  	}
  out:
  	return NOTIFY_DONE;
  }
9a8afc8d3   Neil Horman   Network Drop Moni...
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  
  static struct genl_ops dropmon_ops[] = {
  	{
  		.cmd = NET_DM_CMD_CONFIG,
  		.doit = net_dm_cmd_config,
  	},
  	{
  		.cmd = NET_DM_CMD_START,
  		.doit = net_dm_cmd_trace,
  	},
  	{
  		.cmd = NET_DM_CMD_STOP,
  		.doit = net_dm_cmd_trace,
  	},
  };
4ea7e3869   Neil Horman   dropmon: add abil...
328
329
330
  static struct notifier_block dropmon_net_notifier = {
  	.notifier_call = dropmon_net_event
  };
9a8afc8d3   Neil Horman   Network Drop Moni...
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
  static int __init init_net_drop_monitor(void)
  {
  	int cpu;
  	int rc, i, ret;
  	struct per_cpu_dm_data *data;
  	printk(KERN_INFO "Initalizing network drop monitor service
  ");
  
  	if (sizeof(void *) > 8) {
  		printk(KERN_ERR "Unable to store program counters on this arch, Drop monitor failed
  ");
  		return -ENOSPC;
  	}
  
  	if (genl_register_family(&net_drop_monitor_family) < 0) {
  		printk(KERN_ERR "Could not create drop monitor netlink family
  ");
  		return -EFAULT;
  	}
  
  	rc = -EFAULT;
  
  	for (i = 0; i < ARRAY_SIZE(dropmon_ops); i++) {
  		ret = genl_register_ops(&net_drop_monitor_family,
  					&dropmon_ops[i]);
  		if (ret) {
4ea7e3869   Neil Horman   dropmon: add abil...
357
358
  			printk(KERN_CRIT "Failed to register operation %d
  ",
9a8afc8d3   Neil Horman   Network Drop Moni...
359
360
361
362
  				dropmon_ops[i].cmd);
  			goto out_unreg;
  		}
  	}
4ea7e3869   Neil Horman   dropmon: add abil...
363
364
365
366
367
368
  	rc = register_netdevice_notifier(&dropmon_net_notifier);
  	if (rc < 0) {
  		printk(KERN_CRIT "Failed to register netdevice notifier
  ");
  		goto out_unreg;
  	}
9a8afc8d3   Neil Horman   Network Drop Moni...
369
370
371
372
373
374
375
376
377
378
  	rc = 0;
  
  	for_each_present_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.function = sched_send_work;
  	}
4ea7e3869   Neil Horman   dropmon: add abil...
379

9a8afc8d3   Neil Horman   Network Drop Moni...
380
381
382
383
384
385
386
387
388
  	goto out;
  
  out_unreg:
  	genl_unregister_family(&net_drop_monitor_family);
  out:
  	return rc;
  }
  
  late_initcall(init_net_drop_monitor);