diff options
Diffstat (limited to 'net/ipv6/ip6_fib.c')
| -rw-r--r-- | net/ipv6/ip6_fib.c | 347 | 
1 files changed, 277 insertions, 70 deletions
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 5bec666aba6..cb4459bd1d2 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -9,14 +9,12 @@   *      modify it under the terms of the GNU General Public License   *      as published by the Free Software Foundation; either version   *      2 of the License, or (at your option) any later version. - */ - -/* - * 	Changes: - * 	Yuji SEKIYA @USAGI:	Support default route on router node; - * 				remove ip6_null_entry from the top of - * 				routing table. - * 	Ville Nuorvala:		Fixed routing subtrees. + * + *	Changes: + *	Yuji SEKIYA @USAGI:	Support default route on router node; + *				remove ip6_null_entry from the top of + *				routing table. + *	Ville Nuorvala:		Fixed routing subtrees.   */  #define pr_fmt(fmt) "IPv6: " fmt @@ -46,10 +44,9 @@  #define RT6_TRACE(x...) do { ; } while (0)  #endif -static struct kmem_cache * fib6_node_kmem __read_mostly; +static struct kmem_cache *fib6_node_kmem __read_mostly; -enum fib_walk_state_t -{ +enum fib_walk_state_t {  #ifdef CONFIG_IPV6_SUBTREES  	FWS_S,  #endif @@ -59,8 +56,7 @@ enum fib_walk_state_t  	FWS_U  }; -struct fib6_cleaner_t -{ +struct fib6_cleaner_t {  	struct fib6_walker_t w;  	struct net *net;  	int (*func)(struct rt6_info *, void *arg); @@ -75,8 +71,7 @@ static DEFINE_RWLOCK(fib6_walker_lock);  #define FWS_INIT FWS_L  #endif -static void fib6_prune_clones(struct net *net, struct fib6_node *fn, -			      struct rt6_info *rt); +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_t *w); @@ -138,7 +133,7 @@ static __inline__ __be32 addr_bit_set(const void *token, int fn_bit)  	const __be32 *addr = token;  	/*  	 * Here, -	 * 	1 << ((~fn_bit ^ BITOP_BE32_SWIZZLE) & 0x1f) +	 *	1 << ((~fn_bit ^ BITOP_BE32_SWIZZLE) & 0x1f)  	 * is optimized version of  	 *	htonl(1 << ((~fn_bit)&0x1F))  	 * See include/asm-generic/bitops/le.h. @@ -147,7 +142,7 @@ static __inline__ __be32 addr_bit_set(const void *token, int fn_bit)  	       addr[fn_bit >> 5];  } -static __inline__ struct fib6_node * node_alloc(void) +static __inline__ struct fib6_node *node_alloc(void)  {  	struct fib6_node *fn; @@ -156,7 +151,7 @@ static __inline__ struct fib6_node * node_alloc(void)  	return fn;  } -static __inline__ void node_free(struct fib6_node * fn) +static __inline__ void node_free(struct fib6_node *fn)  {  	kmem_cache_free(fib6_node_kmem, fn);  } @@ -292,7 +287,7 @@ static int fib6_dump_node(struct fib6_walker_t *w)  static void fib6_dump_end(struct netlink_callback *cb)  { -	struct fib6_walker_t *w = (void*)cb->args[2]; +	struct fib6_walker_t *w = (void *)cb->args[2];  	if (w) {  		if (cb->args[4]) { @@ -302,7 +297,7 @@ static void fib6_dump_end(struct netlink_callback *cb)  		cb->args[2] = 0;  		kfree(w);  	} -	cb->done = (void*)cb->args[3]; +	cb->done = (void *)cb->args[3];  	cb->args[1] = 3;  } @@ -485,7 +480,7 @@ static struct fib6_node *fib6_add_1(struct fib6_node *root,  		fn->fn_sernum = sernum;  		dir = addr_bit_set(addr, fn->fn_bit);  		pn = fn; -		fn = dir ? fn->right: fn->left; +		fn = dir ? fn->right : fn->left;  	} while (fn);  	if (!allow_create) { @@ -638,12 +633,41 @@ static inline bool rt6_qualify_for_ecmp(struct rt6_info *rt)  	       RTF_GATEWAY;  } +static int fib6_commit_metrics(struct dst_entry *dst, +			       struct nlattr *mx, int mx_len) +{ +	struct nlattr *nla; +	int remaining; +	u32 *mp; + +	if (dst->flags & DST_HOST) { +		mp = dst_metrics_write_ptr(dst); +	} else { +		mp = kzalloc(sizeof(u32) * RTAX_MAX, GFP_KERNEL); +		if (!mp) +			return -ENOMEM; +		dst_init_metrics(dst, mp, 0); +	} + +	nla_for_each_attr(nla, mx, mx_len, remaining) { +		int type = nla_type(nla); + +		if (type) { +			if (type > RTAX_MAX) +				return -EINVAL; + +			mp[type - 1] = nla_get_u32(nla); +		} +	} +	return 0; +} +  /*   *	Insert routing information in a node.   */  static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, -			    struct nl_info *info) +			    struct nl_info *info, struct nlattr *mx, int mx_len)  {  	struct rt6_info *iter = NULL;  	struct rt6_info **ins; @@ -653,6 +677,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,  		   (info->nlh->nlmsg_flags & NLM_F_CREATE));  	int found = 0;  	bool rt_can_ecmp = rt6_qualify_for_ecmp(rt); +	int err;  	ins = &fn->leaf; @@ -751,6 +776,11 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,  			pr_warn("NLM_F_CREATE should be set when creating new route\n");  add: +		if (mx) { +			err = fib6_commit_metrics(&rt->dst, mx, mx_len); +			if (err) +				return err; +		}  		rt->dst.rt6_next = iter;  		*ins = rt;  		rt->rt6i_node = fn; @@ -770,6 +800,11 @@ add:  			pr_warn("NLM_F_REPLACE set, but no existing node found!\n");  			return -ENOENT;  		} +		if (mx) { +			err = fib6_commit_metrics(&rt->dst, mx, mx_len); +			if (err) +				return err; +		}  		*ins = rt;  		rt->rt6i_node = fn;  		rt->dst.rt6_next = iter->dst.rt6_next; @@ -806,7 +841,8 @@ void fib6_force_start_gc(struct net *net)   *	with source addr info in sub-trees   */ -int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info) +int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info, +	     struct nlattr *mx, int mx_len)  {  	struct fib6_node *fn, *pn = NULL;  	int err = -ENOMEM; @@ -900,11 +936,11 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info)  	}  #endif -	err = fib6_add_rt2node(fn, rt, info); +	err = fib6_add_rt2node(fn, rt, info, mx, mx_len);  	if (!err) {  		fib6_start_gc(info->nl_net, rt);  		if (!(rt->rt6i_flags & RTF_CACHE)) -			fib6_prune_clones(info->nl_net, pn, rt); +			fib6_prune_clones(info->nl_net, pn);  	}  out: @@ -955,8 +991,8 @@ struct lookup_args {  	const struct in6_addr	*addr;		/* search key			*/  }; -static struct fib6_node * fib6_lookup_1(struct fib6_node *root, -					struct lookup_args *args) +static struct fib6_node *fib6_lookup_1(struct fib6_node *root, +				       struct lookup_args *args)  {  	struct fib6_node *fn;  	__be32 dir; @@ -1018,8 +1054,8 @@ backtrack:  	return NULL;  } -struct fib6_node * fib6_lookup(struct fib6_node *root, const struct in6_addr *daddr, -			       const struct in6_addr *saddr) +struct fib6_node *fib6_lookup(struct fib6_node *root, const struct in6_addr *daddr, +			      const struct in6_addr *saddr)  {  	struct fib6_node *fn;  	struct lookup_args args[] = { @@ -1051,9 +1087,9 @@ struct fib6_node * fib6_lookup(struct fib6_node *root, const struct in6_addr *da   */ -static struct fib6_node * fib6_locate_1(struct fib6_node *root, -					const struct in6_addr *addr, -					int plen, int offset) +static struct fib6_node *fib6_locate_1(struct fib6_node *root, +				       const struct in6_addr *addr, +				       int plen, int offset)  {  	struct fib6_node *fn; @@ -1081,9 +1117,9 @@ static struct fib6_node * fib6_locate_1(struct fib6_node *root,  	return NULL;  } -struct fib6_node * fib6_locate(struct fib6_node *root, -			       const struct in6_addr *daddr, int dst_len, -			       const struct in6_addr *saddr, int src_len) +struct fib6_node *fib6_locate(struct fib6_node *root, +			      const struct in6_addr *daddr, int dst_len, +			      const struct in6_addr *saddr, int src_len)  {  	struct fib6_node *fn; @@ -1151,8 +1187,10 @@ static struct fib6_node *fib6_repair_tree(struct net *net,  		children = 0;  		child = NULL; -		if (fn->right) child = fn->right, children |= 1; -		if (fn->left) child = fn->left, children |= 2; +		if (fn->right) +			child = fn->right, children |= 1; +		if (fn->left) +			child = fn->left, children |= 2;  		if (children == 3 || FIB6_SUBTREE(fn)  #ifdef CONFIG_IPV6_SUBTREES @@ -1180,8 +1218,10 @@ static struct fib6_node *fib6_repair_tree(struct net *net,  		} else {  			WARN_ON(fn->fn_flags & RTN_ROOT);  #endif -			if (pn->right == fn) pn->right = child; -			else if (pn->left == fn) pn->left = child; +			if (pn->right == fn) +				pn->right = child; +			else if (pn->left == fn) +				pn->left = child;  #if RT6_DEBUG >= 2  			else  				WARN_ON(1); @@ -1213,10 +1253,10 @@ static struct fib6_node *fib6_repair_tree(struct net *net,  					w->node = child;  					if (children&2) {  						RT6_TRACE("W %p adjusted by delnode 2, s=%d\n", w, w->state); -						w->state = w->state>=FWS_R ? FWS_U : FWS_INIT; +						w->state = w->state >= FWS_R ? FWS_U : FWS_INIT;  					} else {  						RT6_TRACE("W %p adjusted by delnode 2, s=%d\n", w, w->state); -						w->state = w->state>=FWS_C ? FWS_U : FWS_INIT; +						w->state = w->state >= FWS_C ? FWS_U : FWS_INIT;  					}  				}  			} @@ -1314,7 +1354,7 @@ int fib6_del(struct rt6_info *rt, struct nl_info *info)  	struct rt6_info **rtp;  #if RT6_DEBUG >= 2 -	if (rt->dst.obsolete>0) { +	if (rt->dst.obsolete > 0) {  		WARN_ON(fn != NULL);  		return -ENOENT;  	} @@ -1334,7 +1374,7 @@ int fib6_del(struct rt6_info *rt, struct nl_info *info)  			pn = pn->parent;  		}  #endif -		fib6_prune_clones(info->nl_net, pn, rt); +		fib6_prune_clones(info->nl_net, pn);  	}  	/* @@ -1418,7 +1458,7 @@ static int fib6_walk_continue(struct fib6_walker_t *w)  				if (w->skip) {  					w->skip--; -					continue; +					goto skip;  				}  				err = w->func(w); @@ -1428,6 +1468,7 @@ static int fib6_walk_continue(struct fib6_walker_t *w)  				w->count++;  				continue;  			} +skip:  			w->state = FWS_U;  		case FWS_U:  			if (fn == w->root) @@ -1529,27 +1570,8 @@ static void fib6_clean_tree(struct net *net, struct fib6_node *root,  	fib6_walk(&c.w);  } -void fib6_clean_all_ro(struct net *net, int (*func)(struct rt6_info *, void *arg), -		    int prune, void *arg) -{ -	struct fib6_table *table; -	struct hlist_head *head; -	unsigned int h; - -	rcu_read_lock(); -	for (h = 0; h < FIB6_TABLE_HASHSZ; h++) { -		head = &net->ipv6.fib_table_hash[h]; -		hlist_for_each_entry_rcu(table, head, tb6_hlist) { -			read_lock_bh(&table->tb6_lock); -			fib6_clean_tree(net, &table->tb6_root, -					func, prune, arg); -			read_unlock_bh(&table->tb6_lock); -		} -	} -	rcu_read_unlock(); -}  void fib6_clean_all(struct net *net, int (*func)(struct rt6_info *, void *arg), -		    int prune, void *arg) +		    void *arg)  {  	struct fib6_table *table;  	struct hlist_head *head; @@ -1561,7 +1583,7 @@ void fib6_clean_all(struct net *net, int (*func)(struct rt6_info *, void *arg),  		hlist_for_each_entry_rcu(table, head, tb6_hlist) {  			write_lock_bh(&table->tb6_lock);  			fib6_clean_tree(net, &table->tb6_root, -					func, prune, arg); +					func, 0, arg);  			write_unlock_bh(&table->tb6_lock);  		}  	} @@ -1578,10 +1600,9 @@ static int fib6_prune_clone(struct rt6_info *rt, void *arg)  	return 0;  } -static void fib6_prune_clones(struct net *net, struct fib6_node *fn, -			      struct rt6_info *rt) +static void fib6_prune_clones(struct net *net, struct fib6_node *fn)  { -	fib6_clean_tree(net, fn, fib6_prune_clone, 1, rt); +	fib6_clean_tree(net, fn, fib6_prune_clone, 1, NULL);  }  /* @@ -1655,7 +1676,7 @@ void fib6_run_gc(unsigned long expires, struct net *net, bool force)  	gc_args.more = icmp6_dst_gc(); -	fib6_clean_all(net, fib6_age, 0, NULL); +	fib6_clean_all(net, fib6_age, NULL);  	now = jiffies;  	net->ipv6.ip6_rt_last_gc = now; @@ -1726,7 +1747,7 @@ out_rt6_stats:  	kfree(net->ipv6.rt6_stats);  out_timer:  	return -ENOMEM; - } +}  static void fib6_net_exit(struct net *net)  { @@ -1782,3 +1803,189 @@ void fib6_gc_cleanup(void)  	unregister_pernet_subsys(&fib6_net_ops);  	kmem_cache_destroy(fib6_node_kmem);  } + +#ifdef CONFIG_PROC_FS + +struct ipv6_route_iter { +	struct seq_net_private p; +	struct fib6_walker_t w; +	loff_t skip; +	struct fib6_table *tbl; +	__u32 sernum; +}; + +static int ipv6_route_seq_show(struct seq_file *seq, void *v) +{ +	struct rt6_info *rt = v; +	struct ipv6_route_iter *iter = seq->private; + +	seq_printf(seq, "%pi6 %02x ", &rt->rt6i_dst.addr, rt->rt6i_dst.plen); + +#ifdef CONFIG_IPV6_SUBTREES +	seq_printf(seq, "%pi6 %02x ", &rt->rt6i_src.addr, rt->rt6i_src.plen); +#else +	seq_puts(seq, "00000000000000000000000000000000 00 "); +#endif +	if (rt->rt6i_flags & RTF_GATEWAY) +		seq_printf(seq, "%pi6", &rt->rt6i_gateway); +	else +		seq_puts(seq, "00000000000000000000000000000000"); + +	seq_printf(seq, " %08x %08x %08x %08x %8s\n", +		   rt->rt6i_metric, atomic_read(&rt->dst.__refcnt), +		   rt->dst.__use, rt->rt6i_flags, +		   rt->dst.dev ? rt->dst.dev->name : ""); +	iter->w.leaf = NULL; +	return 0; +} + +static int ipv6_route_yield(struct fib6_walker_t *w) +{ +	struct ipv6_route_iter *iter = w->args; + +	if (!iter->skip) +		return 1; + +	do { +		iter->w.leaf = iter->w.leaf->dst.rt6_next; +		iter->skip--; +		if (!iter->skip && iter->w.leaf) +			return 1; +	} while (iter->w.leaf); + +	return 0; +} + +static void ipv6_route_seq_setup_walk(struct ipv6_route_iter *iter) +{ +	memset(&iter->w, 0, sizeof(iter->w)); +	iter->w.func = ipv6_route_yield; +	iter->w.root = &iter->tbl->tb6_root; +	iter->w.state = FWS_INIT; +	iter->w.node = iter->w.root; +	iter->w.args = iter; +	iter->sernum = iter->w.root->fn_sernum; +	INIT_LIST_HEAD(&iter->w.lh); +	fib6_walker_link(&iter->w); +} + +static struct fib6_table *ipv6_route_seq_next_table(struct fib6_table *tbl, +						    struct net *net) +{ +	unsigned int h; +	struct hlist_node *node; + +	if (tbl) { +		h = (tbl->tb6_id & (FIB6_TABLE_HASHSZ - 1)) + 1; +		node = rcu_dereference_bh(hlist_next_rcu(&tbl->tb6_hlist)); +	} else { +		h = 0; +		node = NULL; +	} + +	while (!node && h < FIB6_TABLE_HASHSZ) { +		node = rcu_dereference_bh( +			hlist_first_rcu(&net->ipv6.fib_table_hash[h++])); +	} +	return hlist_entry_safe(node, struct fib6_table, tb6_hlist); +} + +static void ipv6_route_check_sernum(struct ipv6_route_iter *iter) +{ +	if (iter->sernum != iter->w.root->fn_sernum) { +		iter->sernum = iter->w.root->fn_sernum; +		iter->w.state = FWS_INIT; +		iter->w.node = iter->w.root; +		WARN_ON(iter->w.skip); +		iter->w.skip = iter->w.count; +	} +} + +static void *ipv6_route_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ +	int r; +	struct rt6_info *n; +	struct net *net = seq_file_net(seq); +	struct ipv6_route_iter *iter = seq->private; + +	if (!v) +		goto iter_table; + +	n = ((struct rt6_info *)v)->dst.rt6_next; +	if (n) { +		++*pos; +		return n; +	} + +iter_table: +	ipv6_route_check_sernum(iter); +	read_lock(&iter->tbl->tb6_lock); +	r = fib6_walk_continue(&iter->w); +	read_unlock(&iter->tbl->tb6_lock); +	if (r > 0) { +		if (v) +			++*pos; +		return iter->w.leaf; +	} else if (r < 0) { +		fib6_walker_unlink(&iter->w); +		return NULL; +	} +	fib6_walker_unlink(&iter->w); + +	iter->tbl = ipv6_route_seq_next_table(iter->tbl, net); +	if (!iter->tbl) +		return NULL; + +	ipv6_route_seq_setup_walk(iter); +	goto iter_table; +} + +static void *ipv6_route_seq_start(struct seq_file *seq, loff_t *pos) +	__acquires(RCU_BH) +{ +	struct net *net = seq_file_net(seq); +	struct ipv6_route_iter *iter = seq->private; + +	rcu_read_lock_bh(); +	iter->tbl = ipv6_route_seq_next_table(NULL, net); +	iter->skip = *pos; + +	if (iter->tbl) { +		ipv6_route_seq_setup_walk(iter); +		return ipv6_route_seq_next(seq, NULL, pos); +	} else { +		return NULL; +	} +} + +static bool ipv6_route_iter_active(struct ipv6_route_iter *iter) +{ +	struct fib6_walker_t *w = &iter->w; +	return w->node && !(w->state == FWS_U && w->node == w->root); +} + +static void ipv6_route_seq_stop(struct seq_file *seq, void *v) +	__releases(RCU_BH) +{ +	struct ipv6_route_iter *iter = seq->private; + +	if (ipv6_route_iter_active(iter)) +		fib6_walker_unlink(&iter->w); + +	rcu_read_unlock_bh(); +} + +static const struct seq_operations ipv6_route_seq_ops = { +	.start	= ipv6_route_seq_start, +	.next	= ipv6_route_seq_next, +	.stop	= ipv6_route_seq_stop, +	.show	= ipv6_route_seq_show +}; + +int ipv6_route_open(struct inode *inode, struct file *file) +{ +	return seq_open_net(inode, file, &ipv6_route_seq_ops, +			    sizeof(struct ipv6_route_iter)); +} + +#endif /* CONFIG_PROC_FS */  | 
