diff options
Diffstat (limited to 'net/ipv6/anycast.c')
-rw-r--r-- | net/ipv6/anycast.c | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/net/ipv6/anycast.c b/net/ipv6/anycast.c new file mode 100644 index 000000000000..5d22ca3cca2e --- /dev/null +++ b/net/ipv6/anycast.c | |||
@@ -0,0 +1,594 @@ | |||
1 | /* | ||
2 | * Anycast support for IPv6 | ||
3 | * Linux INET6 implementation | ||
4 | * | ||
5 | * Authors: | ||
6 | * David L Stevens (dlstevens@us.ibm.com) | ||
7 | * | ||
8 | * based heavily on net/ipv6/mcast.c | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or | ||
11 | * modify it under the terms of the GNU General Public License | ||
12 | * as published by the Free Software Foundation; either version | ||
13 | * 2 of the License, or (at your option) any later version. | ||
14 | */ | ||
15 | |||
16 | #include <linux/config.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/errno.h> | ||
19 | #include <linux/types.h> | ||
20 | #include <linux/random.h> | ||
21 | #include <linux/string.h> | ||
22 | #include <linux/socket.h> | ||
23 | #include <linux/sockios.h> | ||
24 | #include <linux/sched.h> | ||
25 | #include <linux/net.h> | ||
26 | #include <linux/in6.h> | ||
27 | #include <linux/netdevice.h> | ||
28 | #include <linux/if_arp.h> | ||
29 | #include <linux/route.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/proc_fs.h> | ||
32 | #include <linux/seq_file.h> | ||
33 | |||
34 | #include <net/sock.h> | ||
35 | #include <net/snmp.h> | ||
36 | |||
37 | #include <net/ipv6.h> | ||
38 | #include <net/protocol.h> | ||
39 | #include <net/if_inet6.h> | ||
40 | #include <net/ndisc.h> | ||
41 | #include <net/addrconf.h> | ||
42 | #include <net/ip6_route.h> | ||
43 | |||
44 | #include <net/checksum.h> | ||
45 | |||
46 | static int ipv6_dev_ac_dec(struct net_device *dev, struct in6_addr *addr); | ||
47 | |||
48 | /* Big ac list lock for all the sockets */ | ||
49 | static DEFINE_RWLOCK(ipv6_sk_ac_lock); | ||
50 | |||
51 | static int | ||
52 | ip6_onlink(struct in6_addr *addr, struct net_device *dev) | ||
53 | { | ||
54 | struct inet6_dev *idev; | ||
55 | struct inet6_ifaddr *ifa; | ||
56 | int onlink; | ||
57 | |||
58 | onlink = 0; | ||
59 | read_lock(&addrconf_lock); | ||
60 | idev = __in6_dev_get(dev); | ||
61 | if (idev) { | ||
62 | read_lock_bh(&idev->lock); | ||
63 | for (ifa=idev->addr_list; ifa; ifa=ifa->if_next) { | ||
64 | onlink = ipv6_prefix_equal(addr, &ifa->addr, | ||
65 | ifa->prefix_len); | ||
66 | if (onlink) | ||
67 | break; | ||
68 | } | ||
69 | read_unlock_bh(&idev->lock); | ||
70 | } | ||
71 | read_unlock(&addrconf_lock); | ||
72 | return onlink; | ||
73 | } | ||
74 | |||
75 | /* | ||
76 | * socket join an anycast group | ||
77 | */ | ||
78 | |||
79 | int ipv6_sock_ac_join(struct sock *sk, int ifindex, struct in6_addr *addr) | ||
80 | { | ||
81 | struct ipv6_pinfo *np = inet6_sk(sk); | ||
82 | struct net_device *dev = NULL; | ||
83 | struct inet6_dev *idev; | ||
84 | struct ipv6_ac_socklist *pac; | ||
85 | int ishost = !ipv6_devconf.forwarding; | ||
86 | int err = 0; | ||
87 | |||
88 | if (!capable(CAP_NET_ADMIN)) | ||
89 | return -EPERM; | ||
90 | if (ipv6_addr_is_multicast(addr)) | ||
91 | return -EINVAL; | ||
92 | if (ipv6_chk_addr(addr, NULL, 0)) | ||
93 | return -EINVAL; | ||
94 | |||
95 | pac = sock_kmalloc(sk, sizeof(struct ipv6_ac_socklist), GFP_KERNEL); | ||
96 | if (pac == NULL) | ||
97 | return -ENOMEM; | ||
98 | pac->acl_next = NULL; | ||
99 | ipv6_addr_copy(&pac->acl_addr, addr); | ||
100 | |||
101 | if (ifindex == 0) { | ||
102 | struct rt6_info *rt; | ||
103 | |||
104 | rt = rt6_lookup(addr, NULL, 0, 0); | ||
105 | if (rt) { | ||
106 | dev = rt->rt6i_dev; | ||
107 | dev_hold(dev); | ||
108 | dst_release(&rt->u.dst); | ||
109 | } else if (ishost) { | ||
110 | err = -EADDRNOTAVAIL; | ||
111 | goto out_free_pac; | ||
112 | } else { | ||
113 | /* router, no matching interface: just pick one */ | ||
114 | |||
115 | dev = dev_get_by_flags(IFF_UP, IFF_UP|IFF_LOOPBACK); | ||
116 | } | ||
117 | } else | ||
118 | dev = dev_get_by_index(ifindex); | ||
119 | |||
120 | if (dev == NULL) { | ||
121 | err = -ENODEV; | ||
122 | goto out_free_pac; | ||
123 | } | ||
124 | |||
125 | idev = in6_dev_get(dev); | ||
126 | if (!idev) { | ||
127 | if (ifindex) | ||
128 | err = -ENODEV; | ||
129 | else | ||
130 | err = -EADDRNOTAVAIL; | ||
131 | goto out_dev_put; | ||
132 | } | ||
133 | /* reset ishost, now that we have a specific device */ | ||
134 | ishost = !idev->cnf.forwarding; | ||
135 | in6_dev_put(idev); | ||
136 | |||
137 | pac->acl_ifindex = dev->ifindex; | ||
138 | |||
139 | /* XXX | ||
140 | * For hosts, allow link-local or matching prefix anycasts. | ||
141 | * This obviates the need for propagating anycast routes while | ||
142 | * still allowing some non-router anycast participation. | ||
143 | */ | ||
144 | if (!ip6_onlink(addr, dev)) { | ||
145 | if (ishost) | ||
146 | err = -EADDRNOTAVAIL; | ||
147 | if (err) | ||
148 | goto out_dev_put; | ||
149 | } | ||
150 | |||
151 | err = ipv6_dev_ac_inc(dev, addr); | ||
152 | if (err) | ||
153 | goto out_dev_put; | ||
154 | |||
155 | write_lock_bh(&ipv6_sk_ac_lock); | ||
156 | pac->acl_next = np->ipv6_ac_list; | ||
157 | np->ipv6_ac_list = pac; | ||
158 | write_unlock_bh(&ipv6_sk_ac_lock); | ||
159 | |||
160 | dev_put(dev); | ||
161 | |||
162 | return 0; | ||
163 | |||
164 | out_dev_put: | ||
165 | dev_put(dev); | ||
166 | out_free_pac: | ||
167 | sock_kfree_s(sk, pac, sizeof(*pac)); | ||
168 | return err; | ||
169 | } | ||
170 | |||
171 | /* | ||
172 | * socket leave an anycast group | ||
173 | */ | ||
174 | int ipv6_sock_ac_drop(struct sock *sk, int ifindex, struct in6_addr *addr) | ||
175 | { | ||
176 | struct ipv6_pinfo *np = inet6_sk(sk); | ||
177 | struct net_device *dev; | ||
178 | struct ipv6_ac_socklist *pac, *prev_pac; | ||
179 | |||
180 | write_lock_bh(&ipv6_sk_ac_lock); | ||
181 | prev_pac = NULL; | ||
182 | for (pac = np->ipv6_ac_list; pac; pac = pac->acl_next) { | ||
183 | if ((ifindex == 0 || pac->acl_ifindex == ifindex) && | ||
184 | ipv6_addr_equal(&pac->acl_addr, addr)) | ||
185 | break; | ||
186 | prev_pac = pac; | ||
187 | } | ||
188 | if (!pac) { | ||
189 | write_unlock_bh(&ipv6_sk_ac_lock); | ||
190 | return -ENOENT; | ||
191 | } | ||
192 | if (prev_pac) | ||
193 | prev_pac->acl_next = pac->acl_next; | ||
194 | else | ||
195 | np->ipv6_ac_list = pac->acl_next; | ||
196 | |||
197 | write_unlock_bh(&ipv6_sk_ac_lock); | ||
198 | |||
199 | dev = dev_get_by_index(pac->acl_ifindex); | ||
200 | if (dev) { | ||
201 | ipv6_dev_ac_dec(dev, &pac->acl_addr); | ||
202 | dev_put(dev); | ||
203 | } | ||
204 | sock_kfree_s(sk, pac, sizeof(*pac)); | ||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | void ipv6_sock_ac_close(struct sock *sk) | ||
209 | { | ||
210 | struct ipv6_pinfo *np = inet6_sk(sk); | ||
211 | struct net_device *dev = NULL; | ||
212 | struct ipv6_ac_socklist *pac; | ||
213 | int prev_index; | ||
214 | |||
215 | write_lock_bh(&ipv6_sk_ac_lock); | ||
216 | pac = np->ipv6_ac_list; | ||
217 | np->ipv6_ac_list = NULL; | ||
218 | write_unlock_bh(&ipv6_sk_ac_lock); | ||
219 | |||
220 | prev_index = 0; | ||
221 | while (pac) { | ||
222 | struct ipv6_ac_socklist *next = pac->acl_next; | ||
223 | |||
224 | if (pac->acl_ifindex != prev_index) { | ||
225 | if (dev) | ||
226 | dev_put(dev); | ||
227 | dev = dev_get_by_index(pac->acl_ifindex); | ||
228 | prev_index = pac->acl_ifindex; | ||
229 | } | ||
230 | if (dev) | ||
231 | ipv6_dev_ac_dec(dev, &pac->acl_addr); | ||
232 | sock_kfree_s(sk, pac, sizeof(*pac)); | ||
233 | pac = next; | ||
234 | } | ||
235 | if (dev) | ||
236 | dev_put(dev); | ||
237 | } | ||
238 | |||
239 | #if 0 | ||
240 | /* The function is not used, which is funny. Apparently, author | ||
241 | * supposed to use it to filter out datagrams inside udp/raw but forgot. | ||
242 | * | ||
243 | * It is OK, anycasts are not special comparing to delivery to unicasts. | ||
244 | */ | ||
245 | |||
246 | int inet6_ac_check(struct sock *sk, struct in6_addr *addr, int ifindex) | ||
247 | { | ||
248 | struct ipv6_ac_socklist *pac; | ||
249 | struct ipv6_pinfo *np = inet6_sk(sk); | ||
250 | int found; | ||
251 | |||
252 | found = 0; | ||
253 | read_lock(&ipv6_sk_ac_lock); | ||
254 | for (pac=np->ipv6_ac_list; pac; pac=pac->acl_next) { | ||
255 | if (ifindex && pac->acl_ifindex != ifindex) | ||
256 | continue; | ||
257 | found = ipv6_addr_equal(&pac->acl_addr, addr); | ||
258 | if (found) | ||
259 | break; | ||
260 | } | ||
261 | read_unlock(&ipv6_sk_ac_lock); | ||
262 | |||
263 | return found; | ||
264 | } | ||
265 | |||
266 | #endif | ||
267 | |||
268 | static void aca_put(struct ifacaddr6 *ac) | ||
269 | { | ||
270 | if (atomic_dec_and_test(&ac->aca_refcnt)) { | ||
271 | in6_dev_put(ac->aca_idev); | ||
272 | dst_release(&ac->aca_rt->u.dst); | ||
273 | kfree(ac); | ||
274 | } | ||
275 | } | ||
276 | |||
277 | /* | ||
278 | * device anycast group inc (add if not found) | ||
279 | */ | ||
280 | int ipv6_dev_ac_inc(struct net_device *dev, struct in6_addr *addr) | ||
281 | { | ||
282 | struct ifacaddr6 *aca; | ||
283 | struct inet6_dev *idev; | ||
284 | struct rt6_info *rt; | ||
285 | int err; | ||
286 | |||
287 | idev = in6_dev_get(dev); | ||
288 | |||
289 | if (idev == NULL) | ||
290 | return -EINVAL; | ||
291 | |||
292 | write_lock_bh(&idev->lock); | ||
293 | if (idev->dead) { | ||
294 | err = -ENODEV; | ||
295 | goto out; | ||
296 | } | ||
297 | |||
298 | for (aca = idev->ac_list; aca; aca = aca->aca_next) { | ||
299 | if (ipv6_addr_equal(&aca->aca_addr, addr)) { | ||
300 | aca->aca_users++; | ||
301 | err = 0; | ||
302 | goto out; | ||
303 | } | ||
304 | } | ||
305 | |||
306 | /* | ||
307 | * not found: create a new one. | ||
308 | */ | ||
309 | |||
310 | aca = kmalloc(sizeof(struct ifacaddr6), GFP_ATOMIC); | ||
311 | |||
312 | if (aca == NULL) { | ||
313 | err = -ENOMEM; | ||
314 | goto out; | ||
315 | } | ||
316 | |||
317 | rt = addrconf_dst_alloc(idev, addr, 1); | ||
318 | if (IS_ERR(rt)) { | ||
319 | kfree(aca); | ||
320 | err = PTR_ERR(rt); | ||
321 | goto out; | ||
322 | } | ||
323 | |||
324 | memset(aca, 0, sizeof(struct ifacaddr6)); | ||
325 | |||
326 | ipv6_addr_copy(&aca->aca_addr, addr); | ||
327 | aca->aca_idev = idev; | ||
328 | aca->aca_rt = rt; | ||
329 | aca->aca_users = 1; | ||
330 | /* aca_tstamp should be updated upon changes */ | ||
331 | aca->aca_cstamp = aca->aca_tstamp = jiffies; | ||
332 | atomic_set(&aca->aca_refcnt, 2); | ||
333 | spin_lock_init(&aca->aca_lock); | ||
334 | |||
335 | aca->aca_next = idev->ac_list; | ||
336 | idev->ac_list = aca; | ||
337 | write_unlock_bh(&idev->lock); | ||
338 | |||
339 | dst_hold(&rt->u.dst); | ||
340 | if (ip6_ins_rt(rt, NULL, NULL)) | ||
341 | dst_release(&rt->u.dst); | ||
342 | |||
343 | addrconf_join_solict(dev, &aca->aca_addr); | ||
344 | |||
345 | aca_put(aca); | ||
346 | return 0; | ||
347 | out: | ||
348 | write_unlock_bh(&idev->lock); | ||
349 | in6_dev_put(idev); | ||
350 | return err; | ||
351 | } | ||
352 | |||
353 | /* | ||
354 | * device anycast group decrement | ||
355 | */ | ||
356 | int __ipv6_dev_ac_dec(struct inet6_dev *idev, struct in6_addr *addr) | ||
357 | { | ||
358 | struct ifacaddr6 *aca, *prev_aca; | ||
359 | |||
360 | write_lock_bh(&idev->lock); | ||
361 | prev_aca = NULL; | ||
362 | for (aca = idev->ac_list; aca; aca = aca->aca_next) { | ||
363 | if (ipv6_addr_equal(&aca->aca_addr, addr)) | ||
364 | break; | ||
365 | prev_aca = aca; | ||
366 | } | ||
367 | if (!aca) { | ||
368 | write_unlock_bh(&idev->lock); | ||
369 | return -ENOENT; | ||
370 | } | ||
371 | if (--aca->aca_users > 0) { | ||
372 | write_unlock_bh(&idev->lock); | ||
373 | return 0; | ||
374 | } | ||
375 | if (prev_aca) | ||
376 | prev_aca->aca_next = aca->aca_next; | ||
377 | else | ||
378 | idev->ac_list = aca->aca_next; | ||
379 | write_unlock_bh(&idev->lock); | ||
380 | addrconf_leave_solict(idev, &aca->aca_addr); | ||
381 | |||
382 | dst_hold(&aca->aca_rt->u.dst); | ||
383 | if (ip6_del_rt(aca->aca_rt, NULL, NULL)) | ||
384 | dst_free(&aca->aca_rt->u.dst); | ||
385 | else | ||
386 | dst_release(&aca->aca_rt->u.dst); | ||
387 | |||
388 | aca_put(aca); | ||
389 | return 0; | ||
390 | } | ||
391 | |||
392 | static int ipv6_dev_ac_dec(struct net_device *dev, struct in6_addr *addr) | ||
393 | { | ||
394 | int ret; | ||
395 | struct inet6_dev *idev = in6_dev_get(dev); | ||
396 | if (idev == NULL) | ||
397 | return -ENODEV; | ||
398 | ret = __ipv6_dev_ac_dec(idev, addr); | ||
399 | in6_dev_put(idev); | ||
400 | return ret; | ||
401 | } | ||
402 | |||
403 | /* | ||
404 | * check if the interface has this anycast address | ||
405 | */ | ||
406 | static int ipv6_chk_acast_dev(struct net_device *dev, struct in6_addr *addr) | ||
407 | { | ||
408 | struct inet6_dev *idev; | ||
409 | struct ifacaddr6 *aca; | ||
410 | |||
411 | idev = in6_dev_get(dev); | ||
412 | if (idev) { | ||
413 | read_lock_bh(&idev->lock); | ||
414 | for (aca = idev->ac_list; aca; aca = aca->aca_next) | ||
415 | if (ipv6_addr_equal(&aca->aca_addr, addr)) | ||
416 | break; | ||
417 | read_unlock_bh(&idev->lock); | ||
418 | in6_dev_put(idev); | ||
419 | return aca != 0; | ||
420 | } | ||
421 | return 0; | ||
422 | } | ||
423 | |||
424 | /* | ||
425 | * check if given interface (or any, if dev==0) has this anycast address | ||
426 | */ | ||
427 | int ipv6_chk_acast_addr(struct net_device *dev, struct in6_addr *addr) | ||
428 | { | ||
429 | if (dev) | ||
430 | return ipv6_chk_acast_dev(dev, addr); | ||
431 | read_lock(&dev_base_lock); | ||
432 | for (dev=dev_base; dev; dev=dev->next) | ||
433 | if (ipv6_chk_acast_dev(dev, addr)) | ||
434 | break; | ||
435 | read_unlock(&dev_base_lock); | ||
436 | return dev != 0; | ||
437 | } | ||
438 | |||
439 | |||
440 | #ifdef CONFIG_PROC_FS | ||
441 | struct ac6_iter_state { | ||
442 | struct net_device *dev; | ||
443 | struct inet6_dev *idev; | ||
444 | }; | ||
445 | |||
446 | #define ac6_seq_private(seq) ((struct ac6_iter_state *)(seq)->private) | ||
447 | |||
448 | static inline struct ifacaddr6 *ac6_get_first(struct seq_file *seq) | ||
449 | { | ||
450 | struct ifacaddr6 *im = NULL; | ||
451 | struct ac6_iter_state *state = ac6_seq_private(seq); | ||
452 | |||
453 | for (state->dev = dev_base, state->idev = NULL; | ||
454 | state->dev; | ||
455 | state->dev = state->dev->next) { | ||
456 | struct inet6_dev *idev; | ||
457 | idev = in6_dev_get(state->dev); | ||
458 | if (!idev) | ||
459 | continue; | ||
460 | read_lock_bh(&idev->lock); | ||
461 | im = idev->ac_list; | ||
462 | if (im) { | ||
463 | state->idev = idev; | ||
464 | break; | ||
465 | } | ||
466 | read_unlock_bh(&idev->lock); | ||
467 | } | ||
468 | return im; | ||
469 | } | ||
470 | |||
471 | static struct ifacaddr6 *ac6_get_next(struct seq_file *seq, struct ifacaddr6 *im) | ||
472 | { | ||
473 | struct ac6_iter_state *state = ac6_seq_private(seq); | ||
474 | |||
475 | im = im->aca_next; | ||
476 | while (!im) { | ||
477 | if (likely(state->idev != NULL)) { | ||
478 | read_unlock_bh(&state->idev->lock); | ||
479 | in6_dev_put(state->idev); | ||
480 | } | ||
481 | state->dev = state->dev->next; | ||
482 | if (!state->dev) { | ||
483 | state->idev = NULL; | ||
484 | break; | ||
485 | } | ||
486 | state->idev = in6_dev_get(state->dev); | ||
487 | if (!state->idev) | ||
488 | continue; | ||
489 | read_lock_bh(&state->idev->lock); | ||
490 | im = state->idev->ac_list; | ||
491 | } | ||
492 | return im; | ||
493 | } | ||
494 | |||
495 | static struct ifacaddr6 *ac6_get_idx(struct seq_file *seq, loff_t pos) | ||
496 | { | ||
497 | struct ifacaddr6 *im = ac6_get_first(seq); | ||
498 | if (im) | ||
499 | while (pos && (im = ac6_get_next(seq, im)) != NULL) | ||
500 | --pos; | ||
501 | return pos ? NULL : im; | ||
502 | } | ||
503 | |||
504 | static void *ac6_seq_start(struct seq_file *seq, loff_t *pos) | ||
505 | { | ||
506 | read_lock(&dev_base_lock); | ||
507 | return ac6_get_idx(seq, *pos); | ||
508 | } | ||
509 | |||
510 | static void *ac6_seq_next(struct seq_file *seq, void *v, loff_t *pos) | ||
511 | { | ||
512 | struct ifacaddr6 *im; | ||
513 | im = ac6_get_next(seq, v); | ||
514 | ++*pos; | ||
515 | return im; | ||
516 | } | ||
517 | |||
518 | static void ac6_seq_stop(struct seq_file *seq, void *v) | ||
519 | { | ||
520 | struct ac6_iter_state *state = ac6_seq_private(seq); | ||
521 | if (likely(state->idev != NULL)) { | ||
522 | read_unlock_bh(&state->idev->lock); | ||
523 | in6_dev_put(state->idev); | ||
524 | } | ||
525 | read_unlock(&dev_base_lock); | ||
526 | } | ||
527 | |||
528 | static int ac6_seq_show(struct seq_file *seq, void *v) | ||
529 | { | ||
530 | struct ifacaddr6 *im = (struct ifacaddr6 *)v; | ||
531 | struct ac6_iter_state *state = ac6_seq_private(seq); | ||
532 | |||
533 | seq_printf(seq, | ||
534 | "%-4d %-15s " | ||
535 | "%04x%04x%04x%04x%04x%04x%04x%04x " | ||
536 | "%5d\n", | ||
537 | state->dev->ifindex, state->dev->name, | ||
538 | NIP6(im->aca_addr), | ||
539 | im->aca_users); | ||
540 | return 0; | ||
541 | } | ||
542 | |||
543 | static struct seq_operations ac6_seq_ops = { | ||
544 | .start = ac6_seq_start, | ||
545 | .next = ac6_seq_next, | ||
546 | .stop = ac6_seq_stop, | ||
547 | .show = ac6_seq_show, | ||
548 | }; | ||
549 | |||
550 | static int ac6_seq_open(struct inode *inode, struct file *file) | ||
551 | { | ||
552 | struct seq_file *seq; | ||
553 | int rc = -ENOMEM; | ||
554 | struct ac6_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL); | ||
555 | |||
556 | if (!s) | ||
557 | goto out; | ||
558 | |||
559 | rc = seq_open(file, &ac6_seq_ops); | ||
560 | if (rc) | ||
561 | goto out_kfree; | ||
562 | |||
563 | seq = file->private_data; | ||
564 | seq->private = s; | ||
565 | memset(s, 0, sizeof(*s)); | ||
566 | out: | ||
567 | return rc; | ||
568 | out_kfree: | ||
569 | kfree(s); | ||
570 | goto out; | ||
571 | } | ||
572 | |||
573 | static struct file_operations ac6_seq_fops = { | ||
574 | .owner = THIS_MODULE, | ||
575 | .open = ac6_seq_open, | ||
576 | .read = seq_read, | ||
577 | .llseek = seq_lseek, | ||
578 | .release = seq_release_private, | ||
579 | }; | ||
580 | |||
581 | int __init ac6_proc_init(void) | ||
582 | { | ||
583 | if (!proc_net_fops_create("anycast6", S_IRUGO, &ac6_seq_fops)) | ||
584 | return -ENOMEM; | ||
585 | |||
586 | return 0; | ||
587 | } | ||
588 | |||
589 | void ac6_proc_exit(void) | ||
590 | { | ||
591 | proc_net_remove("anycast6"); | ||
592 | } | ||
593 | #endif | ||
594 | |||