diff options
Diffstat (limited to 'net/ipv4/fib_semantics.c')
| -rw-r--r-- | net/ipv4/fib_semantics.c | 444 | 
1 files changed, 269 insertions, 175 deletions
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 12d3dc3df1b..b10cd43a472 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -14,7 +14,6 @@   */  #include <asm/uaccess.h> -#include <asm/system.h>  #include <linux/bitops.h>  #include <linux/types.h>  #include <linux/kernel.h> @@ -49,7 +48,7 @@  static DEFINE_SPINLOCK(fib_info_lock);  static struct hlist_head *fib_info_hash;  static struct hlist_head *fib_info_laddrhash; -static unsigned int fib_hash_size; +static unsigned int fib_info_hash_size;  static unsigned int fib_info_cnt;  #define DEVINDEX_HASHBITS 8 @@ -90,11 +89,7 @@ static DEFINE_SPINLOCK(fib_multipath_lock);  #define endfor_nexthops(fi) } -static const struct -{ -	int	error; -	u8	scope; -} fib_props[RTN_MAX + 1] = { +const struct fib_prop fib_props[RTN_MAX + 1] = {  	[RTN_UNSPEC] = {  		.error	= 0,  		.scope	= RT_SCOPE_NOWHERE, @@ -145,29 +140,96 @@ static const struct  	},  }; +static void rt_fibinfo_free(struct rtable __rcu **rtp) +{ +	struct rtable *rt = rcu_dereference_protected(*rtp, 1); -/* Release a nexthop info record */ +	if (!rt) +		return; + +	/* Not even needed : RCU_INIT_POINTER(*rtp, NULL); +	 * because we waited an RCU grace period before calling +	 * free_fib_info_rcu() +	 */ + +	dst_free(&rt->dst); +} +static void free_nh_exceptions(struct fib_nh *nh) +{ +	struct fnhe_hash_bucket *hash = nh->nh_exceptions; +	int i; + +	for (i = 0; i < FNHE_HASH_SIZE; i++) { +		struct fib_nh_exception *fnhe; + +		fnhe = rcu_dereference_protected(hash[i].chain, 1); +		while (fnhe) { +			struct fib_nh_exception *next; +			 +			next = rcu_dereference_protected(fnhe->fnhe_next, 1); + +			rt_fibinfo_free(&fnhe->fnhe_rth_input); +			rt_fibinfo_free(&fnhe->fnhe_rth_output); + +			kfree(fnhe); + +			fnhe = next; +		} +	} +	kfree(hash); +} + +static void rt_fibinfo_free_cpus(struct rtable __rcu * __percpu *rtp) +{ +	int cpu; + +	if (!rtp) +		return; + +	for_each_possible_cpu(cpu) { +		struct rtable *rt; + +		rt = rcu_dereference_protected(*per_cpu_ptr(rtp, cpu), 1); +		if (rt) +			dst_free(&rt->dst); +	} +	free_percpu(rtp); +} + +/* Release a nexthop info record */  static void free_fib_info_rcu(struct rcu_head *head)  {  	struct fib_info *fi = container_of(head, struct fib_info, rcu); +	change_nexthops(fi) { +		if (nexthop_nh->nh_dev) +			dev_put(nexthop_nh->nh_dev); +		if (nexthop_nh->nh_exceptions) +			free_nh_exceptions(nexthop_nh); +		rt_fibinfo_free_cpus(nexthop_nh->nh_pcpu_rth_output); +		rt_fibinfo_free(&nexthop_nh->nh_rth_input); +	} endfor_nexthops(fi); + +	release_net(fi->fib_net); +	if (fi->fib_metrics != (u32 *) dst_default_metrics) +		kfree(fi->fib_metrics);  	kfree(fi);  }  void free_fib_info(struct fib_info *fi)  {  	if (fi->fib_dead == 0) { -		pr_warning("Freeing alive fib_info %p\n", fi); +		pr_warn("Freeing alive fib_info %p\n", fi);  		return;  	} +	fib_info_cnt--; +#ifdef CONFIG_IP_ROUTE_CLASSID  	change_nexthops(fi) { -		if (nexthop_nh->nh_dev) -			dev_put(nexthop_nh->nh_dev); -		nexthop_nh->nh_dev = NULL; +		if (nexthop_nh->nh_tclassid) +			fi->fib_net->ipv4.fib_num_tclassid_users--;  	} endfor_nexthops(fi); -	fib_info_cnt--; -	release_net(fi->fib_net); +#endif  	call_rcu(&fi->rcu, free_fib_info_rcu);  } @@ -200,7 +262,7 @@ static inline int nh_comp(const struct fib_info *fi, const struct fib_info *ofi)  #ifdef CONFIG_IP_ROUTE_MULTIPATH  		    nh->nh_weight != onh->nh_weight ||  #endif -#ifdef CONFIG_NET_CLS_ROUTE +#ifdef CONFIG_IP_ROUTE_CLASSID  		    nh->nh_tclassid != onh->nh_tclassid ||  #endif  		    ((nh->nh_flags ^ onh->nh_flags) & ~RTNH_F_DEAD)) @@ -221,10 +283,10 @@ static inline unsigned int fib_devindex_hashfn(unsigned int val)  static inline unsigned int fib_info_hashfn(const struct fib_info *fi)  { -	unsigned int mask = (fib_hash_size - 1); +	unsigned int mask = (fib_info_hash_size - 1);  	unsigned int val = fi->fib_nhs; -	val ^= fi->fib_protocol; +	val ^= (fi->fib_protocol << 8) | fi->fib_scope;  	val ^= (__force u32)fi->fib_prefsrc;  	val ^= fi->fib_priority;  	for_nexthops(fi) { @@ -237,23 +299,24 @@ static inline unsigned int fib_info_hashfn(const struct fib_info *fi)  static struct fib_info *fib_find_info(const struct fib_info *nfi)  {  	struct hlist_head *head; -	struct hlist_node *node;  	struct fib_info *fi;  	unsigned int hash;  	hash = fib_info_hashfn(nfi);  	head = &fib_info_hash[hash]; -	hlist_for_each_entry(fi, node, head, fib_hash) { +	hlist_for_each_entry(fi, head, fib_hash) {  		if (!net_eq(fi->fib_net, nfi->fib_net))  			continue;  		if (fi->fib_nhs != nfi->fib_nhs)  			continue;  		if (nfi->fib_protocol == fi->fib_protocol && +		    nfi->fib_scope == fi->fib_scope &&  		    nfi->fib_prefsrc == fi->fib_prefsrc &&  		    nfi->fib_priority == fi->fib_priority && +		    nfi->fib_type == fi->fib_type &&  		    memcmp(nfi->fib_metrics, fi->fib_metrics, -			   sizeof(fi->fib_metrics)) == 0 && +			   sizeof(u32) * RTAX_MAX) == 0 &&  		    ((nfi->fib_flags ^ fi->fib_flags) & ~RTNH_F_DEAD) == 0 &&  		    (nfi->fib_nhs == 0 || nh_comp(fi, nfi) == 0))  			return fi; @@ -268,7 +331,6 @@ static struct fib_info *fib_find_info(const struct fib_info *nfi)  int ip_fib_check_default(__be32 gw, struct net_device *dev)  {  	struct hlist_head *head; -	struct hlist_node *node;  	struct fib_nh *nh;  	unsigned int hash; @@ -276,7 +338,7 @@ int ip_fib_check_default(__be32 gw, struct net_device *dev)  	hash = fib_devindex_hashfn(dev->ifindex);  	head = &fib_info_devhash[hash]; -	hlist_for_each_entry(nh, node, head, nh_hash) { +	hlist_for_each_entry(nh, head, nh_hash) {  		if (nh->nh_dev == dev &&  		    nh->nh_gw == gw &&  		    !(nh->nh_flags & RTNH_F_DEAD)) { @@ -318,7 +380,7 @@ static inline size_t fib_nlmsg_size(struct fib_info *fi)  }  void rtmsg_fib(int event, __be32 key, struct fib_alias *fa, -	       int dst_len, u32 tb_id, struct nl_info *info, +	       int dst_len, u32 tb_id, const struct nl_info *info,  	       unsigned int nlm_flags)  {  	struct sk_buff *skb; @@ -329,8 +391,8 @@ void rtmsg_fib(int event, __be32 key, struct fib_alias *fa,  	if (skb == NULL)  		goto errout; -	err = fib_dump_info(skb, info->pid, seq, event, tb_id, -			    fa->fa_type, fa->fa_scope, key, dst_len, +	err = fib_dump_info(skb, info->portid, seq, event, tb_id, +			    fa->fa_type, key, dst_len,  			    fa->fa_tos, fa->fa_info, nlm_flags);  	if (err < 0) {  		/* -EMSGSIZE implies BUG in fib_nlmsg_size() */ @@ -338,7 +400,7 @@ void rtmsg_fib(int event, __be32 key, struct fib_alias *fa,  		kfree_skb(skb);  		goto errout;  	} -	rtnl_notify(skb, info->nl_net, info->pid, RTNLGRP_IPV4_ROUTE, +	rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_IPV4_ROUTE,  		    info->nlh, GFP_KERNEL);  	return;  errout: @@ -364,8 +426,9 @@ struct fib_alias *fib_find_alias(struct list_head *fah, u8 tos, u32 prio)  	return NULL;  } -int fib_detect_death(struct fib_info *fi, int order, -		     struct fib_info **last_resort, int *last_idx, int dflt) +static int fib_detect_death(struct fib_info *fi, int order, +			    struct fib_info **last_resort, int *last_idx, +			    int dflt)  {  	struct neighbour *n;  	int state = NUD_NONE; @@ -422,9 +485,11 @@ static int fib_get_nhs(struct fib_info *fi, struct rtnexthop *rtnh,  			nla = nla_find(attrs, attrlen, RTA_GATEWAY);  			nexthop_nh->nh_gw = nla ? nla_get_be32(nla) : 0; -#ifdef CONFIG_NET_CLS_ROUTE +#ifdef CONFIG_IP_ROUTE_CLASSID  			nla = nla_find(attrs, attrlen, RTA_FLOW);  			nexthop_nh->nh_tclassid = nla ? nla_get_u32(nla) : 0; +			if (nexthop_nh->nh_tclassid) +				fi->fib_net->ipv4.fib_num_tclassid_users++;  #endif  		} @@ -476,7 +541,7 @@ int fib_nh_match(struct fib_config *cfg, struct fib_info *fi)  			nla = nla_find(attrs, attrlen, RTA_GATEWAY);  			if (nla && nla_get_be32(nla) != nh->nh_gw)  				return 1; -#ifdef CONFIG_NET_CLS_ROUTE +#ifdef CONFIG_IP_ROUTE_CLASSID  			nla = nla_find(attrs, attrlen, RTA_FLOW);  			if (nla && nla_get_u32(nla) != nh->nh_tclassid)  				return 1; @@ -562,16 +627,17 @@ static int fib_check_nh(struct fib_config *cfg, struct fib_info *fi,  		}  		rcu_read_lock();  		{ -			struct flowi fl = { -				.fl4_dst = nh->nh_gw, -				.fl4_scope = cfg->fc_scope + 1, -				.oif = nh->nh_oif, +			struct flowi4 fl4 = { +				.daddr = nh->nh_gw, +				.flowi4_scope = cfg->fc_scope + 1, +				.flowi4_oif = nh->nh_oif, +				.flowi4_iif = LOOPBACK_IFINDEX,  			};  			/* It is not necessary, but requires a bit of thinking */ -			if (fl.fl4_scope < RT_SCOPE_LINK) -				fl.fl4_scope = RT_SCOPE_LINK; -			err = fib_lookup(net, &fl, &res); +			if (fl4.flowi4_scope < RT_SCOPE_LINK) +				fl4.flowi4_scope = RT_SCOPE_LINK; +			err = fib_lookup(net, &fl4, &res);  			if (err) {  				rcu_read_unlock();  				return err; @@ -613,14 +679,14 @@ out:  static inline unsigned int fib_laddr_hashfn(__be32 val)  { -	unsigned int mask = (fib_hash_size - 1); +	unsigned int mask = (fib_info_hash_size - 1);  	return ((__force u32)val ^  		((__force u32)val >> 7) ^  		((__force u32)val >> 14)) & mask;  } -static struct hlist_head *fib_hash_alloc(int bytes) +static struct hlist_head *fib_info_hash_alloc(int bytes)  {  	if (bytes <= PAGE_SIZE)  		return kzalloc(bytes, GFP_KERNEL); @@ -630,7 +696,7 @@ static struct hlist_head *fib_hash_alloc(int bytes)  					 get_order(bytes));  } -static void fib_hash_free(struct hlist_head *hash, int bytes) +static void fib_info_hash_free(struct hlist_head *hash, int bytes)  {  	if (!hash)  		return; @@ -641,25 +707,25 @@ static void fib_hash_free(struct hlist_head *hash, int bytes)  		free_pages((unsigned long) hash, get_order(bytes));  } -static void fib_hash_move(struct hlist_head *new_info_hash, -			  struct hlist_head *new_laddrhash, -			  unsigned int new_size) +static void fib_info_hash_move(struct hlist_head *new_info_hash, +			       struct hlist_head *new_laddrhash, +			       unsigned int new_size)  {  	struct hlist_head *old_info_hash, *old_laddrhash; -	unsigned int old_size = fib_hash_size; +	unsigned int old_size = fib_info_hash_size;  	unsigned int i, bytes;  	spin_lock_bh(&fib_info_lock);  	old_info_hash = fib_info_hash;  	old_laddrhash = fib_info_laddrhash; -	fib_hash_size = new_size; +	fib_info_hash_size = new_size;  	for (i = 0; i < old_size; i++) {  		struct hlist_head *head = &fib_info_hash[i]; -		struct hlist_node *node, *n; +		struct hlist_node *n;  		struct fib_info *fi; -		hlist_for_each_entry_safe(fi, node, n, head, fib_hash) { +		hlist_for_each_entry_safe(fi, n, head, fib_hash) {  			struct hlist_head *dest;  			unsigned int new_hash; @@ -674,10 +740,10 @@ static void fib_hash_move(struct hlist_head *new_info_hash,  	for (i = 0; i < old_size; i++) {  		struct hlist_head *lhead = &fib_info_laddrhash[i]; -		struct hlist_node *node, *n; +		struct hlist_node *n;  		struct fib_info *fi; -		hlist_for_each_entry_safe(fi, node, n, lhead, fib_lhash) { +		hlist_for_each_entry_safe(fi, n, lhead, fib_lhash) {  			struct hlist_head *ldest;  			unsigned int new_hash; @@ -693,8 +759,18 @@ static void fib_hash_move(struct hlist_head *new_info_hash,  	spin_unlock_bh(&fib_info_lock);  	bytes = old_size * sizeof(struct hlist_head *); -	fib_hash_free(old_info_hash, bytes); -	fib_hash_free(old_laddrhash, bytes); +	fib_info_hash_free(old_info_hash, bytes); +	fib_info_hash_free(old_laddrhash, bytes); +} + +__be32 fib_info_update_nh_saddr(struct net *net, struct fib_nh *nh) +{ +	nh->nh_saddr = inet_select_addr(nh->nh_dev, +					nh->nh_gw, +					nh->nh_parent->fib_scope); +	nh->nh_saddr_genid = atomic_read(&net->ipv4.dev_addr_genid); + +	return nh->nh_saddr;  }  struct fib_info *fib_create_info(struct fib_config *cfg) @@ -705,6 +781,9 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  	int nhs = 1;  	struct net *net = cfg->fc_nlinfo.nl_net; +	if (cfg->fc_type > RTN_MAX) +		goto err_inval; +  	/* Fast check to catch the most weird cases */  	if (fib_props[cfg->fc_type].scope > cfg->fc_scope)  		goto err_inval; @@ -718,24 +797,24 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  #endif  	err = -ENOBUFS; -	if (fib_info_cnt >= fib_hash_size) { -		unsigned int new_size = fib_hash_size << 1; +	if (fib_info_cnt >= fib_info_hash_size) { +		unsigned int new_size = fib_info_hash_size << 1;  		struct hlist_head *new_info_hash;  		struct hlist_head *new_laddrhash;  		unsigned int bytes;  		if (!new_size) -			new_size = 1; +			new_size = 16;  		bytes = new_size * sizeof(struct hlist_head *); -		new_info_hash = fib_hash_alloc(bytes); -		new_laddrhash = fib_hash_alloc(bytes); +		new_info_hash = fib_info_hash_alloc(bytes); +		new_laddrhash = fib_info_hash_alloc(bytes);  		if (!new_info_hash || !new_laddrhash) { -			fib_hash_free(new_info_hash, bytes); -			fib_hash_free(new_laddrhash, bytes); +			fib_info_hash_free(new_info_hash, bytes); +			fib_info_hash_free(new_laddrhash, bytes);  		} else -			fib_hash_move(new_info_hash, new_laddrhash, new_size); +			fib_info_hash_move(new_info_hash, new_laddrhash, new_size); -		if (!fib_hash_size) +		if (!fib_info_hash_size)  			goto failure;  	} @@ -743,16 +822,27 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  	if (fi == NULL)  		goto failure;  	fib_info_cnt++; +	if (cfg->fc_mx) { +		fi->fib_metrics = kzalloc(sizeof(u32) * RTAX_MAX, GFP_KERNEL); +		if (!fi->fib_metrics) +			goto failure; +	} else +		fi->fib_metrics = (u32 *) dst_default_metrics;  	fi->fib_net = hold_net(net);  	fi->fib_protocol = cfg->fc_protocol; +	fi->fib_scope = cfg->fc_scope;  	fi->fib_flags = cfg->fc_flags;  	fi->fib_priority = cfg->fc_priority;  	fi->fib_prefsrc = cfg->fc_prefsrc; +	fi->fib_type = cfg->fc_type;  	fi->fib_nhs = nhs;  	change_nexthops(fi) {  		nexthop_nh->nh_parent = fi; +		nexthop_nh->nh_pcpu_rth_output = alloc_percpu(struct rtable __rcu *); +		if (!nexthop_nh->nh_pcpu_rth_output) +			goto failure;  	} endfor_nexthops(fi)  	if (cfg->fc_mx) { @@ -763,9 +853,16 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  			int type = nla_type(nla);  			if (type) { +				u32 val; +  				if (type > RTAX_MAX)  					goto err_inval; -				fi->fib_metrics[type - 1] = nla_get_u32(nla); +				val = nla_get_u32(nla); +				if (type == RTAX_ADVMSS && val > 65535 - 40) +					val = 65535 - 40; +				if (type == RTAX_MTU && val > 65535 - 15) +					val = 65535 - 15; +				fi->fib_metrics[type - 1] = val;  			}  		}  	} @@ -779,7 +876,7 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  			goto err_inval;  		if (cfg->fc_gw && fi->fib_nh->nh_gw != cfg->fc_gw)  			goto err_inval; -#ifdef CONFIG_NET_CLS_ROUTE +#ifdef CONFIG_IP_ROUTE_CLASSID  		if (cfg->fc_flow && fi->fib_nh->nh_tclassid != cfg->fc_flow)  			goto err_inval;  #endif @@ -792,8 +889,10 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  		nh->nh_oif = cfg->fc_oif;  		nh->nh_gw = cfg->fc_gw;  		nh->nh_flags = cfg->fc_flags; -#ifdef CONFIG_NET_CLS_ROUTE +#ifdef CONFIG_IP_ROUTE_CLASSID  		nh->nh_tclassid = cfg->fc_flow; +		if (nh->nh_tclassid) +			fi->fib_net->ipv4.fib_num_tclassid_users++;  #endif  #ifdef CONFIG_IP_ROUTE_MULTIPATH  		nh->nh_weight = 1; @@ -804,6 +903,17 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  		if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)  			goto err_inval;  		goto link_it; +	} else { +		switch (cfg->fc_type) { +		case RTN_UNICAST: +		case RTN_LOCAL: +		case RTN_BROADCAST: +		case RTN_ANYCAST: +		case RTN_MULTICAST: +			break; +		default: +			goto err_inval; +		}  	}  	if (cfg->fc_scope > RT_SCOPE_HOST) @@ -835,6 +945,10 @@ struct fib_info *fib_create_info(struct fib_config *cfg)  				goto err_inval;  	} +	change_nexthops(fi) { +		fib_info_update_nh_saddr(net, nexthop_nh); +	} endfor_nexthops(fi) +  link_it:  	ofi = fib_find_info(fi);  	if (ofi) { @@ -880,92 +994,14 @@ failure:  	return ERR_PTR(err);  } -/* Note! fib_semantic_match intentionally uses  RCU list functions. */ -int fib_semantic_match(struct list_head *head, const struct flowi *flp, -		       struct fib_result *res, int prefixlen, int fib_flags) -{ -	struct fib_alias *fa; -	int nh_sel = 0; - -	list_for_each_entry_rcu(fa, head, fa_list) { -		int err; - -		if (fa->fa_tos && -		    fa->fa_tos != flp->fl4_tos) -			continue; - -		if (fa->fa_scope < flp->fl4_scope) -			continue; - -		fib_alias_accessed(fa); - -		err = fib_props[fa->fa_type].error; -		if (err == 0) { -			struct fib_info *fi = fa->fa_info; - -			if (fi->fib_flags & RTNH_F_DEAD) -				continue; - -			switch (fa->fa_type) { -			case RTN_UNICAST: -			case RTN_LOCAL: -			case RTN_BROADCAST: -			case RTN_ANYCAST: -			case RTN_MULTICAST: -				for_nexthops(fi) { -					if (nh->nh_flags & RTNH_F_DEAD) -						continue; -					if (!flp->oif || flp->oif == nh->nh_oif) -						break; -				} -#ifdef CONFIG_IP_ROUTE_MULTIPATH -				if (nhsel < fi->fib_nhs) { -					nh_sel = nhsel; -					goto out_fill_res; -				} -#else -				if (nhsel < 1) -					goto out_fill_res; -#endif -				endfor_nexthops(fi); -				continue; - -			default: -				pr_warning("fib_semantic_match bad type %#x\n", -					   fa->fa_type); -				return -EINVAL; -			} -		} -		return err; -	} -	return 1; - -out_fill_res: -	res->prefixlen = prefixlen; -	res->nh_sel = nh_sel; -	res->type = fa->fa_type; -	res->scope = fa->fa_scope; -	res->fi = fa->fa_info; -	if (!(fib_flags & FIB_LOOKUP_NOREF)) -		atomic_inc(&res->fi->fib_clntref); -	return 0; -} - -/* Find appropriate source address to this destination */ - -__be32 __fib_res_prefsrc(struct fib_result *res) -{ -	return inet_select_addr(FIB_RES_DEV(*res), FIB_RES_GW(*res), res->scope); -} - -int fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event, -		  u32 tb_id, u8 type, u8 scope, __be32 dst, int dst_len, u8 tos, +int fib_dump_info(struct sk_buff *skb, u32 portid, u32 seq, int event, +		  u32 tb_id, u8 type, __be32 dst, int dst_len, u8 tos,  		  struct fib_info *fi, unsigned int flags)  {  	struct nlmsghdr *nlh;  	struct rtmsg *rtm; -	nlh = nlmsg_put(skb, pid, seq, event, sizeof(*rtm), flags); +	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*rtm), flags);  	if (nlh == NULL)  		return -EMSGSIZE; @@ -978,33 +1014,36 @@ int fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,  		rtm->rtm_table = tb_id;  	else  		rtm->rtm_table = RT_TABLE_COMPAT; -	NLA_PUT_U32(skb, RTA_TABLE, tb_id); +	if (nla_put_u32(skb, RTA_TABLE, tb_id)) +		goto nla_put_failure;  	rtm->rtm_type = type;  	rtm->rtm_flags = fi->fib_flags; -	rtm->rtm_scope = scope; +	rtm->rtm_scope = fi->fib_scope;  	rtm->rtm_protocol = fi->fib_protocol; -	if (rtm->rtm_dst_len) -		NLA_PUT_BE32(skb, RTA_DST, dst); - -	if (fi->fib_priority) -		NLA_PUT_U32(skb, RTA_PRIORITY, fi->fib_priority); - +	if (rtm->rtm_dst_len && +	    nla_put_be32(skb, RTA_DST, dst)) +		goto nla_put_failure; +	if (fi->fib_priority && +	    nla_put_u32(skb, RTA_PRIORITY, fi->fib_priority)) +		goto nla_put_failure;  	if (rtnetlink_put_metrics(skb, fi->fib_metrics) < 0)  		goto nla_put_failure; -	if (fi->fib_prefsrc) -		NLA_PUT_BE32(skb, RTA_PREFSRC, fi->fib_prefsrc); - +	if (fi->fib_prefsrc && +	    nla_put_be32(skb, RTA_PREFSRC, fi->fib_prefsrc)) +		goto nla_put_failure;  	if (fi->fib_nhs == 1) { -		if (fi->fib_nh->nh_gw) -			NLA_PUT_BE32(skb, RTA_GATEWAY, fi->fib_nh->nh_gw); - -		if (fi->fib_nh->nh_oif) -			NLA_PUT_U32(skb, RTA_OIF, fi->fib_nh->nh_oif); -#ifdef CONFIG_NET_CLS_ROUTE -		if (fi->fib_nh[0].nh_tclassid) -			NLA_PUT_U32(skb, RTA_FLOW, fi->fib_nh[0].nh_tclassid); +		if (fi->fib_nh->nh_gw && +		    nla_put_be32(skb, RTA_GATEWAY, fi->fib_nh->nh_gw)) +			goto nla_put_failure; +		if (fi->fib_nh->nh_oif && +		    nla_put_u32(skb, RTA_OIF, fi->fib_nh->nh_oif)) +			goto nla_put_failure; +#ifdef CONFIG_IP_ROUTE_CLASSID +		if (fi->fib_nh[0].nh_tclassid && +		    nla_put_u32(skb, RTA_FLOW, fi->fib_nh[0].nh_tclassid)) +			goto nla_put_failure;  #endif  	}  #ifdef CONFIG_IP_ROUTE_MULTIPATH @@ -1025,11 +1064,13 @@ int fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,  			rtnh->rtnh_hops = nh->nh_weight - 1;  			rtnh->rtnh_ifindex = nh->nh_oif; -			if (nh->nh_gw) -				NLA_PUT_BE32(skb, RTA_GATEWAY, nh->nh_gw); -#ifdef CONFIG_NET_CLS_ROUTE -			if (nh->nh_tclassid) -				NLA_PUT_U32(skb, RTA_FLOW, nh->nh_tclassid); +			if (nh->nh_gw && +			    nla_put_be32(skb, RTA_GATEWAY, nh->nh_gw)) +				goto nla_put_failure; +#ifdef CONFIG_IP_ROUTE_CLASSID +			if (nh->nh_tclassid && +			    nla_put_u32(skb, RTA_FLOW, nh->nh_tclassid)) +				goto nla_put_failure;  #endif  			/* length of rtnetlink header + attributes */  			rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *) rtnh; @@ -1056,13 +1097,12 @@ int fib_sync_down_addr(struct net *net, __be32 local)  	int ret = 0;  	unsigned int hash = fib_laddr_hashfn(local);  	struct hlist_head *head = &fib_info_laddrhash[hash]; -	struct hlist_node *node;  	struct fib_info *fi;  	if (fib_info_laddrhash == NULL || local == 0)  		return 0; -	hlist_for_each_entry(fi, node, head, fib_lhash) { +	hlist_for_each_entry(fi, head, fib_lhash) {  		if (!net_eq(fi->fib_net, net))  			continue;  		if (fi->fib_prefsrc == local) { @@ -1080,13 +1120,12 @@ int fib_sync_down_dev(struct net_device *dev, int force)  	struct fib_info *prev_fi = NULL;  	unsigned int hash = fib_devindex_hashfn(dev->ifindex);  	struct hlist_head *head = &fib_info_devhash[hash]; -	struct hlist_node *node;  	struct fib_nh *nh;  	if (force)  		scope = -1; -	hlist_for_each_entry(nh, node, head, nh_hash) { +	hlist_for_each_entry(nh, head, nh_hash) {  		struct fib_info *fi = nh->nh_parent;  		int dead; @@ -1125,6 +1164,62 @@ int fib_sync_down_dev(struct net_device *dev, int force)  	return ret;  } +/* Must be invoked inside of an RCU protected region.  */ +void fib_select_default(struct fib_result *res) +{ +	struct fib_info *fi = NULL, *last_resort = NULL; +	struct list_head *fa_head = res->fa_head; +	struct fib_table *tb = res->table; +	int order = -1, last_idx = -1; +	struct fib_alias *fa; + +	list_for_each_entry_rcu(fa, fa_head, fa_list) { +		struct fib_info *next_fi = fa->fa_info; + +		if (next_fi->fib_scope != res->scope || +		    fa->fa_type != RTN_UNICAST) +			continue; + +		if (next_fi->fib_priority > res->fi->fib_priority) +			break; +		if (!next_fi->fib_nh[0].nh_gw || +		    next_fi->fib_nh[0].nh_scope != RT_SCOPE_LINK) +			continue; + +		fib_alias_accessed(fa); + +		if (fi == NULL) { +			if (next_fi != res->fi) +				break; +		} else if (!fib_detect_death(fi, order, &last_resort, +					     &last_idx, tb->tb_default)) { +			fib_result_assign(res, fi); +			tb->tb_default = order; +			goto out; +		} +		fi = next_fi; +		order++; +	} + +	if (order <= 0 || fi == NULL) { +		tb->tb_default = -1; +		goto out; +	} + +	if (!fib_detect_death(fi, order, &last_resort, &last_idx, +				tb->tb_default)) { +		fib_result_assign(res, fi); +		tb->tb_default = order; +		goto out; +	} + +	if (last_idx >= 0) +		fib_result_assign(res, last_resort); +	tb->tb_default = last_idx; +out: +	return; +} +  #ifdef CONFIG_IP_ROUTE_MULTIPATH  /* @@ -1136,7 +1231,6 @@ int fib_sync_up(struct net_device *dev)  	struct fib_info *prev_fi;  	unsigned int hash;  	struct hlist_head *head; -	struct hlist_node *node;  	struct fib_nh *nh;  	int ret; @@ -1148,7 +1242,7 @@ int fib_sync_up(struct net_device *dev)  	head = &fib_info_devhash[hash];  	ret = 0; -	hlist_for_each_entry(nh, node, head, nh_hash) { +	hlist_for_each_entry(nh, head, nh_hash) {  		struct fib_info *fi = nh->nh_parent;  		int alive; @@ -1189,7 +1283,7 @@ int fib_sync_up(struct net_device *dev)   * The algorithm is suboptimal, but it provides really   * fair weighted route distribution.   */ -void fib_select_multipath(const struct flowi *flp, struct fib_result *res) +void fib_select_multipath(struct fib_result *res)  {  	struct fib_info *fi = res->fi;  	int w;  | 
