diff mbox

[net-next,2/3] ipv6: per netns fib6 walkers

Message ID 6df8e224d6337b658a097ac87e72e1467cddf13d.1457085016.git.mkubecek@suse.cz
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Michal Kubecek March 4, 2016, 10:59 a.m. UTC
The IPv6 FIB data structures are separated per network namespace but
there is still only one global walkers list and one global walker list
lock. This means changes in one namespace unnecessarily check walkers
in other namespaces and block them.

Replace the global list with per-netns lists and give each its own
lock.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 include/net/netns/ipv6.h |  2 ++
 net/ipv6/ip6_fib.c       | 67 +++++++++++++++++++++++++++---------------------
 2 files changed, 40 insertions(+), 29 deletions(-)

Comments

David Miller March 7, 2016, 7:48 p.m. UTC | #1
From: Michal Kubecek <mkubecek@suse.cz>
Date: Fri,  4 Mar 2016 11:59:25 +0100 (CET)

> @@ -1978,6 +1980,12 @@ static int ipv6_route_yield(struct fib6_walker *w)
>  
>  static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
>  {
> +#ifdef CONFIG_NET_NS
> +	struct net *net = iter->p.net;
> +#else
> +	struct net *net = &init_net;
> +#endif

Please use read_pnet(), it exists so that we don't need to do ugly CPP
stuff like this in *.c files.

Thanks.
Cong Wang March 8, 2016, 12:26 a.m. UTC | #2
On Fri, Mar 4, 2016 at 2:59 AM, Michal Kubecek <mkubecek@suse.cz> wrote:
>  static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
>  {
> +#ifdef CONFIG_NET_NS
> +       struct net *net = iter->p.net;
> +#else
> +       struct net *net = &init_net;
> +#endif
> +

You should pass the struct net pointer to ipv6_route_seq_setup_walk()
instead of reading it by yourself.

I don't find anyone actually using iter->p, it probably can be just removed.
Cong Wang March 8, 2016, 12:28 a.m. UTC | #3
On Mon, Mar 7, 2016 at 4:26 PM, Cong Wang <xiyou.wangcong@gmail.com> wrote:
> On Fri, Mar 4, 2016 at 2:59 AM, Michal Kubecek <mkubecek@suse.cz> wrote:
>>  static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
>>  {
>> +#ifdef CONFIG_NET_NS
>> +       struct net *net = iter->p.net;
>> +#else
>> +       struct net *net = &init_net;
>> +#endif
>> +
>
> You should pass the struct net pointer to ipv6_route_seq_setup_walk()
> instead of reading it by yourself.
>
> I don't find anyone actually using iter->p, it probably can be just removed.

Er, seq_file_net() uses it... but callers already call it.
Michal Kubecek March 8, 2016, 7:05 a.m. UTC | #4
On Mon, Mar 07, 2016 at 04:28:26PM -0800, Cong Wang wrote:
> On Mon, Mar 7, 2016 at 4:26 PM, Cong Wang <xiyou.wangcong@gmail.com> wrote:
> > On Fri, Mar 4, 2016 at 2:59 AM, Michal Kubecek <mkubecek@suse.cz> wrote:
> >>  static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
> >>  {
> >> +#ifdef CONFIG_NET_NS
> >> +       struct net *net = iter->p.net;
> >> +#else
> >> +       struct net *net = &init_net;
> >> +#endif
> >> +
> >
> > You should pass the struct net pointer to ipv6_route_seq_setup_walk()
> > instead of reading it by yourself.

I considered this. While it probably wouldn't bring any extra overhead
as the function is going to be inlined anyway, it didn't look really
nice. I gues I'll use read_pnet() as David suggested; I just didn't
realize the reason it's a macro in !CONFIG_NET_NS case is to allow
passing a pointer to non-existent struct member.

> > I don't find anyone actually using iter->p, it probably can be just removed.
> 
> Er, seq_file_net() uses it... but callers already call it.

Not only seq_file_net(). The whole infrastructure assumes private data
start with an instance of struct seq_net_private and seq_open_net()
initializes it.

                                                        Michal Kubecek
Michal Kubecek March 8, 2016, 10:03 a.m. UTC | #5
On Tue, Mar 08, 2016 at 08:05:44AM +0100, Michal Kubecek wrote:
> On Mon, Mar 07, 2016 at 04:28:26PM -0800, Cong Wang wrote:
> > On Mon, Mar 7, 2016 at 4:26 PM, Cong Wang <xiyou.wangcong@gmail.com> wrote:
> > > On Fri, Mar 4, 2016 at 2:59 AM, Michal Kubecek <mkubecek@suse.cz> wrote:
> > >>  static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
> > >>  {
> > >> +#ifdef CONFIG_NET_NS
> > >> +       struct net *net = iter->p.net;
> > >> +#else
> > >> +       struct net *net = &init_net;
> > >> +#endif
> > >> +
> > >
> > > You should pass the struct net pointer to ipv6_route_seq_setup_walk()
> > > instead of reading it by yourself.
> 
> I considered this. While it probably wouldn't bring any extra overhead
> as the function is going to be inlined anyway, it didn't look really
> nice. I gues I'll use read_pnet() as David suggested; I just didn't
> realize the reason it's a macro in !CONFIG_NET_NS case is to allow
> passing a pointer to non-existent struct member.

Well, not anymore, actually, since commit 0c5c9fb55106 ("net: Introduce
possible_net_t"). Unfortunately seq_file_net does not use possible_net_t
and fixing that could potentially affect too much code to just throw it
in now without sufficient testing.

I was also wrong about ipv6_route_seq_setup_walk() being inlined as
there are two callers. On the other hand, the only difference is two
register mov instructions (one in caller, one in callee) vs. one read
from memory so it's not clear if there is an actual overhead.

So I'll pass net as second argument after all and submit the
seq_net_private conversion to possible_net_t later, most likely after
net-next opens again for 4.7.

                                                        Michal Kubecek
diff mbox

Patch

diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
index c0368db6df54..f0109b973648 100644
--- a/include/net/netns/ipv6.h
+++ b/include/net/netns/ipv6.h
@@ -58,7 +58,9 @@  struct netns_ipv6 {
 	struct timer_list       ip6_fib_timer;
 	struct hlist_head       *fib_table_hash;
 	struct fib6_table       *fib6_main_tbl;
+	struct list_head	fib6_walkers;
 	struct dst_ops		ip6_dst_ops;
+	rwlock_t		fib6_walker_lock;
 	unsigned int		 ip6_rt_gc_expire;
 	unsigned long		 ip6_rt_last_gc;
 #ifdef CONFIG_IPV6_MULTIPLE_TABLES
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index d7c715accac9..af8a0fb39eda 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -55,8 +55,6 @@  struct fib6_cleaner {
 	void *arg;
 };
 
-static DEFINE_RWLOCK(fib6_walker_lock);
-
 #ifdef CONFIG_IPV6_SUBTREES
 #define FWS_INIT FWS_S
 #else
@@ -66,7 +64,7 @@  static DEFINE_RWLOCK(fib6_walker_lock);
 static void fib6_prune_clones(struct net *net, struct fib6_node *fn);
 static struct rt6_info *fib6_find_prefix(struct net *net, struct fib6_node *fn);
 static struct fib6_node *fib6_repair_tree(struct net *net, struct fib6_node *fn);
-static int fib6_walk(struct fib6_walker *w);
+static int fib6_walk(struct net *net, struct fib6_walker *w);
 static int fib6_walk_continue(struct fib6_walker *w);
 
 /*
@@ -78,21 +76,21 @@  static int fib6_walk_continue(struct fib6_walker *w);
 
 static void fib6_gc_timer_cb(unsigned long arg);
 
-static LIST_HEAD(fib6_walkers);
-#define FOR_WALKERS(w) list_for_each_entry(w, &fib6_walkers, lh)
+#define FOR_WALKERS(net, w) \
+	list_for_each_entry(w, &(net)->ipv6.fib6_walkers, lh)
 
-static void fib6_walker_link(struct fib6_walker *w)
+static void fib6_walker_link(struct net *net, struct fib6_walker *w)
 {
-	write_lock_bh(&fib6_walker_lock);
-	list_add(&w->lh, &fib6_walkers);
-	write_unlock_bh(&fib6_walker_lock);
+	write_lock_bh(&net->ipv6.fib6_walker_lock);
+	list_add(&w->lh, &net->ipv6.fib6_walkers);
+	write_unlock_bh(&net->ipv6.fib6_walker_lock);
 }
 
-static void fib6_walker_unlink(struct fib6_walker *w)
+static void fib6_walker_unlink(struct net *net, struct fib6_walker *w)
 {
-	write_lock_bh(&fib6_walker_lock);
+	write_lock_bh(&net->ipv6.fib6_walker_lock);
 	list_del(&w->lh);
-	write_unlock_bh(&fib6_walker_lock);
+	write_unlock_bh(&net->ipv6.fib6_walker_lock);
 }
 
 static int fib6_new_sernum(struct net *net)
@@ -325,12 +323,13 @@  static int fib6_dump_node(struct fib6_walker *w)
 
 static void fib6_dump_end(struct netlink_callback *cb)
 {
+	struct net *net = sock_net(cb->skb->sk);
 	struct fib6_walker *w = (void *)cb->args[2];
 
 	if (w) {
 		if (cb->args[4]) {
 			cb->args[4] = 0;
-			fib6_walker_unlink(w);
+			fib6_walker_unlink(net, w);
 		}
 		cb->args[2] = 0;
 		kfree(w);
@@ -348,6 +347,7 @@  static int fib6_dump_done(struct netlink_callback *cb)
 static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
 			   struct netlink_callback *cb)
 {
+	struct net *net = sock_net(skb->sk);
 	struct fib6_walker *w;
 	int res;
 
@@ -359,7 +359,7 @@  static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
 		w->skip = 0;
 
 		read_lock_bh(&table->tb6_lock);
-		res = fib6_walk(w);
+		res = fib6_walk(net, w);
 		read_unlock_bh(&table->tb6_lock);
 		if (res > 0) {
 			cb->args[4] = 1;
@@ -379,7 +379,7 @@  static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
 		res = fib6_walk_continue(w);
 		read_unlock_bh(&table->tb6_lock);
 		if (res <= 0) {
-			fib6_walker_unlink(w);
+			fib6_walker_unlink(net, w);
 			cb->args[4] = 0;
 		}
 	}
@@ -1340,8 +1340,8 @@  static struct fib6_node *fib6_repair_tree(struct net *net,
 		}
 #endif
 
-		read_lock(&fib6_walker_lock);
-		FOR_WALKERS(w) {
+		read_lock(&net->ipv6.fib6_walker_lock);
+		FOR_WALKERS(net, w) {
 			if (!child) {
 				if (w->root == fn) {
 					w->root = w->node = NULL;
@@ -1368,7 +1368,7 @@  static struct fib6_node *fib6_repair_tree(struct net *net,
 				}
 			}
 		}
-		read_unlock(&fib6_walker_lock);
+		read_unlock(&net->ipv6.fib6_walker_lock);
 
 		node_free(fn);
 		if (pn->fn_flags & RTN_RTINFO || FIB6_SUBTREE(pn))
@@ -1411,8 +1411,8 @@  static void fib6_del_route(struct fib6_node *fn, struct rt6_info **rtp,
 	}
 
 	/* Adjust walkers */
-	read_lock(&fib6_walker_lock);
-	FOR_WALKERS(w) {
+	read_lock(&net->ipv6.fib6_walker_lock);
+	FOR_WALKERS(net, w) {
 		if (w->state == FWS_C && w->leaf == rt) {
 			RT6_TRACE("walker %p adjusted by delroute\n", w);
 			w->leaf = rt->dst.rt6_next;
@@ -1420,7 +1420,7 @@  static void fib6_del_route(struct fib6_node *fn, struct rt6_info **rtp,
 				w->state = FWS_U;
 		}
 	}
-	read_unlock(&fib6_walker_lock);
+	read_unlock(&net->ipv6.fib6_walker_lock);
 
 	rt->dst.rt6_next = NULL;
 
@@ -1588,17 +1588,17 @@  skip:
 	}
 }
 
-static int fib6_walk(struct fib6_walker *w)
+static int fib6_walk(struct net *net, struct fib6_walker *w)
 {
 	int res;
 
 	w->state = FWS_INIT;
 	w->node = w->root;
 
-	fib6_walker_link(w);
+	fib6_walker_link(net, w);
 	res = fib6_walk_continue(w);
 	if (res <= 0)
-		fib6_walker_unlink(w);
+		fib6_walker_unlink(net, w);
 	return res;
 }
 
@@ -1668,7 +1668,7 @@  static void fib6_clean_tree(struct net *net, struct fib6_node *root,
 	c.arg = arg;
 	c.net = net;
 
-	fib6_walk(&c.w);
+	fib6_walk(net, &c.w);
 }
 
 static void __fib6_clean_all(struct net *net,
@@ -1816,6 +1816,8 @@  static int __net_init fib6_net_init(struct net *net)
 {
 	size_t size = sizeof(struct hlist_head) * FIB6_TABLE_HASHSZ;
 
+	rwlock_init(&net->ipv6.fib6_walker_lock);
+	INIT_LIST_HEAD(&net->ipv6.fib6_walkers);
 	setup_timer(&net->ipv6.ip6_fib_timer, fib6_gc_timer_cb, (unsigned long)net);
 
 	net->ipv6.rt6_stats = kzalloc(sizeof(*net->ipv6.rt6_stats), GFP_KERNEL);
@@ -1978,6 +1980,12 @@  static int ipv6_route_yield(struct fib6_walker *w)
 
 static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
 {
+#ifdef CONFIG_NET_NS
+	struct net *net = iter->p.net;
+#else
+	struct net *net = &init_net;
+#endif
+
 	memset(&iter->w, 0, sizeof(iter->w));
 	iter->w.func = ipv6_route_yield;
 	iter->w.root = &iter->tbl->tb6_root;
@@ -1986,7 +1994,7 @@  static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter)
 	iter->w.args = iter;
 	iter->sernum = iter->w.root->fn_sernum;
 	INIT_LIST_HEAD(&iter->w.lh);
-	fib6_walker_link(&iter->w);
+	fib6_walker_link(net, &iter->w);
 }
 
 static struct fib6_table *ipv6_route_seq_next_table(struct fib6_table *tbl,
@@ -2047,10 +2055,10 @@  iter_table:
 			++*pos;
 		return iter->w.leaf;
 	} else if (r < 0) {
-		fib6_walker_unlink(&iter->w);
+		fib6_walker_unlink(net, &iter->w);
 		return NULL;
 	}
-	fib6_walker_unlink(&iter->w);
+	fib6_walker_unlink(net, &iter->w);
 
 	iter->tbl = ipv6_route_seq_next_table(iter->tbl, net);
 	if (!iter->tbl)
@@ -2087,10 +2095,11 @@  static bool ipv6_route_iter_active(struct ipv6_route_iter *iter)
 static void ipv6_route_seq_stop(struct seq_file *seq, void *v)
 	__releases(RCU_BH)
 {
+	struct net *net = seq_file_net(seq);
 	struct ipv6_route_iter *iter = seq->private;
 
 	if (ipv6_route_iter_active(iter))
-		fib6_walker_unlink(&iter->w);
+		fib6_walker_unlink(net, &iter->w);
 
 	rcu_read_unlock_bh();
 }