dn_neigh.c 15.7 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
/*
 * DECnet       An implementation of the DECnet protocol suite for the LINUX
 *              operating system.  DECnet is implemented using the  BSD Socket
 *              interface as the means of communication with the user level.
 *
6
 *              DECnet Neighbour Functions (Adjacency Database and
Linus Torvalds's avatar
Linus Torvalds committed
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 *                                                        On-Ethernet Cache)
 *
 * Author:      Steve Whitehouse <SteveW@ACM.org>
 *
 *
 * Changes:
 *     Steve Whitehouse     : Fixed router listing routine
 *     Steve Whitehouse     : Added error_report functions
 *     Steve Whitehouse     : Added default router detection
 *     Steve Whitehouse     : Hop counts in outgoing messages
 *     Steve Whitehouse     : Fixed src/dst in outgoing messages so
 *                            forwarding now stands a good chance of
 *                            working.
 *     Steve Whitehouse     : Fixed neighbour states (for now anyway).
 *     Steve Whitehouse     : Made error_report functions dummies. This
 *                            is not the right place to return skbs.
 *     Steve Whitehouse     : Convert to seq_file
 *
 */

#include <linux/net.h>
#include <linux/module.h>
#include <linux/socket.h>
#include <linux/if_arp.h>
31
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
32
33
34
35
36
37
38
39
40
#include <linux/if_ether.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/netfilter_decnet.h>
#include <linux/spinlock.h>
#include <linux/seq_file.h>
#include <linux/rcupdate.h>
#include <linux/jhash.h>
Arun Sharma's avatar
Arun Sharma committed
41
#include <linux/atomic.h>
42
#include <net/net_namespace.h>
Linus Torvalds's avatar
Linus Torvalds committed
43
44
45
46
47
48
49
50
51
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/dn.h>
#include <net/dn_dev.h>
#include <net/dn_neigh.h>
#include <net/dn_route.h>

static int dn_neigh_construct(struct neighbour *);
52
53
static void dn_neigh_error_report(struct neighbour *, struct sk_buff *);
static int dn_neigh_output(struct neighbour *neigh, struct sk_buff *skb);
Linus Torvalds's avatar
Linus Torvalds committed
54
55

/*
56
 * Operations for adding the link layer header.
Linus Torvalds's avatar
Linus Torvalds committed
57
 */
58
static const struct neigh_ops dn_neigh_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
59
	.family =		AF_DECnet,
60
61
62
	.error_report =		dn_neigh_error_report,
	.output =		dn_neigh_output,
	.connected_output =	dn_neigh_output,
Linus Torvalds's avatar
Linus Torvalds committed
63
64
};

65
66
static u32 dn_neigh_hash(const void *pkey,
			 const struct net_device *dev,
67
			 __u32 *hash_rnd)
68
{
69
	return jhash_2words(*(__u16 *)pkey, 0, hash_rnd[0]);
70
71
}

72
73
74
75
76
static bool dn_key_eq(const struct neighbour *neigh, const void *pkey)
{
	return neigh_key_eq16(neigh, pkey);
}

Linus Torvalds's avatar
Linus Torvalds committed
77
78
struct neigh_table dn_neigh_table = {
	.family =			PF_DECnet,
79
	.entry_size =			NEIGH_ENTRY_SIZE(sizeof(struct dn_neigh)),
80
	.key_len =			sizeof(__le16),
81
	.protocol =			cpu_to_be16(ETH_P_DNA_RT),
Linus Torvalds's avatar
Linus Torvalds committed
82
	.hash =				dn_neigh_hash,
83
	.key_eq =			dn_key_eq,
Linus Torvalds's avatar
Linus Torvalds committed
84
85
86
87
	.constructor =			dn_neigh_construct,
	.id =				"dn_neigh_cache",
	.parms ={
		.tbl =			&dn_neigh_table,
Jiri Pirko's avatar
Jiri Pirko committed
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
		.reachable_time =	30 * HZ,
		.data = {
			[NEIGH_VAR_MCAST_PROBES] = 0,
			[NEIGH_VAR_UCAST_PROBES] = 0,
			[NEIGH_VAR_APP_PROBES] = 0,
			[NEIGH_VAR_RETRANS_TIME] = 1 * HZ,
			[NEIGH_VAR_BASE_REACHABLE_TIME] = 30 * HZ,
			[NEIGH_VAR_DELAY_PROBE_TIME] = 5 * HZ,
			[NEIGH_VAR_GC_STALETIME] = 60 * HZ,
			[NEIGH_VAR_QUEUE_LEN_BYTES] = 64*1024,
			[NEIGH_VAR_PROXY_QLEN] = 0,
			[NEIGH_VAR_ANYCAST_DELAY] = 0,
			[NEIGH_VAR_PROXY_DELAY] = 0,
			[NEIGH_VAR_LOCKTIME] = 1 * HZ,
		},
Linus Torvalds's avatar
Linus Torvalds committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
	},
	.gc_interval =			30 * HZ,
	.gc_thresh1 =			128,
	.gc_thresh2 =			512,
	.gc_thresh3 =			1024,
};

static int dn_neigh_construct(struct neighbour *neigh)
{
	struct net_device *dev = neigh->dev;
	struct dn_neigh *dn = (struct dn_neigh *)neigh;
	struct dn_dev *dn_db;
	struct neigh_parms *parms;

	rcu_read_lock();
	dn_db = rcu_dereference(dev->dn_ptr);
	if (dn_db == NULL) {
		rcu_read_unlock();
		return -EINVAL;
	}

	parms = dn_db->neigh_parms;
	if (!parms) {
		rcu_read_unlock();
		return -EINVAL;
	}

	__neigh_parms_put(neigh->parms);
	neigh->parms = neigh_parms_clone(parms);
132
	rcu_read_unlock();
Linus Torvalds's avatar
Linus Torvalds committed
133

134
	neigh->ops = &dn_neigh_ops;
Linus Torvalds's avatar
Linus Torvalds committed
135
136
137
138
139
140
141
142
	neigh->nud_state = NUD_NOARP;
	neigh->output = neigh->ops->connected_output;

	if ((dev->type == ARPHRD_IPGRE) || (dev->flags & IFF_POINTOPOINT))
		memcpy(neigh->ha, dev->broadcast, dev->addr_len);
	else if ((dev->type == ARPHRD_ETHER) || (dev->type == ARPHRD_LOOPBACK))
		dn_dn2eth(neigh->ha, dn->addr);
	else {
143
144
		net_dbg_ratelimited("Trying to create neigh for hw %d\n",
				    dev->type);
Linus Torvalds's avatar
Linus Torvalds committed
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
		return -EINVAL;
	}

	/*
	 * Make an estimate of the remote block size by assuming that its
	 * two less then the device mtu, which it true for ethernet (and
	 * other things which support long format headers) since there is
	 * an extra length field (of 16 bits) which isn't part of the
	 * ethernet headers and which the DECnet specs won't admit is part
	 * of the DECnet routing headers either.
	 *
	 * If we over estimate here its no big deal, the NSP negotiations
	 * will prevent us from sending packets which are too large for the
	 * remote node to handle. In any case this figure is normally updated
	 * by a hello message in most cases.
	 */
	dn->blksize = dev->mtu - 2;

	return 0;
}

166
static void dn_neigh_error_report(struct neighbour *neigh, struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
167
{
168
	printk(KERN_DEBUG "dn_neigh_error_report: called\n");
Linus Torvalds's avatar
Linus Torvalds committed
169
170
171
	kfree_skb(skb);
}

172
static int dn_neigh_output(struct neighbour *neigh, struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
173
{
Eric Dumazet's avatar
Eric Dumazet committed
174
	struct dst_entry *dst = skb_dst(skb);
Linus Torvalds's avatar
Linus Torvalds committed
175
176
177
	struct dn_route *rt = (struct dn_route *)dst;
	struct net_device *dev = neigh->dev;
	char mac_addr[ETH_ALEN];
178
179
	unsigned int seq;
	int err;
Linus Torvalds's avatar
Linus Torvalds committed
180
181

	dn_dn2eth(mac_addr, rt->rt_local_src);
182
183
184
185
186
187
188
189
190
191
192
193
194
	do {
		seq = read_seqbegin(&neigh->ha_lock);
		err = dev_hard_header(skb, dev, ntohs(skb->protocol),
				      neigh->ha, mac_addr, skb->len);
	} while (read_seqretry(&neigh->ha_lock, seq));

	if (err >= 0)
		err = dev_queue_xmit(skb);
	else {
		kfree_skb(skb);
		err = -EINVAL;
	}
	return err;
Linus Torvalds's avatar
Linus Torvalds committed
195
196
}

197
static int dn_neigh_output_packet(struct sock *sk, struct sk_buff *skb)
198
199
200
201
202
203
204
205
206
207
208
{
	struct dst_entry *dst = skb_dst(skb);
	struct dn_route *rt = (struct dn_route *)dst;
	struct neighbour *neigh = rt->n;

	return neigh->output(neigh, skb);
}

/*
 * For talking to broadcast devices: Ethernet & PPP
 */
209
210
static int dn_long_output(struct neighbour *neigh, struct sock *sk,
			  struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
211
212
213
214
215
216
217
218
219
220
221
{
	struct net_device *dev = neigh->dev;
	int headroom = dev->hard_header_len + sizeof(struct dn_long_packet) + 3;
	unsigned char *data;
	struct dn_long_packet *lp;
	struct dn_skb_cb *cb = DN_SKB_CB(skb);


	if (skb_headroom(skb) < headroom) {
		struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
		if (skb2 == NULL) {
222
			net_crit_ratelimited("dn_long_output: no memory\n");
Linus Torvalds's avatar
Linus Torvalds committed
223
224
225
			kfree_skb(skb);
			return -ENOBUFS;
		}
226
		consume_skb(skb);
Linus Torvalds's avatar
Linus Torvalds committed
227
		skb = skb2;
228
		net_info_ratelimited("dn_long_output: Increasing headroom\n");
Linus Torvalds's avatar
Linus Torvalds committed
229
230
231
232
233
	}

	data = skb_push(skb, sizeof(struct dn_long_packet) + 3);
	lp = (struct dn_long_packet *)(data+3);

234
	*((__le16 *)data) = cpu_to_le16(skb->len - 2);
Linus Torvalds's avatar
Linus Torvalds committed
235
236
237
238
	*(data + 2) = 1 | DN_RT_F_PF; /* Padding */

	lp->msgflg   = DN_RT_PKT_LONG|(cb->rt_flags&(DN_RT_F_IE|DN_RT_F_RQR|DN_RT_F_RTS));
	lp->d_area   = lp->d_subarea = 0;
239
	dn_dn2eth(lp->d_id, cb->dst);
Linus Torvalds's avatar
Linus Torvalds committed
240
	lp->s_area   = lp->s_subarea = 0;
241
	dn_dn2eth(lp->s_id, cb->src);
Linus Torvalds's avatar
Linus Torvalds committed
242
243
244
245
246
	lp->nl2      = 0;
	lp->visit_ct = cb->hops & 0x3f;
	lp->s_class  = 0;
	lp->pt       = 0;

247
	skb_reset_network_header(skb);
Linus Torvalds's avatar
Linus Torvalds committed
248

249
250
251
	return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING,
		       &init_net, sk, skb, NULL, neigh->dev,
		       dn_neigh_output_packet);
Linus Torvalds's avatar
Linus Torvalds committed
252
253
}

254
255
256
/*
 * For talking to pointopoint and multidrop devices: DDCMP and X.25
 */
257
258
static int dn_short_output(struct neighbour *neigh, struct sock *sk,
			   struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
259
260
261
262
263
264
265
266
{
	struct net_device *dev = neigh->dev;
	int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
	struct dn_short_packet *sp;
	unsigned char *data;
	struct dn_skb_cb *cb = DN_SKB_CB(skb);


267
268
269
	if (skb_headroom(skb) < headroom) {
		struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
		if (skb2 == NULL) {
270
			net_crit_ratelimited("dn_short_output: no memory\n");
271
272
273
			kfree_skb(skb);
			return -ENOBUFS;
		}
274
		consume_skb(skb);
275
		skb = skb2;
276
		net_info_ratelimited("dn_short_output: Increasing headroom\n");
277
	}
Linus Torvalds's avatar
Linus Torvalds committed
278
279

	data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
280
	*((__le16 *)data) = cpu_to_le16(skb->len - 2);
Linus Torvalds's avatar
Linus Torvalds committed
281
282
283
284
285
286
287
	sp = (struct dn_short_packet *)(data+2);

	sp->msgflg     = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
	sp->dstnode    = cb->dst;
	sp->srcnode    = cb->src;
	sp->forward    = cb->hops & 0x3f;

288
	skb_reset_network_header(skb);
Linus Torvalds's avatar
Linus Torvalds committed
289

290
291
292
	return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING,
		       &init_net, sk, skb, NULL, neigh->dev,
		       dn_neigh_output_packet);
Linus Torvalds's avatar
Linus Torvalds committed
293
294
295
}

/*
296
297
 * For talking to DECnet phase III nodes
 * Phase 3 output is the same as short output, execpt that
Linus Torvalds's avatar
Linus Torvalds committed
298
299
 * it clears the area bits before transmission.
 */
300
301
static int dn_phase3_output(struct neighbour *neigh, struct sock *sk,
			    struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
302
303
304
305
306
307
308
309
310
311
{
	struct net_device *dev = neigh->dev;
	int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
	struct dn_short_packet *sp;
	unsigned char *data;
	struct dn_skb_cb *cb = DN_SKB_CB(skb);

	if (skb_headroom(skb) < headroom) {
		struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
		if (skb2 == NULL) {
312
			net_crit_ratelimited("dn_phase3_output: no memory\n");
Linus Torvalds's avatar
Linus Torvalds committed
313
314
315
			kfree_skb(skb);
			return -ENOBUFS;
		}
316
		consume_skb(skb);
Linus Torvalds's avatar
Linus Torvalds committed
317
		skb = skb2;
318
		net_info_ratelimited("dn_phase3_output: Increasing headroom\n");
Linus Torvalds's avatar
Linus Torvalds committed
319
320
321
	}

	data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
322
	*((__le16 *)data) = cpu_to_le16(skb->len - 2);
Linus Torvalds's avatar
Linus Torvalds committed
323
324
325
	sp = (struct dn_short_packet *)(data + 2);

	sp->msgflg   = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
326
327
	sp->dstnode  = cb->dst & cpu_to_le16(0x03ff);
	sp->srcnode  = cb->src & cpu_to_le16(0x03ff);
Linus Torvalds's avatar
Linus Torvalds committed
328
329
	sp->forward  = cb->hops & 0x3f;

330
	skb_reset_network_header(skb);
Linus Torvalds's avatar
Linus Torvalds committed
331

332
333
334
	return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING,
		       &init_net, sk, skb, NULL, neigh->dev,
		       dn_neigh_output_packet);
Linus Torvalds's avatar
Linus Torvalds committed
335
336
}

337
int dn_to_neigh_output(struct sock *sk, struct sk_buff *skb)
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
{
	struct dst_entry *dst = skb_dst(skb);
	struct dn_route *rt = (struct dn_route *) dst;
	struct neighbour *neigh = rt->n;
	struct dn_neigh *dn = (struct dn_neigh *)neigh;
	struct dn_dev *dn_db;
	bool use_long;

	rcu_read_lock();
	dn_db = rcu_dereference(neigh->dev->dn_ptr);
	if (dn_db == NULL) {
		rcu_read_unlock();
		return -EINVAL;
	}
	use_long = dn_db->use_long;
	rcu_read_unlock();

	if (dn->flags & DN_NDFLAG_P3)
356
		return dn_phase3_output(neigh, sk, skb);
357
	if (use_long)
358
		return dn_long_output(neigh, sk, skb);
359
	else
360
		return dn_short_output(neigh, sk, skb);
361
362
}

Linus Torvalds's avatar
Linus Torvalds committed
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/*
 * Unfortunately, the neighbour code uses the device in its hash
 * function, so we don't get any advantage from it. This function
 * basically does a neigh_lookup(), but without comparing the device
 * field. This is required for the On-Ethernet cache
 */

/*
 * Pointopoint link receives a hello message
 */
void dn_neigh_pointopoint_hello(struct sk_buff *skb)
{
	kfree_skb(skb);
}

/*
 * Ethernet router hello message received
 */
381
int dn_neigh_router_hello(struct sock *sk, struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
382
383
384
385
386
387
{
	struct rtnode_hello_message *msg = (struct rtnode_hello_message *)skb->data;

	struct neighbour *neigh;
	struct dn_neigh *dn;
	struct dn_dev *dn_db;
388
	__le16 src;
Linus Torvalds's avatar
Linus Torvalds committed
389

390
	src = dn_eth2dn(msg->id);
Linus Torvalds's avatar
Linus Torvalds committed
391
392
393
394
395
396
397
398
399

	neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);

	dn = (struct dn_neigh *)neigh;

	if (neigh) {
		write_lock(&neigh->lock);

		neigh->used = jiffies;
400
		dn_db = rcu_dereference(neigh->dev->dn_ptr);
Linus Torvalds's avatar
Linus Torvalds committed
401
402
403
404
405
406
407

		if (!(neigh->nud_state & NUD_PERMANENT)) {
			neigh->updated = jiffies;

			if (neigh->dev->type == ARPHRD_ETHER)
				memcpy(neigh->ha, &eth_hdr(skb)->h_source, ETH_ALEN);

408
			dn->blksize  = le16_to_cpu(msg->blksize);
Linus Torvalds's avatar
Linus Torvalds committed
409
410
411
412
			dn->priority = msg->priority;

			dn->flags &= ~DN_NDFLAG_P3;

413
414
415
416
417
418
419
			switch (msg->iinfo & DN_RT_INFO_TYPE) {
			case DN_RT_INFO_L1RT:
				dn->flags &=~DN_NDFLAG_R2;
				dn->flags |= DN_NDFLAG_R1;
				break;
			case DN_RT_INFO_L2RT:
				dn->flags |= DN_NDFLAG_R2;
Linus Torvalds's avatar
Linus Torvalds committed
420
421
422
			}
		}

423
		/* Only use routers in our area */
424
		if ((le16_to_cpu(src)>>10) == (le16_to_cpu((decnet_address))>>10)) {
425
426
427
428
429
430
			if (!dn_db->router) {
				dn_db->router = neigh_clone(neigh);
			} else {
				if (msg->priority > ((struct dn_neigh *)dn_db->router)->priority)
					neigh_release(xchg(&dn_db->router, neigh_clone(neigh)));
			}
Linus Torvalds's avatar
Linus Torvalds committed
431
432
433
434
435
436
437
438
439
440
441
442
		}
		write_unlock(&neigh->lock);
		neigh_release(neigh);
	}

	kfree_skb(skb);
	return 0;
}

/*
 * Endnode hello message received
 */
443
int dn_neigh_endnode_hello(struct sock *sk, struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
444
445
446
447
{
	struct endnode_hello_message *msg = (struct endnode_hello_message *)skb->data;
	struct neighbour *neigh;
	struct dn_neigh *dn;
448
	__le16 src;
Linus Torvalds's avatar
Linus Torvalds committed
449

450
	src = dn_eth2dn(msg->id);
Linus Torvalds's avatar
Linus Torvalds committed
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466

	neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);

	dn = (struct dn_neigh *)neigh;

	if (neigh) {
		write_lock(&neigh->lock);

		neigh->used = jiffies;

		if (!(neigh->nud_state & NUD_PERMANENT)) {
			neigh->updated = jiffies;

			if (neigh->dev->type == ARPHRD_ETHER)
				memcpy(neigh->ha, &eth_hdr(skb)->h_source, ETH_ALEN);
			dn->flags   &= ~(DN_NDFLAG_R1 | DN_NDFLAG_R2);
467
			dn->blksize  = le16_to_cpu(msg->blksize);
Linus Torvalds's avatar
Linus Torvalds committed
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
			dn->priority = 0;
		}

		write_unlock(&neigh->lock);
		neigh_release(neigh);
	}

	kfree_skb(skb);
	return 0;
}

static char *dn_find_slot(char *base, int max, int priority)
{
	int i;
	unsigned char *min = NULL;

	base += 6; /* skip first id */

	for(i = 0; i < max; i++) {
		if (!min || (*base < *min))
			min = base;
		base += 7; /* find next priority */
	}

	if (!min)
		return NULL;

	return (*min < priority) ? (min - 6) : NULL;
}

struct elist_cb_state {
	struct net_device *dev;
	unsigned char *ptr;
	unsigned char *rs;
	int t, n;
};

static void neigh_elist_cb(struct neighbour *neigh, void *_info)
{
	struct elist_cb_state *s = _info;
	struct dn_neigh *dn;

	if (neigh->dev != s->dev)
		return;

	dn = (struct dn_neigh *) neigh;
	if (!(dn->flags & (DN_NDFLAG_R1|DN_NDFLAG_R2)))
		return;

	if (s->t == s->n)
		s->rs = dn_find_slot(s->ptr, s->n, dn->priority);
	else
		s->t++;
	if (s->rs == NULL)
		return;

	dn_dn2eth(s->rs, dn->addr);
	s->rs += 6;
	*(s->rs) = neigh->nud_state & NUD_CONNECTED ? 0x80 : 0x0;
	*(s->rs) |= dn->priority;
	s->rs++;
}

int dn_neigh_elist(struct net_device *dev, unsigned char *ptr, int n)
{
	struct elist_cb_state state;

	state.dev = dev;
	state.t = 0;
	state.n = n;
	state.ptr = ptr;
	state.rs = ptr;

	neigh_for_each(&dn_neigh_table, neigh_elist_cb, &state);

	return state.t;
}


#ifdef CONFIG_PROC_FS

static inline void dn_neigh_format_entry(struct seq_file *seq,
					 struct neighbour *n)
{
	struct dn_neigh *dn = (struct dn_neigh *) n;
	char buf[DN_ASCBUF_LEN];

	read_lock(&n->lock);
	seq_printf(seq, "%-7s %s%s%s   %02x    %02d  %07ld %-8s\n",
557
		   dn_addr2asc(le16_to_cpu(dn->addr), buf),
Linus Torvalds's avatar
Linus Torvalds committed
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
		   (dn->flags&DN_NDFLAG_R1) ? "1" : "-",
		   (dn->flags&DN_NDFLAG_R2) ? "2" : "-",
		   (dn->flags&DN_NDFLAG_P3) ? "3" : "-",
		   dn->n.nud_state,
		   atomic_read(&dn->n.refcnt),
		   dn->blksize,
		   (dn->n.dev) ? dn->n.dev->name : "?");
	read_unlock(&n->lock);
}

static int dn_neigh_seq_show(struct seq_file *seq, void *v)
{
	if (v == SEQ_START_TOKEN) {
		seq_puts(seq, "Addr    Flags State Use Blksize Dev\n");
	} else {
		dn_neigh_format_entry(seq, v);
	}

	return 0;
}

static void *dn_neigh_seq_start(struct seq_file *seq, loff_t *pos)
{
	return neigh_seq_start(seq, pos, &dn_neigh_table,
			       NEIGH_SEQ_NEIGH_ONLY);
}

585
static const struct seq_operations dn_neigh_seq_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
586
587
588
589
590
591
592
593
	.start = dn_neigh_seq_start,
	.next  = neigh_seq_next,
	.stop  = neigh_seq_stop,
	.show  = dn_neigh_seq_show,
};

static int dn_neigh_seq_open(struct inode *inode, struct file *file)
{
594
595
	return seq_open_net(inode, file, &dn_neigh_seq_ops,
			    sizeof(struct neigh_seq_state));
Linus Torvalds's avatar
Linus Torvalds committed
596
597
}

598
static const struct file_operations dn_neigh_seq_fops = {
Linus Torvalds's avatar
Linus Torvalds committed
599
600
601
602
	.owner		= THIS_MODULE,
	.open		= dn_neigh_seq_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
603
	.release	= seq_release_net,
Linus Torvalds's avatar
Linus Torvalds committed
604
605
606
607
608
609
};

#endif

void __init dn_neigh_init(void)
{
610
	neigh_table_init(NEIGH_DN_TABLE, &dn_neigh_table);
611
612
	proc_create("decnet_neigh", S_IRUGO, init_net.proc_net,
		    &dn_neigh_seq_fops);
Linus Torvalds's avatar
Linus Torvalds committed
613
614
615
616
}

void __exit dn_neigh_cleanup(void)
{
617
	remove_proc_entry("decnet_neigh", init_net.proc_net);
618
	neigh_table_clear(NEIGH_DN_TABLE, &dn_neigh_table);
Linus Torvalds's avatar
Linus Torvalds committed
619
}