[IPV6]: Lost locking in fl6_sock_lookup
[safe/jmp/linux-2.6] / net / ipv6 / ip6_flowlabel.c
1 /*
2  *      ip6_flowlabel.c         IPv6 flowlabel manager.
3  *
4  *      This program is free software; you can redistribute it and/or
5  *      modify it under the terms of the GNU General Public License
6  *      as published by the Free Software Foundation; either version
7  *      2 of the License, or (at your option) any later version.
8  *
9  *      Authors:        Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
10  */
11
12 #include <linux/capability.h>
13 #include <linux/errno.h>
14 #include <linux/types.h>
15 #include <linux/socket.h>
16 #include <linux/net.h>
17 #include <linux/netdevice.h>
18 #include <linux/if_arp.h>
19 #include <linux/in6.h>
20 #include <linux/route.h>
21 #include <linux/proc_fs.h>
22 #include <linux/seq_file.h>
23
24 #include <net/net_namespace.h>
25 #include <net/sock.h>
26
27 #include <net/ipv6.h>
28 #include <net/ndisc.h>
29 #include <net/protocol.h>
30 #include <net/ip6_route.h>
31 #include <net/addrconf.h>
32 #include <net/rawv6.h>
33 #include <net/icmp.h>
34 #include <net/transp_v6.h>
35
36 #include <asm/uaccess.h>
37
38 #define FL_MIN_LINGER   6       /* Minimal linger. It is set to 6sec specified
39                                    in old IPv6 RFC. Well, it was reasonable value.
40                                  */
41 #define FL_MAX_LINGER   60      /* Maximal linger timeout */
42
43 /* FL hash table */
44
45 #define FL_MAX_PER_SOCK 32
46 #define FL_MAX_SIZE     4096
47 #define FL_HASH_MASK    255
48 #define FL_HASH(l)      (ntohl(l)&FL_HASH_MASK)
49
50 static atomic_t fl_size = ATOMIC_INIT(0);
51 static struct ip6_flowlabel *fl_ht[FL_HASH_MASK+1];
52
53 static void ip6_fl_gc(unsigned long dummy);
54 static DEFINE_TIMER(ip6_fl_gc_timer, ip6_fl_gc, 0, 0);
55
56 /* FL hash table lock: it protects only of GC */
57
58 static DEFINE_RWLOCK(ip6_fl_lock);
59
60 /* Big socket sock */
61
62 static DEFINE_RWLOCK(ip6_sk_fl_lock);
63
64
65 static __inline__ struct ip6_flowlabel * __fl_lookup(__be32 label)
66 {
67         struct ip6_flowlabel *fl;
68
69         for (fl=fl_ht[FL_HASH(label)]; fl; fl = fl->next) {
70                 if (fl->label == label)
71                         return fl;
72         }
73         return NULL;
74 }
75
76 static struct ip6_flowlabel * fl_lookup(__be32 label)
77 {
78         struct ip6_flowlabel *fl;
79
80         read_lock_bh(&ip6_fl_lock);
81         fl = __fl_lookup(label);
82         if (fl)
83                 atomic_inc(&fl->users);
84         read_unlock_bh(&ip6_fl_lock);
85         return fl;
86 }
87
88
89 static void fl_free(struct ip6_flowlabel *fl)
90 {
91         if (fl)
92                 kfree(fl->opt);
93         kfree(fl);
94 }
95
96 static void fl_release(struct ip6_flowlabel *fl)
97 {
98         write_lock_bh(&ip6_fl_lock);
99
100         fl->lastuse = jiffies;
101         if (atomic_dec_and_test(&fl->users)) {
102                 unsigned long ttd = fl->lastuse + fl->linger;
103                 if (time_after(ttd, fl->expires))
104                         fl->expires = ttd;
105                 ttd = fl->expires;
106                 if (fl->opt && fl->share == IPV6_FL_S_EXCL) {
107                         struct ipv6_txoptions *opt = fl->opt;
108                         fl->opt = NULL;
109                         kfree(opt);
110                 }
111                 if (!timer_pending(&ip6_fl_gc_timer) ||
112                     time_after(ip6_fl_gc_timer.expires, ttd))
113                         mod_timer(&ip6_fl_gc_timer, ttd);
114         }
115
116         write_unlock_bh(&ip6_fl_lock);
117 }
118
119 static void ip6_fl_gc(unsigned long dummy)
120 {
121         int i;
122         unsigned long now = jiffies;
123         unsigned long sched = 0;
124
125         write_lock(&ip6_fl_lock);
126
127         for (i=0; i<=FL_HASH_MASK; i++) {
128                 struct ip6_flowlabel *fl, **flp;
129                 flp = &fl_ht[i];
130                 while ((fl=*flp) != NULL) {
131                         if (atomic_read(&fl->users) == 0) {
132                                 unsigned long ttd = fl->lastuse + fl->linger;
133                                 if (time_after(ttd, fl->expires))
134                                         fl->expires = ttd;
135                                 ttd = fl->expires;
136                                 if (time_after_eq(now, ttd)) {
137                                         *flp = fl->next;
138                                         fl_free(fl);
139                                         atomic_dec(&fl_size);
140                                         continue;
141                                 }
142                                 if (!sched || time_before(ttd, sched))
143                                         sched = ttd;
144                         }
145                         flp = &fl->next;
146                 }
147         }
148         if (!sched && atomic_read(&fl_size))
149                 sched = now + FL_MAX_LINGER;
150         if (sched) {
151                 ip6_fl_gc_timer.expires = sched;
152                 add_timer(&ip6_fl_gc_timer);
153         }
154         write_unlock(&ip6_fl_lock);
155 }
156
157 static int fl_intern(struct ip6_flowlabel *fl, __be32 label)
158 {
159         fl->label = label & IPV6_FLOWLABEL_MASK;
160
161         write_lock_bh(&ip6_fl_lock);
162         if (label == 0) {
163                 for (;;) {
164                         fl->label = htonl(net_random())&IPV6_FLOWLABEL_MASK;
165                         if (fl->label) {
166                                 struct ip6_flowlabel *lfl;
167                                 lfl = __fl_lookup(fl->label);
168                                 if (lfl == NULL)
169                                         break;
170                         }
171                 }
172         }
173
174         fl->lastuse = jiffies;
175         fl->next = fl_ht[FL_HASH(fl->label)];
176         fl_ht[FL_HASH(fl->label)] = fl;
177         atomic_inc(&fl_size);
178         write_unlock_bh(&ip6_fl_lock);
179         return 0;
180 }
181
182
183
184 /* Socket flowlabel lists */
185
186 struct ip6_flowlabel * fl6_sock_lookup(struct sock *sk, __be32 label)
187 {
188         struct ipv6_fl_socklist *sfl;
189         struct ipv6_pinfo *np = inet6_sk(sk);
190
191         label &= IPV6_FLOWLABEL_MASK;
192
193         read_lock_bh(&ip6_sk_fl_lock);
194         for (sfl=np->ipv6_fl_list; sfl; sfl = sfl->next) {
195                 struct ip6_flowlabel *fl = sfl->fl;
196                 if (fl->label == label) {
197                         read_unlock_bh(&ip6_sk_fl_lock);
198                         fl->lastuse = jiffies;
199                         atomic_inc(&fl->users);
200                         return fl;
201                 }
202         }
203         read_unlock_bh(&ip6_sk_fl_lock);
204         return NULL;
205 }
206
207 EXPORT_SYMBOL_GPL(fl6_sock_lookup);
208
209 void fl6_free_socklist(struct sock *sk)
210 {
211         struct ipv6_pinfo *np = inet6_sk(sk);
212         struct ipv6_fl_socklist *sfl;
213
214         while ((sfl = np->ipv6_fl_list) != NULL) {
215                 np->ipv6_fl_list = sfl->next;
216                 fl_release(sfl->fl);
217                 kfree(sfl);
218         }
219 }
220
221 /* Service routines */
222
223
224 /*
225    It is the only difficult place. flowlabel enforces equal headers
226    before and including routing header, however user may supply options
227    following rthdr.
228  */
229
230 struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
231                                          struct ip6_flowlabel * fl,
232                                          struct ipv6_txoptions * fopt)
233 {
234         struct ipv6_txoptions * fl_opt = fl->opt;
235
236         if (fopt == NULL || fopt->opt_flen == 0)
237                 return fl_opt;
238
239         if (fl_opt != NULL) {
240                 opt_space->hopopt = fl_opt->hopopt;
241                 opt_space->dst0opt = fl_opt->dst0opt;
242                 opt_space->srcrt = fl_opt->srcrt;
243                 opt_space->opt_nflen = fl_opt->opt_nflen;
244         } else {
245                 if (fopt->opt_nflen == 0)
246                         return fopt;
247                 opt_space->hopopt = NULL;
248                 opt_space->dst0opt = NULL;
249                 opt_space->srcrt = NULL;
250                 opt_space->opt_nflen = 0;
251         }
252         opt_space->dst1opt = fopt->dst1opt;
253         opt_space->opt_flen = fopt->opt_flen;
254         return opt_space;
255 }
256
257 static unsigned long check_linger(unsigned long ttl)
258 {
259         if (ttl < FL_MIN_LINGER)
260                 return FL_MIN_LINGER*HZ;
261         if (ttl > FL_MAX_LINGER && !capable(CAP_NET_ADMIN))
262                 return 0;
263         return ttl*HZ;
264 }
265
266 static int fl6_renew(struct ip6_flowlabel *fl, unsigned long linger, unsigned long expires)
267 {
268         linger = check_linger(linger);
269         if (!linger)
270                 return -EPERM;
271         expires = check_linger(expires);
272         if (!expires)
273                 return -EPERM;
274         fl->lastuse = jiffies;
275         if (time_before(fl->linger, linger))
276                 fl->linger = linger;
277         if (time_before(expires, fl->linger))
278                 expires = fl->linger;
279         if (time_before(fl->expires, fl->lastuse + expires))
280                 fl->expires = fl->lastuse + expires;
281         return 0;
282 }
283
284 static struct ip6_flowlabel *
285 fl_create(struct in6_flowlabel_req *freq, char __user *optval, int optlen, int *err_p)
286 {
287         struct ip6_flowlabel *fl;
288         int olen;
289         int addr_type;
290         int err;
291
292         err = -ENOMEM;
293         fl = kzalloc(sizeof(*fl), GFP_KERNEL);
294         if (fl == NULL)
295                 goto done;
296
297         olen = optlen - CMSG_ALIGN(sizeof(*freq));
298         if (olen > 0) {
299                 struct msghdr msg;
300                 struct flowi flowi;
301                 int junk;
302
303                 err = -ENOMEM;
304                 fl->opt = kmalloc(sizeof(*fl->opt) + olen, GFP_KERNEL);
305                 if (fl->opt == NULL)
306                         goto done;
307
308                 memset(fl->opt, 0, sizeof(*fl->opt));
309                 fl->opt->tot_len = sizeof(*fl->opt) + olen;
310                 err = -EFAULT;
311                 if (copy_from_user(fl->opt+1, optval+CMSG_ALIGN(sizeof(*freq)), olen))
312                         goto done;
313
314                 msg.msg_controllen = olen;
315                 msg.msg_control = (void*)(fl->opt+1);
316                 flowi.oif = 0;
317
318                 err = datagram_send_ctl(&msg, &flowi, fl->opt, &junk, &junk);
319                 if (err)
320                         goto done;
321                 err = -EINVAL;
322                 if (fl->opt->opt_flen)
323                         goto done;
324                 if (fl->opt->opt_nflen == 0) {
325                         kfree(fl->opt);
326                         fl->opt = NULL;
327                 }
328         }
329
330         fl->expires = jiffies;
331         err = fl6_renew(fl, freq->flr_linger, freq->flr_expires);
332         if (err)
333                 goto done;
334         fl->share = freq->flr_share;
335         addr_type = ipv6_addr_type(&freq->flr_dst);
336         if ((addr_type&IPV6_ADDR_MAPPED)
337             || addr_type == IPV6_ADDR_ANY) {
338                 err = -EINVAL;
339                 goto done;
340         }
341         ipv6_addr_copy(&fl->dst, &freq->flr_dst);
342         atomic_set(&fl->users, 1);
343         switch (fl->share) {
344         case IPV6_FL_S_EXCL:
345         case IPV6_FL_S_ANY:
346                 break;
347         case IPV6_FL_S_PROCESS:
348                 fl->owner = current->pid;
349                 break;
350         case IPV6_FL_S_USER:
351                 fl->owner = current->euid;
352                 break;
353         default:
354                 err = -EINVAL;
355                 goto done;
356         }
357         return fl;
358
359 done:
360         fl_free(fl);
361         *err_p = err;
362         return NULL;
363 }
364
365 static int mem_check(struct sock *sk)
366 {
367         struct ipv6_pinfo *np = inet6_sk(sk);
368         struct ipv6_fl_socklist *sfl;
369         int room = FL_MAX_SIZE - atomic_read(&fl_size);
370         int count = 0;
371
372         if (room > FL_MAX_SIZE - FL_MAX_PER_SOCK)
373                 return 0;
374
375         for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next)
376                 count++;
377
378         if (room <= 0 ||
379             ((count >= FL_MAX_PER_SOCK ||
380              (count > 0 && room < FL_MAX_SIZE/2) || room < FL_MAX_SIZE/4)
381              && !capable(CAP_NET_ADMIN)))
382                 return -ENOBUFS;
383
384         return 0;
385 }
386
387 static int ipv6_hdr_cmp(struct ipv6_opt_hdr *h1, struct ipv6_opt_hdr *h2)
388 {
389         if (h1 == h2)
390                 return 0;
391         if (h1 == NULL || h2 == NULL)
392                 return 1;
393         if (h1->hdrlen != h2->hdrlen)
394                 return 1;
395         return memcmp(h1+1, h2+1, ((h1->hdrlen+1)<<3) - sizeof(*h1));
396 }
397
398 static int ipv6_opt_cmp(struct ipv6_txoptions *o1, struct ipv6_txoptions *o2)
399 {
400         if (o1 == o2)
401                 return 0;
402         if (o1 == NULL || o2 == NULL)
403                 return 1;
404         if (o1->opt_nflen != o2->opt_nflen)
405                 return 1;
406         if (ipv6_hdr_cmp(o1->hopopt, o2->hopopt))
407                 return 1;
408         if (ipv6_hdr_cmp(o1->dst0opt, o2->dst0opt))
409                 return 1;
410         if (ipv6_hdr_cmp((struct ipv6_opt_hdr *)o1->srcrt, (struct ipv6_opt_hdr *)o2->srcrt))
411                 return 1;
412         return 0;
413 }
414
415 static inline void fl_link(struct ipv6_pinfo *np, struct ipv6_fl_socklist *sfl,
416                 struct ip6_flowlabel *fl)
417 {
418         write_lock_bh(&ip6_sk_fl_lock);
419         sfl->fl = fl;
420         sfl->next = np->ipv6_fl_list;
421         np->ipv6_fl_list = sfl;
422         write_unlock_bh(&ip6_sk_fl_lock);
423 }
424
425 int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
426 {
427         int err;
428         struct ipv6_pinfo *np = inet6_sk(sk);
429         struct in6_flowlabel_req freq;
430         struct ipv6_fl_socklist *sfl1=NULL;
431         struct ipv6_fl_socklist *sfl, **sflp;
432         struct ip6_flowlabel *fl;
433
434         if (optlen < sizeof(freq))
435                 return -EINVAL;
436
437         if (copy_from_user(&freq, optval, sizeof(freq)))
438                 return -EFAULT;
439
440         switch (freq.flr_action) {
441         case IPV6_FL_A_PUT:
442                 write_lock_bh(&ip6_sk_fl_lock);
443                 for (sflp = &np->ipv6_fl_list; (sfl=*sflp)!=NULL; sflp = &sfl->next) {
444                         if (sfl->fl->label == freq.flr_label) {
445                                 if (freq.flr_label == (np->flow_label&IPV6_FLOWLABEL_MASK))
446                                         np->flow_label &= ~IPV6_FLOWLABEL_MASK;
447                                 *sflp = sfl->next;
448                                 write_unlock_bh(&ip6_sk_fl_lock);
449                                 fl_release(sfl->fl);
450                                 kfree(sfl);
451                                 return 0;
452                         }
453                 }
454                 write_unlock_bh(&ip6_sk_fl_lock);
455                 return -ESRCH;
456
457         case IPV6_FL_A_RENEW:
458                 read_lock_bh(&ip6_sk_fl_lock);
459                 for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) {
460                         if (sfl->fl->label == freq.flr_label) {
461                                 err = fl6_renew(sfl->fl, freq.flr_linger, freq.flr_expires);
462                                 read_unlock_bh(&ip6_sk_fl_lock);
463                                 return err;
464                         }
465                 }
466                 read_unlock_bh(&ip6_sk_fl_lock);
467
468                 if (freq.flr_share == IPV6_FL_S_NONE && capable(CAP_NET_ADMIN)) {
469                         fl = fl_lookup(freq.flr_label);
470                         if (fl) {
471                                 err = fl6_renew(fl, freq.flr_linger, freq.flr_expires);
472                                 fl_release(fl);
473                                 return err;
474                         }
475                 }
476                 return -ESRCH;
477
478         case IPV6_FL_A_GET:
479                 if (freq.flr_label & ~IPV6_FLOWLABEL_MASK)
480                         return -EINVAL;
481
482                 fl = fl_create(&freq, optval, optlen, &err);
483                 if (fl == NULL)
484                         return err;
485                 sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);
486
487                 if (freq.flr_label) {
488                         struct ip6_flowlabel *fl1 = NULL;
489
490                         err = -EEXIST;
491                         read_lock_bh(&ip6_sk_fl_lock);
492                         for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) {
493                                 if (sfl->fl->label == freq.flr_label) {
494                                         if (freq.flr_flags&IPV6_FL_F_EXCL) {
495                                                 read_unlock_bh(&ip6_sk_fl_lock);
496                                                 goto done;
497                                         }
498                                         fl1 = sfl->fl;
499                                         atomic_inc(&fl1->users);
500                                         break;
501                                 }
502                         }
503                         read_unlock_bh(&ip6_sk_fl_lock);
504
505                         if (fl1 == NULL)
506                                 fl1 = fl_lookup(freq.flr_label);
507                         if (fl1) {
508                                 err = -EEXIST;
509                                 if (freq.flr_flags&IPV6_FL_F_EXCL)
510                                         goto release;
511                                 err = -EPERM;
512                                 if (fl1->share == IPV6_FL_S_EXCL ||
513                                     fl1->share != fl->share ||
514                                     fl1->owner != fl->owner)
515                                         goto release;
516
517                                 err = -EINVAL;
518                                 if (!ipv6_addr_equal(&fl1->dst, &fl->dst) ||
519                                     ipv6_opt_cmp(fl1->opt, fl->opt))
520                                         goto release;
521
522                                 err = -ENOMEM;
523                                 if (sfl1 == NULL)
524                                         goto release;
525                                 if (fl->linger > fl1->linger)
526                                         fl1->linger = fl->linger;
527                                 if ((long)(fl->expires - fl1->expires) > 0)
528                                         fl1->expires = fl->expires;
529                                 fl_link(np, sfl1, fl1);
530                                 fl_free(fl);
531                                 return 0;
532
533 release:
534                                 fl_release(fl1);
535                                 goto done;
536                         }
537                 }
538                 err = -ENOENT;
539                 if (!(freq.flr_flags&IPV6_FL_F_CREATE))
540                         goto done;
541
542                 err = -ENOMEM;
543                 if (sfl1 == NULL || (err = mem_check(sk)) != 0)
544                         goto done;
545
546                 err = fl_intern(fl, freq.flr_label);
547                 if (err)
548                         goto done;
549
550                 if (!freq.flr_label) {
551                         if (copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label,
552                                          &fl->label, sizeof(fl->label))) {
553                                 /* Intentionally ignore fault. */
554                         }
555                 }
556
557                 fl_link(np, sfl1, fl);
558                 return 0;
559
560         default:
561                 return -EINVAL;
562         }
563
564 done:
565         fl_free(fl);
566         kfree(sfl1);
567         return err;
568 }
569
570 #ifdef CONFIG_PROC_FS
571
572 struct ip6fl_iter_state {
573         int bucket;
574 };
575
576 #define ip6fl_seq_private(seq)  ((struct ip6fl_iter_state *)(seq)->private)
577
578 static struct ip6_flowlabel *ip6fl_get_first(struct seq_file *seq)
579 {
580         struct ip6_flowlabel *fl = NULL;
581         struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
582
583         for (state->bucket = 0; state->bucket <= FL_HASH_MASK; ++state->bucket) {
584                 if (fl_ht[state->bucket]) {
585                         fl = fl_ht[state->bucket];
586                         break;
587                 }
588         }
589         return fl;
590 }
591
592 static struct ip6_flowlabel *ip6fl_get_next(struct seq_file *seq, struct ip6_flowlabel *fl)
593 {
594         struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
595
596         fl = fl->next;
597         while (!fl) {
598                 if (++state->bucket <= FL_HASH_MASK)
599                         fl = fl_ht[state->bucket];
600                 else
601                         break;
602         }
603         return fl;
604 }
605
606 static struct ip6_flowlabel *ip6fl_get_idx(struct seq_file *seq, loff_t pos)
607 {
608         struct ip6_flowlabel *fl = ip6fl_get_first(seq);
609         if (fl)
610                 while (pos && (fl = ip6fl_get_next(seq, fl)) != NULL)
611                         --pos;
612         return pos ? NULL : fl;
613 }
614
615 static void *ip6fl_seq_start(struct seq_file *seq, loff_t *pos)
616 {
617         read_lock_bh(&ip6_fl_lock);
618         return *pos ? ip6fl_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
619 }
620
621 static void *ip6fl_seq_next(struct seq_file *seq, void *v, loff_t *pos)
622 {
623         struct ip6_flowlabel *fl;
624
625         if (v == SEQ_START_TOKEN)
626                 fl = ip6fl_get_first(seq);
627         else
628                 fl = ip6fl_get_next(seq, v);
629         ++*pos;
630         return fl;
631 }
632
633 static void ip6fl_seq_stop(struct seq_file *seq, void *v)
634 {
635         read_unlock_bh(&ip6_fl_lock);
636 }
637
638 static int ip6fl_seq_show(struct seq_file *seq, void *v)
639 {
640         if (v == SEQ_START_TOKEN)
641                 seq_printf(seq, "%-5s %-1s %-6s %-6s %-6s %-8s %-32s %s\n",
642                            "Label", "S", "Owner", "Users", "Linger", "Expires", "Dst", "Opt");
643         else {
644                 struct ip6_flowlabel *fl = v;
645                 seq_printf(seq,
646                            "%05X %-1d %-6d %-6d %-6ld %-8ld " NIP6_SEQFMT " %-4d\n",
647                            (unsigned)ntohl(fl->label),
648                            fl->share,
649                            (unsigned)fl->owner,
650                            atomic_read(&fl->users),
651                            fl->linger/HZ,
652                            (long)(fl->expires - jiffies)/HZ,
653                            NIP6(fl->dst),
654                            fl->opt ? fl->opt->opt_nflen : 0);
655         }
656         return 0;
657 }
658
659 static const struct seq_operations ip6fl_seq_ops = {
660         .start  =       ip6fl_seq_start,
661         .next   =       ip6fl_seq_next,
662         .stop   =       ip6fl_seq_stop,
663         .show   =       ip6fl_seq_show,
664 };
665
666 static int ip6fl_seq_open(struct inode *inode, struct file *file)
667 {
668         return seq_open_private(file, &ip6fl_seq_ops,
669                         sizeof(struct ip6fl_iter_state));
670 }
671
672 static const struct file_operations ip6fl_seq_fops = {
673         .owner          =       THIS_MODULE,
674         .open           =       ip6fl_seq_open,
675         .read           =       seq_read,
676         .llseek         =       seq_lseek,
677         .release        =       seq_release_private,
678 };
679 #endif
680
681
682 void ip6_flowlabel_init(void)
683 {
684 #ifdef CONFIG_PROC_FS
685         proc_net_fops_create(&init_net, "ip6_flowlabel", S_IRUGO, &ip6fl_seq_fops);
686 #endif
687 }
688
689 void ip6_flowlabel_cleanup(void)
690 {
691         del_timer(&ip6_fl_gc_timer);
692 #ifdef CONFIG_PROC_FS
693         proc_net_remove(&init_net, "ip6_flowlabel");
694 #endif
695 }