2273 lines · 52.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * INET		An implementation of the TCP/IP protocol suite for the LINUX *		operating system.  INET is implemented using the  BSD Socket *		interface as the means of communication with the user level. * *		IPv4 Forwarding Information Base: semantics. * * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> */ #include <linux/uaccess.h>#include <linux/bitops.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/jiffies.h>#include <linux/mm.h>#include <linux/string.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/errno.h>#include <linux/in.h>#include <linux/inet.h>#include <linux/inetdevice.h>#include <linux/netdevice.h>#include <linux/if_arp.h>#include <linux/proc_fs.h>#include <linux/skbuff.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/netlink.h>#include <linux/hash.h>#include <linux/nospec.h> #include <net/arp.h>#include <net/inet_dscp.h>#include <net/ip.h>#include <net/protocol.h>#include <net/route.h>#include <net/tcp.h>#include <net/sock.h>#include <net/ip_fib.h>#include <net/ip6_fib.h>#include <net/nexthop.h>#include <net/netlink.h>#include <net/rtnh.h>#include <net/lwtunnel.h>#include <net/fib_notifier.h>#include <net/addrconf.h> #include "fib_lookup.h" /* for_nexthops and change_nexthops only used when nexthop object * is not set in a fib_info. The logic within can reference fib_nh. */#ifdef CONFIG_IP_ROUTE_MULTIPATH #define for_nexthops(fi) {						\	int nhsel; const struct fib_nh *nh;				\	for (nhsel = 0, nh = (fi)->fib_nh;				\	     nhsel < fib_info_num_path((fi));				\	     nh++, nhsel++) #define change_nexthops(fi) {						\	int nhsel; struct fib_nh *nexthop_nh;				\	for (nhsel = 0,	nexthop_nh = (struct fib_nh *)((fi)->fib_nh);	\	     nhsel < fib_info_num_path((fi));				\	     nexthop_nh++, nhsel++) #else /* CONFIG_IP_ROUTE_MULTIPATH */ /* Hope, that gcc will optimize it to get rid of dummy loop */ #define for_nexthops(fi) {						\	int nhsel; const struct fib_nh *nh = (fi)->fib_nh;		\	for (nhsel = 0; nhsel < 1; nhsel++) #define change_nexthops(fi) {						\	int nhsel;							\	struct fib_nh *nexthop_nh = (struct fib_nh *)((fi)->fib_nh);	\	for (nhsel = 0; nhsel < 1; nhsel++) #endif /* CONFIG_IP_ROUTE_MULTIPATH */ #define endfor_nexthops(fi) }  const struct fib_prop fib_props[RTN_MAX + 1] = {	[RTN_UNSPEC] = {		.error	= 0,		.scope	= RT_SCOPE_NOWHERE,	},	[RTN_UNICAST] = {		.error	= 0,		.scope	= RT_SCOPE_UNIVERSE,	},	[RTN_LOCAL] = {		.error	= 0,		.scope	= RT_SCOPE_HOST,	},	[RTN_BROADCAST] = {		.error	= 0,		.scope	= RT_SCOPE_LINK,	},	[RTN_ANYCAST] = {		.error	= 0,		.scope	= RT_SCOPE_LINK,	},	[RTN_MULTICAST] = {		.error	= 0,		.scope	= RT_SCOPE_UNIVERSE,	},	[RTN_BLACKHOLE] = {		.error	= -EINVAL,		.scope	= RT_SCOPE_UNIVERSE,	},	[RTN_UNREACHABLE] = {		.error	= -EHOSTUNREACH,		.scope	= RT_SCOPE_UNIVERSE,	},	[RTN_PROHIBIT] = {		.error	= -EACCES,		.scope	= RT_SCOPE_UNIVERSE,	},	[RTN_THROW] = {		.error	= -EAGAIN,		.scope	= RT_SCOPE_UNIVERSE,	},	[RTN_NAT] = {		.error	= -EINVAL,		.scope	= RT_SCOPE_NOWHERE,	},	[RTN_XRESOLVE] = {		.error	= -EINVAL,		.scope	= RT_SCOPE_NOWHERE,	},}; static void rt_fibinfo_free(struct rtable __rcu **rtp){	struct rtable *rt = rcu_dereference_protected(*rtp, 1); 	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_dev_put(&rt->dst);	dst_release_immediate(&rt->dst);} static void free_nh_exceptions(struct fib_nh_common *nhc){	struct fnhe_hash_bucket *hash;	int i; 	hash = rcu_dereference_protected(nhc->nhc_exceptions, 1);	if (!hash)		return;	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_dev_put(&rt->dst);			dst_release_immediate(&rt->dst);		}	}	free_percpu(rtp);} void fib_nh_common_release(struct fib_nh_common *nhc){	netdev_put(nhc->nhc_dev, &nhc->nhc_dev_tracker);	lwtstate_put(nhc->nhc_lwtstate);	rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);	rt_fibinfo_free(&nhc->nhc_rth_input);	free_nh_exceptions(nhc);} void fib_nh_release(struct net *net, struct fib_nh *fib_nh){#ifdef CONFIG_IP_ROUTE_CLASSID	if (fib_nh->nh_tclassid)		atomic_dec(&net->ipv4.fib_num_tclassid_users);#endif	fib_nh_common_release(&fib_nh->nh_common);} /* 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); 	if (fi->nh) {		nexthop_put(fi->nh);	} else {		change_nexthops(fi) {			fib_nh_release(fi->fib_net, nexthop_nh);		} endfor_nexthops(fi);	} 	ip_fib_metrics_put(fi->fib_metrics); 	kfree(fi);} void free_fib_info(struct fib_info *fi){	if (fi->fib_dead == 0) {		pr_warn("Freeing alive fib_info %p\n", fi);		return;	} 	call_rcu_hurry(&fi->rcu, free_fib_info_rcu);}EXPORT_SYMBOL_GPL(free_fib_info); void fib_release_info(struct fib_info *fi){	ASSERT_RTNL();	if (fi && refcount_dec_and_test(&fi->fib_treeref)) {		hlist_del(&fi->fib_hash);		fi->fib_net->ipv4.fib_info_cnt--; 		if (fi->fib_prefsrc)			hlist_del(&fi->fib_lhash);		if (fi->nh) {			list_del(&fi->nh_list);		} else {			change_nexthops(fi) {				if (!nexthop_nh->fib_nh_dev)					continue;				hlist_del_rcu(&nexthop_nh->nh_hash);			} endfor_nexthops(fi)		}		/* Paired with READ_ONCE() from fib_table_lookup() */		WRITE_ONCE(fi->fib_dead, 1);		fib_info_put(fi);	}} static inline int nh_comp(struct fib_info *fi, struct fib_info *ofi){	const struct fib_nh *onh; 	if (fi->nh || ofi->nh)		return nexthop_cmp(fi->nh, ofi->nh) ? 0 : -1; 	if (ofi->fib_nhs == 0)		return 0; 	for_nexthops(fi) {		onh = fib_info_nh(ofi, nhsel); 		if (nh->fib_nh_oif != onh->fib_nh_oif ||		    nh->fib_nh_gw_family != onh->fib_nh_gw_family ||		    nh->fib_nh_scope != onh->fib_nh_scope ||#ifdef CONFIG_IP_ROUTE_MULTIPATH		    nh->fib_nh_weight != onh->fib_nh_weight ||#endif#ifdef CONFIG_IP_ROUTE_CLASSID		    nh->nh_tclassid != onh->nh_tclassid ||#endif		    lwtunnel_cmp_encap(nh->fib_nh_lws, onh->fib_nh_lws) ||		    ((nh->fib_nh_flags ^ onh->fib_nh_flags) & ~RTNH_COMPARE_MASK))			return -1; 		if (nh->fib_nh_gw_family == AF_INET &&		    nh->fib_nh_gw4 != onh->fib_nh_gw4)			return -1; 		if (nh->fib_nh_gw_family == AF_INET6 &&		    ipv6_addr_cmp(&nh->fib_nh_gw6, &onh->fib_nh_gw6))			return -1;	} endfor_nexthops(fi);	return 0;} static struct hlist_head *fib_nh_head(struct net_device *dev){	return &dev->fib_nh_head;} static unsigned int fib_info_hashfn_1(int init_val, u8 protocol, u8 scope,				      u32 prefsrc, u32 priority){	unsigned int val = init_val; 	val ^= (protocol << 8) | scope;	val ^= prefsrc;	val ^= priority; 	return val;} static unsigned int fib_info_hashfn_result(const struct net *net,					   unsigned int val){	return hash_32(val ^ net_hash_mix(net), net->ipv4.fib_info_hash_bits);} static struct hlist_head *fib_info_hash_bucket(struct fib_info *fi){	struct net *net = fi->fib_net;	unsigned int val; 	val = fib_info_hashfn_1(fi->fib_nhs, fi->fib_protocol,				fi->fib_scope, (__force u32)fi->fib_prefsrc,				fi->fib_priority); 	if (fi->nh) {		val ^= fi->nh->id;	} else {		for_nexthops(fi) {			val ^= nh->fib_nh_oif;		} endfor_nexthops(fi)	} 	return &net->ipv4.fib_info_hash[fib_info_hashfn_result(net, val)];} static struct hlist_head *fib_info_laddrhash_bucket(const struct net *net,						    __be32 val){	unsigned int hash_bits = net->ipv4.fib_info_hash_bits;	u32 slot; 	slot = hash_32(net_hash_mix(net) ^ (__force u32)val, hash_bits); 	return &net->ipv4.fib_info_hash[(1 << hash_bits) + slot];} static struct hlist_head *fib_info_hash_alloc(unsigned int hash_bits){	/* The second half is used for prefsrc */	return kvzalloc_objs(struct hlist_head, (1 << hash_bits) * 2);} static void fib_info_hash_free(struct hlist_head *head){	kvfree(head);} static void fib_info_hash_grow(struct net *net){	unsigned int old_size = 1 << net->ipv4.fib_info_hash_bits;	struct hlist_head *new_info_hash, *old_info_hash;	unsigned int i; 	if (net->ipv4.fib_info_cnt < old_size)		return; 	new_info_hash = fib_info_hash_alloc(net->ipv4.fib_info_hash_bits + 1);	if (!new_info_hash)		return; 	old_info_hash = net->ipv4.fib_info_hash;	net->ipv4.fib_info_hash = new_info_hash;	net->ipv4.fib_info_hash_bits += 1; 	for (i = 0; i < old_size; i++) {		struct hlist_head *head = &old_info_hash[i];		struct hlist_node *n;		struct fib_info *fi; 		hlist_for_each_entry_safe(fi, n, head, fib_hash)			hlist_add_head(&fi->fib_hash, fib_info_hash_bucket(fi));	} 	for (i = 0; i < old_size; i++) {		struct hlist_head *lhead = &old_info_hash[old_size + i];		struct hlist_node *n;		struct fib_info *fi; 		hlist_for_each_entry_safe(fi, n, lhead, fib_lhash)			hlist_add_head(&fi->fib_lhash,				       fib_info_laddrhash_bucket(fi->fib_net,								 fi->fib_prefsrc));	} 	fib_info_hash_free(old_info_hash);} /* no metrics, only nexthop id */static struct fib_info *fib_find_info_nh(struct net *net,					 const struct fib_config *cfg){	struct hlist_head *head;	struct fib_info *fi;	unsigned int hash; 	hash = fib_info_hashfn_1(cfg->fc_nh_id,				 cfg->fc_protocol, cfg->fc_scope,				 (__force u32)cfg->fc_prefsrc,				 cfg->fc_priority);	hash = fib_info_hashfn_result(net, hash);	head = &net->ipv4.fib_info_hash[hash]; 	hlist_for_each_entry(fi, head, fib_hash) {		if (!fi->nh || fi->nh->id != cfg->fc_nh_id)			continue; 		if (cfg->fc_protocol == fi->fib_protocol &&		    cfg->fc_scope == fi->fib_scope &&		    cfg->fc_prefsrc == fi->fib_prefsrc &&		    cfg->fc_priority == fi->fib_priority &&		    cfg->fc_type == fi->fib_type &&		    cfg->fc_table == fi->fib_tb_id &&		    !((cfg->fc_flags ^ fi->fib_flags) & ~RTNH_COMPARE_MASK))			return fi;	} 	return NULL;} static struct fib_info *fib_find_info(struct fib_info *nfi){	struct hlist_head *head = fib_info_hash_bucket(nfi);	struct fib_info *fi; 	hlist_for_each_entry(fi, head, fib_hash) {		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 &&		    nfi->fib_tb_id == fi->fib_tb_id &&		    memcmp(nfi->fib_metrics, fi->fib_metrics,			   sizeof(u32) * RTAX_MAX) == 0 &&		    !((nfi->fib_flags ^ fi->fib_flags) & ~RTNH_COMPARE_MASK) &&		    nh_comp(fi, nfi) == 0)			return fi;	} 	return NULL;} /* Check, that the gateway is already configured. * Used only by redirect accept routine, under rcu_read_lock(); */int ip_fib_check_default(__be32 gw, struct net_device *dev){	struct hlist_head *head;	struct fib_nh *nh; 	head = fib_nh_head(dev); 	hlist_for_each_entry_rcu(nh, head, nh_hash) {		DEBUG_NET_WARN_ON_ONCE(nh->fib_nh_dev != dev);		if (nh->fib_nh_gw4 == gw &&		    !(nh->fib_nh_flags & RTNH_F_DEAD)) {			return 0;		}	} 	return -1;} static size_t fib_nexthop_nlmsg_size(const struct fib_nh_common *nhc,				     bool skip_oif){	size_t nhsize = 0; 	switch (nhc->nhc_gw_family) {	case AF_INET:		nhsize += nla_total_size(4); /* RTA_GATEWAY */

Lines 1–500 of 2273.