xref: /openbmc/linux/net/ipv6/ip6_flowlabel.c (revision 864e2a1f)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  *	ip6_flowlabel.c		IPv6 flowlabel manager.
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  *	This program is free software; you can redistribute it and/or
51da177e4SLinus Torvalds  *      modify it under the terms of the GNU General Public License
61da177e4SLinus Torvalds  *      as published by the Free Software Foundation; either version
71da177e4SLinus Torvalds  *      2 of the License, or (at your option) any later version.
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  *	Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
101da177e4SLinus Torvalds  */
111da177e4SLinus Torvalds 
124fc268d2SRandy Dunlap #include <linux/capability.h>
131da177e4SLinus Torvalds #include <linux/errno.h>
141da177e4SLinus Torvalds #include <linux/types.h>
151da177e4SLinus Torvalds #include <linux/socket.h>
161da177e4SLinus Torvalds #include <linux/net.h>
171da177e4SLinus Torvalds #include <linux/netdevice.h>
181da177e4SLinus Torvalds #include <linux/in6.h>
191da177e4SLinus Torvalds #include <linux/proc_fs.h>
201da177e4SLinus Torvalds #include <linux/seq_file.h>
215a0e3ad6STejun Heo #include <linux/slab.h>
22bc3b2d7fSPaul Gortmaker #include <linux/export.h>
234f82f457SEric W. Biederman #include <linux/pid_namespace.h>
241da177e4SLinus Torvalds 
25457c4cbcSEric W. Biederman #include <net/net_namespace.h>
261da177e4SLinus Torvalds #include <net/sock.h>
271da177e4SLinus Torvalds 
281da177e4SLinus Torvalds #include <net/ipv6.h>
291da177e4SLinus Torvalds #include <net/rawv6.h>
301da177e4SLinus Torvalds #include <net/transp_v6.h>
311da177e4SLinus Torvalds 
327c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
331da177e4SLinus Torvalds 
341da177e4SLinus Torvalds #define FL_MIN_LINGER	6	/* Minimal linger. It is set to 6sec specified
351da177e4SLinus Torvalds 				   in old IPv6 RFC. Well, it was reasonable value.
361da177e4SLinus Torvalds 				 */
3753b47106SFlorent Fourcot #define FL_MAX_LINGER	150	/* Maximal linger timeout */
381da177e4SLinus Torvalds 
391da177e4SLinus Torvalds /* FL hash table */
401da177e4SLinus Torvalds 
411da177e4SLinus Torvalds #define FL_MAX_PER_SOCK	32
421da177e4SLinus Torvalds #define FL_MAX_SIZE	4096
431da177e4SLinus Torvalds #define FL_HASH_MASK	255
441da177e4SLinus Torvalds #define FL_HASH(l)	(ntohl(l)&FL_HASH_MASK)
451da177e4SLinus Torvalds 
461da177e4SLinus Torvalds static atomic_t fl_size = ATOMIC_INIT(0);
47d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 static struct ip6_flowlabel __rcu *fl_ht[FL_HASH_MASK+1];
481da177e4SLinus Torvalds 
491da177e4SLinus Torvalds static void ip6_fl_gc(unsigned long dummy);
508d06afabSIngo Molnar static DEFINE_TIMER(ip6_fl_gc_timer, ip6_fl_gc, 0, 0);
511da177e4SLinus Torvalds 
521da177e4SLinus Torvalds /* FL hash table lock: it protects only of GC */
531da177e4SLinus Torvalds 
54d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 static DEFINE_SPINLOCK(ip6_fl_lock);
551da177e4SLinus Torvalds 
561da177e4SLinus Torvalds /* Big socket sock */
571da177e4SLinus Torvalds 
5818367681SYOSHIFUJI Hideaki / 吉藤英明 static DEFINE_SPINLOCK(ip6_sk_fl_lock);
591da177e4SLinus Torvalds 
60d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 #define for_each_fl_rcu(hash, fl)				\
616a98dcf0SAmerigo Wang 	for (fl = rcu_dereference_bh(fl_ht[(hash)]);		\
62d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	     fl != NULL;					\
636a98dcf0SAmerigo Wang 	     fl = rcu_dereference_bh(fl->next))
64d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 #define for_each_fl_continue_rcu(fl)				\
656a98dcf0SAmerigo Wang 	for (fl = rcu_dereference_bh(fl->next);			\
66d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	     fl != NULL;					\
676a98dcf0SAmerigo Wang 	     fl = rcu_dereference_bh(fl->next))
681da177e4SLinus Torvalds 
6918367681SYOSHIFUJI Hideaki / 吉藤英明 #define for_each_sk_fl_rcu(np, sfl)				\
7018367681SYOSHIFUJI Hideaki / 吉藤英明 	for (sfl = rcu_dereference_bh(np->ipv6_fl_list);	\
7118367681SYOSHIFUJI Hideaki / 吉藤英明 	     sfl != NULL;					\
7218367681SYOSHIFUJI Hideaki / 吉藤英明 	     sfl = rcu_dereference_bh(sfl->next))
7318367681SYOSHIFUJI Hideaki / 吉藤英明 
7460e8fbc4SBenjamin Thery static inline struct ip6_flowlabel *__fl_lookup(struct net *net, __be32 label)
751da177e4SLinus Torvalds {
761da177e4SLinus Torvalds 	struct ip6_flowlabel *fl;
771da177e4SLinus Torvalds 
78d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	for_each_fl_rcu(FL_HASH(label), fl) {
7909ad9bc7SOctavian Purdila 		if (fl->label == label && net_eq(fl->fl_net, net))
801da177e4SLinus Torvalds 			return fl;
811da177e4SLinus Torvalds 	}
821da177e4SLinus Torvalds 	return NULL;
831da177e4SLinus Torvalds }
841da177e4SLinus Torvalds 
8560e8fbc4SBenjamin Thery static struct ip6_flowlabel *fl_lookup(struct net *net, __be32 label)
861da177e4SLinus Torvalds {
871da177e4SLinus Torvalds 	struct ip6_flowlabel *fl;
881da177e4SLinus Torvalds 
89d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	rcu_read_lock_bh();
9060e8fbc4SBenjamin Thery 	fl = __fl_lookup(net, label);
91d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	if (fl && !atomic_inc_not_zero(&fl->users))
92d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		fl = NULL;
93d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	rcu_read_unlock_bh();
941da177e4SLinus Torvalds 	return fl;
951da177e4SLinus Torvalds }
961da177e4SLinus Torvalds 
971da177e4SLinus Torvalds 
981da177e4SLinus Torvalds static void fl_free(struct ip6_flowlabel *fl)
991da177e4SLinus Torvalds {
10060e8fbc4SBenjamin Thery 	if (fl) {
101898132aeSDan Carpenter 		if (fl->share == IPV6_FL_S_PROCESS)
102898132aeSDan Carpenter 			put_pid(fl->owner.pid);
1031da177e4SLinus Torvalds 		kfree(fl->opt);
104d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		kfree_rcu(fl, rcu);
10560e8fbc4SBenjamin Thery 	}
1061da177e4SLinus Torvalds }
1071da177e4SLinus Torvalds 
1081da177e4SLinus Torvalds static void fl_release(struct ip6_flowlabel *fl)
1091da177e4SLinus Torvalds {
110d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	spin_lock_bh(&ip6_fl_lock);
1111da177e4SLinus Torvalds 
1121da177e4SLinus Torvalds 	fl->lastuse = jiffies;
1131da177e4SLinus Torvalds 	if (atomic_dec_and_test(&fl->users)) {
1141da177e4SLinus Torvalds 		unsigned long ttd = fl->lastuse + fl->linger;
1151da177e4SLinus Torvalds 		if (time_after(ttd, fl->expires))
1161da177e4SLinus Torvalds 			fl->expires = ttd;
1171da177e4SLinus Torvalds 		ttd = fl->expires;
1181da177e4SLinus Torvalds 		if (fl->opt && fl->share == IPV6_FL_S_EXCL) {
1191da177e4SLinus Torvalds 			struct ipv6_txoptions *opt = fl->opt;
1201da177e4SLinus Torvalds 			fl->opt = NULL;
1211da177e4SLinus Torvalds 			kfree(opt);
1221da177e4SLinus Torvalds 		}
1231da177e4SLinus Torvalds 		if (!timer_pending(&ip6_fl_gc_timer) ||
1241da177e4SLinus Torvalds 		    time_after(ip6_fl_gc_timer.expires, ttd))
1251da177e4SLinus Torvalds 			mod_timer(&ip6_fl_gc_timer, ttd);
1261da177e4SLinus Torvalds 	}
127d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	spin_unlock_bh(&ip6_fl_lock);
1281da177e4SLinus Torvalds }
1291da177e4SLinus Torvalds 
1301da177e4SLinus Torvalds static void ip6_fl_gc(unsigned long dummy)
1311da177e4SLinus Torvalds {
1321da177e4SLinus Torvalds 	int i;
1331da177e4SLinus Torvalds 	unsigned long now = jiffies;
1341da177e4SLinus Torvalds 	unsigned long sched = 0;
1351da177e4SLinus Torvalds 
136d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	spin_lock(&ip6_fl_lock);
1371da177e4SLinus Torvalds 
1381da177e4SLinus Torvalds 	for (i = 0; i <= FL_HASH_MASK; i++) {
1397f0e44acSEric Dumazet 		struct ip6_flowlabel *fl;
1407f0e44acSEric Dumazet 		struct ip6_flowlabel __rcu **flp;
1417f0e44acSEric Dumazet 
1421da177e4SLinus Torvalds 		flp = &fl_ht[i];
143d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		while ((fl = rcu_dereference_protected(*flp,
144d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 						       lockdep_is_held(&ip6_fl_lock))) != NULL) {
1451da177e4SLinus Torvalds 			if (atomic_read(&fl->users) == 0) {
1461da177e4SLinus Torvalds 				unsigned long ttd = fl->lastuse + fl->linger;
1471da177e4SLinus Torvalds 				if (time_after(ttd, fl->expires))
1481da177e4SLinus Torvalds 					fl->expires = ttd;
1491da177e4SLinus Torvalds 				ttd = fl->expires;
1501da177e4SLinus Torvalds 				if (time_after_eq(now, ttd)) {
1511da177e4SLinus Torvalds 					*flp = fl->next;
1521da177e4SLinus Torvalds 					fl_free(fl);
1531da177e4SLinus Torvalds 					atomic_dec(&fl_size);
1541da177e4SLinus Torvalds 					continue;
1551da177e4SLinus Torvalds 				}
1561da177e4SLinus Torvalds 				if (!sched || time_before(ttd, sched))
1571da177e4SLinus Torvalds 					sched = ttd;
1581da177e4SLinus Torvalds 			}
1591da177e4SLinus Torvalds 			flp = &fl->next;
1601da177e4SLinus Torvalds 		}
1611da177e4SLinus Torvalds 	}
1621da177e4SLinus Torvalds 	if (!sched && atomic_read(&fl_size))
1631da177e4SLinus Torvalds 		sched = now + FL_MAX_LINGER;
1641da177e4SLinus Torvalds 	if (sched) {
16560e8fbc4SBenjamin Thery 		mod_timer(&ip6_fl_gc_timer, sched);
1661da177e4SLinus Torvalds 	}
167d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	spin_unlock(&ip6_fl_lock);
1681da177e4SLinus Torvalds }
1691da177e4SLinus Torvalds 
1702c8c1e72SAlexey Dobriyan static void __net_exit ip6_fl_purge(struct net *net)
17160e8fbc4SBenjamin Thery {
17260e8fbc4SBenjamin Thery 	int i;
17360e8fbc4SBenjamin Thery 
1744762fb98SJan Stancek 	spin_lock_bh(&ip6_fl_lock);
17560e8fbc4SBenjamin Thery 	for (i = 0; i <= FL_HASH_MASK; i++) {
1767f0e44acSEric Dumazet 		struct ip6_flowlabel *fl;
1777f0e44acSEric Dumazet 		struct ip6_flowlabel __rcu **flp;
1787f0e44acSEric Dumazet 
17960e8fbc4SBenjamin Thery 		flp = &fl_ht[i];
180d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		while ((fl = rcu_dereference_protected(*flp,
181d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 						       lockdep_is_held(&ip6_fl_lock))) != NULL) {
18209ad9bc7SOctavian Purdila 			if (net_eq(fl->fl_net, net) &&
18309ad9bc7SOctavian Purdila 			    atomic_read(&fl->users) == 0) {
18460e8fbc4SBenjamin Thery 				*flp = fl->next;
18560e8fbc4SBenjamin Thery 				fl_free(fl);
18660e8fbc4SBenjamin Thery 				atomic_dec(&fl_size);
18760e8fbc4SBenjamin Thery 				continue;
18860e8fbc4SBenjamin Thery 			}
18960e8fbc4SBenjamin Thery 			flp = &fl->next;
19060e8fbc4SBenjamin Thery 		}
19160e8fbc4SBenjamin Thery 	}
1924762fb98SJan Stancek 	spin_unlock_bh(&ip6_fl_lock);
19360e8fbc4SBenjamin Thery }
19460e8fbc4SBenjamin Thery 
19560e8fbc4SBenjamin Thery static struct ip6_flowlabel *fl_intern(struct net *net,
19660e8fbc4SBenjamin Thery 				       struct ip6_flowlabel *fl, __be32 label)
1971da177e4SLinus Torvalds {
19878c2e502SPavel Emelyanov 	struct ip6_flowlabel *lfl;
19978c2e502SPavel Emelyanov 
2001da177e4SLinus Torvalds 	fl->label = label & IPV6_FLOWLABEL_MASK;
2011da177e4SLinus Torvalds 
202d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	spin_lock_bh(&ip6_fl_lock);
2031da177e4SLinus Torvalds 	if (label == 0) {
2041da177e4SLinus Torvalds 		for (;;) {
20563862b5bSAruna-Hewapathirane 			fl->label = htonl(prandom_u32())&IPV6_FLOWLABEL_MASK;
2061da177e4SLinus Torvalds 			if (fl->label) {
20760e8fbc4SBenjamin Thery 				lfl = __fl_lookup(net, fl->label);
20863159f29SIan Morris 				if (!lfl)
2091da177e4SLinus Torvalds 					break;
2101da177e4SLinus Torvalds 			}
2111da177e4SLinus Torvalds 		}
21278c2e502SPavel Emelyanov 	} else {
21378c2e502SPavel Emelyanov 		/*
21478c2e502SPavel Emelyanov 		 * we dropper the ip6_fl_lock, so this entry could reappear
21578c2e502SPavel Emelyanov 		 * and we need to recheck with it.
21678c2e502SPavel Emelyanov 		 *
21778c2e502SPavel Emelyanov 		 * OTOH no need to search the active socket first, like it is
21878c2e502SPavel Emelyanov 		 * done in ipv6_flowlabel_opt - sock is locked, so new entry
21978c2e502SPavel Emelyanov 		 * with the same label can only appear on another sock
22078c2e502SPavel Emelyanov 		 */
22160e8fbc4SBenjamin Thery 		lfl = __fl_lookup(net, fl->label);
22253b24b8fSIan Morris 		if (lfl) {
22378c2e502SPavel Emelyanov 			atomic_inc(&lfl->users);
224d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 			spin_unlock_bh(&ip6_fl_lock);
22578c2e502SPavel Emelyanov 			return lfl;
22678c2e502SPavel Emelyanov 		}
2271da177e4SLinus Torvalds 	}
2281da177e4SLinus Torvalds 
2291da177e4SLinus Torvalds 	fl->lastuse = jiffies;
2301da177e4SLinus Torvalds 	fl->next = fl_ht[FL_HASH(fl->label)];
231d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	rcu_assign_pointer(fl_ht[FL_HASH(fl->label)], fl);
2321da177e4SLinus Torvalds 	atomic_inc(&fl_size);
233d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	spin_unlock_bh(&ip6_fl_lock);
23478c2e502SPavel Emelyanov 	return NULL;
2351da177e4SLinus Torvalds }
2361da177e4SLinus Torvalds 
2371da177e4SLinus Torvalds 
2381da177e4SLinus Torvalds 
2391da177e4SLinus Torvalds /* Socket flowlabel lists */
2401da177e4SLinus Torvalds 
24190bcaf7bSAl Viro struct ip6_flowlabel *fl6_sock_lookup(struct sock *sk, __be32 label)
2421da177e4SLinus Torvalds {
2431da177e4SLinus Torvalds 	struct ipv6_fl_socklist *sfl;
2441da177e4SLinus Torvalds 	struct ipv6_pinfo *np = inet6_sk(sk);
2451da177e4SLinus Torvalds 
2461da177e4SLinus Torvalds 	label &= IPV6_FLOWLABEL_MASK;
2471da177e4SLinus Torvalds 
24818367681SYOSHIFUJI Hideaki / 吉藤英明 	rcu_read_lock_bh();
24918367681SYOSHIFUJI Hideaki / 吉藤英明 	for_each_sk_fl_rcu(np, sfl) {
2501da177e4SLinus Torvalds 		struct ip6_flowlabel *fl = sfl->fl;
2511da177e4SLinus Torvalds 		if (fl->label == label) {
2521da177e4SLinus Torvalds 			fl->lastuse = jiffies;
2531da177e4SLinus Torvalds 			atomic_inc(&fl->users);
25418367681SYOSHIFUJI Hideaki / 吉藤英明 			rcu_read_unlock_bh();
2551da177e4SLinus Torvalds 			return fl;
2561da177e4SLinus Torvalds 		}
2571da177e4SLinus Torvalds 	}
25818367681SYOSHIFUJI Hideaki / 吉藤英明 	rcu_read_unlock_bh();
2591da177e4SLinus Torvalds 	return NULL;
2601da177e4SLinus Torvalds }
2613cf3dc6cSArnaldo Carvalho de Melo EXPORT_SYMBOL_GPL(fl6_sock_lookup);
2623cf3dc6cSArnaldo Carvalho de Melo 
2631da177e4SLinus Torvalds void fl6_free_socklist(struct sock *sk)
2641da177e4SLinus Torvalds {
2651da177e4SLinus Torvalds 	struct ipv6_pinfo *np = inet6_sk(sk);
2661da177e4SLinus Torvalds 	struct ipv6_fl_socklist *sfl;
2671da177e4SLinus Torvalds 
26818367681SYOSHIFUJI Hideaki / 吉藤英明 	if (!rcu_access_pointer(np->ipv6_fl_list))
269f256dc59SYOSHIFUJI Hideaki / 吉藤英明 		return;
270f256dc59SYOSHIFUJI Hideaki / 吉藤英明 
27118367681SYOSHIFUJI Hideaki / 吉藤英明 	spin_lock_bh(&ip6_sk_fl_lock);
27218367681SYOSHIFUJI Hideaki / 吉藤英明 	while ((sfl = rcu_dereference_protected(np->ipv6_fl_list,
27318367681SYOSHIFUJI Hideaki / 吉藤英明 						lockdep_is_held(&ip6_sk_fl_lock))) != NULL) {
27418367681SYOSHIFUJI Hideaki / 吉藤英明 		np->ipv6_fl_list = sfl->next;
27518367681SYOSHIFUJI Hideaki / 吉藤英明 		spin_unlock_bh(&ip6_sk_fl_lock);
276f256dc59SYOSHIFUJI Hideaki / 吉藤英明 
2771da177e4SLinus Torvalds 		fl_release(sfl->fl);
27818367681SYOSHIFUJI Hideaki / 吉藤英明 		kfree_rcu(sfl, rcu);
27918367681SYOSHIFUJI Hideaki / 吉藤英明 
28018367681SYOSHIFUJI Hideaki / 吉藤英明 		spin_lock_bh(&ip6_sk_fl_lock);
2811da177e4SLinus Torvalds 	}
28218367681SYOSHIFUJI Hideaki / 吉藤英明 	spin_unlock_bh(&ip6_sk_fl_lock);
2831da177e4SLinus Torvalds }
2841da177e4SLinus Torvalds 
2851da177e4SLinus Torvalds /* Service routines */
2861da177e4SLinus Torvalds 
2871da177e4SLinus Torvalds 
2881da177e4SLinus Torvalds /*
2891da177e4SLinus Torvalds    It is the only difficult place. flowlabel enforces equal headers
2901da177e4SLinus Torvalds    before and including routing header, however user may supply options
2911da177e4SLinus Torvalds    following rthdr.
2921da177e4SLinus Torvalds  */
2931da177e4SLinus Torvalds 
2941da177e4SLinus Torvalds struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions *opt_space,
2951da177e4SLinus Torvalds 					 struct ip6_flowlabel *fl,
2961da177e4SLinus Torvalds 					 struct ipv6_txoptions *fopt)
2971da177e4SLinus Torvalds {
298df9890c3SYOSHIFUJI Hideaki 	struct ipv6_txoptions *fl_opt = fl->opt;
2991da177e4SLinus Torvalds 
30063159f29SIan Morris 	if (!fopt || fopt->opt_flen == 0)
3011da177e4SLinus Torvalds 		return fl_opt;
3021da177e4SLinus Torvalds 
30353b24b8fSIan Morris 	if (fl_opt) {
3041da177e4SLinus Torvalds 		opt_space->hopopt = fl_opt->hopopt;
305df9890c3SYOSHIFUJI Hideaki 		opt_space->dst0opt = fl_opt->dst0opt;
3061da177e4SLinus Torvalds 		opt_space->srcrt = fl_opt->srcrt;
3071da177e4SLinus Torvalds 		opt_space->opt_nflen = fl_opt->opt_nflen;
3081da177e4SLinus Torvalds 	} else {
3091da177e4SLinus Torvalds 		if (fopt->opt_nflen == 0)
3101da177e4SLinus Torvalds 			return fopt;
3111da177e4SLinus Torvalds 		opt_space->hopopt = NULL;
3121da177e4SLinus Torvalds 		opt_space->dst0opt = NULL;
3131da177e4SLinus Torvalds 		opt_space->srcrt = NULL;
3141da177e4SLinus Torvalds 		opt_space->opt_nflen = 0;
3151da177e4SLinus Torvalds 	}
3161da177e4SLinus Torvalds 	opt_space->dst1opt = fopt->dst1opt;
3171da177e4SLinus Torvalds 	opt_space->opt_flen = fopt->opt_flen;
318864e2a1fSEric Dumazet 	opt_space->tot_len = fopt->tot_len;
3191da177e4SLinus Torvalds 	return opt_space;
3201da177e4SLinus Torvalds }
321a495f836SChris Elston EXPORT_SYMBOL_GPL(fl6_merge_options);
3221da177e4SLinus Torvalds 
3231da177e4SLinus Torvalds static unsigned long check_linger(unsigned long ttl)
3241da177e4SLinus Torvalds {
3251da177e4SLinus Torvalds 	if (ttl < FL_MIN_LINGER)
3261da177e4SLinus Torvalds 		return FL_MIN_LINGER*HZ;
3271da177e4SLinus Torvalds 	if (ttl > FL_MAX_LINGER && !capable(CAP_NET_ADMIN))
3281da177e4SLinus Torvalds 		return 0;
3291da177e4SLinus Torvalds 	return ttl*HZ;
3301da177e4SLinus Torvalds }
3311da177e4SLinus Torvalds 
3321da177e4SLinus Torvalds static int fl6_renew(struct ip6_flowlabel *fl, unsigned long linger, unsigned long expires)
3331da177e4SLinus Torvalds {
3341da177e4SLinus Torvalds 	linger = check_linger(linger);
3351da177e4SLinus Torvalds 	if (!linger)
3361da177e4SLinus Torvalds 		return -EPERM;
3371da177e4SLinus Torvalds 	expires = check_linger(expires);
3381da177e4SLinus Torvalds 	if (!expires)
3391da177e4SLinus Torvalds 		return -EPERM;
340394055f6SFlorent Fourcot 
341394055f6SFlorent Fourcot 	spin_lock_bh(&ip6_fl_lock);
3421da177e4SLinus Torvalds 	fl->lastuse = jiffies;
3431da177e4SLinus Torvalds 	if (time_before(fl->linger, linger))
3441da177e4SLinus Torvalds 		fl->linger = linger;
3451da177e4SLinus Torvalds 	if (time_before(expires, fl->linger))
3461da177e4SLinus Torvalds 		expires = fl->linger;
3471da177e4SLinus Torvalds 	if (time_before(fl->expires, fl->lastuse + expires))
3481da177e4SLinus Torvalds 		fl->expires = fl->lastuse + expires;
349394055f6SFlorent Fourcot 	spin_unlock_bh(&ip6_fl_lock);
350394055f6SFlorent Fourcot 
3511da177e4SLinus Torvalds 	return 0;
3521da177e4SLinus Torvalds }
3531da177e4SLinus Torvalds 
3541da177e4SLinus Torvalds static struct ip6_flowlabel *
355ec0506dbSMaciej Żenczykowski fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq,
356ec0506dbSMaciej Żenczykowski 	  char __user *optval, int optlen, int *err_p)
3571da177e4SLinus Torvalds {
358684de409SDavid S. Miller 	struct ip6_flowlabel *fl = NULL;
3591da177e4SLinus Torvalds 	int olen;
3601da177e4SLinus Torvalds 	int addr_type;
3611da177e4SLinus Torvalds 	int err;
3621da177e4SLinus Torvalds 
363684de409SDavid S. Miller 	olen = optlen - CMSG_ALIGN(sizeof(*freq));
364684de409SDavid S. Miller 	err = -EINVAL;
365684de409SDavid S. Miller 	if (olen > 64 * 1024)
366684de409SDavid S. Miller 		goto done;
367684de409SDavid S. Miller 
3681da177e4SLinus Torvalds 	err = -ENOMEM;
3690c600edaSIngo Oeser 	fl = kzalloc(sizeof(*fl), GFP_KERNEL);
37063159f29SIan Morris 	if (!fl)
3711da177e4SLinus Torvalds 		goto done;
3721da177e4SLinus Torvalds 
3731da177e4SLinus Torvalds 	if (olen > 0) {
3741da177e4SLinus Torvalds 		struct msghdr msg;
3754c9483b2SDavid S. Miller 		struct flowi6 flowi6;
376ad1e46a8SSoheil Hassas Yeganeh 		struct sockcm_cookie sockc_junk;
37726879da5SWei Wang 		struct ipcm6_cookie ipc6;
3781da177e4SLinus Torvalds 
3791da177e4SLinus Torvalds 		err = -ENOMEM;
3801da177e4SLinus Torvalds 		fl->opt = kmalloc(sizeof(*fl->opt) + olen, GFP_KERNEL);
38163159f29SIan Morris 		if (!fl->opt)
3821da177e4SLinus Torvalds 			goto done;
3831da177e4SLinus Torvalds 
3841da177e4SLinus Torvalds 		memset(fl->opt, 0, sizeof(*fl->opt));
3851da177e4SLinus Torvalds 		fl->opt->tot_len = sizeof(*fl->opt) + olen;
3861da177e4SLinus Torvalds 		err = -EFAULT;
3871da177e4SLinus Torvalds 		if (copy_from_user(fl->opt+1, optval+CMSG_ALIGN(sizeof(*freq)), olen))
3881da177e4SLinus Torvalds 			goto done;
3891da177e4SLinus Torvalds 
3901da177e4SLinus Torvalds 		msg.msg_controllen = olen;
3911da177e4SLinus Torvalds 		msg.msg_control = (void *)(fl->opt+1);
3924c9483b2SDavid S. Miller 		memset(&flowi6, 0, sizeof(flowi6));
3931da177e4SLinus Torvalds 
39426879da5SWei Wang 		ipc6.opt = fl->opt;
39526879da5SWei Wang 		err = ip6_datagram_send_ctl(net, sk, &msg, &flowi6, &ipc6, &sockc_junk);
3961da177e4SLinus Torvalds 		if (err)
3971da177e4SLinus Torvalds 			goto done;
3981da177e4SLinus Torvalds 		err = -EINVAL;
3991da177e4SLinus Torvalds 		if (fl->opt->opt_flen)
4001da177e4SLinus Torvalds 			goto done;
4011da177e4SLinus Torvalds 		if (fl->opt->opt_nflen == 0) {
4021da177e4SLinus Torvalds 			kfree(fl->opt);
4031da177e4SLinus Torvalds 			fl->opt = NULL;
4041da177e4SLinus Torvalds 		}
4051da177e4SLinus Torvalds 	}
4061da177e4SLinus Torvalds 
407efd7ef1cSEric W. Biederman 	fl->fl_net = net;
4081da177e4SLinus Torvalds 	fl->expires = jiffies;
4091da177e4SLinus Torvalds 	err = fl6_renew(fl, freq->flr_linger, freq->flr_expires);
4101da177e4SLinus Torvalds 	if (err)
4111da177e4SLinus Torvalds 		goto done;
4121da177e4SLinus Torvalds 	fl->share = freq->flr_share;
4131da177e4SLinus Torvalds 	addr_type = ipv6_addr_type(&freq->flr_dst);
41435700212SJoe Perches 	if ((addr_type & IPV6_ADDR_MAPPED) ||
41535700212SJoe Perches 	    addr_type == IPV6_ADDR_ANY) {
416c6817e4cSJames Morris 		err = -EINVAL;
4171da177e4SLinus Torvalds 		goto done;
418c6817e4cSJames Morris 	}
4194e3fd7a0SAlexey Dobriyan 	fl->dst = freq->flr_dst;
4201da177e4SLinus Torvalds 	atomic_set(&fl->users, 1);
4211da177e4SLinus Torvalds 	switch (fl->share) {
4221da177e4SLinus Torvalds 	case IPV6_FL_S_EXCL:
4231da177e4SLinus Torvalds 	case IPV6_FL_S_ANY:
4241da177e4SLinus Torvalds 		break;
4251da177e4SLinus Torvalds 	case IPV6_FL_S_PROCESS:
4264f82f457SEric W. Biederman 		fl->owner.pid = get_task_pid(current, PIDTYPE_PID);
4271da177e4SLinus Torvalds 		break;
4281da177e4SLinus Torvalds 	case IPV6_FL_S_USER:
4294f82f457SEric W. Biederman 		fl->owner.uid = current_euid();
4301da177e4SLinus Torvalds 		break;
4311da177e4SLinus Torvalds 	default:
4321da177e4SLinus Torvalds 		err = -EINVAL;
4331da177e4SLinus Torvalds 		goto done;
4341da177e4SLinus Torvalds 	}
4351da177e4SLinus Torvalds 	return fl;
4361da177e4SLinus Torvalds 
4371da177e4SLinus Torvalds done:
4381da177e4SLinus Torvalds 	fl_free(fl);
4391da177e4SLinus Torvalds 	*err_p = err;
4401da177e4SLinus Torvalds 	return NULL;
4411da177e4SLinus Torvalds }
4421da177e4SLinus Torvalds 
4431da177e4SLinus Torvalds static int mem_check(struct sock *sk)
4441da177e4SLinus Torvalds {
4451da177e4SLinus Torvalds 	struct ipv6_pinfo *np = inet6_sk(sk);
4461da177e4SLinus Torvalds 	struct ipv6_fl_socklist *sfl;
4471da177e4SLinus Torvalds 	int room = FL_MAX_SIZE - atomic_read(&fl_size);
4481da177e4SLinus Torvalds 	int count = 0;
4491da177e4SLinus Torvalds 
4501da177e4SLinus Torvalds 	if (room > FL_MAX_SIZE - FL_MAX_PER_SOCK)
4511da177e4SLinus Torvalds 		return 0;
4521da177e4SLinus Torvalds 
453f8c31c8fSHannes Frederic Sowa 	rcu_read_lock_bh();
45418367681SYOSHIFUJI Hideaki / 吉藤英明 	for_each_sk_fl_rcu(np, sfl)
4551da177e4SLinus Torvalds 		count++;
456f8c31c8fSHannes Frederic Sowa 	rcu_read_unlock_bh();
4571da177e4SLinus Torvalds 
4581da177e4SLinus Torvalds 	if (room <= 0 ||
4591da177e4SLinus Torvalds 	    ((count >= FL_MAX_PER_SOCK ||
46035700212SJoe Perches 	      (count > 0 && room < FL_MAX_SIZE/2) || room < FL_MAX_SIZE/4) &&
46135700212SJoe Perches 	     !capable(CAP_NET_ADMIN)))
4621da177e4SLinus Torvalds 		return -ENOBUFS;
4631da177e4SLinus Torvalds 
4641da177e4SLinus Torvalds 	return 0;
4651da177e4SLinus Torvalds }
4661da177e4SLinus Torvalds 
46704028045SPavel Emelyanov static inline void fl_link(struct ipv6_pinfo *np, struct ipv6_fl_socklist *sfl,
46804028045SPavel Emelyanov 		struct ip6_flowlabel *fl)
46904028045SPavel Emelyanov {
47018367681SYOSHIFUJI Hideaki / 吉藤英明 	spin_lock_bh(&ip6_sk_fl_lock);
47104028045SPavel Emelyanov 	sfl->fl = fl;
47204028045SPavel Emelyanov 	sfl->next = np->ipv6_fl_list;
47318367681SYOSHIFUJI Hideaki / 吉藤英明 	rcu_assign_pointer(np->ipv6_fl_list, sfl);
47418367681SYOSHIFUJI Hideaki / 吉藤英明 	spin_unlock_bh(&ip6_sk_fl_lock);
47504028045SPavel Emelyanov }
47604028045SPavel Emelyanov 
47746e5f401SFlorent Fourcot int ipv6_flowlabel_opt_get(struct sock *sk, struct in6_flowlabel_req *freq,
47846e5f401SFlorent Fourcot 			   int flags)
4793fdfa5ffSFlorent Fourcot {
4803fdfa5ffSFlorent Fourcot 	struct ipv6_pinfo *np = inet6_sk(sk);
4813fdfa5ffSFlorent Fourcot 	struct ipv6_fl_socklist *sfl;
4823fdfa5ffSFlorent Fourcot 
48346e5f401SFlorent Fourcot 	if (flags & IPV6_FL_F_REMOTE) {
48446e5f401SFlorent Fourcot 		freq->flr_label = np->rcv_flowinfo & IPV6_FLOWLABEL_MASK;
48546e5f401SFlorent Fourcot 		return 0;
48646e5f401SFlorent Fourcot 	}
48746e5f401SFlorent Fourcot 
488df3687ffSFlorent Fourcot 	if (np->repflow) {
489df3687ffSFlorent Fourcot 		freq->flr_label = np->flow_label;
490df3687ffSFlorent Fourcot 		return 0;
491df3687ffSFlorent Fourcot 	}
492df3687ffSFlorent Fourcot 
4933fdfa5ffSFlorent Fourcot 	rcu_read_lock_bh();
4943fdfa5ffSFlorent Fourcot 
4953fdfa5ffSFlorent Fourcot 	for_each_sk_fl_rcu(np, sfl) {
4963fdfa5ffSFlorent Fourcot 		if (sfl->fl->label == (np->flow_label & IPV6_FLOWLABEL_MASK)) {
4973fdfa5ffSFlorent Fourcot 			spin_lock_bh(&ip6_fl_lock);
4983fdfa5ffSFlorent Fourcot 			freq->flr_label = sfl->fl->label;
4993fdfa5ffSFlorent Fourcot 			freq->flr_dst = sfl->fl->dst;
5003fdfa5ffSFlorent Fourcot 			freq->flr_share = sfl->fl->share;
5013fdfa5ffSFlorent Fourcot 			freq->flr_expires = (sfl->fl->expires - jiffies) / HZ;
5023fdfa5ffSFlorent Fourcot 			freq->flr_linger = sfl->fl->linger / HZ;
5033fdfa5ffSFlorent Fourcot 
5043fdfa5ffSFlorent Fourcot 			spin_unlock_bh(&ip6_fl_lock);
5053fdfa5ffSFlorent Fourcot 			rcu_read_unlock_bh();
5063fdfa5ffSFlorent Fourcot 			return 0;
5073fdfa5ffSFlorent Fourcot 		}
5083fdfa5ffSFlorent Fourcot 	}
5093fdfa5ffSFlorent Fourcot 	rcu_read_unlock_bh();
5103fdfa5ffSFlorent Fourcot 
5113fdfa5ffSFlorent Fourcot 	return -ENOENT;
5123fdfa5ffSFlorent Fourcot }
5133fdfa5ffSFlorent Fourcot 
5141da177e4SLinus Torvalds int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
5151da177e4SLinus Torvalds {
51655205d40SIngo Molnar 	int uninitialized_var(err);
51760e8fbc4SBenjamin Thery 	struct net *net = sock_net(sk);
5181da177e4SLinus Torvalds 	struct ipv6_pinfo *np = inet6_sk(sk);
5191da177e4SLinus Torvalds 	struct in6_flowlabel_req freq;
5201da177e4SLinus Torvalds 	struct ipv6_fl_socklist *sfl1 = NULL;
5217f0e44acSEric Dumazet 	struct ipv6_fl_socklist *sfl;
5227f0e44acSEric Dumazet 	struct ipv6_fl_socklist __rcu **sflp;
52378c2e502SPavel Emelyanov 	struct ip6_flowlabel *fl, *fl1 = NULL;
52478c2e502SPavel Emelyanov 
5251da177e4SLinus Torvalds 
5261da177e4SLinus Torvalds 	if (optlen < sizeof(freq))
5271da177e4SLinus Torvalds 		return -EINVAL;
5281da177e4SLinus Torvalds 
5291da177e4SLinus Torvalds 	if (copy_from_user(&freq, optval, sizeof(freq)))
5301da177e4SLinus Torvalds 		return -EFAULT;
5311da177e4SLinus Torvalds 
5321da177e4SLinus Torvalds 	switch (freq.flr_action) {
5331da177e4SLinus Torvalds 	case IPV6_FL_A_PUT:
534df3687ffSFlorent Fourcot 		if (freq.flr_flags & IPV6_FL_F_REFLECT) {
535df3687ffSFlorent Fourcot 			if (sk->sk_protocol != IPPROTO_TCP)
536df3687ffSFlorent Fourcot 				return -ENOPROTOOPT;
537df3687ffSFlorent Fourcot 			if (!np->repflow)
538df3687ffSFlorent Fourcot 				return -ESRCH;
539df3687ffSFlorent Fourcot 			np->flow_label = 0;
540df3687ffSFlorent Fourcot 			np->repflow = 0;
541df3687ffSFlorent Fourcot 			return 0;
542df3687ffSFlorent Fourcot 		}
54318367681SYOSHIFUJI Hideaki / 吉藤英明 		spin_lock_bh(&ip6_sk_fl_lock);
54418367681SYOSHIFUJI Hideaki / 吉藤英明 		for (sflp = &np->ipv6_fl_list;
54544c3d0c1SEric Dumazet 		     (sfl = rcu_dereference_protected(*sflp,
54644c3d0c1SEric Dumazet 						      lockdep_is_held(&ip6_sk_fl_lock))) != NULL;
54718367681SYOSHIFUJI Hideaki / 吉藤英明 		     sflp = &sfl->next) {
5481da177e4SLinus Torvalds 			if (sfl->fl->label == freq.flr_label) {
5491da177e4SLinus Torvalds 				if (freq.flr_label == (np->flow_label&IPV6_FLOWLABEL_MASK))
5501da177e4SLinus Torvalds 					np->flow_label &= ~IPV6_FLOWLABEL_MASK;
55144c3d0c1SEric Dumazet 				*sflp = sfl->next;
55218367681SYOSHIFUJI Hideaki / 吉藤英明 				spin_unlock_bh(&ip6_sk_fl_lock);
5531da177e4SLinus Torvalds 				fl_release(sfl->fl);
55418367681SYOSHIFUJI Hideaki / 吉藤英明 				kfree_rcu(sfl, rcu);
5551da177e4SLinus Torvalds 				return 0;
5561da177e4SLinus Torvalds 			}
5571da177e4SLinus Torvalds 		}
55818367681SYOSHIFUJI Hideaki / 吉藤英明 		spin_unlock_bh(&ip6_sk_fl_lock);
5591da177e4SLinus Torvalds 		return -ESRCH;
5601da177e4SLinus Torvalds 
5611da177e4SLinus Torvalds 	case IPV6_FL_A_RENEW:
56218367681SYOSHIFUJI Hideaki / 吉藤英明 		rcu_read_lock_bh();
56318367681SYOSHIFUJI Hideaki / 吉藤英明 		for_each_sk_fl_rcu(np, sfl) {
5641da177e4SLinus Torvalds 			if (sfl->fl->label == freq.flr_label) {
5651da177e4SLinus Torvalds 				err = fl6_renew(sfl->fl, freq.flr_linger, freq.flr_expires);
56618367681SYOSHIFUJI Hideaki / 吉藤英明 				rcu_read_unlock_bh();
5671da177e4SLinus Torvalds 				return err;
5681da177e4SLinus Torvalds 			}
5691da177e4SLinus Torvalds 		}
57018367681SYOSHIFUJI Hideaki / 吉藤英明 		rcu_read_unlock_bh();
5711da177e4SLinus Torvalds 
572af31f412SEric W. Biederman 		if (freq.flr_share == IPV6_FL_S_NONE &&
573af31f412SEric W. Biederman 		    ns_capable(net->user_ns, CAP_NET_ADMIN)) {
57460e8fbc4SBenjamin Thery 			fl = fl_lookup(net, freq.flr_label);
5751da177e4SLinus Torvalds 			if (fl) {
5761da177e4SLinus Torvalds 				err = fl6_renew(fl, freq.flr_linger, freq.flr_expires);
5771da177e4SLinus Torvalds 				fl_release(fl);
5781da177e4SLinus Torvalds 				return err;
5791da177e4SLinus Torvalds 			}
5801da177e4SLinus Torvalds 		}
5811da177e4SLinus Torvalds 		return -ESRCH;
5821da177e4SLinus Torvalds 
5831da177e4SLinus Torvalds 	case IPV6_FL_A_GET:
584df3687ffSFlorent Fourcot 		if (freq.flr_flags & IPV6_FL_F_REFLECT) {
5856444f72bSFlorent Fourcot 			struct net *net = sock_net(sk);
5866444f72bSFlorent Fourcot 			if (net->ipv6.sysctl.flowlabel_consistency) {
5876444f72bSFlorent Fourcot 				net_info_ratelimited("Can not set IPV6_FL_F_REFLECT if flowlabel_consistency sysctl is enable\n");
5886444f72bSFlorent Fourcot 				return -EPERM;
5896444f72bSFlorent Fourcot 			}
5906444f72bSFlorent Fourcot 
591df3687ffSFlorent Fourcot 			if (sk->sk_protocol != IPPROTO_TCP)
592df3687ffSFlorent Fourcot 				return -ENOPROTOOPT;
5936444f72bSFlorent Fourcot 
594df3687ffSFlorent Fourcot 			np->repflow = 1;
595df3687ffSFlorent Fourcot 			return 0;
596df3687ffSFlorent Fourcot 		}
597df3687ffSFlorent Fourcot 
5981da177e4SLinus Torvalds 		if (freq.flr_label & ~IPV6_FLOWLABEL_MASK)
5991da177e4SLinus Torvalds 			return -EINVAL;
6001da177e4SLinus Torvalds 
60182a584b7STom Herbert 		if (net->ipv6.sysctl.flowlabel_state_ranges &&
60282a584b7STom Herbert 		    (freq.flr_label & IPV6_FLOWLABEL_STATELESS_FLAG))
60382a584b7STom Herbert 			return -ERANGE;
60482a584b7STom Herbert 
605ec0506dbSMaciej Żenczykowski 		fl = fl_create(net, sk, &freq, optval, optlen, &err);
60663159f29SIan Morris 		if (!fl)
6071da177e4SLinus Torvalds 			return err;
6081da177e4SLinus Torvalds 		sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);
6091da177e4SLinus Torvalds 
6101da177e4SLinus Torvalds 		if (freq.flr_label) {
6111da177e4SLinus Torvalds 			err = -EEXIST;
61218367681SYOSHIFUJI Hideaki / 吉藤英明 			rcu_read_lock_bh();
61318367681SYOSHIFUJI Hideaki / 吉藤英明 			for_each_sk_fl_rcu(np, sfl) {
6141da177e4SLinus Torvalds 				if (sfl->fl->label == freq.flr_label) {
6151da177e4SLinus Torvalds 					if (freq.flr_flags&IPV6_FL_F_EXCL) {
61618367681SYOSHIFUJI Hideaki / 吉藤英明 						rcu_read_unlock_bh();
6171da177e4SLinus Torvalds 						goto done;
6181da177e4SLinus Torvalds 					}
6191da177e4SLinus Torvalds 					fl1 = sfl->fl;
6204ea6a804SYan Zheng 					atomic_inc(&fl1->users);
6211da177e4SLinus Torvalds 					break;
6221da177e4SLinus Torvalds 				}
6231da177e4SLinus Torvalds 			}
62418367681SYOSHIFUJI Hideaki / 吉藤英明 			rcu_read_unlock_bh();
6251da177e4SLinus Torvalds 
62663159f29SIan Morris 			if (!fl1)
62760e8fbc4SBenjamin Thery 				fl1 = fl_lookup(net, freq.flr_label);
6281da177e4SLinus Torvalds 			if (fl1) {
62978c2e502SPavel Emelyanov recheck:
6301da177e4SLinus Torvalds 				err = -EEXIST;
6311da177e4SLinus Torvalds 				if (freq.flr_flags&IPV6_FL_F_EXCL)
6321da177e4SLinus Torvalds 					goto release;
6331da177e4SLinus Torvalds 				err = -EPERM;
6341da177e4SLinus Torvalds 				if (fl1->share == IPV6_FL_S_EXCL ||
6351da177e4SLinus Torvalds 				    fl1->share != fl->share ||
6364f82f457SEric W. Biederman 				    ((fl1->share == IPV6_FL_S_PROCESS) &&
6374f82f457SEric W. Biederman 				     (fl1->owner.pid == fl->owner.pid)) ||
6384f82f457SEric W. Biederman 				    ((fl1->share == IPV6_FL_S_USER) &&
6394f82f457SEric W. Biederman 				     uid_eq(fl1->owner.uid, fl->owner.uid)))
6401da177e4SLinus Torvalds 					goto release;
6411da177e4SLinus Torvalds 
6421da177e4SLinus Torvalds 				err = -ENOMEM;
64363159f29SIan Morris 				if (!sfl1)
6441da177e4SLinus Torvalds 					goto release;
6451da177e4SLinus Torvalds 				if (fl->linger > fl1->linger)
6461da177e4SLinus Torvalds 					fl1->linger = fl->linger;
6471da177e4SLinus Torvalds 				if ((long)(fl->expires - fl1->expires) > 0)
6481da177e4SLinus Torvalds 					fl1->expires = fl->expires;
64904028045SPavel Emelyanov 				fl_link(np, sfl1, fl1);
6501da177e4SLinus Torvalds 				fl_free(fl);
6511da177e4SLinus Torvalds 				return 0;
6521da177e4SLinus Torvalds 
6531da177e4SLinus Torvalds release:
6541da177e4SLinus Torvalds 				fl_release(fl1);
6551da177e4SLinus Torvalds 				goto done;
6561da177e4SLinus Torvalds 			}
6571da177e4SLinus Torvalds 		}
6581da177e4SLinus Torvalds 		err = -ENOENT;
6591da177e4SLinus Torvalds 		if (!(freq.flr_flags&IPV6_FL_F_CREATE))
6601da177e4SLinus Torvalds 			goto done;
6611da177e4SLinus Torvalds 
6621da177e4SLinus Torvalds 		err = -ENOMEM;
66363159f29SIan Morris 		if (!sfl1)
664e5d08d71SIan Morris 			goto done;
665e5d08d71SIan Morris 
666e5d08d71SIan Morris 		err = mem_check(sk);
667e5d08d71SIan Morris 		if (err != 0)
6681da177e4SLinus Torvalds 			goto done;
6691da177e4SLinus Torvalds 
67060e8fbc4SBenjamin Thery 		fl1 = fl_intern(net, fl, freq.flr_label);
67153b24b8fSIan Morris 		if (fl1)
67278c2e502SPavel Emelyanov 			goto recheck;
6731da177e4SLinus Torvalds 
6746c94d361SDavid S. Miller 		if (!freq.flr_label) {
6756c94d361SDavid S. Miller 			if (copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label,
6766c94d361SDavid S. Miller 					 &fl->label, sizeof(fl->label))) {
6776c94d361SDavid S. Miller 				/* Intentionally ignore fault. */
6786c94d361SDavid S. Miller 			}
6796c94d361SDavid S. Miller 		}
6801da177e4SLinus Torvalds 
68104028045SPavel Emelyanov 		fl_link(np, sfl1, fl);
6821da177e4SLinus Torvalds 		return 0;
6831da177e4SLinus Torvalds 
6841da177e4SLinus Torvalds 	default:
6851da177e4SLinus Torvalds 		return -EINVAL;
6861da177e4SLinus Torvalds 	}
6871da177e4SLinus Torvalds 
6881da177e4SLinus Torvalds done:
6891da177e4SLinus Torvalds 	fl_free(fl);
6901da177e4SLinus Torvalds 	kfree(sfl1);
6911da177e4SLinus Torvalds 	return err;
6921da177e4SLinus Torvalds }
6931da177e4SLinus Torvalds 
6941da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
6951da177e4SLinus Torvalds 
6961da177e4SLinus Torvalds struct ip6fl_iter_state {
6975983a3dfSBenjamin Thery 	struct seq_net_private p;
6984f82f457SEric W. Biederman 	struct pid_namespace *pid_ns;
6991da177e4SLinus Torvalds 	int bucket;
7001da177e4SLinus Torvalds };
7011da177e4SLinus Torvalds 
7021da177e4SLinus Torvalds #define ip6fl_seq_private(seq)	((struct ip6fl_iter_state *)(seq)->private)
7031da177e4SLinus Torvalds 
7041da177e4SLinus Torvalds static struct ip6_flowlabel *ip6fl_get_first(struct seq_file *seq)
7051da177e4SLinus Torvalds {
7061da177e4SLinus Torvalds 	struct ip6_flowlabel *fl = NULL;
7071da177e4SLinus Torvalds 	struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
7085983a3dfSBenjamin Thery 	struct net *net = seq_file_net(seq);
7091da177e4SLinus Torvalds 
7101da177e4SLinus Torvalds 	for (state->bucket = 0; state->bucket <= FL_HASH_MASK; ++state->bucket) {
711d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		for_each_fl_rcu(state->bucket, fl) {
712d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 			if (net_eq(fl->fl_net, net))
713d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 				goto out;
7141da177e4SLinus Torvalds 		}
715d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	}
716d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	fl = NULL;
717d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 out:
7181da177e4SLinus Torvalds 	return fl;
7191da177e4SLinus Torvalds }
7201da177e4SLinus Torvalds 
7211da177e4SLinus Torvalds static struct ip6_flowlabel *ip6fl_get_next(struct seq_file *seq, struct ip6_flowlabel *fl)
7221da177e4SLinus Torvalds {
7231da177e4SLinus Torvalds 	struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
7245983a3dfSBenjamin Thery 	struct net *net = seq_file_net(seq);
7251da177e4SLinus Torvalds 
726d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	for_each_fl_continue_rcu(fl) {
727d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		if (net_eq(fl->fl_net, net))
728d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 			goto out;
7291da177e4SLinus Torvalds 	}
730d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 
731d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 try_again:
732d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	if (++state->bucket <= FL_HASH_MASK) {
733d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		for_each_fl_rcu(state->bucket, fl) {
734d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 			if (net_eq(fl->fl_net, net))
735d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 				goto out;
736d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		}
737d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 		goto try_again;
738d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	}
739d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	fl = NULL;
740d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 
741d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 out:
7421da177e4SLinus Torvalds 	return fl;
7431da177e4SLinus Torvalds }
7441da177e4SLinus Torvalds 
7451da177e4SLinus Torvalds static struct ip6_flowlabel *ip6fl_get_idx(struct seq_file *seq, loff_t pos)
7461da177e4SLinus Torvalds {
7471da177e4SLinus Torvalds 	struct ip6_flowlabel *fl = ip6fl_get_first(seq);
7481da177e4SLinus Torvalds 	if (fl)
7491da177e4SLinus Torvalds 		while (pos && (fl = ip6fl_get_next(seq, fl)) != NULL)
7501da177e4SLinus Torvalds 			--pos;
7511da177e4SLinus Torvalds 	return pos ? NULL : fl;
7521da177e4SLinus Torvalds }
7531da177e4SLinus Torvalds 
7541da177e4SLinus Torvalds static void *ip6fl_seq_start(struct seq_file *seq, loff_t *pos)
755d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	__acquires(RCU)
7561da177e4SLinus Torvalds {
757d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	rcu_read_lock_bh();
7581da177e4SLinus Torvalds 	return *pos ? ip6fl_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
7591da177e4SLinus Torvalds }
7601da177e4SLinus Torvalds 
7611da177e4SLinus Torvalds static void *ip6fl_seq_next(struct seq_file *seq, void *v, loff_t *pos)
7621da177e4SLinus Torvalds {
7631da177e4SLinus Torvalds 	struct ip6_flowlabel *fl;
7641da177e4SLinus Torvalds 
7651da177e4SLinus Torvalds 	if (v == SEQ_START_TOKEN)
7661da177e4SLinus Torvalds 		fl = ip6fl_get_first(seq);
7671da177e4SLinus Torvalds 	else
7681da177e4SLinus Torvalds 		fl = ip6fl_get_next(seq, v);
7691da177e4SLinus Torvalds 	++*pos;
7701da177e4SLinus Torvalds 	return fl;
7711da177e4SLinus Torvalds }
7721da177e4SLinus Torvalds 
7731da177e4SLinus Torvalds static void ip6fl_seq_stop(struct seq_file *seq, void *v)
774d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	__releases(RCU)
7751da177e4SLinus Torvalds {
776d3aedd5eSYOSHIFUJI Hideaki / 吉藤英明 	rcu_read_unlock_bh();
7771da177e4SLinus Torvalds }
7781da177e4SLinus Torvalds 
7791b7c2dbcSJames Morris static int ip6fl_seq_show(struct seq_file *seq, void *v)
7801da177e4SLinus Torvalds {
7814f82f457SEric W. Biederman 	struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
782869ba988SFlorent Fourcot 	if (v == SEQ_START_TOKEN) {
7831744bea1SJoe Perches 		seq_puts(seq, "Label S Owner  Users  Linger Expires  Dst                              Opt\n");
784869ba988SFlorent Fourcot 	} else {
7851b7c2dbcSJames Morris 		struct ip6_flowlabel *fl = v;
7861da177e4SLinus Torvalds 		seq_printf(seq,
7874b7a4274SHarvey Harrison 			   "%05X %-1d %-6d %-6d %-6ld %-8ld %pi6 %-4d\n",
78895c96174SEric Dumazet 			   (unsigned int)ntohl(fl->label),
7891da177e4SLinus Torvalds 			   fl->share,
7904f82f457SEric W. Biederman 			   ((fl->share == IPV6_FL_S_PROCESS) ?
7914f82f457SEric W. Biederman 			    pid_nr_ns(fl->owner.pid, state->pid_ns) :
7924f82f457SEric W. Biederman 			    ((fl->share == IPV6_FL_S_USER) ?
7934f82f457SEric W. Biederman 			     from_kuid_munged(seq_user_ns(seq), fl->owner.uid) :
7944f82f457SEric W. Biederman 			     0)),
7951da177e4SLinus Torvalds 			   atomic_read(&fl->users),
7961da177e4SLinus Torvalds 			   fl->linger/HZ,
7971da177e4SLinus Torvalds 			   (long)(fl->expires - jiffies)/HZ,
798b071195dSHarvey Harrison 			   &fl->dst,
7991da177e4SLinus Torvalds 			   fl->opt ? fl->opt->opt_nflen : 0);
8001da177e4SLinus Torvalds 	}
8011da177e4SLinus Torvalds 	return 0;
8021da177e4SLinus Torvalds }
8031da177e4SLinus Torvalds 
80456b3d975SPhilippe De Muyter static const struct seq_operations ip6fl_seq_ops = {
8051da177e4SLinus Torvalds 	.start	=	ip6fl_seq_start,
8061da177e4SLinus Torvalds 	.next	=	ip6fl_seq_next,
8071da177e4SLinus Torvalds 	.stop	=	ip6fl_seq_stop,
8081da177e4SLinus Torvalds 	.show	=	ip6fl_seq_show,
8091da177e4SLinus Torvalds };
8101da177e4SLinus Torvalds 
8111da177e4SLinus Torvalds static int ip6fl_seq_open(struct inode *inode, struct file *file)
8121da177e4SLinus Torvalds {
8134f82f457SEric W. Biederman 	struct seq_file *seq;
8144f82f457SEric W. Biederman 	struct ip6fl_iter_state *state;
8154f82f457SEric W. Biederman 	int err;
8164f82f457SEric W. Biederman 
8174f82f457SEric W. Biederman 	err = seq_open_net(inode, file, &ip6fl_seq_ops,
818cf7732e4SPavel Emelyanov 			   sizeof(struct ip6fl_iter_state));
8194f82f457SEric W. Biederman 
8204f82f457SEric W. Biederman 	if (!err) {
8214f82f457SEric W. Biederman 		seq = file->private_data;
8224f82f457SEric W. Biederman 		state = ip6fl_seq_private(seq);
8234f82f457SEric W. Biederman 		rcu_read_lock();
8244f82f457SEric W. Biederman 		state->pid_ns = get_pid_ns(task_active_pid_ns(current));
8254f82f457SEric W. Biederman 		rcu_read_unlock();
8264f82f457SEric W. Biederman 	}
8274f82f457SEric W. Biederman 	return err;
8284f82f457SEric W. Biederman }
8294f82f457SEric W. Biederman 
8304f82f457SEric W. Biederman static int ip6fl_seq_release(struct inode *inode, struct file *file)
8314f82f457SEric W. Biederman {
8324f82f457SEric W. Biederman 	struct seq_file *seq = file->private_data;
8334f82f457SEric W. Biederman 	struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
8344f82f457SEric W. Biederman 	put_pid_ns(state->pid_ns);
8354f82f457SEric W. Biederman 	return seq_release_net(inode, file);
8361da177e4SLinus Torvalds }
8371da177e4SLinus Torvalds 
8389a32144eSArjan van de Ven static const struct file_operations ip6fl_seq_fops = {
8391da177e4SLinus Torvalds 	.owner		=	THIS_MODULE,
8401da177e4SLinus Torvalds 	.open		=	ip6fl_seq_open,
8411da177e4SLinus Torvalds 	.read		=	seq_read,
8421da177e4SLinus Torvalds 	.llseek		=	seq_lseek,
8434f82f457SEric W. Biederman 	.release	=	ip6fl_seq_release,
8441da177e4SLinus Torvalds };
8451da177e4SLinus Torvalds 
8462c8c1e72SAlexey Dobriyan static int __net_init ip6_flowlabel_proc_init(struct net *net)
8471da177e4SLinus Torvalds {
848d4beaa66SGao feng 	if (!proc_create("ip6_flowlabel", S_IRUGO, net->proc_net,
849d4beaa66SGao feng 			 &ip6fl_seq_fops))
8500a3e78acSDaniel Lezcano 		return -ENOMEM;
8510a3e78acSDaniel Lezcano 	return 0;
8520a3e78acSDaniel Lezcano }
8530a3e78acSDaniel Lezcano 
8542c8c1e72SAlexey Dobriyan static void __net_exit ip6_flowlabel_proc_fini(struct net *net)
8550a3e78acSDaniel Lezcano {
856ece31ffdSGao feng 	remove_proc_entry("ip6_flowlabel", net->proc_net);
8570a3e78acSDaniel Lezcano }
8580a3e78acSDaniel Lezcano #else
8590a3e78acSDaniel Lezcano static inline int ip6_flowlabel_proc_init(struct net *net)
8600a3e78acSDaniel Lezcano {
8610a3e78acSDaniel Lezcano 	return 0;
8620a3e78acSDaniel Lezcano }
8630a3e78acSDaniel Lezcano static inline void ip6_flowlabel_proc_fini(struct net *net)
8640a3e78acSDaniel Lezcano {
8650a3e78acSDaniel Lezcano }
8661da177e4SLinus Torvalds #endif
8670a3e78acSDaniel Lezcano 
8682c8c1e72SAlexey Dobriyan static void __net_exit ip6_flowlabel_net_exit(struct net *net)
86960e8fbc4SBenjamin Thery {
87060e8fbc4SBenjamin Thery 	ip6_fl_purge(net);
8715983a3dfSBenjamin Thery 	ip6_flowlabel_proc_fini(net);
87260e8fbc4SBenjamin Thery }
87360e8fbc4SBenjamin Thery 
87460e8fbc4SBenjamin Thery static struct pernet_operations ip6_flowlabel_net_ops = {
8755983a3dfSBenjamin Thery 	.init = ip6_flowlabel_proc_init,
87660e8fbc4SBenjamin Thery 	.exit = ip6_flowlabel_net_exit,
87760e8fbc4SBenjamin Thery };
87860e8fbc4SBenjamin Thery 
8790a3e78acSDaniel Lezcano int ip6_flowlabel_init(void)
8800a3e78acSDaniel Lezcano {
8815983a3dfSBenjamin Thery 	return register_pernet_subsys(&ip6_flowlabel_net_ops);
8821da177e4SLinus Torvalds }
8831da177e4SLinus Torvalds 
8841da177e4SLinus Torvalds void ip6_flowlabel_cleanup(void)
8851da177e4SLinus Torvalds {
8861da177e4SLinus Torvalds 	del_timer(&ip6_fl_gc_timer);
88760e8fbc4SBenjamin Thery 	unregister_pernet_subsys(&ip6_flowlabel_net_ops);
8881da177e4SLinus Torvalds }
889