cregit-Linux how code gets into the kernel

Release 4.11 net/ipv6/route.c

Directory: net/ipv6
/*
 *      Linux INET6 implementation
 *      FIB front-end.
 *
 *      Authors:
 *      Pedro Roque             <roque@di.fc.ul.pt>
 *
 *      This program is free software; you can redistribute it and/or
 *      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:
 *
 *      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 <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/nexthop.h>
#include <net/lwtunnel.h>
#include <net/ip_tunnels.h>
#include <net/l3mdev.h>
#include <trace/events/fib6.h>

#include <linux/uaccess.h>

#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#endif


enum rt6_nud_state {
	
RT6_NUD_FAIL_HARD = -3,
	
RT6_NUD_FAIL_PROBE = -2,
	
RT6_NUD_FAIL_DO_RR = -1,
	
RT6_NUD_SUCCEED = 1
};

static void ip6_rt_copy_init(struct rt6_info *rt, struct rt6_info *ort);
static struct dst_entry	*ip6_dst_check(struct dst_entry *dst, u32 cookie);
static unsigned int	 ip6_default_advmss(const struct dst_entry *dst);
static unsigned int	 ip6_mtu(const struct dst_entry *dst);
static struct dst_entry *ip6_negative_advice(struct dst_entry *);
static void		ip6_dst_destroy(struct dst_entry *);
static void		ip6_dst_ifdown(struct dst_entry *,
				       struct net_device *dev, int how);
static int		 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);
static void		rt6_do_redirect(struct dst_entry *dst, struct sock *sk,
					struct sk_buff *skb);
static void		rt6_dst_from_metrics_check(struct rt6_info *rt);
static int rt6_score_route(struct rt6_info *rt, int oif, int strict);
static size_t rt6_nlmsg_size(struct rt6_info *rt);
static int rt6_fill_node(struct net *net,
			 struct sk_buff *skb, struct rt6_info *rt,
			 struct in6_addr *dst, struct in6_addr *src,
			 int iif, int type, u32 portid, u32 seq,
			 unsigned int flags);

#ifdef CONFIG_IPV6_ROUTE_INFO
static struct rt6_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 rt6_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);


static void rt6_uncached_list_add(struct rt6_info *rt) { struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list); rt->dst.flags |= DST_NOCACHE; rt->rt6i_uncached_list = ul; spin_lock_bh(&ul->lock); list_add_tail(&rt->rt6i_uncached, &ul->head); spin_unlock_bh(&ul->lock); }

Contributors

PersonTokensPropCommitsCommitProp
Martin KaFai Lau65100.00%1100.00%
Total65100.00%1100.00%


static void rt6_uncached_list_del(struct rt6_info *rt) { if (!list_empty(&rt->rt6i_uncached)) { struct uncached_list *ul = rt->rt6i_uncached_list; spin_lock_bh(&ul->lock); list_del(&rt->rt6i_uncached); spin_unlock_bh(&ul->lock); } }

Contributors

PersonTokensPropCommitsCommitProp
Martin KaFai Lau57100.00%1100.00%
Total57100.00%1100.00%


static void rt6_uncached_list_flush_dev(struct net *net, struct net_device *dev) { struct net_device *loopback_dev = net->loopback_dev; int cpu; if (dev == loopback_dev) return; for_each_possible_cpu(cpu) { struct uncached_list *ul = per_cpu_ptr(&rt6_uncached_list, cpu); struct rt6_info *rt; spin_lock_bh(&ul->lock); list_for_each_entry(rt, &ul->head, rt6i_uncached) { struct inet6_dev *rt_idev = rt->rt6i_idev; struct net_device *rt_dev = rt->dst.dev; if (rt_idev->dev == dev) { rt->rt6i_idev = in6_dev_get(loopback_dev); in6_dev_put(rt_idev); } if (rt_dev == dev) { rt->dst.dev = loopback_dev; dev_hold(rt->dst.dev); dev_put(rt_dev); } } spin_unlock_bh(&ul->lock); } }

Contributors

PersonTokensPropCommitsCommitProp
Martin KaFai Lau15295.60%150.00%
Eric W. Biedermann74.40%150.00%
Total159100.00%2100.00%


static u32 *rt6_pcpu_cow_metrics(struct rt6_info *rt) { return dst_metrics_write_ptr(rt->dst.from); }

Contributors

PersonTokensPropCommitsCommitProp
Martin KaFai Lau22100.00%1100.00%
Total22100.00%1100.00%


static u32 *ipv6_cow_metrics(struct dst_entry *dst, unsigned long old) { struct rt6_info *rt = (struct rt6_info *)dst; if (rt->rt6i_flags & RTF_PCPU) return rt6_pcpu_cow_metrics(rt); else if (rt->rt6i_flags & RTF_CACHE) return NULL; else return dst_cow_metrics_generic(dst, old); }

Contributors

PersonTokensPropCommitsCommitProp
David S. Miller3047.62%120.00%
Martin KaFai Lau2742.86%360.00%
Zheng Yan69.52%120.00%
Total63100.00%5100.00%


static inline const void *choose_neigh_daddr(struct rt6_info *rt, struct sk_buff *skb, const void *daddr) { struct in6_addr *p = &rt->rt6i_gateway; if (!ipv6_addr_any(p)) return (const void *) p; else if (skb) return &ipv6_hdr(skb)->daddr; return daddr; }

Contributors

PersonTokensPropCommitsCommitProp
David S. Miller67100.00%3100.00%
Total67100.00%3100.00%


static struct neighbour *ip6_neigh_lookup(const struct dst_entry *dst, struct sk_buff *skb, const void *daddr) { struct rt6_info *rt = (struct rt6_info *) dst; struct neighbour *n; daddr = choose_neigh_daddr(rt, skb, daddr); n = __ipv6_neigh_lookup(dst->dev, daddr); if (n) return n; return neigh_create(&nd_tbl, daddr, dst->dev); }

Contributors

PersonTokensPropCommitsCommitProp
David S. Miller83100.00%4100.00%
Total83100.00%4100.00%


static void ip6_confirm_neigh(const struct dst_entry *dst, const void *daddr) { struct net_device *dev = dst->dev; struct rt6_info *rt = (struct rt6_info *)dst; daddr = choose_neigh_daddr(rt, 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); }

Contributors

PersonTokensPropCommitsCommitProp
Julian Anastasov89100.00%1100.00%
Total89100.00%1100.00%

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 = ipv6_cow_metrics, .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_neigh_lookup, .confirm_neigh = ip6_confirm_neigh, };
static unsigned int ip6_blackhole_mtu(const struct dst_entry *dst) { unsigned int mtu = dst_metric_raw(dst, RTAX_MTU); return mtu ? : dst->dev->mtu; }

Contributors

PersonTokensPropCommitsCommitProp
Steffen Klassert2058.82%375.00%
Roland Dreier1441.18%125.00%
Total34100.00%4100.00%


static void ip6_rt_blackhole_update_pmtu(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb, u32 mtu) { }

Contributors

PersonTokensPropCommitsCommitProp
David S. Miller23100.00%2100.00%
Total23100.00%2100.00%


static void ip6_rt_blackhole_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb) { }

Contributors

PersonTokensPropCommitsCommitProp
David S. Miller20100.00%2100.00%
Total20100.00%2100.00%

static struct dst_ops ip6_dst_blackhole_ops = { .family = AF_INET6, .destroy = ip6_dst_destroy, .check = ip6_dst_check, .mtu = ip6_blackhole_mtu, .default_advmss = ip6_default_advmss, .update_pmtu = ip6_rt_blackhole_update_pmtu, .redirect = ip6_rt_blackhole_redirect, .cow_metrics = dst_cow_metrics_generic, .neigh_lookup = ip6_neigh_lookup, }; static const u32 ip6_template_metrics[RTAX_MAX] = { [RTAX_HOPLIMIT - 1] = 0, }; static const struct rt6_info ip6_null_entry_template = { .dst = { .__refcnt = ATOMIC_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), .rt6i_protocol = RTPROT_KERNEL, .rt6i_metric = ~(u32) 0, .rt6i_ref = ATOMIC_INIT(1), }; #ifdef CONFIG_IPV6_MULTIPLE_TABLES static const struct rt6_info ip6_prohibit_entry_template = { .dst = { .__refcnt = ATOMIC_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), .rt6i_protocol = RTPROT_KERNEL, .rt6i_metric = ~(u32) 0, .rt6i_ref = ATOMIC_INIT(1), }; static const struct rt6_info ip6_blk_hole_entry_template = { .dst = { .__refcnt = ATOMIC_INIT(1), .__use = 1, .obsolete = DST_OBSOLETE_FORCE_CHK, .error = -EINVAL, .input = dst_discard, .output = dst_discard_out, }, .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP), .rt6i_protocol = RTPROT_KERNEL, .rt6i_metric = ~(u32) 0, .rt6i_ref = ATOMIC_INIT(1), }; #endif
static void rt6_info_init(struct rt6_info *rt) { struct dst_entry *dst = &rt->dst; memset(dst + 1, 0, sizeof(*rt) - sizeof(*dst)); INIT_LIST_HEAD(&rt->rt6i_siblings); INIT_LIST_HEAD(&rt->rt6i_uncached); }

Contributors

PersonTokensPropCommitsCommitProp
David S. Miller2136.21%222.22%
Steffen Klassert1424.14%111.11%
Martin KaFai Lau1118.97%222.22%
Nicolas Dichtel813.79%111.11%
Hideaki Yoshifuji / 吉藤英明23.45%222.22%
Benjamin Thery23.45%111.11%
Total58100.00%9100.00%

/* allocate dst with ip6_dst_ops */
static 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, 0, DST_OBSOLETE_FORCE_CHK, flags); if (rt) rt6_info_init(rt); return rt; }

Contributors

PersonTokensPropCommitsCommitProp
Martin KaFai Lau5292.86%133.33%
David S. Miller23.57%133.33%
Kazunori Miyazawa23.57%133.33%
Total56100.00%3100.00%


struct rt6_info *ip6_dst_alloc(struct net *net, struct net_device *dev, int flags) { struct rt6_info *rt = __ip6_dst_alloc(net, dev, flags); if (rt) { rt->rt6i_pcpu = alloc_percpu_gfp(struct rt6_info *, GFP_ATOMIC); if (rt->rt6i_pcpu) { int cpu; for_each_possible_cpu(cpu) { struct rt6_info **p; p = per_cpu_ptr(rt->rt6i_pcpu, cpu); /* no one shares rt */ *p = NULL; } } else { dst_destroy((struct dst_entry *)rt); return NULL; } } return rt; }

Contributors

PersonTokensPropCommitsCommitProp
Martin KaFai Lau112100.00%1100.00%
Total112100.00%1100.00%

EXPORT_SYMBOL(ip6_dst_alloc);
static void ip6_dst_destroy(struct dst_entry *dst) { struct rt6_info *rt = (struct rt6_info *)dst; struct dst_entry *from = dst->from; struct inet6_dev *idev; dst_destroy_metrics_generic(dst); free_percpu(rt->rt6i_pcpu); rt6_uncached_list_del(rt); idev = rt->rt6i_idev; if (idev) { rt->rt6i_idev = NULL; in6_dev_put(idev); } dst->from = NULL; dst_release(from); }

Contributors

PersonTokensPropCommitsCommitProp
Hideaki Yoshifuji / 吉藤英明4955.68%337.50%
Martin KaFai Lau2326.14%225.00%
Gao Feng89.09%112.50%
Zheng Yan77.95%112.50%
David S. Miller11.14%112.50%
Total88100.00%8100.00%


static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev, int how) { struct rt6_info *rt = (struct rt6_info *)dst; struct inet6_dev *idev = rt->rt6i_idev; struct net_device *loopback_dev = dev_net(dev)->loopback_dev; if (dev != loopback_dev) { if (idev && idev->dev == dev) { struct inet6_dev *loopback_idev = in6_dev_get(loopback_dev); if (loopback_idev) { rt->rt6i_idev = loopback_idev; in6_dev_put(idev); } } } }

Contributors

PersonTokensPropCommitsCommitProp
Hideaki Yoshifuji / 吉藤英明7575.76%457.14%
Herbert Xu1010.10%114.29%
Denis V. Lunev99.09%114.29%
David S. Miller55.05%114.29%
Total99100.00%7100.00%


static bool __rt6_check_expired(const struct rt6_info *rt) { if (rt->rt6i_flags & RTF_EXPIRES) return time_after(jiffies, rt->dst.expires); else return false; }

Contributors

PersonTokensPropCommitsCommitProp
Martin KaFai Lau36100.00%1100.00%
Total36100.00%1100.00%


static bool rt6_check_expired(const struct rt6_info *rt) { if (rt->rt6i_flags & RTF_EXPIRES) { if (time_after(jiffies, rt->dst.expires)) return true; } else if (rt->dst.from) { return rt6_check_expired((struct rt6_info *) rt->dst.from); } return false; }

Contributors

PersonTokensPropCommitsCommitProp
Gao Feng4465.67%125.00%
Hideaki Yoshifuji / 吉藤英明1725.37%125.00%
Li RongQing34.48%125.00%
Eric Dumazet34.48%125.00%
Total67100.00%4100.00%

/* Multipath route selection: * Hash based function using packet header and flowlabel. * Adapted from fib_info_hashfn() */
static int rt6_info_hash_nhsfn(unsigned int candidate_count, const struct flowi6 *fl6) { return get_hash_from_flowi6(fl6) % candidate_count; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel2083.33%133.33%
Hideaki Yoshifuji / 吉藤英明28.33%133.33%
Tom Herbert28.33%133.33%
Total24100.00%3100.00%


static struct rt6_info *rt6_multipath_select(struct rt6_info *match, struct flowi6 *fl6, int oif, int strict) { struct rt6_info *sibling, *next_sibling; int route_choosen; route_choosen = rt6_info_hash_nhsfn(match->rt6i_nsiblings + 1, fl6); /* Don't change the route, if route_choosen == 0 * (siblings does not include ourself) */ if (route_choosen) list_for_each_entry_safe(sibling, next_sibling, &match->rt6i_siblings, rt6i_siblings) { route_choosen--; if (route_choosen == 0) { if (rt6_score_route(sibling, oif, strict) < 0) break; match = sibling; break; } } return match; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel98100.00%2100.00%
Total98100.00%2100.00%

/* * Route lookup. Any table->tb6_lock is implied. */
static inline struct rt6_info *rt6_device_match(struct net *net, struct rt6_info *rt, const struct in6_addr *saddr, int oif, int flags) { struct rt6_info *local = NULL; struct rt6_info *sprt; if (!oif && ipv6_addr_any(saddr)) goto out; for (sprt = rt; sprt; sprt = sprt->dst.rt6_next) { struct net_device *dev = sprt->dst.dev; if (oif) { if (dev->ifindex == oif) return sprt; if (dev->flags & IFF_LOOPBACK) { if (!sprt->rt6i_idev || sprt->rt6i_idev->dev->ifindex != oif) { if (flags & RT6_LOOKUP_F_IFACE) continue; if (local && local->rt6i_idev->dev->ifindex == oif) continue; } local = sprt; } } else { if (ipv6_chk_addr(net, saddr, dev, flags & RT6_LOOKUP_F_IFACE)) return sprt; } } if (oif) { if (local) return local; if (flags & RT6_LOOKUP_F_IFACE) return net->ipv6.ip6_null_entry; } out: return rt; }

Contributors

PersonTokensPropCommitsCommitProp
Hideaki Yoshifuji / 吉藤英明9344.50%433.33%
Linus Torvalds (pre-git)7133.97%325.00%
Daniel Lezcano3717.70%18.33%
Eric Dumazet41.91%216.67%
David S. Miller41.91%216.67%
Total209100.00%12100.00%

#ifdef CONFIG_IPV6_ROUTER_PREF struct __rt6_probe_work { struct work_struct work; struct in6_addr target; struct net_device *dev; };
static void rt6_probe_deferred(struct work_struct *w) { struct in6_addr mcaddr; struct __rt6_probe_work *work = container_of(w, struct __rt6_probe_work, work); addrconf_addr_solict_mult(&work->target, &mcaddr); ndisc_send_ns(work->dev, &work->target, &mcaddr, NULL, 0); dev_put(work->dev); kfree(work); }

Contributors

PersonTokensPropCommitsCommitProp
Hannes Frederic Sowa6995.83%133.33%
Erik Nordmark22.78%133.33%
Michael Büsch11.39%133.33%
Total72100.00%3100.00%


static void rt6_probe(struct rt6_info *rt) { struct __rt6_probe_work *work; struct neighbour *neigh; /* * Okay, this does not seem to be appropriate * for now, however, we need to check if it * is really so; aka Router Reachability Probing. * * Router Reachability Probe MUST be rate-limited * to no more than one per minute. */ if (!rt || !(rt->rt6i_flags & RTF_GATEWAY)) return; rcu_read_lock_bh(); neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway); if (neigh) { if (neigh->nud_state & NUD_VALID) goto out; work = NULL; write_lock(&neigh->lock); if (!(neigh->nud_state & NUD_VALID) && time_after(jiffies, neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) { work = kmalloc(sizeof(*work), GFP_ATOMIC); if (work) __neigh_set_probe_once(neigh); } write_unlock(&neigh->lock); } else { work = kmalloc(sizeof(*work), GFP_ATOMIC); } if (work) { INIT_WORK(&work->work, rt6_probe_deferred); work->target = rt->rt6i_gateway; dev_hold(rt->dst.dev); work->dev = rt->dst.dev; schedule_work(&work->work); } out: rcu_read_unlock_bh(); }

Contributors

PersonTokensPropCommitsCommitProp
Hideaki Yoshifuji / 吉藤英明11352.07%541.67%
Hannes Frederic Sowa4721.66%18.33%
Martin KaFai Lau4420.28%216.67%
Eric Dumazet62.76%18.33%
Jiri Benc31.38%18.33%
David S. Miller31.38%18.33%
Daniel Lezcano10.46%18.33%
Total217100.00%12100.00%

#else
static inline void rt6_probe(struct rt6_info *rt) { }

Contributors

PersonTokensPropCommitsCommitProp
Hideaki Yoshifuji / 吉藤英明1090.91%150.00%
Joe Perches19.09%150.00%
Total11100.00%2100.00%

#endif /* * Default Router Selection (RFC 2461 6.3.6) */
static inline int rt6_check_dev(struct rt6_info *rt, int oif) { struct net_device *dev = rt->dst.dev; if (!oif || dev->ifindex == oif) return 2; if ((dev->flags & IFF_LOOPBACK) && rt->rt6i_idev && rt->rt6i_idev->dev->ifindex == oif) return 1; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Hideaki Yoshifuji / 吉藤英明5578.57%457.14%
David S. Miller1420.00%228.57%
Dave Jones11.43%114.29%
Total70100.00%7100.00%


static inline enum rt6_nud_state rt6_check_neigh(struct rt6_info *rt)