7035 lines · 172.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *	Linux INET6 implementation *	FIB front-end. * *	Authors: *	Pedro Roque		<roque@di.fc.ul.pt> */ /*	Changes: * *	YOSHIFUJI Hideaki @USAGI *		reworked default router selection. *		- respect outgoing interface *		- select from (probably) reachable routers (i.e. *		routers in REACHABLE, STALE, DELAY or PROBE states). *		- always select the same router if it is (probably) *		reachable.  otherwise, round-robin the list. *	Ville Nuorvala *		Fixed routing subtrees. */ #define pr_fmt(fmt) "IPv6: " fmt #include <linux/capability.h>#include <linux/errno.h>#include <linux/export.h>#include <linux/types.h>#include <linux/times.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/net.h>#include <linux/route.h>#include <linux/netdevice.h>#include <linux/in6.h>#include <linux/mroute6.h>#include <linux/init.h>#include <linux/if_arp.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/nsproxy.h>#include <linux/slab.h>#include <linux/jhash.h>#include <linux/siphash.h>#include <net/net_namespace.h>#include <net/snmp.h>#include <net/ipv6.h>#include <net/ip6_fib.h>#include <net/ip6_route.h>#include <net/ndisc.h>#include <net/addrconf.h>#include <net/tcp.h>#include <linux/rtnetlink.h>#include <net/dst.h>#include <net/dst_metadata.h>#include <net/xfrm.h>#include <net/netevent.h>#include <net/netlink.h>#include <net/rtnh.h>#include <net/lwtunnel.h>#include <net/ip_tunnels.h>#include <net/l3mdev.h>#include <net/ip.h>#include <linux/uaccess.h>#include <linux/btf_ids.h> #ifdef CONFIG_SYSCTL#include <linux/sysctl.h>#endif static int ip6_rt_type_to_error(u8 fib6_type); #define CREATE_TRACE_POINTS#include <trace/events/fib6.h>EXPORT_TRACEPOINT_SYMBOL_GPL(fib6_table_lookup);#undef CREATE_TRACE_POINTS enum rt6_nud_state {	RT6_NUD_FAIL_HARD = -3,	RT6_NUD_FAIL_PROBE = -2,	RT6_NUD_FAIL_DO_RR = -1,	RT6_NUD_SUCCEED = 1}; INDIRECT_CALLABLE_SCOPEstruct dst_entry	*ip6_dst_check(struct dst_entry *dst, u32 cookie);static unsigned int	 ip6_default_advmss(const struct dst_entry *dst);INDIRECT_CALLABLE_SCOPEunsigned int		ip6_mtu(const struct dst_entry *dst);static void		ip6_negative_advice(struct sock *sk,					    struct dst_entry *dst);static void		ip6_dst_destroy(struct dst_entry *);static void		ip6_dst_ifdown(struct dst_entry *,				       struct net_device *dev);static void		 ip6_dst_gc(struct dst_ops *ops); static int		ip6_pkt_discard(struct sk_buff *skb);static int		ip6_pkt_discard_out(struct net *net, struct sock *sk, struct sk_buff *skb);static int		ip6_pkt_prohibit(struct sk_buff *skb);static int		ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb);static void		ip6_link_failure(struct sk_buff *skb);static void		ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,					   struct sk_buff *skb, u32 mtu,					   bool confirm_neigh);static void		rt6_do_redirect(struct dst_entry *dst, struct sock *sk,					struct sk_buff *skb);static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,			   int strict);static size_t rt6_nlmsg_size(struct fib6_info *f6i);static int rt6_fill_node(struct net *net, struct sk_buff *skb,			 struct fib6_info *rt, struct dst_entry *dst,			 struct in6_addr *dest, struct in6_addr *src,			 int iif, int type, u32 portid, u32 seq,			 unsigned int flags, enum rt_del_reason del_reason);static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,					   const struct in6_addr *daddr,					   const struct in6_addr *saddr); #ifdef CONFIG_IPV6_ROUTE_INFOstatic struct fib6_info *rt6_add_route_info(struct net *net,					   const struct in6_addr *prefix, int prefixlen,					   const struct in6_addr *gwaddr,					   struct net_device *dev,					   unsigned int pref);static struct fib6_info *rt6_get_route_info(struct net *net,					   const struct in6_addr *prefix, int prefixlen,					   const struct in6_addr *gwaddr,					   struct net_device *dev);#endif struct uncached_list {	spinlock_t		lock;	struct list_head	head;}; static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt6_uncached_list); void rt6_uncached_list_add(struct rt6_info *rt){	struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list); 	rt->dst.rt_uncached_list = ul; 	spin_lock_bh(&ul->lock);	list_add_tail(&rt->dst.rt_uncached, &ul->head);	spin_unlock_bh(&ul->lock);} void rt6_uncached_list_del(struct rt6_info *rt){	struct uncached_list *ul = rt->dst.rt_uncached_list; 	if (ul) {		spin_lock_bh(&ul->lock);		list_del_init(&rt->dst.rt_uncached);		spin_unlock_bh(&ul->lock);	}} static void rt6_uncached_list_flush_dev(struct net_device *dev){	int cpu; 	for_each_possible_cpu(cpu) {		struct uncached_list *ul = per_cpu_ptr(&rt6_uncached_list, cpu);		struct rt6_info *rt, *safe; 		if (list_empty(&ul->head))			continue; 		spin_lock_bh(&ul->lock);		list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) {			struct inet6_dev *rt_idev = rt->rt6i_idev;			struct net_device *rt_dev = rt->dst.dev;			bool handled = false; 			if (rt_idev && rt_idev->dev == dev) {				rt->rt6i_idev = in6_dev_get(blackhole_netdev);				in6_dev_put(rt_idev);				handled = true;			} 			if (rt_dev == dev) {				rt->dst.dev = blackhole_netdev;				netdev_ref_replace(rt_dev, blackhole_netdev,						   &rt->dst.dev_tracker,						   GFP_ATOMIC);				handled = true;			}			if (handled)				list_del_init(&rt->dst.rt_uncached);		}		spin_unlock_bh(&ul->lock);	}} static inline const void *choose_neigh_daddr(const struct in6_addr *p,					     struct sk_buff *skb,					     const void *daddr){	if (!ipv6_addr_any(p))		return (const void *) p;	else if (skb)		return &ipv6_hdr(skb)->daddr;	return daddr;} struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,				   struct net_device *dev,				   struct sk_buff *skb,				   const void *daddr){	struct neighbour *n; 	daddr = choose_neigh_daddr(gw, skb, daddr);	n = __ipv6_neigh_lookup(dev, daddr);	if (n)		return n; 	n = neigh_create(&nd_tbl, daddr, dev);	return IS_ERR(n) ? NULL : n;} static struct neighbour *ip6_dst_neigh_lookup(const struct dst_entry *dst,					      struct sk_buff *skb,					      const void *daddr){	const struct rt6_info *rt = dst_rt6_info(dst); 	return ip6_neigh_lookup(rt6_nexthop(rt, &in6addr_any),				dst_dev(dst), skb, daddr);} static void ip6_confirm_neigh(const struct dst_entry *dst, const void *daddr){	const struct rt6_info *rt = dst_rt6_info(dst);	struct net_device *dev = dst_dev(dst); 	daddr = choose_neigh_daddr(rt6_nexthop(rt, &in6addr_any), NULL, daddr);	if (!daddr)		return;	if (dev->flags & (IFF_NOARP | IFF_LOOPBACK))		return;	if (ipv6_addr_is_multicast((const struct in6_addr *)daddr))		return;	__ipv6_confirm_neigh(dev, daddr);} static struct dst_ops ip6_dst_ops_template = {	.family			=	AF_INET6,	.gc			=	ip6_dst_gc,	.gc_thresh		=	1024,	.check			=	ip6_dst_check,	.default_advmss		=	ip6_default_advmss,	.mtu			=	ip6_mtu,	.cow_metrics		=	dst_cow_metrics_generic,	.destroy		=	ip6_dst_destroy,	.ifdown			=	ip6_dst_ifdown,	.negative_advice	=	ip6_negative_advice,	.link_failure		=	ip6_link_failure,	.update_pmtu		=	ip6_rt_update_pmtu,	.redirect		=	rt6_do_redirect,	.local_out		=	__ip6_local_out,	.neigh_lookup		=	ip6_dst_neigh_lookup,	.confirm_neigh		=	ip6_confirm_neigh,}; static struct dst_ops ip6_dst_blackhole_ops = {	.family			= AF_INET6,	.default_advmss		= ip6_default_advmss,	.neigh_lookup		= ip6_dst_neigh_lookup,	.check			= ip6_dst_check,	.destroy		= ip6_dst_destroy,	.cow_metrics		= dst_cow_metrics_generic,	.update_pmtu		= dst_blackhole_update_pmtu,	.redirect		= dst_blackhole_redirect,	.mtu			= dst_blackhole_mtu,}; static const u32 ip6_template_metrics[RTAX_MAX] = {	[RTAX_HOPLIMIT - 1] = 0,}; static const struct fib6_info fib6_null_entry_template = {	.fib6_flags	= (RTF_REJECT | RTF_NONEXTHOP),	.fib6_protocol  = RTPROT_KERNEL,	.fib6_metric	= ~(u32)0,	.fib6_ref	= REFCOUNT_INIT(1),	.fib6_type	= RTN_UNREACHABLE,	.fib6_metrics	= (struct dst_metrics *)&dst_default_metrics,}; static const struct rt6_info ip6_null_entry_template = {	.dst = {		.__rcuref	= RCUREF_INIT(1),		.__use		= 1,		.obsolete	= DST_OBSOLETE_FORCE_CHK,		.error		= -ENETUNREACH,		.input		= ip6_pkt_discard,		.output		= ip6_pkt_discard_out,	},	.rt6i_flags	= (RTF_REJECT | RTF_NONEXTHOP),}; #ifdef CONFIG_IPV6_MULTIPLE_TABLES static const struct rt6_info ip6_prohibit_entry_template = {	.dst = {		.__rcuref	= RCUREF_INIT(1),		.__use		= 1,		.obsolete	= DST_OBSOLETE_FORCE_CHK,		.error		= -EACCES,		.input		= ip6_pkt_prohibit,		.output		= ip6_pkt_prohibit_out,	},	.rt6i_flags	= (RTF_REJECT | RTF_NONEXTHOP),}; static const struct rt6_info ip6_blk_hole_entry_template = {	.dst = {		.__rcuref	= RCUREF_INIT(1),		.__use		= 1,		.obsolete	= DST_OBSOLETE_FORCE_CHK,		.error		= -EINVAL,		.input		= dst_discard,		.output		= dst_discard_out,	},	.rt6i_flags	= (RTF_REJECT | RTF_NONEXTHOP),}; #endif static void rt6_info_init(struct rt6_info *rt){	memset_after(rt, 0, dst);} /* allocate dst with ip6_dst_ops */struct rt6_info *ip6_dst_alloc(struct net *net, struct net_device *dev,			       int flags){	struct rt6_info *rt = dst_alloc(&net->ipv6.ip6_dst_ops, dev,					DST_OBSOLETE_FORCE_CHK, flags); 	if (rt) {		rt6_info_init(rt);		atomic_inc(&net->ipv6.rt6_stats->fib_rt_alloc);	} 	return rt;}EXPORT_SYMBOL(ip6_dst_alloc); static void ip6_dst_destroy(struct dst_entry *dst){	struct rt6_info *rt = dst_rt6_info(dst);	struct fib6_info *from;	struct inet6_dev *idev; 	ip_dst_metrics_put(dst);	rt6_uncached_list_del(rt); 	idev = rt->rt6i_idev;	if (idev) {		rt->rt6i_idev = NULL;		in6_dev_put(idev);	} 	from = unrcu_pointer(xchg(&rt->from, NULL));	fib6_info_release(from);} static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev){	struct rt6_info *rt = dst_rt6_info(dst);	struct inet6_dev *idev = rt->rt6i_idev;	struct fib6_info *from; 	if (idev && idev->dev != blackhole_netdev) {		struct inet6_dev *blackhole_idev = in6_dev_get(blackhole_netdev); 		if (blackhole_idev) {			rt->rt6i_idev = blackhole_idev;			in6_dev_put(idev);		}	}	from = unrcu_pointer(xchg(&rt->from, NULL));	fib6_info_release(from);} static bool __rt6_check_expired(const struct rt6_info *rt){	if (rt->rt6i_flags & RTF_EXPIRES)		return time_after(jiffies, READ_ONCE(rt->dst.expires));	return false;} static bool rt6_check_expired(const struct rt6_info *rt){	struct fib6_info *from; 	from = rcu_dereference(rt->from); 	if (rt->rt6i_flags & RTF_EXPIRES) {		if (time_after(jiffies, READ_ONCE(rt->dst.expires)))			return true;	} else if (from) {		return READ_ONCE(rt->dst.obsolete) != DST_OBSOLETE_FORCE_CHK ||			fib6_check_expired(from);	}	return false;} static struct fib6_info *rt6_multipath_first_sibling_rcu(const struct fib6_info *rt){	struct fib6_info *iter;	struct fib6_node *fn; 	fn = rcu_dereference(rt->fib6_node);	if (!fn)		goto out;	iter = rcu_dereference(fn->leaf);	if (!iter)		goto out; 	while (iter) {		if (iter->fib6_metric == rt->fib6_metric &&		    rt6_qualify_for_ecmp(iter))			return iter;		iter = rcu_dereference(iter->fib6_next);	} out:	return NULL;} void fib6_select_path(const struct net *net, struct fib6_result *res,		      struct flowi6 *fl6, int oif, bool have_oif_match,		      const struct sk_buff *skb, int strict){	struct fib6_info *first, *match = res->f6i;	struct fib6_info *sibling;	int hash; 	if (!match->nh && (!match->fib6_nsiblings || have_oif_match))		goto out; 	if (match->nh && have_oif_match && res->nh)		return; 	if (skb)		IP6CB(skb)->flags |= IP6SKB_MULTIPATH; 	/* We might have already computed the hash for ICMPv6 errors. In such	 * case it will always be non-zero. Otherwise now is the time to do it.	 */	if (!fl6->mp_hash &&	    (!match->nh || nexthop_is_multipath(match->nh)))		fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL); 	if (unlikely(match->nh)) {		nexthop_path_fib6_result(res, fl6->mp_hash);		return;	} 	first = rt6_multipath_first_sibling_rcu(match);	if (!first)		goto out; 	hash = fl6->mp_hash;	if (hash <= atomic_read(&first->fib6_nh->fib_nh_upper_bound)) {		if (rt6_score_route(first->fib6_nh, first->fib6_flags, oif,				    strict) >= 0)			match = first;		goto out;	} 	list_for_each_entry_rcu(sibling, &first->fib6_siblings,				fib6_siblings) {		const struct fib6_nh *nh = sibling->fib6_nh;		int nh_upper_bound; 		if (!READ_ONCE(first->fib6_nsiblings))			break; 		nh_upper_bound = atomic_read(&nh->fib_nh_upper_bound);		if (hash > nh_upper_bound)			continue;		if (rt6_score_route(nh, sibling->fib6_flags, oif, strict) < 0)			break;		match = sibling;		break;	} out:	res->f6i = match;	res->nh = match->fib6_nh;} 

Lines 1–500 of 7035.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t