12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * Linux INET6 implementation
41da177e4SLinus Torvalds * FIB front-end.
51da177e4SLinus Torvalds *
61da177e4SLinus Torvalds * Authors:
71da177e4SLinus Torvalds * Pedro Roque <roque@di.fc.ul.pt>
81da177e4SLinus Torvalds */
91da177e4SLinus Torvalds
101da177e4SLinus Torvalds /* Changes:
111da177e4SLinus Torvalds *
121da177e4SLinus Torvalds * YOSHIFUJI Hideaki @USAGI
131da177e4SLinus Torvalds * reworked default router selection.
141da177e4SLinus Torvalds * - respect outgoing interface
151da177e4SLinus Torvalds * - select from (probably) reachable routers (i.e.
161da177e4SLinus Torvalds * routers in REACHABLE, STALE, DELAY or PROBE states).
171da177e4SLinus Torvalds * - always select the same router if it is (probably)
181da177e4SLinus Torvalds * reachable. otherwise, round-robin the list.
19c0bece9fSYOSHIFUJI Hideaki * Ville Nuorvala
20c0bece9fSYOSHIFUJI Hideaki * Fixed routing subtrees.
211da177e4SLinus Torvalds */
221da177e4SLinus Torvalds
23f3213831SJoe Perches #define pr_fmt(fmt) "IPv6: " fmt
24f3213831SJoe Perches
254fc268d2SRandy Dunlap #include <linux/capability.h>
261da177e4SLinus Torvalds #include <linux/errno.h>
27bc3b2d7fSPaul Gortmaker #include <linux/export.h>
281da177e4SLinus Torvalds #include <linux/types.h>
291da177e4SLinus Torvalds #include <linux/times.h>
301da177e4SLinus Torvalds #include <linux/socket.h>
311da177e4SLinus Torvalds #include <linux/sockios.h>
321da177e4SLinus Torvalds #include <linux/net.h>
331da177e4SLinus Torvalds #include <linux/route.h>
341da177e4SLinus Torvalds #include <linux/netdevice.h>
351da177e4SLinus Torvalds #include <linux/in6.h>
367bc570c8SYOSHIFUJI Hideaki #include <linux/mroute6.h>
371da177e4SLinus Torvalds #include <linux/init.h>
381da177e4SLinus Torvalds #include <linux/if_arp.h>
391da177e4SLinus Torvalds #include <linux/proc_fs.h>
401da177e4SLinus Torvalds #include <linux/seq_file.h>
415b7c931dSDaniel Lezcano #include <linux/nsproxy.h>
425a0e3ad6STejun Heo #include <linux/slab.h>
4335732d01SWei Wang #include <linux/jhash.h>
444785305cSEric Dumazet #include <linux/siphash.h>
45457c4cbcSEric W. Biederman #include <net/net_namespace.h>
461da177e4SLinus Torvalds #include <net/snmp.h>
471da177e4SLinus Torvalds #include <net/ipv6.h>
481da177e4SLinus Torvalds #include <net/ip6_fib.h>
491da177e4SLinus Torvalds #include <net/ip6_route.h>
501da177e4SLinus Torvalds #include <net/ndisc.h>
511da177e4SLinus Torvalds #include <net/addrconf.h>
521da177e4SLinus Torvalds #include <net/tcp.h>
531da177e4SLinus Torvalds #include <linux/rtnetlink.h>
541da177e4SLinus Torvalds #include <net/dst.h>
55904af04dSJiri Benc #include <net/dst_metadata.h>
561da177e4SLinus Torvalds #include <net/xfrm.h>
578d71740cSTom Tucker #include <net/netevent.h>
5821713ebcSThomas Graf #include <net/netlink.h>
593c618c1dSDavid Ahern #include <net/rtnh.h>
6019e42e45SRoopa Prabhu #include <net/lwtunnel.h>
61904af04dSJiri Benc #include <net/ip_tunnels.h>
62ca254490SDavid Ahern #include <net/l3mdev.h>
63eacb9384SRoopa Prabhu #include <net/ip.h>
647c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
65951cf368SYonghong Song #include <linux/btf_ids.h>
661da177e4SLinus Torvalds
671da177e4SLinus Torvalds #ifdef CONFIG_SYSCTL
681da177e4SLinus Torvalds #include <linux/sysctl.h>
691da177e4SLinus Torvalds #endif
701da177e4SLinus Torvalds
7130d444d3SDavid Ahern static int ip6_rt_type_to_error(u8 fib6_type);
7230d444d3SDavid Ahern
7330d444d3SDavid Ahern #define CREATE_TRACE_POINTS
7430d444d3SDavid Ahern #include <trace/events/fib6.h>
7530d444d3SDavid Ahern EXPORT_TRACEPOINT_SYMBOL_GPL(fib6_table_lookup);
7630d444d3SDavid Ahern #undef CREATE_TRACE_POINTS
7730d444d3SDavid Ahern
78afc154e9SHannes Frederic Sowa enum rt6_nud_state {
797e980569SJiri Benc RT6_NUD_FAIL_HARD = -3,
807e980569SJiri Benc RT6_NUD_FAIL_PROBE = -2,
817e980569SJiri Benc RT6_NUD_FAIL_DO_RR = -1,
82afc154e9SHannes Frederic Sowa RT6_NUD_SUCCEED = 1
83afc154e9SHannes Frederic Sowa };
84afc154e9SHannes Frederic Sowa
85bbd807dfSBrian Vazquez INDIRECT_CALLABLE_SCOPE
86bbd807dfSBrian Vazquez struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie);
870dbaee3bSDavid S. Miller static unsigned int ip6_default_advmss(const struct dst_entry *dst);
88f67fbeaeSBrian Vazquez INDIRECT_CALLABLE_SCOPE
89f67fbeaeSBrian Vazquez unsigned int ip6_mtu(const struct dst_entry *dst);
905af198c3SEric Dumazet static void ip6_negative_advice(struct sock *sk,
915af198c3SEric Dumazet struct dst_entry *dst);
921da177e4SLinus Torvalds static void ip6_dst_destroy(struct dst_entry *);
931da177e4SLinus Torvalds static void ip6_dst_ifdown(struct dst_entry *,
9443c28172SZhengchao Shao struct net_device *dev);
95af6d1034SJon Maxwell static void ip6_dst_gc(struct dst_ops *ops);
961da177e4SLinus Torvalds
971da177e4SLinus Torvalds static int ip6_pkt_discard(struct sk_buff *skb);
98ede2059dSEric W. Biederman static int ip6_pkt_discard_out(struct net *net, struct sock *sk, struct sk_buff *skb);
997150aedeSKamala R static int ip6_pkt_prohibit(struct sk_buff *skb);
100ede2059dSEric W. Biederman static int ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb);
1011da177e4SLinus Torvalds static void ip6_link_failure(struct sk_buff *skb);
1026700c270SDavid S. Miller static void ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
103bd085ef6SHangbin Liu struct sk_buff *skb, u32 mtu,
104bd085ef6SHangbin Liu bool confirm_neigh);
1056700c270SDavid S. Miller static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk,
1066700c270SDavid S. Miller struct sk_buff *skb);
107702cea56SDavid Ahern static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
108702cea56SDavid Ahern int strict);
109a1b7a1f0SDavid Ahern static size_t rt6_nlmsg_size(struct fib6_info *f6i);
110d4ead6b3SDavid Ahern static int rt6_fill_node(struct net *net, struct sk_buff *skb,
1118d1c802bSDavid Ahern struct fib6_info *rt, struct dst_entry *dst,
112d4ead6b3SDavid Ahern struct in6_addr *dest, struct in6_addr *src,
11316a16cd3SDavid Ahern int iif, int type, u32 portid, u32 seq,
11416a16cd3SDavid Ahern unsigned int flags);
1157e4b5128SDavid Ahern static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,
116510e2cedSWei Wang const struct in6_addr *daddr,
117510e2cedSWei Wang const struct in6_addr *saddr);
1181da177e4SLinus Torvalds
11970ceb4f5SYOSHIFUJI Hideaki #ifdef CONFIG_IPV6_ROUTE_INFO
1208d1c802bSDavid Ahern static struct fib6_info *rt6_add_route_info(struct net *net,
121b71d1d42SEric Dumazet const struct in6_addr *prefix, int prefixlen,
122830218c1SDavid Ahern const struct in6_addr *gwaddr,
123830218c1SDavid Ahern struct net_device *dev,
12495c96174SEric Dumazet unsigned int pref);
1258d1c802bSDavid Ahern static struct fib6_info *rt6_get_route_info(struct net *net,
126b71d1d42SEric Dumazet const struct in6_addr *prefix, int prefixlen,
127830218c1SDavid Ahern const struct in6_addr *gwaddr,
128830218c1SDavid Ahern struct net_device *dev);
12970ceb4f5SYOSHIFUJI Hideaki #endif
13070ceb4f5SYOSHIFUJI Hideaki
1318d0b94afSMartin KaFai Lau struct uncached_list {
1328d0b94afSMartin KaFai Lau spinlock_t lock;
1338d0b94afSMartin KaFai Lau struct list_head head;
134ba55ef81SEric Dumazet struct list_head quarantine;
1358d0b94afSMartin KaFai Lau };
1368d0b94afSMartin KaFai Lau
1378d0b94afSMartin KaFai Lau static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt6_uncached_list);
1388d0b94afSMartin KaFai Lau
rt6_uncached_list_add(struct rt6_info * rt)139510c321bSXin Long void rt6_uncached_list_add(struct rt6_info *rt)
1408d0b94afSMartin KaFai Lau {
1418d0b94afSMartin KaFai Lau struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list);
1428d0b94afSMartin KaFai Lau
143d288a162SWangyang Guo rt->dst.rt_uncached_list = ul;
1448d0b94afSMartin KaFai Lau
1458d0b94afSMartin KaFai Lau spin_lock_bh(&ul->lock);
146d288a162SWangyang Guo list_add_tail(&rt->dst.rt_uncached, &ul->head);
1478d0b94afSMartin KaFai Lau spin_unlock_bh(&ul->lock);
1488d0b94afSMartin KaFai Lau }
1498d0b94afSMartin KaFai Lau
rt6_uncached_list_del(struct rt6_info * rt)150510c321bSXin Long void rt6_uncached_list_del(struct rt6_info *rt)
1518d0b94afSMartin KaFai Lau {
152d288a162SWangyang Guo if (!list_empty(&rt->dst.rt_uncached)) {
153d288a162SWangyang Guo struct uncached_list *ul = rt->dst.rt_uncached_list;
1548d0b94afSMartin KaFai Lau
1558d0b94afSMartin KaFai Lau spin_lock_bh(&ul->lock);
156d288a162SWangyang Guo list_del_init(&rt->dst.rt_uncached);
1578d0b94afSMartin KaFai Lau spin_unlock_bh(&ul->lock);
1588d0b94afSMartin KaFai Lau }
1598d0b94afSMartin KaFai Lau }
1608d0b94afSMartin KaFai Lau
rt6_uncached_list_flush_dev(struct net_device * dev)161e5f80fcfSEric Dumazet static void rt6_uncached_list_flush_dev(struct net_device *dev)
1628d0b94afSMartin KaFai Lau {
1638d0b94afSMartin KaFai Lau int cpu;
1648d0b94afSMartin KaFai Lau
1658d0b94afSMartin KaFai Lau for_each_possible_cpu(cpu) {
1668d0b94afSMartin KaFai Lau struct uncached_list *ul = per_cpu_ptr(&rt6_uncached_list, cpu);
167ba55ef81SEric Dumazet struct rt6_info *rt, *safe;
168ba55ef81SEric Dumazet
169ba55ef81SEric Dumazet if (list_empty(&ul->head))
170ba55ef81SEric Dumazet continue;
1718d0b94afSMartin KaFai Lau
1728d0b94afSMartin KaFai Lau spin_lock_bh(&ul->lock);
173d288a162SWangyang Guo list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) {
1748d0b94afSMartin KaFai Lau struct inet6_dev *rt_idev = rt->rt6i_idev;
1758d0b94afSMartin KaFai Lau struct net_device *rt_dev = rt->dst.dev;
176ba55ef81SEric Dumazet bool handled = false;
1778d0b94afSMartin KaFai Lau
1780ceb2f2bSEric Dumazet if (rt_idev && rt_idev->dev == dev) {
179e5f80fcfSEric Dumazet rt->rt6i_idev = in6_dev_get(blackhole_netdev);
1808d0b94afSMartin KaFai Lau in6_dev_put(rt_idev);
181ba55ef81SEric Dumazet handled = true;
1828d0b94afSMartin KaFai Lau }
1838d0b94afSMartin KaFai Lau
184e332bc67SEric W. Biederman if (rt_dev == dev) {
1858d7017fdSMahesh Bandewar rt->dst.dev = blackhole_netdev;
186d62607c3SJakub Kicinski netdev_ref_replace(rt_dev, blackhole_netdev,
1879038c320SEric Dumazet &rt->dst.dev_tracker,
1889038c320SEric Dumazet GFP_ATOMIC);
189ba55ef81SEric Dumazet handled = true;
1908d0b94afSMartin KaFai Lau }
191ba55ef81SEric Dumazet if (handled)
192d288a162SWangyang Guo list_move(&rt->dst.rt_uncached,
193ba55ef81SEric Dumazet &ul->quarantine);
1948d0b94afSMartin KaFai Lau }
1958d0b94afSMartin KaFai Lau spin_unlock_bh(&ul->lock);
1968d0b94afSMartin KaFai Lau }
1978d0b94afSMartin KaFai Lau }
1988d0b94afSMartin KaFai Lau
choose_neigh_daddr(const struct in6_addr * p,struct sk_buff * skb,const void * daddr)199f8a1b43bSDavid Ahern static inline const void *choose_neigh_daddr(const struct in6_addr *p,
200f894cbf8SDavid S. Miller struct sk_buff *skb,
201f894cbf8SDavid S. Miller const void *daddr)
20239232973SDavid S. Miller {
203a7563f34SDavid S. Miller if (!ipv6_addr_any(p))
20439232973SDavid S. Miller return (const void *) p;
205f894cbf8SDavid S. Miller else if (skb)
206f894cbf8SDavid S. Miller return &ipv6_hdr(skb)->daddr;
20739232973SDavid S. Miller return daddr;
20839232973SDavid S. Miller }
20939232973SDavid S. Miller
ip6_neigh_lookup(const struct in6_addr * gw,struct net_device * dev,struct sk_buff * skb,const void * daddr)210f8a1b43bSDavid Ahern struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,
211f8a1b43bSDavid Ahern struct net_device *dev,
212f894cbf8SDavid S. Miller struct sk_buff *skb,
213f894cbf8SDavid S. Miller const void *daddr)
214d3aaeb38SDavid S. Miller {
21539232973SDavid S. Miller struct neighbour *n;
21639232973SDavid S. Miller
217f8a1b43bSDavid Ahern daddr = choose_neigh_daddr(gw, skb, daddr);
218f8a1b43bSDavid Ahern n = __ipv6_neigh_lookup(dev, daddr);
219f83c7790SDavid S. Miller if (n)
220f83c7790SDavid S. Miller return n;
2217adf3246SStefano Brivio
2227adf3246SStefano Brivio n = neigh_create(&nd_tbl, daddr, dev);
2237adf3246SStefano Brivio return IS_ERR(n) ? NULL : n;
224f8a1b43bSDavid Ahern }
225f8a1b43bSDavid Ahern
ip6_dst_neigh_lookup(const struct dst_entry * dst,struct sk_buff * skb,const void * daddr)226f8a1b43bSDavid Ahern static struct neighbour *ip6_dst_neigh_lookup(const struct dst_entry *dst,
227f8a1b43bSDavid Ahern struct sk_buff *skb,
228f8a1b43bSDavid Ahern const void *daddr)
229f8a1b43bSDavid Ahern {
230797a4c1fSEric Dumazet const struct rt6_info *rt = dst_rt6_info(dst);
231f8a1b43bSDavid Ahern
2322c6b55f4SNicolas Dichtel return ip6_neigh_lookup(rt6_nexthop(rt, &in6addr_any),
2332c6b55f4SNicolas Dichtel dst->dev, skb, daddr);
234f83c7790SDavid S. Miller }
235f83c7790SDavid S. Miller
ip6_confirm_neigh(const struct dst_entry * dst,const void * daddr)23663fca65dSJulian Anastasov static void ip6_confirm_neigh(const struct dst_entry *dst, const void *daddr)
23763fca65dSJulian Anastasov {
238797a4c1fSEric Dumazet const struct rt6_info *rt = dst_rt6_info(dst);
23963fca65dSJulian Anastasov struct net_device *dev = dst->dev;
24063fca65dSJulian Anastasov
241cbfd6891SStefano Brivio daddr = choose_neigh_daddr(rt6_nexthop(rt, &in6addr_any), NULL, daddr);
24263fca65dSJulian Anastasov if (!daddr)
24363fca65dSJulian Anastasov return;
24463fca65dSJulian Anastasov if (dev->flags & (IFF_NOARP | IFF_LOOPBACK))
24563fca65dSJulian Anastasov return;
24663fca65dSJulian Anastasov if (ipv6_addr_is_multicast((const struct in6_addr *)daddr))
24763fca65dSJulian Anastasov return;
24863fca65dSJulian Anastasov __ipv6_confirm_neigh(dev, daddr);
24963fca65dSJulian Anastasov }
25063fca65dSJulian Anastasov
2519a7ec3a9SDaniel Lezcano static struct dst_ops ip6_dst_ops_template = {
2521da177e4SLinus Torvalds .family = AF_INET6,
2531da177e4SLinus Torvalds .gc = ip6_dst_gc,
2541da177e4SLinus Torvalds .gc_thresh = 1024,
2551da177e4SLinus Torvalds .check = ip6_dst_check,
2560dbaee3bSDavid S. Miller .default_advmss = ip6_default_advmss,
257ebb762f2SSteffen Klassert .mtu = ip6_mtu,
258d4ead6b3SDavid Ahern .cow_metrics = dst_cow_metrics_generic,
2591da177e4SLinus Torvalds .destroy = ip6_dst_destroy,
2601da177e4SLinus Torvalds .ifdown = ip6_dst_ifdown,
2611da177e4SLinus Torvalds .negative_advice = ip6_negative_advice,
2621da177e4SLinus Torvalds .link_failure = ip6_link_failure,
2631da177e4SLinus Torvalds .update_pmtu = ip6_rt_update_pmtu,
2646e157b6aSDavid S. Miller .redirect = rt6_do_redirect,
2659f8955ccSEric W. Biederman .local_out = __ip6_local_out,
266f8a1b43bSDavid Ahern .neigh_lookup = ip6_dst_neigh_lookup,
26763fca65dSJulian Anastasov .confirm_neigh = ip6_confirm_neigh,
2681da177e4SLinus Torvalds };
2691da177e4SLinus Torvalds
27014e50e57SDavid S. Miller static struct dst_ops ip6_dst_blackhole_ops = {
27114e50e57SDavid S. Miller .family = AF_INET6,
272214f45c9SEric Dumazet .default_advmss = ip6_default_advmss,
273f8a1b43bSDavid Ahern .neigh_lookup = ip6_dst_neigh_lookup,
274c4c877b2SDaniel Borkmann .check = ip6_dst_check,
275c4c877b2SDaniel Borkmann .destroy = ip6_dst_destroy,
276c4c877b2SDaniel Borkmann .cow_metrics = dst_cow_metrics_generic,
277c4c877b2SDaniel Borkmann .update_pmtu = dst_blackhole_update_pmtu,
278c4c877b2SDaniel Borkmann .redirect = dst_blackhole_redirect,
279c4c877b2SDaniel Borkmann .mtu = dst_blackhole_mtu,
28014e50e57SDavid S. Miller };
28114e50e57SDavid S. Miller
28262fa8a84SDavid S. Miller static const u32 ip6_template_metrics[RTAX_MAX] = {
28314edd87dSLi RongQing [RTAX_HOPLIMIT - 1] = 0,
28462fa8a84SDavid S. Miller };
28562fa8a84SDavid S. Miller
2868d1c802bSDavid Ahern static const struct fib6_info fib6_null_entry_template = {
28793c2fb25SDavid Ahern .fib6_flags = (RTF_REJECT | RTF_NONEXTHOP),
28893c2fb25SDavid Ahern .fib6_protocol = RTPROT_KERNEL,
28993c2fb25SDavid Ahern .fib6_metric = ~(u32)0,
290f05713e0SEric Dumazet .fib6_ref = REFCOUNT_INIT(1),
291421842edSDavid Ahern .fib6_type = RTN_UNREACHABLE,
292421842edSDavid Ahern .fib6_metrics = (struct dst_metrics *)&dst_default_metrics,
293421842edSDavid Ahern };
294421842edSDavid Ahern
295fb0af4c7SEric Dumazet static const struct rt6_info ip6_null_entry_template = {
2961da177e4SLinus Torvalds .dst = {
297bc9d3a9fSThomas Gleixner .__rcuref = RCUREF_INIT(1),
2981da177e4SLinus Torvalds .__use = 1,
2992c20cbd7SNicolas Dichtel .obsolete = DST_OBSOLETE_FORCE_CHK,
3001da177e4SLinus Torvalds .error = -ENETUNREACH,
3011da177e4SLinus Torvalds .input = ip6_pkt_discard,
3021da177e4SLinus Torvalds .output = ip6_pkt_discard_out,
3031da177e4SLinus Torvalds },
3041da177e4SLinus Torvalds .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP),
3051da177e4SLinus Torvalds };
3061da177e4SLinus Torvalds
307101367c2SThomas Graf #ifdef CONFIG_IPV6_MULTIPLE_TABLES
308101367c2SThomas Graf
309fb0af4c7SEric Dumazet static const struct rt6_info ip6_prohibit_entry_template = {
310101367c2SThomas Graf .dst = {
311bc9d3a9fSThomas Gleixner .__rcuref = RCUREF_INIT(1),
312101367c2SThomas Graf .__use = 1,
3132c20cbd7SNicolas Dichtel .obsolete = DST_OBSOLETE_FORCE_CHK,
314101367c2SThomas Graf .error = -EACCES,
3159ce8ade0SThomas Graf .input = ip6_pkt_prohibit,
3169ce8ade0SThomas Graf .output = ip6_pkt_prohibit_out,
317101367c2SThomas Graf },
318101367c2SThomas Graf .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP),
319101367c2SThomas Graf };
320101367c2SThomas Graf
321fb0af4c7SEric Dumazet static const struct rt6_info ip6_blk_hole_entry_template = {
322101367c2SThomas Graf .dst = {
323bc9d3a9fSThomas Gleixner .__rcuref = RCUREF_INIT(1),
324101367c2SThomas Graf .__use = 1,
3252c20cbd7SNicolas Dichtel .obsolete = DST_OBSOLETE_FORCE_CHK,
326101367c2SThomas Graf .error = -EINVAL,
327352e512cSHerbert Xu .input = dst_discard,
328ede2059dSEric W. Biederman .output = dst_discard_out,
329101367c2SThomas Graf },
330101367c2SThomas Graf .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP),
331101367c2SThomas Graf };
332101367c2SThomas Graf
333101367c2SThomas Graf #endif
334101367c2SThomas Graf
rt6_info_init(struct rt6_info * rt)335ebfa45f0SMartin KaFai Lau static void rt6_info_init(struct rt6_info *rt)
336ebfa45f0SMartin KaFai Lau {
3378f2a83b4SKees Cook memset_after(rt, 0, dst);
338ebfa45f0SMartin KaFai Lau }
339ebfa45f0SMartin KaFai Lau
3401da177e4SLinus Torvalds /* allocate dst with ip6_dst_ops */
ip6_dst_alloc(struct net * net,struct net_device * dev,int flags)34193531c67SDavid Ahern struct rt6_info *ip6_dst_alloc(struct net *net, struct net_device *dev,
342ad706862SMartin KaFai Lau int flags)
3431da177e4SLinus Torvalds {
34497bab73fSDavid S. Miller struct rt6_info *rt = dst_alloc(&net->ipv6.ip6_dst_ops, dev,
345b2a9c0edSWei Wang 1, DST_OBSOLETE_FORCE_CHK, flags);
346cf911662SDavid S. Miller
34781eb8447SWei Wang if (rt) {
348ebfa45f0SMartin KaFai Lau rt6_info_init(rt);
34981eb8447SWei Wang atomic_inc(&net->ipv6.rt6_stats->fib_rt_alloc);
35081eb8447SWei Wang }
3518104891bSSteffen Klassert
352cf911662SDavid S. Miller return rt;
3531da177e4SLinus Torvalds }
3549ab179d8SDavid Ahern EXPORT_SYMBOL(ip6_dst_alloc);
355d52d3997SMartin KaFai Lau
ip6_dst_destroy(struct dst_entry * dst)3561da177e4SLinus Torvalds static void ip6_dst_destroy(struct dst_entry *dst)
3571da177e4SLinus Torvalds {
358797a4c1fSEric Dumazet struct rt6_info *rt = dst_rt6_info(dst);
359a68886a6SDavid Ahern struct fib6_info *from;
3608d0b94afSMartin KaFai Lau struct inet6_dev *idev;
3611da177e4SLinus Torvalds
3621620a336SDavid Ahern ip_dst_metrics_put(dst);
3638d0b94afSMartin KaFai Lau rt6_uncached_list_del(rt);
3648d0b94afSMartin KaFai Lau
3658d0b94afSMartin KaFai Lau idev = rt->rt6i_idev;
36638308473SDavid S. Miller if (idev) {
3671da177e4SLinus Torvalds rt->rt6i_idev = NULL;
3681da177e4SLinus Torvalds in6_dev_put(idev);
3691da177e4SLinus Torvalds }
3701716a961SGao feng
37170530a2fSEric Dumazet from = unrcu_pointer(xchg(&rt->from, NULL));
37293531c67SDavid Ahern fib6_info_release(from);
373b3419363SDavid S. Miller }
374b3419363SDavid S. Miller
ip6_dst_ifdown(struct dst_entry * dst,struct net_device * dev)37543c28172SZhengchao Shao static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev)
3761da177e4SLinus Torvalds {
377797a4c1fSEric Dumazet struct rt6_info *rt = dst_rt6_info(dst);
3781da177e4SLinus Torvalds struct inet6_dev *idev = rt->rt6i_idev;
37943e25adcSPaolo Abeni struct fib6_info *from;
3801da177e4SLinus Torvalds
381e5f80fcfSEric Dumazet if (idev && idev->dev != blackhole_netdev) {
382e5f80fcfSEric Dumazet struct inet6_dev *blackhole_idev = in6_dev_get(blackhole_netdev);
383e5f80fcfSEric Dumazet
384e5f80fcfSEric Dumazet if (blackhole_idev) {
385e5f80fcfSEric Dumazet rt->rt6i_idev = blackhole_idev;
3861da177e4SLinus Torvalds in6_dev_put(idev);
3871da177e4SLinus Torvalds }
3881da177e4SLinus Torvalds }
38943e25adcSPaolo Abeni from = unrcu_pointer(xchg(&rt->from, NULL));
39043e25adcSPaolo Abeni fib6_info_release(from);
39197cac082SDavid S. Miller }
3921da177e4SLinus Torvalds
__rt6_check_expired(const struct rt6_info * rt)3935973fb1eSMartin KaFai Lau static bool __rt6_check_expired(const struct rt6_info *rt)
3945973fb1eSMartin KaFai Lau {
3955973fb1eSMartin KaFai Lau if (rt->rt6i_flags & RTF_EXPIRES)
3965973fb1eSMartin KaFai Lau return time_after(jiffies, rt->dst.expires);
3975973fb1eSMartin KaFai Lau else
3985973fb1eSMartin KaFai Lau return false;
3995973fb1eSMartin KaFai Lau }
4005973fb1eSMartin KaFai Lau
rt6_check_expired(const struct rt6_info * rt)401a50feda5SEric Dumazet static bool rt6_check_expired(const struct rt6_info *rt)
4021da177e4SLinus Torvalds {
403a68886a6SDavid Ahern struct fib6_info *from;
404a68886a6SDavid Ahern
405a68886a6SDavid Ahern from = rcu_dereference(rt->from);
406a68886a6SDavid Ahern
4071716a961SGao feng if (rt->rt6i_flags & RTF_EXPIRES) {
4081716a961SGao feng if (time_after(jiffies, rt->dst.expires))
409a50feda5SEric Dumazet return true;
410a68886a6SDavid Ahern } else if (from) {
4111e2ea8adSXin Long return rt->dst.obsolete != DST_OBSOLETE_FORCE_CHK ||
412a68886a6SDavid Ahern fib6_check_expired(from);
4131716a961SGao feng }
414a50feda5SEric Dumazet return false;
4151da177e4SLinus Torvalds }
4161da177e4SLinus Torvalds
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)417b1d40991SDavid Ahern void fib6_select_path(const struct net *net, struct fib6_result *res,
418b1d40991SDavid Ahern struct flowi6 *fl6, int oif, bool have_oif_match,
419b1d40991SDavid Ahern const struct sk_buff *skb, int strict)
42051ebd318SNicolas Dichtel {
4218d1c802bSDavid Ahern struct fib6_info *sibling, *next_sibling;
422b1d40991SDavid Ahern struct fib6_info *match = res->f6i;
423b1d40991SDavid Ahern
42434fe5a1cSDavid Ahern if (!match->nh && (!match->fib6_nsiblings || have_oif_match))
425b1d40991SDavid Ahern goto out;
42651ebd318SNicolas Dichtel
42734fe5a1cSDavid Ahern if (match->nh && have_oif_match && res->nh)
42834fe5a1cSDavid Ahern return;
42934fe5a1cSDavid Ahern
4308423be89SSriram Yagnaraman if (skb)
4318423be89SSriram Yagnaraman IP6CB(skb)->flags |= IP6SKB_MULTIPATH;
4328423be89SSriram Yagnaraman
433b673d6ccSJakub Sitnicki /* We might have already computed the hash for ICMPv6 errors. In such
434b673d6ccSJakub Sitnicki * case it will always be non-zero. Otherwise now is the time to do it.
435b673d6ccSJakub Sitnicki */
436f88d8ea6SDavid Ahern if (!fl6->mp_hash &&
437f88d8ea6SDavid Ahern (!match->nh || nexthop_is_multipath(match->nh)))
438b4bac172SDavid Ahern fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL);
439b673d6ccSJakub Sitnicki
440f88d8ea6SDavid Ahern if (unlikely(match->nh)) {
441f88d8ea6SDavid Ahern nexthop_path_fib6_result(res, fl6->mp_hash);
442f88d8ea6SDavid Ahern return;
443f88d8ea6SDavid Ahern }
444f88d8ea6SDavid Ahern
4451cf844c7SDavid Ahern if (fl6->mp_hash <= atomic_read(&match->fib6_nh->fib_nh_upper_bound))
446b1d40991SDavid Ahern goto out;
447bbfcd776SIdo Schimmel
44893c2fb25SDavid Ahern list_for_each_entry_safe(sibling, next_sibling, &match->fib6_siblings,
44993c2fb25SDavid Ahern fib6_siblings) {
4501cf844c7SDavid Ahern const struct fib6_nh *nh = sibling->fib6_nh;
4515e670d84SDavid Ahern int nh_upper_bound;
4525e670d84SDavid Ahern
453702cea56SDavid Ahern nh_upper_bound = atomic_read(&nh->fib_nh_upper_bound);
4545e670d84SDavid Ahern if (fl6->mp_hash > nh_upper_bound)
4553d709f69SIdo Schimmel continue;
456702cea56SDavid Ahern if (rt6_score_route(nh, sibling->fib6_flags, oif, strict) < 0)
45752bd4c0cSNicolas Dichtel break;
45851ebd318SNicolas Dichtel match = sibling;
45951ebd318SNicolas Dichtel break;
46051ebd318SNicolas Dichtel }
4613d709f69SIdo Schimmel
462b1d40991SDavid Ahern out:
463b1d40991SDavid Ahern res->f6i = match;
4641cf844c7SDavid Ahern res->nh = match->fib6_nh;
46551ebd318SNicolas Dichtel }
46651ebd318SNicolas Dichtel
4671da177e4SLinus Torvalds /*
46866f5d6ceSWei Wang * Route lookup. rcu_read_lock() should be held.
4691da177e4SLinus Torvalds */
4701da177e4SLinus Torvalds
__rt6_device_match(struct net * net,const struct fib6_nh * nh,const struct in6_addr * saddr,int oif,int flags)4710c59d006SDavid Ahern static bool __rt6_device_match(struct net *net, const struct fib6_nh *nh,
4720c59d006SDavid Ahern const struct in6_addr *saddr, int oif, int flags)
4730c59d006SDavid Ahern {
4740c59d006SDavid Ahern const struct net_device *dev;
4750c59d006SDavid Ahern
4760c59d006SDavid Ahern if (nh->fib_nh_flags & RTNH_F_DEAD)
4770c59d006SDavid Ahern return false;
4780c59d006SDavid Ahern
4790c59d006SDavid Ahern dev = nh->fib_nh_dev;
4800c59d006SDavid Ahern if (oif) {
4810c59d006SDavid Ahern if (dev->ifindex == oif)
4820c59d006SDavid Ahern return true;
4830c59d006SDavid Ahern } else {
4840c59d006SDavid Ahern if (ipv6_chk_addr(net, saddr, dev,
4850c59d006SDavid Ahern flags & RT6_LOOKUP_F_IFACE))
4860c59d006SDavid Ahern return true;
4870c59d006SDavid Ahern }
4880c59d006SDavid Ahern
4890c59d006SDavid Ahern return false;
4900c59d006SDavid Ahern }
4910c59d006SDavid Ahern
492962b6803SDavid Ahern struct fib6_nh_dm_arg {
493962b6803SDavid Ahern struct net *net;
494962b6803SDavid Ahern const struct in6_addr *saddr;
495962b6803SDavid Ahern int oif;
496962b6803SDavid Ahern int flags;
497962b6803SDavid Ahern struct fib6_nh *nh;
498962b6803SDavid Ahern };
499962b6803SDavid Ahern
__rt6_nh_dev_match(struct fib6_nh * nh,void * _arg)500962b6803SDavid Ahern static int __rt6_nh_dev_match(struct fib6_nh *nh, void *_arg)
501962b6803SDavid Ahern {
502962b6803SDavid Ahern struct fib6_nh_dm_arg *arg = _arg;
503962b6803SDavid Ahern
504962b6803SDavid Ahern arg->nh = nh;
505962b6803SDavid Ahern return __rt6_device_match(arg->net, nh, arg->saddr, arg->oif,
506962b6803SDavid Ahern arg->flags);
507962b6803SDavid Ahern }
508962b6803SDavid Ahern
509962b6803SDavid Ahern /* returns fib6_nh from nexthop or NULL */
rt6_nh_dev_match(struct net * net,struct nexthop * nh,struct fib6_result * res,const struct in6_addr * saddr,int oif,int flags)510962b6803SDavid Ahern static struct fib6_nh *rt6_nh_dev_match(struct net *net, struct nexthop *nh,
511962b6803SDavid Ahern struct fib6_result *res,
512962b6803SDavid Ahern const struct in6_addr *saddr,
513962b6803SDavid Ahern int oif, int flags)
514962b6803SDavid Ahern {
515962b6803SDavid Ahern struct fib6_nh_dm_arg arg = {
516962b6803SDavid Ahern .net = net,
517962b6803SDavid Ahern .saddr = saddr,
518962b6803SDavid Ahern .oif = oif,
519962b6803SDavid Ahern .flags = flags,
520962b6803SDavid Ahern };
521962b6803SDavid Ahern
522962b6803SDavid Ahern if (nexthop_is_blackhole(nh))
523962b6803SDavid Ahern return NULL;
524962b6803SDavid Ahern
525962b6803SDavid Ahern if (nexthop_for_each_fib6_nh(nh, __rt6_nh_dev_match, &arg))
526962b6803SDavid Ahern return arg.nh;
527962b6803SDavid Ahern
528962b6803SDavid Ahern return NULL;
529962b6803SDavid Ahern }
530962b6803SDavid Ahern
rt6_device_match(struct net * net,struct fib6_result * res,const struct in6_addr * saddr,int oif,int flags)53175ef7389SDavid Ahern static void rt6_device_match(struct net *net, struct fib6_result *res,
53275ef7389SDavid Ahern const struct in6_addr *saddr, int oif, int flags)
5331da177e4SLinus Torvalds {
53475ef7389SDavid Ahern struct fib6_info *f6i = res->f6i;
53575ef7389SDavid Ahern struct fib6_info *spf6i;
53675ef7389SDavid Ahern struct fib6_nh *nh;
5371da177e4SLinus Torvalds
53875ef7389SDavid Ahern if (!oif && ipv6_addr_any(saddr)) {
539f88d8ea6SDavid Ahern if (unlikely(f6i->nh)) {
540f88d8ea6SDavid Ahern nh = nexthop_fib6_nh(f6i->nh);
541f88d8ea6SDavid Ahern if (nexthop_is_blackhole(f6i->nh))
542f88d8ea6SDavid Ahern goto out_blackhole;
543f88d8ea6SDavid Ahern } else {
5441cf844c7SDavid Ahern nh = f6i->fib6_nh;
545f88d8ea6SDavid Ahern }
5467d21fec9SDavid Ahern if (!(nh->fib_nh_flags & RTNH_F_DEAD))
5477d21fec9SDavid Ahern goto out;
5481da177e4SLinus Torvalds }
5491da177e4SLinus Torvalds
55075ef7389SDavid Ahern for (spf6i = f6i; spf6i; spf6i = rcu_dereference(spf6i->fib6_next)) {
551962b6803SDavid Ahern bool matched = false;
552962b6803SDavid Ahern
553962b6803SDavid Ahern if (unlikely(spf6i->nh)) {
554962b6803SDavid Ahern nh = rt6_nh_dev_match(net, spf6i->nh, res, saddr,
555962b6803SDavid Ahern oif, flags);
556962b6803SDavid Ahern if (nh)
557962b6803SDavid Ahern matched = true;
558962b6803SDavid Ahern } else {
5591cf844c7SDavid Ahern nh = spf6i->fib6_nh;
560962b6803SDavid Ahern if (__rt6_device_match(net, nh, saddr, oif, flags))
561962b6803SDavid Ahern matched = true;
562962b6803SDavid Ahern }
563962b6803SDavid Ahern if (matched) {
56475ef7389SDavid Ahern res->f6i = spf6i;
5657d21fec9SDavid Ahern goto out;
56675ef7389SDavid Ahern }
56775ef7389SDavid Ahern }
5681da177e4SLinus Torvalds
56975ef7389SDavid Ahern if (oif && flags & RT6_LOOKUP_F_IFACE) {
57075ef7389SDavid Ahern res->f6i = net->ipv6.fib6_null_entry;
5711cf844c7SDavid Ahern nh = res->f6i->fib6_nh;
5727d21fec9SDavid Ahern goto out;
57375ef7389SDavid Ahern }
57475ef7389SDavid Ahern
575f88d8ea6SDavid Ahern if (unlikely(f6i->nh)) {
576f88d8ea6SDavid Ahern nh = nexthop_fib6_nh(f6i->nh);
577f88d8ea6SDavid Ahern if (nexthop_is_blackhole(f6i->nh))
578f88d8ea6SDavid Ahern goto out_blackhole;
579f88d8ea6SDavid Ahern } else {
5801cf844c7SDavid Ahern nh = f6i->fib6_nh;
581f88d8ea6SDavid Ahern }
582f88d8ea6SDavid Ahern
5837d21fec9SDavid Ahern if (nh->fib_nh_flags & RTNH_F_DEAD) {
58475ef7389SDavid Ahern res->f6i = net->ipv6.fib6_null_entry;
5851cf844c7SDavid Ahern nh = res->f6i->fib6_nh;
58675ef7389SDavid Ahern }
5877d21fec9SDavid Ahern out:
5887d21fec9SDavid Ahern res->nh = nh;
5897d21fec9SDavid Ahern res->fib6_type = res->f6i->fib6_type;
5907d21fec9SDavid Ahern res->fib6_flags = res->f6i->fib6_flags;
591f88d8ea6SDavid Ahern return;
592f88d8ea6SDavid Ahern
593f88d8ea6SDavid Ahern out_blackhole:
594f88d8ea6SDavid Ahern res->fib6_flags |= RTF_REJECT;
595f88d8ea6SDavid Ahern res->fib6_type = RTN_BLACKHOLE;
596f88d8ea6SDavid Ahern res->nh = nh;
5971da177e4SLinus Torvalds }
5981da177e4SLinus Torvalds
59927097255SYOSHIFUJI Hideaki #ifdef CONFIG_IPV6_ROUTER_PREF
600c2f17e82SHannes Frederic Sowa struct __rt6_probe_work {
601c2f17e82SHannes Frederic Sowa struct work_struct work;
602c2f17e82SHannes Frederic Sowa struct in6_addr target;
603c2f17e82SHannes Frederic Sowa struct net_device *dev;
604fb67510bSEric Dumazet netdevice_tracker dev_tracker;
605c2f17e82SHannes Frederic Sowa };
606c2f17e82SHannes Frederic Sowa
rt6_probe_deferred(struct work_struct * w)607c2f17e82SHannes Frederic Sowa static void rt6_probe_deferred(struct work_struct *w)
608c2f17e82SHannes Frederic Sowa {
609c2f17e82SHannes Frederic Sowa struct in6_addr mcaddr;
610c2f17e82SHannes Frederic Sowa struct __rt6_probe_work *work =
611c2f17e82SHannes Frederic Sowa container_of(w, struct __rt6_probe_work, work);
612c2f17e82SHannes Frederic Sowa
613c2f17e82SHannes Frederic Sowa addrconf_addr_solict_mult(&work->target, &mcaddr);
614adc176c5SErik Nordmark ndisc_send_ns(work->dev, &work->target, &mcaddr, NULL, 0);
615d62607c3SJakub Kicinski netdev_put(work->dev, &work->dev_tracker);
616662f5533SMichael Büsch kfree(work);
617c2f17e82SHannes Frederic Sowa }
618c2f17e82SHannes Frederic Sowa
rt6_probe(struct fib6_nh * fib6_nh)619cc3a86c8SDavid Ahern static void rt6_probe(struct fib6_nh *fib6_nh)
62027097255SYOSHIFUJI Hideaki {
621f547fac6SSabrina Dubroca struct __rt6_probe_work *work = NULL;
6225e670d84SDavid Ahern const struct in6_addr *nh_gw;
6231bef4c22SEric Dumazet unsigned long last_probe;
624f2c31e32SEric Dumazet struct neighbour *neigh;
6255e670d84SDavid Ahern struct net_device *dev;
626f547fac6SSabrina Dubroca struct inet6_dev *idev;
6275e670d84SDavid Ahern
62827097255SYOSHIFUJI Hideaki /*
62927097255SYOSHIFUJI Hideaki * Okay, this does not seem to be appropriate
63027097255SYOSHIFUJI Hideaki * for now, however, we need to check if it
63127097255SYOSHIFUJI Hideaki * is really so; aka Router Reachability Probing.
63227097255SYOSHIFUJI Hideaki *
63327097255SYOSHIFUJI Hideaki * Router Reachability Probe MUST be rate-limited
63427097255SYOSHIFUJI Hideaki * to no more than one per minute.
63527097255SYOSHIFUJI Hideaki */
636004b3942SHangbin Liu if (!fib6_nh->fib_nh_gw_family)
637fdd6681dSAmerigo Wang return;
6385e670d84SDavid Ahern
639cc3a86c8SDavid Ahern nh_gw = &fib6_nh->fib_nh_gw6;
640cc3a86c8SDavid Ahern dev = fib6_nh->fib_nh_dev;
64109eed119SEric Dumazet rcu_read_lock();
6421bef4c22SEric Dumazet last_probe = READ_ONCE(fib6_nh->last_probe);
643f547fac6SSabrina Dubroca idev = __in6_dev_get(dev);
64473e7c8caSEric Dumazet if (!idev)
64573e7c8caSEric Dumazet goto out;
6465e670d84SDavid Ahern neigh = __ipv6_neigh_lookup_noref(dev, nh_gw);
6472152caeaSYOSHIFUJI Hideaki / 吉藤英明 if (neigh) {
648b071af52SEric Dumazet if (READ_ONCE(neigh->nud_state) & NUD_VALID)
6498d6c31bfSMartin KaFai Lau goto out;
6508d6c31bfSMartin KaFai Lau
65109eed119SEric Dumazet write_lock_bh(&neigh->lock);
652990edb42SMartin KaFai Lau if (!(neigh->nud_state & NUD_VALID) &&
653990edb42SMartin KaFai Lau time_after(jiffies,
654dcd1f572SDavid Ahern neigh->updated + idev->cnf.rtr_probe_interval)) {
655c2f17e82SHannes Frederic Sowa work = kmalloc(sizeof(*work), GFP_ATOMIC);
656990edb42SMartin KaFai Lau if (work)
6577e980569SJiri Benc __neigh_set_probe_once(neigh);
658990edb42SMartin KaFai Lau }
65909eed119SEric Dumazet write_unlock_bh(&neigh->lock);
6601bef4c22SEric Dumazet } else if (time_after(jiffies, last_probe +
661f547fac6SSabrina Dubroca idev->cnf.rtr_probe_interval)) {
662990edb42SMartin KaFai Lau work = kmalloc(sizeof(*work), GFP_ATOMIC);
663990edb42SMartin KaFai Lau }
664c2f17e82SHannes Frederic Sowa
6651bef4c22SEric Dumazet if (!work || cmpxchg(&fib6_nh->last_probe,
6661bef4c22SEric Dumazet last_probe, jiffies) != last_probe) {
6671bef4c22SEric Dumazet kfree(work);
6681bef4c22SEric Dumazet } else {
669c2f17e82SHannes Frederic Sowa INIT_WORK(&work->work, rt6_probe_deferred);
6705e670d84SDavid Ahern work->target = *nh_gw;
671d62607c3SJakub Kicinski netdev_hold(dev, &work->dev_tracker, GFP_ATOMIC);
6725e670d84SDavid Ahern work->dev = dev;
673c2f17e82SHannes Frederic Sowa schedule_work(&work->work);
674c2f17e82SHannes Frederic Sowa }
675990edb42SMartin KaFai Lau
6768d6c31bfSMartin KaFai Lau out:
67709eed119SEric Dumazet rcu_read_unlock();
678f2c31e32SEric Dumazet }
67927097255SYOSHIFUJI Hideaki #else
rt6_probe(struct fib6_nh * fib6_nh)680cc3a86c8SDavid Ahern static inline void rt6_probe(struct fib6_nh *fib6_nh)
68127097255SYOSHIFUJI Hideaki {
68227097255SYOSHIFUJI Hideaki }
68327097255SYOSHIFUJI Hideaki #endif
68427097255SYOSHIFUJI Hideaki
6851da177e4SLinus Torvalds /*
686554cfb7eSYOSHIFUJI Hideaki * Default Router Selection (RFC 2461 6.3.6)
6871da177e4SLinus Torvalds */
rt6_check_neigh(const struct fib6_nh * fib6_nh)6881ba9a895SDavid Ahern static enum rt6_nud_state rt6_check_neigh(const struct fib6_nh *fib6_nh)
6891da177e4SLinus Torvalds {
690afc154e9SHannes Frederic Sowa enum rt6_nud_state ret = RT6_NUD_FAIL_HARD;
6915e670d84SDavid Ahern struct neighbour *neigh;
692f2c31e32SEric Dumazet
69309eed119SEric Dumazet rcu_read_lock();
6941ba9a895SDavid Ahern neigh = __ipv6_neigh_lookup_noref(fib6_nh->fib_nh_dev,
6951ba9a895SDavid Ahern &fib6_nh->fib_nh_gw6);
696145a3621SYOSHIFUJI Hideaki / 吉藤英明 if (neigh) {
697c486640aSEric Dumazet u8 nud_state = READ_ONCE(neigh->nud_state);
698c486640aSEric Dumazet
699c486640aSEric Dumazet if (nud_state & NUD_VALID)
700afc154e9SHannes Frederic Sowa ret = RT6_NUD_SUCCEED;
701398bcbebSYOSHIFUJI Hideaki #ifdef CONFIG_IPV6_ROUTER_PREF
702c486640aSEric Dumazet else if (!(nud_state & NUD_FAILED))
703afc154e9SHannes Frederic Sowa ret = RT6_NUD_SUCCEED;
7047e980569SJiri Benc else
7057e980569SJiri Benc ret = RT6_NUD_FAIL_PROBE;
706398bcbebSYOSHIFUJI Hideaki #endif
707afc154e9SHannes Frederic Sowa } else {
708afc154e9SHannes Frederic Sowa ret = IS_ENABLED(CONFIG_IPV6_ROUTER_PREF) ?
7097e980569SJiri Benc RT6_NUD_SUCCEED : RT6_NUD_FAIL_DO_RR;
710a5a81f0bSPaul Marks }
71109eed119SEric Dumazet rcu_read_unlock();
712145a3621SYOSHIFUJI Hideaki / 吉藤英明
713a5a81f0bSPaul Marks return ret;
7141da177e4SLinus Torvalds }
7151da177e4SLinus Torvalds
rt6_score_route(const struct fib6_nh * nh,u32 fib6_flags,int oif,int strict)716702cea56SDavid Ahern static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
717702cea56SDavid Ahern int strict)
718554cfb7eSYOSHIFUJI Hideaki {
7196e1809a5SDavid Ahern int m = 0;
7204d0c5911SYOSHIFUJI Hideaki
7216e1809a5SDavid Ahern if (!oif || nh->fib_nh_dev->ifindex == oif)
7226e1809a5SDavid Ahern m = 2;
7236e1809a5SDavid Ahern
72477d16f45SYOSHIFUJI Hideaki if (!m && (strict & RT6_LOOKUP_F_IFACE))
725afc154e9SHannes Frederic Sowa return RT6_NUD_FAIL_HARD;
726ebacaaa0SYOSHIFUJI Hideaki #ifdef CONFIG_IPV6_ROUTER_PREF
727702cea56SDavid Ahern m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(fib6_flags)) << 2;
728ebacaaa0SYOSHIFUJI Hideaki #endif
7291ba9a895SDavid Ahern if ((strict & RT6_LOOKUP_F_REACHABLE) &&
730702cea56SDavid Ahern !(fib6_flags & RTF_NONEXTHOP) && nh->fib_nh_gw_family) {
7311ba9a895SDavid Ahern int n = rt6_check_neigh(nh);
732afc154e9SHannes Frederic Sowa if (n < 0)
733afc154e9SHannes Frederic Sowa return n;
734afc154e9SHannes Frederic Sowa }
735554cfb7eSYOSHIFUJI Hideaki return m;
736554cfb7eSYOSHIFUJI Hideaki }
737554cfb7eSYOSHIFUJI Hideaki
find_match(struct fib6_nh * nh,u32 fib6_flags,int oif,int strict,int * mpri,bool * do_rr)73828679ed1SDavid Ahern static bool find_match(struct fib6_nh *nh, u32 fib6_flags,
73928679ed1SDavid Ahern int oif, int strict, int *mpri, bool *do_rr)
740554cfb7eSYOSHIFUJI Hideaki {
741afc154e9SHannes Frederic Sowa bool match_do_rr = false;
74228679ed1SDavid Ahern bool rc = false;
74328679ed1SDavid Ahern int m;
74435103d11SAndy Gospodarek
74528679ed1SDavid Ahern if (nh->fib_nh_flags & RTNH_F_DEAD)
7468067bb8cSIdo Schimmel goto out;
7478067bb8cSIdo Schimmel
74828679ed1SDavid Ahern if (ip6_ignore_linkdown(nh->fib_nh_dev) &&
74928679ed1SDavid Ahern nh->fib_nh_flags & RTNH_F_LINKDOWN &&
750d5d32e4bSDavid Ahern !(strict & RT6_LOOKUP_F_IGNORE_LINKSTATE))
75135103d11SAndy Gospodarek goto out;
752554cfb7eSYOSHIFUJI Hideaki
75328679ed1SDavid Ahern m = rt6_score_route(nh, fib6_flags, oif, strict);
7547e980569SJiri Benc if (m == RT6_NUD_FAIL_DO_RR) {
755afc154e9SHannes Frederic Sowa match_do_rr = true;
756afc154e9SHannes Frederic Sowa m = 0; /* lowest valid score */
7577e980569SJiri Benc } else if (m == RT6_NUD_FAIL_HARD) {
758f11e6659SDavid S. Miller goto out;
7591da177e4SLinus Torvalds }
760f11e6659SDavid S. Miller
761afc154e9SHannes Frederic Sowa if (strict & RT6_LOOKUP_F_REACHABLE)
76228679ed1SDavid Ahern rt6_probe(nh);
763afc154e9SHannes Frederic Sowa
7647e980569SJiri Benc /* note that m can be RT6_NUD_FAIL_PROBE at this point */
765afc154e9SHannes Frederic Sowa if (m > *mpri) {
766afc154e9SHannes Frederic Sowa *do_rr = match_do_rr;
767afc154e9SHannes Frederic Sowa *mpri = m;
76828679ed1SDavid Ahern rc = true;
769afc154e9SHannes Frederic Sowa }
770f11e6659SDavid S. Miller out:
77128679ed1SDavid Ahern return rc;
7721da177e4SLinus Torvalds }
7731da177e4SLinus Torvalds
77417a5984eSDavid Ahern struct fib6_nh_frl_arg {
77517a5984eSDavid Ahern u32 flags;
77617a5984eSDavid Ahern int oif;
77717a5984eSDavid Ahern int strict;
77817a5984eSDavid Ahern int *mpri;
77917a5984eSDavid Ahern bool *do_rr;
78017a5984eSDavid Ahern struct fib6_nh *nh;
78117a5984eSDavid Ahern };
78217a5984eSDavid Ahern
rt6_nh_find_match(struct fib6_nh * nh,void * _arg)78317a5984eSDavid Ahern static int rt6_nh_find_match(struct fib6_nh *nh, void *_arg)
78417a5984eSDavid Ahern {
78517a5984eSDavid Ahern struct fib6_nh_frl_arg *arg = _arg;
78617a5984eSDavid Ahern
78717a5984eSDavid Ahern arg->nh = nh;
78817a5984eSDavid Ahern return find_match(nh, arg->flags, arg->oif, arg->strict,
78917a5984eSDavid Ahern arg->mpri, arg->do_rr);
79017a5984eSDavid Ahern }
79117a5984eSDavid Ahern
__find_rr_leaf(struct fib6_info * f6i_start,struct fib6_info * nomatch,u32 metric,struct fib6_result * res,struct fib6_info ** cont,int oif,int strict,bool * do_rr,int * mpri)792b7bc4b6aSDavid Ahern static void __find_rr_leaf(struct fib6_info *f6i_start,
79330c15f03SDavid Ahern struct fib6_info *nomatch, u32 metric,
794b7bc4b6aSDavid Ahern struct fib6_result *res, struct fib6_info **cont,
79530c15f03SDavid Ahern int oif, int strict, bool *do_rr, int *mpri)
79630c15f03SDavid Ahern {
797b7bc4b6aSDavid Ahern struct fib6_info *f6i;
79830c15f03SDavid Ahern
799b7bc4b6aSDavid Ahern for (f6i = f6i_start;
800b7bc4b6aSDavid Ahern f6i && f6i != nomatch;
801b7bc4b6aSDavid Ahern f6i = rcu_dereference(f6i->fib6_next)) {
80217a5984eSDavid Ahern bool matched = false;
80330c15f03SDavid Ahern struct fib6_nh *nh;
80430c15f03SDavid Ahern
805b7bc4b6aSDavid Ahern if (cont && f6i->fib6_metric != metric) {
806b7bc4b6aSDavid Ahern *cont = f6i;
80730c15f03SDavid Ahern return;
80830c15f03SDavid Ahern }
80930c15f03SDavid Ahern
810b7bc4b6aSDavid Ahern if (fib6_check_expired(f6i))
81130c15f03SDavid Ahern continue;
81230c15f03SDavid Ahern
81317a5984eSDavid Ahern if (unlikely(f6i->nh)) {
81417a5984eSDavid Ahern struct fib6_nh_frl_arg arg = {
81517a5984eSDavid Ahern .flags = f6i->fib6_flags,
81617a5984eSDavid Ahern .oif = oif,
81717a5984eSDavid Ahern .strict = strict,
81817a5984eSDavid Ahern .mpri = mpri,
81917a5984eSDavid Ahern .do_rr = do_rr
82017a5984eSDavid Ahern };
82117a5984eSDavid Ahern
82217a5984eSDavid Ahern if (nexthop_is_blackhole(f6i->nh)) {
82317a5984eSDavid Ahern res->fib6_flags = RTF_REJECT;
82417a5984eSDavid Ahern res->fib6_type = RTN_BLACKHOLE;
82517a5984eSDavid Ahern res->f6i = f6i;
82617a5984eSDavid Ahern res->nh = nexthop_fib6_nh(f6i->nh);
82717a5984eSDavid Ahern return;
82817a5984eSDavid Ahern }
82917a5984eSDavid Ahern if (nexthop_for_each_fib6_nh(f6i->nh, rt6_nh_find_match,
83017a5984eSDavid Ahern &arg)) {
83117a5984eSDavid Ahern matched = true;
83217a5984eSDavid Ahern nh = arg.nh;
83317a5984eSDavid Ahern }
83417a5984eSDavid Ahern } else {
8351cf844c7SDavid Ahern nh = f6i->fib6_nh;
83617a5984eSDavid Ahern if (find_match(nh, f6i->fib6_flags, oif, strict,
83717a5984eSDavid Ahern mpri, do_rr))
83817a5984eSDavid Ahern matched = true;
83917a5984eSDavid Ahern }
84017a5984eSDavid Ahern if (matched) {
841b7bc4b6aSDavid Ahern res->f6i = f6i;
842b7bc4b6aSDavid Ahern res->nh = nh;
8437d21fec9SDavid Ahern res->fib6_flags = f6i->fib6_flags;
8447d21fec9SDavid Ahern res->fib6_type = f6i->fib6_type;
845b7bc4b6aSDavid Ahern }
84630c15f03SDavid Ahern }
84730c15f03SDavid Ahern }
84830c15f03SDavid Ahern
find_rr_leaf(struct fib6_node * fn,struct fib6_info * leaf,struct fib6_info * rr_head,int oif,int strict,bool * do_rr,struct fib6_result * res)849b7bc4b6aSDavid Ahern static void find_rr_leaf(struct fib6_node *fn, struct fib6_info *leaf,
850b7bc4b6aSDavid Ahern struct fib6_info *rr_head, int oif, int strict,
851b7bc4b6aSDavid Ahern bool *do_rr, struct fib6_result *res)
852f11e6659SDavid S. Miller {
853b7bc4b6aSDavid Ahern u32 metric = rr_head->fib6_metric;
854b7bc4b6aSDavid Ahern struct fib6_info *cont = NULL;
855f11e6659SDavid S. Miller int mpri = -1;
856f11e6659SDavid S. Miller
857b7bc4b6aSDavid Ahern __find_rr_leaf(rr_head, NULL, metric, res, &cont,
85830c15f03SDavid Ahern oif, strict, do_rr, &mpri);
8599fbdcfafSSteffen Klassert
860b7bc4b6aSDavid Ahern __find_rr_leaf(leaf, rr_head, metric, res, &cont,
86130c15f03SDavid Ahern oif, strict, do_rr, &mpri);
8629fbdcfafSSteffen Klassert
863b7bc4b6aSDavid Ahern if (res->f6i || !cont)
864b7bc4b6aSDavid Ahern return;
8659fbdcfafSSteffen Klassert
866b7bc4b6aSDavid Ahern __find_rr_leaf(cont, NULL, metric, res, NULL,
86730c15f03SDavid Ahern oif, strict, do_rr, &mpri);
868f11e6659SDavid S. Miller }
869f11e6659SDavid S. Miller
rt6_select(struct net * net,struct fib6_node * fn,int oif,struct fib6_result * res,int strict)870b7bc4b6aSDavid Ahern static void rt6_select(struct net *net, struct fib6_node *fn, int oif,
871b7bc4b6aSDavid Ahern struct fib6_result *res, int strict)
872f11e6659SDavid S. Miller {
8738d1c802bSDavid Ahern struct fib6_info *leaf = rcu_dereference(fn->leaf);
874b7bc4b6aSDavid Ahern struct fib6_info *rt0;
875afc154e9SHannes Frederic Sowa bool do_rr = false;
87617ecf590SWei Wang int key_plen;
877f11e6659SDavid S. Miller
878b7bc4b6aSDavid Ahern /* make sure this function or its helpers sets f6i */
879b7bc4b6aSDavid Ahern res->f6i = NULL;
880b7bc4b6aSDavid Ahern
881421842edSDavid Ahern if (!leaf || leaf == net->ipv6.fib6_null_entry)
882b7bc4b6aSDavid Ahern goto out;
8838d1040e8SWei Wang
88466f5d6ceSWei Wang rt0 = rcu_dereference(fn->rr_ptr);
885f11e6659SDavid S. Miller if (!rt0)
88666f5d6ceSWei Wang rt0 = leaf;
887f11e6659SDavid S. Miller
88817ecf590SWei Wang /* Double check to make sure fn is not an intermediate node
88917ecf590SWei Wang * and fn->leaf does not points to its child's leaf
89017ecf590SWei Wang * (This might happen if all routes under fn are deleted from
89117ecf590SWei Wang * the tree and fib6_repair_tree() is called on the node.)
89217ecf590SWei Wang */
89393c2fb25SDavid Ahern key_plen = rt0->fib6_dst.plen;
89417ecf590SWei Wang #ifdef CONFIG_IPV6_SUBTREES
89593c2fb25SDavid Ahern if (rt0->fib6_src.plen)
89693c2fb25SDavid Ahern key_plen = rt0->fib6_src.plen;
89717ecf590SWei Wang #endif
89817ecf590SWei Wang if (fn->fn_bit != key_plen)
899b7bc4b6aSDavid Ahern goto out;
90017ecf590SWei Wang
901b7bc4b6aSDavid Ahern find_rr_leaf(fn, leaf, rt0, oif, strict, &do_rr, res);
902afc154e9SHannes Frederic Sowa if (do_rr) {
9038fb11a9aSDavid Ahern struct fib6_info *next = rcu_dereference(rt0->fib6_next);
904f11e6659SDavid S. Miller
905554cfb7eSYOSHIFUJI Hideaki /* no entries matched; do round-robin */
90693c2fb25SDavid Ahern if (!next || next->fib6_metric != rt0->fib6_metric)
9078d1040e8SWei Wang next = leaf;
908f11e6659SDavid S. Miller
90966f5d6ceSWei Wang if (next != rt0) {
91093c2fb25SDavid Ahern spin_lock_bh(&leaf->fib6_table->tb6_lock);
91166f5d6ceSWei Wang /* make sure next is not being deleted from the tree */
91293c2fb25SDavid Ahern if (next->fib6_node)
91366f5d6ceSWei Wang rcu_assign_pointer(fn->rr_ptr, next);
91493c2fb25SDavid Ahern spin_unlock_bh(&leaf->fib6_table->tb6_lock);
91566f5d6ceSWei Wang }
916554cfb7eSYOSHIFUJI Hideaki }
917554cfb7eSYOSHIFUJI Hideaki
918b7bc4b6aSDavid Ahern out:
919b7bc4b6aSDavid Ahern if (!res->f6i) {
920b7bc4b6aSDavid Ahern res->f6i = net->ipv6.fib6_null_entry;
9211cf844c7SDavid Ahern res->nh = res->f6i->fib6_nh;
9227d21fec9SDavid Ahern res->fib6_flags = res->f6i->fib6_flags;
9237d21fec9SDavid Ahern res->fib6_type = res->f6i->fib6_type;
924b7bc4b6aSDavid Ahern }
9251da177e4SLinus Torvalds }
9261da177e4SLinus Torvalds
rt6_is_gw_or_nonexthop(const struct fib6_result * res)92785bd05deSDavid Ahern static bool rt6_is_gw_or_nonexthop(const struct fib6_result *res)
9288b9df265SMartin KaFai Lau {
92985bd05deSDavid Ahern return (res->f6i->fib6_flags & RTF_NONEXTHOP) ||
93085bd05deSDavid Ahern res->nh->fib_nh_gw_family;
9318b9df265SMartin KaFai Lau }
9328b9df265SMartin KaFai Lau
93370ceb4f5SYOSHIFUJI Hideaki #ifdef CONFIG_IPV6_ROUTE_INFO
rt6_route_rcv(struct net_device * dev,u8 * opt,int len,const struct in6_addr * gwaddr)93470ceb4f5SYOSHIFUJI Hideaki int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
935b71d1d42SEric Dumazet const struct in6_addr *gwaddr)
93670ceb4f5SYOSHIFUJI Hideaki {
937c346dca1SYOSHIFUJI Hideaki struct net *net = dev_net(dev);
93870ceb4f5SYOSHIFUJI Hideaki struct route_info *rinfo = (struct route_info *) opt;
93970ceb4f5SYOSHIFUJI Hideaki struct in6_addr prefix_buf, *prefix;
94070ceb4f5SYOSHIFUJI Hideaki unsigned int pref;
9414bed72e4SYOSHIFUJI Hideaki unsigned long lifetime;
9428d1c802bSDavid Ahern struct fib6_info *rt;
94370ceb4f5SYOSHIFUJI Hideaki
94470ceb4f5SYOSHIFUJI Hideaki if (len < sizeof(struct route_info)) {
94570ceb4f5SYOSHIFUJI Hideaki return -EINVAL;
94670ceb4f5SYOSHIFUJI Hideaki }
94770ceb4f5SYOSHIFUJI Hideaki
94870ceb4f5SYOSHIFUJI Hideaki /* Sanity check for prefix_len and length */
94970ceb4f5SYOSHIFUJI Hideaki if (rinfo->length > 3) {
95070ceb4f5SYOSHIFUJI Hideaki return -EINVAL;
95170ceb4f5SYOSHIFUJI Hideaki } else if (rinfo->prefix_len > 128) {
95270ceb4f5SYOSHIFUJI Hideaki return -EINVAL;
95370ceb4f5SYOSHIFUJI Hideaki } else if (rinfo->prefix_len > 64) {
95470ceb4f5SYOSHIFUJI Hideaki if (rinfo->length < 2) {
95570ceb4f5SYOSHIFUJI Hideaki return -EINVAL;
95670ceb4f5SYOSHIFUJI Hideaki }
95770ceb4f5SYOSHIFUJI Hideaki } else if (rinfo->prefix_len > 0) {
95870ceb4f5SYOSHIFUJI Hideaki if (rinfo->length < 1) {
95970ceb4f5SYOSHIFUJI Hideaki return -EINVAL;
96070ceb4f5SYOSHIFUJI Hideaki }
96170ceb4f5SYOSHIFUJI Hideaki }
96270ceb4f5SYOSHIFUJI Hideaki
96370ceb4f5SYOSHIFUJI Hideaki pref = rinfo->route_pref;
96470ceb4f5SYOSHIFUJI Hideaki if (pref == ICMPV6_ROUTER_PREF_INVALID)
9653933fc95SJens Rosenboom return -EINVAL;
96670ceb4f5SYOSHIFUJI Hideaki
9674bed72e4SYOSHIFUJI Hideaki lifetime = addrconf_timeout_fixup(ntohl(rinfo->lifetime), HZ);
96870ceb4f5SYOSHIFUJI Hideaki
96970ceb4f5SYOSHIFUJI Hideaki if (rinfo->length == 3)
97070ceb4f5SYOSHIFUJI Hideaki prefix = (struct in6_addr *)rinfo->prefix;
97170ceb4f5SYOSHIFUJI Hideaki else {
97270ceb4f5SYOSHIFUJI Hideaki /* this function is safe */
97370ceb4f5SYOSHIFUJI Hideaki ipv6_addr_prefix(&prefix_buf,
97470ceb4f5SYOSHIFUJI Hideaki (struct in6_addr *)rinfo->prefix,
97570ceb4f5SYOSHIFUJI Hideaki rinfo->prefix_len);
97670ceb4f5SYOSHIFUJI Hideaki prefix = &prefix_buf;
97770ceb4f5SYOSHIFUJI Hideaki }
97870ceb4f5SYOSHIFUJI Hideaki
979f104a567SDuan Jiong if (rinfo->prefix_len == 0)
980afb1d4b5SDavid Ahern rt = rt6_get_dflt_router(net, gwaddr, dev);
981f104a567SDuan Jiong else
982f104a567SDuan Jiong rt = rt6_get_route_info(net, prefix, rinfo->prefix_len,
983830218c1SDavid Ahern gwaddr, dev);
98470ceb4f5SYOSHIFUJI Hideaki
98570ceb4f5SYOSHIFUJI Hideaki if (rt && !lifetime) {
98611dd74b3SRoopa Prabhu ip6_del_rt(net, rt, false);
98770ceb4f5SYOSHIFUJI Hideaki rt = NULL;
98870ceb4f5SYOSHIFUJI Hideaki }
98970ceb4f5SYOSHIFUJI Hideaki
99070ceb4f5SYOSHIFUJI Hideaki if (!rt && lifetime)
991830218c1SDavid Ahern rt = rt6_add_route_info(net, prefix, rinfo->prefix_len, gwaddr,
992830218c1SDavid Ahern dev, pref);
99370ceb4f5SYOSHIFUJI Hideaki else if (rt)
99493c2fb25SDavid Ahern rt->fib6_flags = RTF_ROUTEINFO |
99593c2fb25SDavid Ahern (rt->fib6_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
99670ceb4f5SYOSHIFUJI Hideaki
99770ceb4f5SYOSHIFUJI Hideaki if (rt) {
9981716a961SGao feng if (!addrconf_finite_timeout(lifetime))
99914895687SDavid Ahern fib6_clean_expires(rt);
10001716a961SGao feng else
100114895687SDavid Ahern fib6_set_expires(rt, jiffies + HZ * lifetime);
10021716a961SGao feng
100393531c67SDavid Ahern fib6_info_release(rt);
100470ceb4f5SYOSHIFUJI Hideaki }
100570ceb4f5SYOSHIFUJI Hideaki return 0;
100670ceb4f5SYOSHIFUJI Hideaki }
100770ceb4f5SYOSHIFUJI Hideaki #endif
100870ceb4f5SYOSHIFUJI Hideaki
1009ae90d867SDavid Ahern /*
1010ae90d867SDavid Ahern * Misc support functions
1011ae90d867SDavid Ahern */
1012ae90d867SDavid Ahern
1013ae90d867SDavid Ahern /* called with rcu_lock held */
ip6_rt_get_dev_rcu(const struct fib6_result * res)10140d161581SDavid Ahern static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res)
1015ae90d867SDavid Ahern {
10160d161581SDavid Ahern struct net_device *dev = res->nh->fib_nh_dev;
1017ae90d867SDavid Ahern
10187d21fec9SDavid Ahern if (res->fib6_flags & (RTF_LOCAL | RTF_ANYCAST)) {
1019ae90d867SDavid Ahern /* for copies of local routes, dst->dev needs to be the
1020ae90d867SDavid Ahern * device if it is a master device, the master device if
1021ae90d867SDavid Ahern * device is enslaved, and the loopback as the default
1022ae90d867SDavid Ahern */
1023ae90d867SDavid Ahern if (netif_is_l3_slave(dev) &&
10247d21fec9SDavid Ahern !rt6_need_strict(&res->f6i->fib6_dst.addr))
1025ae90d867SDavid Ahern dev = l3mdev_master_dev_rcu(dev);
1026ae90d867SDavid Ahern else if (!netif_is_l3_master(dev))
1027ae90d867SDavid Ahern dev = dev_net(dev)->loopback_dev;
1028ae90d867SDavid Ahern /* last case is netif_is_l3_master(dev) is true in which
1029ae90d867SDavid Ahern * case we want dev returned to be dev
1030ae90d867SDavid Ahern */
1031ae90d867SDavid Ahern }
1032ae90d867SDavid Ahern
1033ae90d867SDavid Ahern return dev;
1034ae90d867SDavid Ahern }
1035ae90d867SDavid Ahern
10366edb3c96SDavid Ahern static const int fib6_prop[RTN_MAX + 1] = {
10376edb3c96SDavid Ahern [RTN_UNSPEC] = 0,
10386edb3c96SDavid Ahern [RTN_UNICAST] = 0,
10396edb3c96SDavid Ahern [RTN_LOCAL] = 0,
10406edb3c96SDavid Ahern [RTN_BROADCAST] = 0,
10416edb3c96SDavid Ahern [RTN_ANYCAST] = 0,
10426edb3c96SDavid Ahern [RTN_MULTICAST] = 0,
10436edb3c96SDavid Ahern [RTN_BLACKHOLE] = -EINVAL,
10446edb3c96SDavid Ahern [RTN_UNREACHABLE] = -EHOSTUNREACH,
10456edb3c96SDavid Ahern [RTN_PROHIBIT] = -EACCES,
10466edb3c96SDavid Ahern [RTN_THROW] = -EAGAIN,
10476edb3c96SDavid Ahern [RTN_NAT] = -EINVAL,
10486edb3c96SDavid Ahern [RTN_XRESOLVE] = -EINVAL,
10496edb3c96SDavid Ahern };
10506edb3c96SDavid Ahern
ip6_rt_type_to_error(u8 fib6_type)10516edb3c96SDavid Ahern static int ip6_rt_type_to_error(u8 fib6_type)
10526edb3c96SDavid Ahern {
10536edb3c96SDavid Ahern return fib6_prop[fib6_type];
10546edb3c96SDavid Ahern }
10556edb3c96SDavid Ahern
fib6_info_dst_flags(struct fib6_info * rt)10568d1c802bSDavid Ahern static unsigned short fib6_info_dst_flags(struct fib6_info *rt)
10573b6761d1SDavid Ahern {
10583b6761d1SDavid Ahern unsigned short flags = 0;
10593b6761d1SDavid Ahern
10603b6761d1SDavid Ahern if (rt->dst_nocount)
10613b6761d1SDavid Ahern flags |= DST_NOCOUNT;
10623b6761d1SDavid Ahern if (rt->dst_nopolicy)
10633b6761d1SDavid Ahern flags |= DST_NOPOLICY;
10643b6761d1SDavid Ahern
10653b6761d1SDavid Ahern return flags;
10663b6761d1SDavid Ahern }
10673b6761d1SDavid Ahern
ip6_rt_init_dst_reject(struct rt6_info * rt,u8 fib6_type)10687d21fec9SDavid Ahern static void ip6_rt_init_dst_reject(struct rt6_info *rt, u8 fib6_type)
10696edb3c96SDavid Ahern {
10707d21fec9SDavid Ahern rt->dst.error = ip6_rt_type_to_error(fib6_type);
10716edb3c96SDavid Ahern
10727d21fec9SDavid Ahern switch (fib6_type) {
10736edb3c96SDavid Ahern case RTN_BLACKHOLE:
10746edb3c96SDavid Ahern rt->dst.output = dst_discard_out;
10756edb3c96SDavid Ahern rt->dst.input = dst_discard;
10766edb3c96SDavid Ahern break;
10776edb3c96SDavid Ahern case RTN_PROHIBIT:
10786edb3c96SDavid Ahern rt->dst.output = ip6_pkt_prohibit_out;
10796edb3c96SDavid Ahern rt->dst.input = ip6_pkt_prohibit;
10806edb3c96SDavid Ahern break;
10816edb3c96SDavid Ahern case RTN_THROW:
10826edb3c96SDavid Ahern case RTN_UNREACHABLE:
10836edb3c96SDavid Ahern default:
10846edb3c96SDavid Ahern rt->dst.output = ip6_pkt_discard_out;
10856edb3c96SDavid Ahern rt->dst.input = ip6_pkt_discard;
10866edb3c96SDavid Ahern break;
10876edb3c96SDavid Ahern }
10886edb3c96SDavid Ahern }
10896edb3c96SDavid Ahern
ip6_rt_init_dst(struct rt6_info * rt,const struct fib6_result * res)10900d161581SDavid Ahern static void ip6_rt_init_dst(struct rt6_info *rt, const struct fib6_result *res)
10916edb3c96SDavid Ahern {
10927d21fec9SDavid Ahern struct fib6_info *f6i = res->f6i;
10930d161581SDavid Ahern
10947d21fec9SDavid Ahern if (res->fib6_flags & RTF_REJECT) {
10957d21fec9SDavid Ahern ip6_rt_init_dst_reject(rt, res->fib6_type);
10966edb3c96SDavid Ahern return;
10976edb3c96SDavid Ahern }
10986edb3c96SDavid Ahern
10996edb3c96SDavid Ahern rt->dst.error = 0;
11006edb3c96SDavid Ahern rt->dst.output = ip6_output;
11016edb3c96SDavid Ahern
11027d21fec9SDavid Ahern if (res->fib6_type == RTN_LOCAL || res->fib6_type == RTN_ANYCAST) {
11036edb3c96SDavid Ahern rt->dst.input = ip6_input;
11047d21fec9SDavid Ahern } else if (ipv6_addr_type(&f6i->fib6_dst.addr) & IPV6_ADDR_MULTICAST) {
11056edb3c96SDavid Ahern rt->dst.input = ip6_mc_input;
11066edb3c96SDavid Ahern } else {
11076edb3c96SDavid Ahern rt->dst.input = ip6_forward;
11086edb3c96SDavid Ahern }
11096edb3c96SDavid Ahern
11100d161581SDavid Ahern if (res->nh->fib_nh_lws) {
11110d161581SDavid Ahern rt->dst.lwtstate = lwtstate_get(res->nh->fib_nh_lws);
11126edb3c96SDavid Ahern lwtunnel_set_redirect(&rt->dst);
11136edb3c96SDavid Ahern }
11146edb3c96SDavid Ahern
11156edb3c96SDavid Ahern rt->dst.lastuse = jiffies;
11166edb3c96SDavid Ahern }
11176edb3c96SDavid Ahern
1118e873e4b9SWei Wang /* Caller must already hold reference to @from */
rt6_set_from(struct rt6_info * rt,struct fib6_info * from)11198d1c802bSDavid Ahern static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
1120ae90d867SDavid Ahern {
1121ae90d867SDavid Ahern rt->rt6i_flags &= ~RTF_EXPIRES;
1122a68886a6SDavid Ahern rcu_assign_pointer(rt->from, from);
1123e1255ed4SDavid Ahern ip_dst_init_metrics(&rt->dst, from->fib6_metrics);
1124ae90d867SDavid Ahern }
1125ae90d867SDavid Ahern
11260d161581SDavid Ahern /* Caller must already hold reference to f6i in result */
ip6_rt_copy_init(struct rt6_info * rt,const struct fib6_result * res)11270d161581SDavid Ahern static void ip6_rt_copy_init(struct rt6_info *rt, const struct fib6_result *res)
1128ae90d867SDavid Ahern {
11290d161581SDavid Ahern const struct fib6_nh *nh = res->nh;
11300d161581SDavid Ahern const struct net_device *dev = nh->fib_nh_dev;
11310d161581SDavid Ahern struct fib6_info *f6i = res->f6i;
1132dcd1f572SDavid Ahern
11330d161581SDavid Ahern ip6_rt_init_dst(rt, res);
11346edb3c96SDavid Ahern
11350d161581SDavid Ahern rt->rt6i_dst = f6i->fib6_dst;
1136dcd1f572SDavid Ahern rt->rt6i_idev = dev ? in6_dev_get(dev) : NULL;
11377d21fec9SDavid Ahern rt->rt6i_flags = res->fib6_flags;
11380d161581SDavid Ahern if (nh->fib_nh_gw_family) {
11390d161581SDavid Ahern rt->rt6i_gateway = nh->fib_nh_gw6;
11402b2450caSDavid Ahern rt->rt6i_flags |= RTF_GATEWAY;
11412b2450caSDavid Ahern }
11420d161581SDavid Ahern rt6_set_from(rt, f6i);
1143ae90d867SDavid Ahern #ifdef CONFIG_IPV6_SUBTREES
11440d161581SDavid Ahern rt->rt6i_src = f6i->fib6_src;
1145ae90d867SDavid Ahern #endif
1146ae90d867SDavid Ahern }
1147ae90d867SDavid Ahern
fib6_backtrack(struct fib6_node * fn,struct in6_addr * saddr)1148a3c00e46SMartin KaFai Lau static struct fib6_node* fib6_backtrack(struct fib6_node *fn,
1149a3c00e46SMartin KaFai Lau struct in6_addr *saddr)
1150a3c00e46SMartin KaFai Lau {
115166f5d6ceSWei Wang struct fib6_node *pn, *sn;
1152a3c00e46SMartin KaFai Lau while (1) {
1153a3c00e46SMartin KaFai Lau if (fn->fn_flags & RTN_TL_ROOT)
1154a3c00e46SMartin KaFai Lau return NULL;
115566f5d6ceSWei Wang pn = rcu_dereference(fn->parent);
115666f5d6ceSWei Wang sn = FIB6_SUBTREE(pn);
115766f5d6ceSWei Wang if (sn && sn != fn)
11586454743bSDavid Ahern fn = fib6_node_lookup(sn, NULL, saddr);
1159a3c00e46SMartin KaFai Lau else
1160a3c00e46SMartin KaFai Lau fn = pn;
1161a3c00e46SMartin KaFai Lau if (fn->fn_flags & RTN_RTINFO)
1162a3c00e46SMartin KaFai Lau return fn;
1163a3c00e46SMartin KaFai Lau }
1164a3c00e46SMartin KaFai Lau }
1165c71099acSThomas Graf
ip6_hold_safe(struct net * net,struct rt6_info ** prt)116610585b43SDavid Ahern static bool ip6_hold_safe(struct net *net, struct rt6_info **prt)
1167d3843fe5SWei Wang {
1168d3843fe5SWei Wang struct rt6_info *rt = *prt;
1169d3843fe5SWei Wang
1170d3843fe5SWei Wang if (dst_hold_safe(&rt->dst))
1171d3843fe5SWei Wang return true;
117210585b43SDavid Ahern if (net) {
1173d3843fe5SWei Wang rt = net->ipv6.ip6_null_entry;
1174d3843fe5SWei Wang dst_hold(&rt->dst);
1175d3843fe5SWei Wang } else {
1176d3843fe5SWei Wang rt = NULL;
1177d3843fe5SWei Wang }
1178d3843fe5SWei Wang *prt = rt;
1179d3843fe5SWei Wang return false;
1180d3843fe5SWei Wang }
1181d3843fe5SWei Wang
1182dec9b0e2SDavid Ahern /* called with rcu_lock held */
ip6_create_rt_rcu(const struct fib6_result * res)11839b6b35abSDavid Ahern static struct rt6_info *ip6_create_rt_rcu(const struct fib6_result *res)
1184dec9b0e2SDavid Ahern {
11859b6b35abSDavid Ahern struct net_device *dev = res->nh->fib_nh_dev;
11869b6b35abSDavid Ahern struct fib6_info *f6i = res->f6i;
11879b6b35abSDavid Ahern unsigned short flags;
1188dec9b0e2SDavid Ahern struct rt6_info *nrt;
1189dec9b0e2SDavid Ahern
11909b6b35abSDavid Ahern if (!fib6_info_hold_safe(f6i))
11911c87e79aSXin Long goto fallback;
1192e873e4b9SWei Wang
11939b6b35abSDavid Ahern flags = fib6_info_dst_flags(f6i);
119493531c67SDavid Ahern nrt = ip6_dst_alloc(dev_net(dev), dev, flags);
11951c87e79aSXin Long if (!nrt) {
11969b6b35abSDavid Ahern fib6_info_release(f6i);
11971c87e79aSXin Long goto fallback;
11981c87e79aSXin Long }
1199dec9b0e2SDavid Ahern
12000d161581SDavid Ahern ip6_rt_copy_init(nrt, res);
12011c87e79aSXin Long return nrt;
12021c87e79aSXin Long
12031c87e79aSXin Long fallback:
12041c87e79aSXin Long nrt = dev_net(dev)->ipv6.ip6_null_entry;
12051c87e79aSXin Long dst_hold(&nrt->dst);
1206dec9b0e2SDavid Ahern return nrt;
1207dec9b0e2SDavid Ahern }
1208dec9b0e2SDavid Ahern
ip6_pol_route_lookup(struct net * net,struct fib6_table * table,struct flowi6 * fl6,const struct sk_buff * skb,int flags)120955cced4fSBrian Vazquez INDIRECT_CALLABLE_SCOPE struct rt6_info *ip6_pol_route_lookup(struct net *net,
12108ed67789SDaniel Lezcano struct fib6_table *table,
1211b75cc8f9SDavid Ahern struct flowi6 *fl6,
1212b75cc8f9SDavid Ahern const struct sk_buff *skb,
1213b75cc8f9SDavid Ahern int flags)
12141da177e4SLinus Torvalds {
1215b1d40991SDavid Ahern struct fib6_result res = {};
12161da177e4SLinus Torvalds struct fib6_node *fn;
121723fb93a4SDavid Ahern struct rt6_info *rt;
12181da177e4SLinus Torvalds
121966f5d6ceSWei Wang rcu_read_lock();
12206454743bSDavid Ahern fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
1221c71099acSThomas Graf restart:
1222b1d40991SDavid Ahern res.f6i = rcu_dereference(fn->leaf);
1223b1d40991SDavid Ahern if (!res.f6i)
1224b1d40991SDavid Ahern res.f6i = net->ipv6.fib6_null_entry;
1225af52a52cSDavid Ahern else
122675ef7389SDavid Ahern rt6_device_match(net, &res, &fl6->saddr, fl6->flowi6_oif,
122775ef7389SDavid Ahern flags);
1228af52a52cSDavid Ahern
1229b1d40991SDavid Ahern if (res.f6i == net->ipv6.fib6_null_entry) {
1230a3c00e46SMartin KaFai Lau fn = fib6_backtrack(fn, &fl6->saddr);
1231a3c00e46SMartin KaFai Lau if (fn)
1232a3c00e46SMartin KaFai Lau goto restart;
1233af52a52cSDavid Ahern
1234af52a52cSDavid Ahern rt = net->ipv6.ip6_null_entry;
1235af52a52cSDavid Ahern dst_hold(&rt->dst);
1236af52a52cSDavid Ahern goto out;
1237f88d8ea6SDavid Ahern } else if (res.fib6_flags & RTF_REJECT) {
1238f88d8ea6SDavid Ahern goto do_create;
1239a3c00e46SMartin KaFai Lau }
12402b760fcfSWei Wang
1241b1d40991SDavid Ahern fib6_select_path(net, &res, fl6, fl6->flowi6_oif,
1242b1d40991SDavid Ahern fl6->flowi6_oif != 0, skb, flags);
1243b1d40991SDavid Ahern
12444c9483b2SDavid S. Miller /* Search through exception table */
12457e4b5128SDavid Ahern rt = rt6_find_cached_rt(&res, &fl6->daddr, &fl6->saddr);
124623fb93a4SDavid Ahern if (rt) {
124710585b43SDavid Ahern if (ip6_hold_safe(net, &rt))
1248d3843fe5SWei Wang dst_use_noref(&rt->dst, jiffies);
124923fb93a4SDavid Ahern } else {
1250f88d8ea6SDavid Ahern do_create:
12519b6b35abSDavid Ahern rt = ip6_create_rt_rcu(&res);
1252dec9b0e2SDavid Ahern }
1253d3843fe5SWei Wang
1254af52a52cSDavid Ahern out:
12558ff2e5b2SDavid Ahern trace_fib6_table_lookup(net, &res, table, fl6);
1256af52a52cSDavid Ahern
125766f5d6ceSWei Wang rcu_read_unlock();
1258b811580dSDavid Ahern
12591da177e4SLinus Torvalds return rt;
1260c71099acSThomas Graf }
1261c71099acSThomas Graf
ip6_route_lookup(struct net * net,struct flowi6 * fl6,const struct sk_buff * skb,int flags)1262ea6e574eSFlorian Westphal struct dst_entry *ip6_route_lookup(struct net *net, struct flowi6 *fl6,
1263b75cc8f9SDavid Ahern const struct sk_buff *skb, int flags)
1264ea6e574eSFlorian Westphal {
1265b75cc8f9SDavid Ahern return fib6_rule_lookup(net, fl6, skb, flags, ip6_pol_route_lookup);
1266ea6e574eSFlorian Westphal }
1267ea6e574eSFlorian Westphal EXPORT_SYMBOL_GPL(ip6_route_lookup);
1268ea6e574eSFlorian Westphal
rt6_lookup(struct net * net,const struct in6_addr * daddr,const struct in6_addr * saddr,int oif,const struct sk_buff * skb,int strict)12699acd9f3aSYOSHIFUJI Hideaki struct rt6_info *rt6_lookup(struct net *net, const struct in6_addr *daddr,
1270b75cc8f9SDavid Ahern const struct in6_addr *saddr, int oif,
1271b75cc8f9SDavid Ahern const struct sk_buff *skb, int strict)
1272c71099acSThomas Graf {
12734c9483b2SDavid S. Miller struct flowi6 fl6 = {
12744c9483b2SDavid S. Miller .flowi6_oif = oif,
12754c9483b2SDavid S. Miller .daddr = *daddr,
1276c71099acSThomas Graf };
1277c71099acSThomas Graf struct dst_entry *dst;
127877d16f45SYOSHIFUJI Hideaki int flags = strict ? RT6_LOOKUP_F_IFACE : 0;
1279c71099acSThomas Graf
1280adaa70bbSThomas Graf if (saddr) {
12814c9483b2SDavid S. Miller memcpy(&fl6.saddr, saddr, sizeof(*saddr));
1282adaa70bbSThomas Graf flags |= RT6_LOOKUP_F_HAS_SADDR;
1283adaa70bbSThomas Graf }
1284adaa70bbSThomas Graf
1285b75cc8f9SDavid Ahern dst = fib6_rule_lookup(net, &fl6, skb, flags, ip6_pol_route_lookup);
1286c71099acSThomas Graf if (dst->error == 0)
1287797a4c1fSEric Dumazet return dst_rt6_info(dst);
1288c71099acSThomas Graf
1289c71099acSThomas Graf dst_release(dst);
1290c71099acSThomas Graf
12911da177e4SLinus Torvalds return NULL;
12921da177e4SLinus Torvalds }
12937159039aSYOSHIFUJI Hideaki EXPORT_SYMBOL(rt6_lookup);
12947159039aSYOSHIFUJI Hideaki
1295c71099acSThomas Graf /* ip6_ins_rt is called with FREE table->tb6_lock.
12961cfb71eeSWei Wang * It takes new route entry, the addition fails by any reason the
12971cfb71eeSWei Wang * route is released.
12981cfb71eeSWei Wang * Caller must hold dst before calling it.
12991da177e4SLinus Torvalds */
13001da177e4SLinus Torvalds
__ip6_ins_rt(struct fib6_info * rt,struct nl_info * info,struct netlink_ext_ack * extack)13018d1c802bSDavid Ahern static int __ip6_ins_rt(struct fib6_info *rt, struct nl_info *info,
1302333c4301SDavid Ahern struct netlink_ext_ack *extack)
13031da177e4SLinus Torvalds {
13041da177e4SLinus Torvalds int err;
1305c71099acSThomas Graf struct fib6_table *table;
13061da177e4SLinus Torvalds
130793c2fb25SDavid Ahern table = rt->fib6_table;
130866f5d6ceSWei Wang spin_lock_bh(&table->tb6_lock);
1309d4ead6b3SDavid Ahern err = fib6_add(&table->tb6_root, rt, info, extack);
131066f5d6ceSWei Wang spin_unlock_bh(&table->tb6_lock);
13111da177e4SLinus Torvalds
13121da177e4SLinus Torvalds return err;
13131da177e4SLinus Torvalds }
13141da177e4SLinus Torvalds
ip6_ins_rt(struct net * net,struct fib6_info * rt)13158d1c802bSDavid Ahern int ip6_ins_rt(struct net *net, struct fib6_info *rt)
131640e22e8fSThomas Graf {
1317afb1d4b5SDavid Ahern struct nl_info info = { .nl_net = net, };
1318e715b6d3SFlorian Westphal
1319d4ead6b3SDavid Ahern return __ip6_ins_rt(rt, &info, NULL);
132040e22e8fSThomas Graf }
132140e22e8fSThomas Graf
ip6_rt_cache_alloc(const struct fib6_result * res,const struct in6_addr * daddr,const struct in6_addr * saddr)132285bd05deSDavid Ahern static struct rt6_info *ip6_rt_cache_alloc(const struct fib6_result *res,
132321efcfa0SEric Dumazet const struct in6_addr *daddr,
1324b71d1d42SEric Dumazet const struct in6_addr *saddr)
13251da177e4SLinus Torvalds {
132685bd05deSDavid Ahern struct fib6_info *f6i = res->f6i;
13274832c30dSDavid Ahern struct net_device *dev;
13281da177e4SLinus Torvalds struct rt6_info *rt;
13291da177e4SLinus Torvalds
13301da177e4SLinus Torvalds /*
13311da177e4SLinus Torvalds * Clone the route.
13321da177e4SLinus Torvalds */
13331da177e4SLinus Torvalds
133485bd05deSDavid Ahern if (!fib6_info_hold_safe(f6i))
1335e873e4b9SWei Wang return NULL;
1336e873e4b9SWei Wang
13370d161581SDavid Ahern dev = ip6_rt_get_dev_rcu(res);
133893531c67SDavid Ahern rt = ip6_dst_alloc(dev_net(dev), dev, 0);
1339e873e4b9SWei Wang if (!rt) {
134085bd05deSDavid Ahern fib6_info_release(f6i);
134183a09abdSMartin KaFai Lau return NULL;
1342e873e4b9SWei Wang }
134383a09abdSMartin KaFai Lau
13440d161581SDavid Ahern ip6_rt_copy_init(rt, res);
13458b9df265SMartin KaFai Lau rt->rt6i_flags |= RTF_CACHE;
134683a09abdSMartin KaFai Lau rt->rt6i_dst.addr = *daddr;
134783a09abdSMartin KaFai Lau rt->rt6i_dst.plen = 128;
13488b9df265SMartin KaFai Lau
134985bd05deSDavid Ahern if (!rt6_is_gw_or_nonexthop(res)) {
135085bd05deSDavid Ahern if (f6i->fib6_dst.plen != 128 &&
135185bd05deSDavid Ahern ipv6_addr_equal(&f6i->fib6_dst.addr, daddr))
135258c4fb86SYOSHIFUJI Hideaki rt->rt6i_flags |= RTF_ANYCAST;
13531da177e4SLinus Torvalds #ifdef CONFIG_IPV6_SUBTREES
13541da177e4SLinus Torvalds if (rt->rt6i_src.plen && saddr) {
13554e3fd7a0SAlexey Dobriyan rt->rt6i_src.addr = *saddr;
13561da177e4SLinus Torvalds rt->rt6i_src.plen = 128;
13571da177e4SLinus Torvalds }
13581da177e4SLinus Torvalds #endif
135995a9a5baSYOSHIFUJI Hideaki }
136095a9a5baSYOSHIFUJI Hideaki
1361299d9939SYOSHIFUJI Hideaki return rt;
1362299d9939SYOSHIFUJI Hideaki }
1363299d9939SYOSHIFUJI Hideaki
ip6_rt_pcpu_alloc(const struct fib6_result * res)1364db3fedeeSDavid Ahern static struct rt6_info *ip6_rt_pcpu_alloc(const struct fib6_result *res)
1365d52d3997SMartin KaFai Lau {
1366db3fedeeSDavid Ahern struct fib6_info *f6i = res->f6i;
1367db3fedeeSDavid Ahern unsigned short flags = fib6_info_dst_flags(f6i);
13684832c30dSDavid Ahern struct net_device *dev;
1369d52d3997SMartin KaFai Lau struct rt6_info *pcpu_rt;
1370d52d3997SMartin KaFai Lau
1371db3fedeeSDavid Ahern if (!fib6_info_hold_safe(f6i))
1372e873e4b9SWei Wang return NULL;
1373e873e4b9SWei Wang
13744832c30dSDavid Ahern rcu_read_lock();
13750d161581SDavid Ahern dev = ip6_rt_get_dev_rcu(res);
1376d8882935SEric Dumazet pcpu_rt = ip6_dst_alloc(dev_net(dev), dev, flags | DST_NOCOUNT);
13774832c30dSDavid Ahern rcu_read_unlock();
1378e873e4b9SWei Wang if (!pcpu_rt) {
1379db3fedeeSDavid Ahern fib6_info_release(f6i);
1380d52d3997SMartin KaFai Lau return NULL;
1381e873e4b9SWei Wang }
13820d161581SDavid Ahern ip6_rt_copy_init(pcpu_rt, res);
1383d52d3997SMartin KaFai Lau pcpu_rt->rt6i_flags |= RTF_PCPU;
13848f34e53bSDavid Ahern
13858f34e53bSDavid Ahern if (f6i->nh)
13868f34e53bSDavid Ahern pcpu_rt->sernum = rt_genid_ipv6(dev_net(dev));
13878f34e53bSDavid Ahern
1388d52d3997SMartin KaFai Lau return pcpu_rt;
1389d52d3997SMartin KaFai Lau }
1390d52d3997SMartin KaFai Lau
rt6_is_valid(const struct rt6_info * rt6)13918f34e53bSDavid Ahern static bool rt6_is_valid(const struct rt6_info *rt6)
13928f34e53bSDavid Ahern {
13938f34e53bSDavid Ahern return rt6->sernum == rt_genid_ipv6(dev_net(rt6->dst.dev));
13948f34e53bSDavid Ahern }
13958f34e53bSDavid Ahern
139666f5d6ceSWei Wang /* It should be called with rcu_read_lock() acquired */
rt6_get_pcpu_route(const struct fib6_result * res)1397db3fedeeSDavid Ahern static struct rt6_info *rt6_get_pcpu_route(const struct fib6_result *res)
1398d52d3997SMartin KaFai Lau {
1399c353071aSEric Dumazet struct rt6_info *pcpu_rt;
1400d52d3997SMartin KaFai Lau
1401c353071aSEric Dumazet pcpu_rt = this_cpu_read(*res->nh->rt6i_pcpu);
1402d52d3997SMartin KaFai Lau
14038f34e53bSDavid Ahern if (pcpu_rt && pcpu_rt->sernum && !rt6_is_valid(pcpu_rt)) {
14048f34e53bSDavid Ahern struct rt6_info *prev, **p;
14058f34e53bSDavid Ahern
14068f34e53bSDavid Ahern p = this_cpu_ptr(res->nh->rt6i_pcpu);
14077e796c3fSEric Dumazet /* Paired with READ_ONCE() in __fib6_drop_pcpu_from() */
14088f34e53bSDavid Ahern prev = xchg(p, NULL);
14098f34e53bSDavid Ahern if (prev) {
14108f34e53bSDavid Ahern dst_dev_put(&prev->dst);
14118f34e53bSDavid Ahern dst_release(&prev->dst);
14128f34e53bSDavid Ahern }
14138f34e53bSDavid Ahern
14148f34e53bSDavid Ahern pcpu_rt = NULL;
14158f34e53bSDavid Ahern }
14168f34e53bSDavid Ahern
1417a73e4195SMartin KaFai Lau return pcpu_rt;
1418a73e4195SMartin KaFai Lau }
1419a73e4195SMartin KaFai Lau
rt6_make_pcpu_route(struct net * net,const struct fib6_result * res)1420afb1d4b5SDavid Ahern static struct rt6_info *rt6_make_pcpu_route(struct net *net,
1421db3fedeeSDavid Ahern const struct fib6_result *res)
1422a73e4195SMartin KaFai Lau {
1423a73e4195SMartin KaFai Lau struct rt6_info *pcpu_rt, *prev, **p;
1424d52d3997SMartin KaFai Lau
1425db3fedeeSDavid Ahern pcpu_rt = ip6_rt_pcpu_alloc(res);
14260e09edccSWei Wang if (!pcpu_rt)
14270e09edccSWei Wang return NULL;
1428d52d3997SMartin KaFai Lau
1429f40b6ae2SDavid Ahern p = this_cpu_ptr(res->nh->rt6i_pcpu);
1430d52d3997SMartin KaFai Lau prev = cmpxchg(p, NULL, pcpu_rt);
1431951f788aSEric Dumazet BUG_ON(prev);
1432a94b9367SWei Wang
143361fb0d01SEric Dumazet if (res->f6i->fib6_destroying) {
143461fb0d01SEric Dumazet struct fib6_info *from;
143561fb0d01SEric Dumazet
143670530a2fSEric Dumazet from = unrcu_pointer(xchg(&pcpu_rt->from, NULL));
143761fb0d01SEric Dumazet fib6_info_release(from);
143861fb0d01SEric Dumazet }
143961fb0d01SEric Dumazet
1440d52d3997SMartin KaFai Lau return pcpu_rt;
1441d52d3997SMartin KaFai Lau }
1442d52d3997SMartin KaFai Lau
144335732d01SWei Wang /* exception hash table implementation
144435732d01SWei Wang */
144535732d01SWei Wang static DEFINE_SPINLOCK(rt6_exception_lock);
144635732d01SWei Wang
144735732d01SWei Wang /* Remove rt6_ex from hash table and free the memory
144835732d01SWei Wang * Caller must hold rt6_exception_lock
144935732d01SWei Wang */
rt6_remove_exception(struct rt6_exception_bucket * bucket,struct rt6_exception * rt6_ex)145035732d01SWei Wang static void rt6_remove_exception(struct rt6_exception_bucket *bucket,
145135732d01SWei Wang struct rt6_exception *rt6_ex)
145235732d01SWei Wang {
1453b2427e67SColin Ian King struct net *net;
145481eb8447SWei Wang
145535732d01SWei Wang if (!bucket || !rt6_ex)
145635732d01SWei Wang return;
1457b2427e67SColin Ian King
1458b2427e67SColin Ian King net = dev_net(rt6_ex->rt6i->dst.dev);
1459f5b51fe8SPaolo Abeni net->ipv6.rt6_stats->fib_rt_cache--;
1460f5b51fe8SPaolo Abeni
1461f5b51fe8SPaolo Abeni /* purge completely the exception to allow releasing the held resources:
1462f5b51fe8SPaolo Abeni * some [sk] cache may keep the dst around for unlimited time
1463f5b51fe8SPaolo Abeni */
1464f5b51fe8SPaolo Abeni dst_dev_put(&rt6_ex->rt6i->dst);
1465f5b51fe8SPaolo Abeni
146635732d01SWei Wang hlist_del_rcu(&rt6_ex->hlist);
146777634cc6SDavid Ahern dst_release(&rt6_ex->rt6i->dst);
146835732d01SWei Wang kfree_rcu(rt6_ex, rcu);
146935732d01SWei Wang WARN_ON_ONCE(!bucket->depth);
147035732d01SWei Wang bucket->depth--;
147135732d01SWei Wang }
147235732d01SWei Wang
147335732d01SWei Wang /* Remove oldest rt6_ex in bucket and free the memory
147435732d01SWei Wang * Caller must hold rt6_exception_lock
147535732d01SWei Wang */
rt6_exception_remove_oldest(struct rt6_exception_bucket * bucket)147635732d01SWei Wang static void rt6_exception_remove_oldest(struct rt6_exception_bucket *bucket)
147735732d01SWei Wang {
147835732d01SWei Wang struct rt6_exception *rt6_ex, *oldest = NULL;
147935732d01SWei Wang
148035732d01SWei Wang if (!bucket)
148135732d01SWei Wang return;
148235732d01SWei Wang
148335732d01SWei Wang hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) {
148435732d01SWei Wang if (!oldest || time_before(rt6_ex->stamp, oldest->stamp))
148535732d01SWei Wang oldest = rt6_ex;
148635732d01SWei Wang }
148735732d01SWei Wang rt6_remove_exception(bucket, oldest);
148835732d01SWei Wang }
148935732d01SWei Wang
rt6_exception_hash(const struct in6_addr * dst,const struct in6_addr * src)149035732d01SWei Wang static u32 rt6_exception_hash(const struct in6_addr *dst,
149135732d01SWei Wang const struct in6_addr *src)
149235732d01SWei Wang {
149349ecc2e9SEric Dumazet static siphash_aligned_key_t rt6_exception_key;
14944785305cSEric Dumazet struct {
14954785305cSEric Dumazet struct in6_addr dst;
14964785305cSEric Dumazet struct in6_addr src;
14974785305cSEric Dumazet } __aligned(SIPHASH_ALIGNMENT) combined = {
14984785305cSEric Dumazet .dst = *dst,
14994785305cSEric Dumazet };
15004785305cSEric Dumazet u64 val;
150135732d01SWei Wang
15024785305cSEric Dumazet net_get_random_once(&rt6_exception_key, sizeof(rt6_exception_key));
150335732d01SWei Wang
150435732d01SWei Wang #ifdef CONFIG_IPV6_SUBTREES
150535732d01SWei Wang if (src)
15064785305cSEric Dumazet combined.src = *src;
150735732d01SWei Wang #endif
15084785305cSEric Dumazet val = siphash(&combined, sizeof(combined), &rt6_exception_key);
15094785305cSEric Dumazet
15104785305cSEric Dumazet return hash_64(val, FIB6_EXCEPTION_BUCKET_SIZE_SHIFT);
151135732d01SWei Wang }
151235732d01SWei Wang
151335732d01SWei Wang /* Helper function to find the cached rt in the hash table
151435732d01SWei Wang * and update bucket pointer to point to the bucket for this
151535732d01SWei Wang * (daddr, saddr) pair
151635732d01SWei Wang * Caller must hold rt6_exception_lock
151735732d01SWei Wang */
151835732d01SWei Wang static struct rt6_exception *
__rt6_find_exception_spinlock(struct rt6_exception_bucket ** bucket,const struct in6_addr * daddr,const struct in6_addr * saddr)151935732d01SWei Wang __rt6_find_exception_spinlock(struct rt6_exception_bucket **bucket,
152035732d01SWei Wang const struct in6_addr *daddr,
152135732d01SWei Wang const struct in6_addr *saddr)
152235732d01SWei Wang {
152335732d01SWei Wang struct rt6_exception *rt6_ex;
152435732d01SWei Wang u32 hval;
152535732d01SWei Wang
152635732d01SWei Wang if (!(*bucket) || !daddr)
152735732d01SWei Wang return NULL;
152835732d01SWei Wang
152935732d01SWei Wang hval = rt6_exception_hash(daddr, saddr);
153035732d01SWei Wang *bucket += hval;
153135732d01SWei Wang
153235732d01SWei Wang hlist_for_each_entry(rt6_ex, &(*bucket)->chain, hlist) {
153335732d01SWei Wang struct rt6_info *rt6 = rt6_ex->rt6i;
153435732d01SWei Wang bool matched = ipv6_addr_equal(daddr, &rt6->rt6i_dst.addr);
153535732d01SWei Wang
153635732d01SWei Wang #ifdef CONFIG_IPV6_SUBTREES
153735732d01SWei Wang if (matched && saddr)
153835732d01SWei Wang matched = ipv6_addr_equal(saddr, &rt6->rt6i_src.addr);
153935732d01SWei Wang #endif
154035732d01SWei Wang if (matched)
154135732d01SWei Wang return rt6_ex;
154235732d01SWei Wang }
154335732d01SWei Wang return NULL;
154435732d01SWei Wang }
154535732d01SWei Wang
154635732d01SWei Wang /* Helper function to find the cached rt in the hash table
154735732d01SWei Wang * and update bucket pointer to point to the bucket for this
154835732d01SWei Wang * (daddr, saddr) pair
154935732d01SWei Wang * Caller must hold rcu_read_lock()
155035732d01SWei Wang */
155135732d01SWei Wang static struct rt6_exception *
__rt6_find_exception_rcu(struct rt6_exception_bucket ** bucket,const struct in6_addr * daddr,const struct in6_addr * saddr)155235732d01SWei Wang __rt6_find_exception_rcu(struct rt6_exception_bucket **bucket,
155335732d01SWei Wang const struct in6_addr *daddr,
155435732d01SWei Wang const struct in6_addr *saddr)
155535732d01SWei Wang {
155635732d01SWei Wang struct rt6_exception *rt6_ex;
155735732d01SWei Wang u32 hval;
155835732d01SWei Wang
155935732d01SWei Wang WARN_ON_ONCE(!rcu_read_lock_held());
156035732d01SWei Wang
156135732d01SWei Wang if (!(*bucket) || !daddr)
156235732d01SWei Wang return NULL;
156335732d01SWei Wang
156435732d01SWei Wang hval = rt6_exception_hash(daddr, saddr);
156535732d01SWei Wang *bucket += hval;
156635732d01SWei Wang
156735732d01SWei Wang hlist_for_each_entry_rcu(rt6_ex, &(*bucket)->chain, hlist) {
156835732d01SWei Wang struct rt6_info *rt6 = rt6_ex->rt6i;
156935732d01SWei Wang bool matched = ipv6_addr_equal(daddr, &rt6->rt6i_dst.addr);
157035732d01SWei Wang
157135732d01SWei Wang #ifdef CONFIG_IPV6_SUBTREES
157235732d01SWei Wang if (matched && saddr)
157335732d01SWei Wang matched = ipv6_addr_equal(saddr, &rt6->rt6i_src.addr);
157435732d01SWei Wang #endif
157535732d01SWei Wang if (matched)
157635732d01SWei Wang return rt6_ex;
157735732d01SWei Wang }
157835732d01SWei Wang return NULL;
157935732d01SWei Wang }
158035732d01SWei Wang
fib6_mtu(const struct fib6_result * res)1581b748f260SDavid Ahern static unsigned int fib6_mtu(const struct fib6_result *res)
158235732d01SWei Wang {
1583b748f260SDavid Ahern const struct fib6_nh *nh = res->nh;
1584d4ead6b3SDavid Ahern unsigned int mtu;
1585d4ead6b3SDavid Ahern
1586b748f260SDavid Ahern if (res->f6i->fib6_pmtu) {
1587b748f260SDavid Ahern mtu = res->f6i->fib6_pmtu;
1588dcd1f572SDavid Ahern } else {
1589b748f260SDavid Ahern struct net_device *dev = nh->fib_nh_dev;
1590dcd1f572SDavid Ahern struct inet6_dev *idev;
1591dcd1f572SDavid Ahern
1592dcd1f572SDavid Ahern rcu_read_lock();
1593dcd1f572SDavid Ahern idev = __in6_dev_get(dev);
1594dcd1f572SDavid Ahern mtu = idev->cnf.mtu6;
1595dcd1f572SDavid Ahern rcu_read_unlock();
1596dcd1f572SDavid Ahern }
1597dcd1f572SDavid Ahern
1598d4ead6b3SDavid Ahern mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);
1599d4ead6b3SDavid Ahern
1600b748f260SDavid Ahern return mtu - lwtunnel_headroom(nh->fib_nh_lws, mtu);
1601d4ead6b3SDavid Ahern }
1602d4ead6b3SDavid Ahern
1603cc5c073aSDavid Ahern #define FIB6_EXCEPTION_BUCKET_FLUSHED 0x1UL
1604cc5c073aSDavid Ahern
1605cc5c073aSDavid Ahern /* used when the flushed bit is not relevant, only access to the bucket
1606cc5c073aSDavid Ahern * (ie., all bucket users except rt6_insert_exception);
1607cc5c073aSDavid Ahern *
1608cc5c073aSDavid Ahern * called under rcu lock; sometimes called with rt6_exception_lock held
1609cc5c073aSDavid Ahern */
1610cc5c073aSDavid Ahern static
fib6_nh_get_excptn_bucket(const struct fib6_nh * nh,spinlock_t * lock)1611cc5c073aSDavid Ahern struct rt6_exception_bucket *fib6_nh_get_excptn_bucket(const struct fib6_nh *nh,
1612cc5c073aSDavid Ahern spinlock_t *lock)
1613cc5c073aSDavid Ahern {
1614cc5c073aSDavid Ahern struct rt6_exception_bucket *bucket;
1615cc5c073aSDavid Ahern
1616cc5c073aSDavid Ahern if (lock)
1617cc5c073aSDavid Ahern bucket = rcu_dereference_protected(nh->rt6i_exception_bucket,
1618cc5c073aSDavid Ahern lockdep_is_held(lock));
1619cc5c073aSDavid Ahern else
1620cc5c073aSDavid Ahern bucket = rcu_dereference(nh->rt6i_exception_bucket);
1621cc5c073aSDavid Ahern
1622cc5c073aSDavid Ahern /* remove bucket flushed bit if set */
1623cc5c073aSDavid Ahern if (bucket) {
1624cc5c073aSDavid Ahern unsigned long p = (unsigned long)bucket;
1625cc5c073aSDavid Ahern
1626cc5c073aSDavid Ahern p &= ~FIB6_EXCEPTION_BUCKET_FLUSHED;
1627cc5c073aSDavid Ahern bucket = (struct rt6_exception_bucket *)p;
1628cc5c073aSDavid Ahern }
1629cc5c073aSDavid Ahern
1630cc5c073aSDavid Ahern return bucket;
1631cc5c073aSDavid Ahern }
1632cc5c073aSDavid Ahern
fib6_nh_excptn_bucket_flushed(struct rt6_exception_bucket * bucket)1633cc5c073aSDavid Ahern static bool fib6_nh_excptn_bucket_flushed(struct rt6_exception_bucket *bucket)
1634cc5c073aSDavid Ahern {
1635cc5c073aSDavid Ahern unsigned long p = (unsigned long)bucket;
1636cc5c073aSDavid Ahern
1637cc5c073aSDavid Ahern return !!(p & FIB6_EXCEPTION_BUCKET_FLUSHED);
1638cc5c073aSDavid Ahern }
1639cc5c073aSDavid Ahern
1640cc5c073aSDavid Ahern /* called with rt6_exception_lock held */
fib6_nh_excptn_bucket_set_flushed(struct fib6_nh * nh,spinlock_t * lock)1641cc5c073aSDavid Ahern static void fib6_nh_excptn_bucket_set_flushed(struct fib6_nh *nh,
1642cc5c073aSDavid Ahern spinlock_t *lock)
1643cc5c073aSDavid Ahern {
1644cc5c073aSDavid Ahern struct rt6_exception_bucket *bucket;
1645cc5c073aSDavid Ahern unsigned long p;
1646cc5c073aSDavid Ahern
1647cc5c073aSDavid Ahern bucket = rcu_dereference_protected(nh->rt6i_exception_bucket,
1648cc5c073aSDavid Ahern lockdep_is_held(lock));
1649cc5c073aSDavid Ahern
1650cc5c073aSDavid Ahern p = (unsigned long)bucket;
1651cc5c073aSDavid Ahern p |= FIB6_EXCEPTION_BUCKET_FLUSHED;
1652cc5c073aSDavid Ahern bucket = (struct rt6_exception_bucket *)p;
1653cc5c073aSDavid Ahern rcu_assign_pointer(nh->rt6i_exception_bucket, bucket);
1654cc5c073aSDavid Ahern }
1655cc5c073aSDavid Ahern
rt6_insert_exception(struct rt6_info * nrt,const struct fib6_result * res)165635732d01SWei Wang static int rt6_insert_exception(struct rt6_info *nrt,
16575012f0a5SDavid Ahern const struct fib6_result *res)
165835732d01SWei Wang {
16595e670d84SDavid Ahern struct net *net = dev_net(nrt->dst.dev);
166035732d01SWei Wang struct rt6_exception_bucket *bucket;
1661cc5c073aSDavid Ahern struct fib6_info *f6i = res->f6i;
166235732d01SWei Wang struct in6_addr *src_key = NULL;
166335732d01SWei Wang struct rt6_exception *rt6_ex;
1664cc5c073aSDavid Ahern struct fib6_nh *nh = res->nh;
1665a00df2caSEric Dumazet int max_depth;
166635732d01SWei Wang int err = 0;
166735732d01SWei Wang
166835732d01SWei Wang spin_lock_bh(&rt6_exception_lock);
166935732d01SWei Wang
1670cc5c073aSDavid Ahern bucket = rcu_dereference_protected(nh->rt6i_exception_bucket,
167135732d01SWei Wang lockdep_is_held(&rt6_exception_lock));
167235732d01SWei Wang if (!bucket) {
167335732d01SWei Wang bucket = kcalloc(FIB6_EXCEPTION_BUCKET_SIZE, sizeof(*bucket),
167435732d01SWei Wang GFP_ATOMIC);
167535732d01SWei Wang if (!bucket) {
167635732d01SWei Wang err = -ENOMEM;
167735732d01SWei Wang goto out;
167835732d01SWei Wang }
1679cc5c073aSDavid Ahern rcu_assign_pointer(nh->rt6i_exception_bucket, bucket);
1680cc5c073aSDavid Ahern } else if (fib6_nh_excptn_bucket_flushed(bucket)) {
1681cc5c073aSDavid Ahern err = -EINVAL;
1682cc5c073aSDavid Ahern goto out;
168335732d01SWei Wang }
168435732d01SWei Wang
168535732d01SWei Wang #ifdef CONFIG_IPV6_SUBTREES
16865012f0a5SDavid Ahern /* fib6_src.plen != 0 indicates f6i is in subtree
168735732d01SWei Wang * and exception table is indexed by a hash of
16885012f0a5SDavid Ahern * both fib6_dst and fib6_src.
168935732d01SWei Wang * Otherwise, the exception table is indexed by
16905012f0a5SDavid Ahern * a hash of only fib6_dst.
169135732d01SWei Wang */
16925012f0a5SDavid Ahern if (f6i->fib6_src.plen)
169335732d01SWei Wang src_key = &nrt->rt6i_src.addr;
169435732d01SWei Wang #endif
16955012f0a5SDavid Ahern /* rt6_mtu_change() might lower mtu on f6i.
1696f5bbe7eeSWei Wang * Only insert this exception route if its mtu
16975012f0a5SDavid Ahern * is less than f6i's mtu value.
1698f5bbe7eeSWei Wang */
1699b748f260SDavid Ahern if (dst_metric_raw(&nrt->dst, RTAX_MTU) >= fib6_mtu(res)) {
1700f5bbe7eeSWei Wang err = -EINVAL;
1701f5bbe7eeSWei Wang goto out;
1702f5bbe7eeSWei Wang }
170360006a48SWei Wang
170435732d01SWei Wang rt6_ex = __rt6_find_exception_spinlock(&bucket, &nrt->rt6i_dst.addr,
170535732d01SWei Wang src_key);
170635732d01SWei Wang if (rt6_ex)
170735732d01SWei Wang rt6_remove_exception(bucket, rt6_ex);
170835732d01SWei Wang
170935732d01SWei Wang rt6_ex = kzalloc(sizeof(*rt6_ex), GFP_ATOMIC);
171035732d01SWei Wang if (!rt6_ex) {
171135732d01SWei Wang err = -ENOMEM;
171235732d01SWei Wang goto out;
171335732d01SWei Wang }
171435732d01SWei Wang rt6_ex->rt6i = nrt;
171535732d01SWei Wang rt6_ex->stamp = jiffies;
171635732d01SWei Wang hlist_add_head_rcu(&rt6_ex->hlist, &bucket->chain);
171735732d01SWei Wang bucket->depth++;
171881eb8447SWei Wang net->ipv6.rt6_stats->fib_rt_cache++;
171935732d01SWei Wang
1720a00df2caSEric Dumazet /* Randomize max depth to avoid some side channels attacks. */
17218032bf12SJason A. Donenfeld max_depth = FIB6_MAX_DEPTH + get_random_u32_below(FIB6_MAX_DEPTH);
1722a00df2caSEric Dumazet while (bucket->depth > max_depth)
172335732d01SWei Wang rt6_exception_remove_oldest(bucket);
172435732d01SWei Wang
172535732d01SWei Wang out:
172635732d01SWei Wang spin_unlock_bh(&rt6_exception_lock);
172735732d01SWei Wang
172835732d01SWei Wang /* Update fn->fn_sernum to invalidate all cached dst */
1729b886d5f2SPaolo Abeni if (!err) {
17305012f0a5SDavid Ahern spin_lock_bh(&f6i->fib6_table->tb6_lock);
17315012f0a5SDavid Ahern fib6_update_sernum(net, f6i);
17325012f0a5SDavid Ahern spin_unlock_bh(&f6i->fib6_table->tb6_lock);
1733b886d5f2SPaolo Abeni fib6_force_start_gc(net);
1734b886d5f2SPaolo Abeni }
173535732d01SWei Wang
173635732d01SWei Wang return err;
173735732d01SWei Wang }
173835732d01SWei Wang
fib6_nh_flush_exceptions(struct fib6_nh * nh,struct fib6_info * from)1739c0b220cfSDavid Ahern static void fib6_nh_flush_exceptions(struct fib6_nh *nh, struct fib6_info *from)
174035732d01SWei Wang {
174135732d01SWei Wang struct rt6_exception_bucket *bucket;
174235732d01SWei Wang struct rt6_exception *rt6_ex;
174335732d01SWei Wang struct hlist_node *tmp;
174435732d01SWei Wang int i;
174535732d01SWei Wang
174635732d01SWei Wang spin_lock_bh(&rt6_exception_lock);
174735732d01SWei Wang
1748cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
174935732d01SWei Wang if (!bucket)
175035732d01SWei Wang goto out;
175135732d01SWei Wang
1752cc5c073aSDavid Ahern /* Prevent rt6_insert_exception() to recreate the bucket list */
1753cc5c073aSDavid Ahern if (!from)
1754cc5c073aSDavid Ahern fib6_nh_excptn_bucket_set_flushed(nh, &rt6_exception_lock);
1755cc5c073aSDavid Ahern
175635732d01SWei Wang for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
1757cc5c073aSDavid Ahern hlist_for_each_entry_safe(rt6_ex, tmp, &bucket->chain, hlist) {
1758cc5c073aSDavid Ahern if (!from ||
1759cc5c073aSDavid Ahern rcu_access_pointer(rt6_ex->rt6i->from) == from)
176035732d01SWei Wang rt6_remove_exception(bucket, rt6_ex);
1761cc5c073aSDavid Ahern }
1762cc5c073aSDavid Ahern WARN_ON_ONCE(!from && bucket->depth);
176335732d01SWei Wang bucket++;
176435732d01SWei Wang }
176535732d01SWei Wang out:
176635732d01SWei Wang spin_unlock_bh(&rt6_exception_lock);
176735732d01SWei Wang }
176835732d01SWei Wang
rt6_nh_flush_exceptions(struct fib6_nh * nh,void * arg)1769e659ba31SDavid Ahern static int rt6_nh_flush_exceptions(struct fib6_nh *nh, void *arg)
1770e659ba31SDavid Ahern {
1771e659ba31SDavid Ahern struct fib6_info *f6i = arg;
1772e659ba31SDavid Ahern
1773e659ba31SDavid Ahern fib6_nh_flush_exceptions(nh, f6i);
1774e659ba31SDavid Ahern
1775e659ba31SDavid Ahern return 0;
1776e659ba31SDavid Ahern }
1777e659ba31SDavid Ahern
rt6_flush_exceptions(struct fib6_info * f6i)1778c0b220cfSDavid Ahern void rt6_flush_exceptions(struct fib6_info *f6i)
1779c0b220cfSDavid Ahern {
1780e659ba31SDavid Ahern if (f6i->nh)
1781e659ba31SDavid Ahern nexthop_for_each_fib6_nh(f6i->nh, rt6_nh_flush_exceptions,
1782e659ba31SDavid Ahern f6i);
1783e659ba31SDavid Ahern else
17841cf844c7SDavid Ahern fib6_nh_flush_exceptions(f6i->fib6_nh, f6i);
1785c0b220cfSDavid Ahern }
1786c0b220cfSDavid Ahern
178735732d01SWei Wang /* Find cached rt in the hash table inside passed in rt
178835732d01SWei Wang * Caller has to hold rcu_read_lock()
178935732d01SWei Wang */
rt6_find_cached_rt(const struct fib6_result * res,const struct in6_addr * daddr,const struct in6_addr * saddr)17907e4b5128SDavid Ahern static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,
1791510e2cedSWei Wang const struct in6_addr *daddr,
1792510e2cedSWei Wang const struct in6_addr *saddr)
179335732d01SWei Wang {
1794510e2cedSWei Wang const struct in6_addr *src_key = NULL;
179535732d01SWei Wang struct rt6_exception_bucket *bucket;
179635732d01SWei Wang struct rt6_exception *rt6_ex;
17977e4b5128SDavid Ahern struct rt6_info *ret = NULL;
179835732d01SWei Wang
179935732d01SWei Wang #ifdef CONFIG_IPV6_SUBTREES
18007e4b5128SDavid Ahern /* fib6i_src.plen != 0 indicates f6i is in subtree
180135732d01SWei Wang * and exception table is indexed by a hash of
18027e4b5128SDavid Ahern * both fib6_dst and fib6_src.
1803510e2cedSWei Wang * However, the src addr used to create the hash
1804510e2cedSWei Wang * might not be exactly the passed in saddr which
1805510e2cedSWei Wang * is a /128 addr from the flow.
1806510e2cedSWei Wang * So we need to use f6i->fib6_src to redo lookup
1807510e2cedSWei Wang * if the passed in saddr does not find anything.
1808510e2cedSWei Wang * (See the logic in ip6_rt_cache_alloc() on how
1809510e2cedSWei Wang * rt->rt6i_src is updated.)
181035732d01SWei Wang */
18117e4b5128SDavid Ahern if (res->f6i->fib6_src.plen)
181235732d01SWei Wang src_key = saddr;
1813510e2cedSWei Wang find_ex:
181435732d01SWei Wang #endif
1815cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(res->nh, NULL);
181635732d01SWei Wang rt6_ex = __rt6_find_exception_rcu(&bucket, daddr, src_key);
181735732d01SWei Wang
181835732d01SWei Wang if (rt6_ex && !rt6_check_expired(rt6_ex->rt6i))
18197e4b5128SDavid Ahern ret = rt6_ex->rt6i;
182035732d01SWei Wang
1821510e2cedSWei Wang #ifdef CONFIG_IPV6_SUBTREES
1822510e2cedSWei Wang /* Use fib6_src as src_key and redo lookup */
1823510e2cedSWei Wang if (!ret && src_key && src_key != &res->f6i->fib6_src.addr) {
1824510e2cedSWei Wang src_key = &res->f6i->fib6_src.addr;
1825510e2cedSWei Wang goto find_ex;
1826510e2cedSWei Wang }
1827510e2cedSWei Wang #endif
1828510e2cedSWei Wang
18297e4b5128SDavid Ahern return ret;
183035732d01SWei Wang }
183135732d01SWei Wang
183235732d01SWei Wang /* Remove the passed in cached rt from the hash table that contains it */
fib6_nh_remove_exception(const struct fib6_nh * nh,int plen,const struct rt6_info * rt)1833cc5c073aSDavid Ahern static int fib6_nh_remove_exception(const struct fib6_nh *nh, int plen,
1834c0b220cfSDavid Ahern const struct rt6_info *rt)
183535732d01SWei Wang {
1836c0b220cfSDavid Ahern const struct in6_addr *src_key = NULL;
183735732d01SWei Wang struct rt6_exception_bucket *bucket;
183835732d01SWei Wang struct rt6_exception *rt6_ex;
183935732d01SWei Wang int err;
184035732d01SWei Wang
1841cc5c073aSDavid Ahern if (!rcu_access_pointer(nh->rt6i_exception_bucket))
184235732d01SWei Wang return -ENOENT;
184335732d01SWei Wang
184435732d01SWei Wang spin_lock_bh(&rt6_exception_lock);
1845cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
1846cc5c073aSDavid Ahern
184735732d01SWei Wang #ifdef CONFIG_IPV6_SUBTREES
1848cc5c073aSDavid Ahern /* rt6i_src.plen != 0 indicates 'from' is in subtree
1849cc5c073aSDavid Ahern * and exception table is indexed by a hash of
1850cc5c073aSDavid Ahern * both rt6i_dst and rt6i_src.
185135732d01SWei Wang * Otherwise, the exception table is indexed by
185235732d01SWei Wang * a hash of only rt6i_dst.
185335732d01SWei Wang */
1854c0b220cfSDavid Ahern if (plen)
185535732d01SWei Wang src_key = &rt->rt6i_src.addr;
185635732d01SWei Wang #endif
185735732d01SWei Wang rt6_ex = __rt6_find_exception_spinlock(&bucket,
185835732d01SWei Wang &rt->rt6i_dst.addr,
185935732d01SWei Wang src_key);
186035732d01SWei Wang if (rt6_ex) {
186135732d01SWei Wang rt6_remove_exception(bucket, rt6_ex);
186235732d01SWei Wang err = 0;
186335732d01SWei Wang } else {
186435732d01SWei Wang err = -ENOENT;
186535732d01SWei Wang }
186635732d01SWei Wang
186735732d01SWei Wang spin_unlock_bh(&rt6_exception_lock);
186835732d01SWei Wang return err;
186935732d01SWei Wang }
187035732d01SWei Wang
1871e659ba31SDavid Ahern struct fib6_nh_excptn_arg {
1872e659ba31SDavid Ahern struct rt6_info *rt;
1873e659ba31SDavid Ahern int plen;
1874e659ba31SDavid Ahern };
1875e659ba31SDavid Ahern
rt6_nh_remove_exception_rt(struct fib6_nh * nh,void * _arg)1876e659ba31SDavid Ahern static int rt6_nh_remove_exception_rt(struct fib6_nh *nh, void *_arg)
1877e659ba31SDavid Ahern {
1878e659ba31SDavid Ahern struct fib6_nh_excptn_arg *arg = _arg;
1879e659ba31SDavid Ahern int err;
1880e659ba31SDavid Ahern
1881e659ba31SDavid Ahern err = fib6_nh_remove_exception(nh, arg->plen, arg->rt);
1882e659ba31SDavid Ahern if (err == 0)
1883e659ba31SDavid Ahern return 1;
1884e659ba31SDavid Ahern
1885e659ba31SDavid Ahern return 0;
1886e659ba31SDavid Ahern }
1887e659ba31SDavid Ahern
rt6_remove_exception_rt(struct rt6_info * rt)1888c0b220cfSDavid Ahern static int rt6_remove_exception_rt(struct rt6_info *rt)
1889c0b220cfSDavid Ahern {
1890c0b220cfSDavid Ahern struct fib6_info *from;
1891c0b220cfSDavid Ahern
1892c0b220cfSDavid Ahern from = rcu_dereference(rt->from);
1893cc5c073aSDavid Ahern if (!from || !(rt->rt6i_flags & RTF_CACHE))
1894c0b220cfSDavid Ahern return -EINVAL;
1895c0b220cfSDavid Ahern
1896e659ba31SDavid Ahern if (from->nh) {
1897e659ba31SDavid Ahern struct fib6_nh_excptn_arg arg = {
1898e659ba31SDavid Ahern .rt = rt,
1899e659ba31SDavid Ahern .plen = from->fib6_src.plen
1900e659ba31SDavid Ahern };
1901e659ba31SDavid Ahern int rc;
1902e659ba31SDavid Ahern
1903e659ba31SDavid Ahern /* rc = 1 means an entry was found */
1904e659ba31SDavid Ahern rc = nexthop_for_each_fib6_nh(from->nh,
1905e659ba31SDavid Ahern rt6_nh_remove_exception_rt,
1906e659ba31SDavid Ahern &arg);
1907e659ba31SDavid Ahern return rc ? 0 : -ENOENT;
1908e659ba31SDavid Ahern }
1909e659ba31SDavid Ahern
19101cf844c7SDavid Ahern return fib6_nh_remove_exception(from->fib6_nh,
1911cc5c073aSDavid Ahern from->fib6_src.plen, rt);
1912c0b220cfSDavid Ahern }
1913c0b220cfSDavid Ahern
191435732d01SWei Wang /* Find rt6_ex which contains the passed in rt cache and
191535732d01SWei Wang * refresh its stamp
191635732d01SWei Wang */
fib6_nh_update_exception(const struct fib6_nh * nh,int plen,const struct rt6_info * rt)1917cc5c073aSDavid Ahern static void fib6_nh_update_exception(const struct fib6_nh *nh, int plen,
1918c0b220cfSDavid Ahern const struct rt6_info *rt)
191935732d01SWei Wang {
1920c0b220cfSDavid Ahern const struct in6_addr *src_key = NULL;
192135732d01SWei Wang struct rt6_exception_bucket *bucket;
192235732d01SWei Wang struct rt6_exception *rt6_ex;
1923193f3685SPaolo Abeni
1924cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(nh, NULL);
192535732d01SWei Wang #ifdef CONFIG_IPV6_SUBTREES
1926cc5c073aSDavid Ahern /* rt6i_src.plen != 0 indicates 'from' is in subtree
1927cc5c073aSDavid Ahern * and exception table is indexed by a hash of
1928cc5c073aSDavid Ahern * both rt6i_dst and rt6i_src.
192935732d01SWei Wang * Otherwise, the exception table is indexed by
193035732d01SWei Wang * a hash of only rt6i_dst.
193135732d01SWei Wang */
1932c0b220cfSDavid Ahern if (plen)
193335732d01SWei Wang src_key = &rt->rt6i_src.addr;
193435732d01SWei Wang #endif
1935cc5c073aSDavid Ahern rt6_ex = __rt6_find_exception_rcu(&bucket, &rt->rt6i_dst.addr, src_key);
193635732d01SWei Wang if (rt6_ex)
193735732d01SWei Wang rt6_ex->stamp = jiffies;
1938c0b220cfSDavid Ahern }
193935732d01SWei Wang
1940e659ba31SDavid Ahern struct fib6_nh_match_arg {
1941e659ba31SDavid Ahern const struct net_device *dev;
1942e659ba31SDavid Ahern const struct in6_addr *gw;
1943e659ba31SDavid Ahern struct fib6_nh *match;
1944e659ba31SDavid Ahern };
1945e659ba31SDavid Ahern
1946e659ba31SDavid Ahern /* determine if fib6_nh has given device and gateway */
fib6_nh_find_match(struct fib6_nh * nh,void * _arg)1947e659ba31SDavid Ahern static int fib6_nh_find_match(struct fib6_nh *nh, void *_arg)
1948e659ba31SDavid Ahern {
1949e659ba31SDavid Ahern struct fib6_nh_match_arg *arg = _arg;
1950e659ba31SDavid Ahern
1951e659ba31SDavid Ahern if (arg->dev != nh->fib_nh_dev ||
1952e659ba31SDavid Ahern (arg->gw && !nh->fib_nh_gw_family) ||
1953e659ba31SDavid Ahern (!arg->gw && nh->fib_nh_gw_family) ||
1954e659ba31SDavid Ahern (arg->gw && !ipv6_addr_equal(arg->gw, &nh->fib_nh_gw6)))
1955e659ba31SDavid Ahern return 0;
1956e659ba31SDavid Ahern
1957e659ba31SDavid Ahern arg->match = nh;
1958e659ba31SDavid Ahern
1959e659ba31SDavid Ahern /* found a match, break the loop */
1960e659ba31SDavid Ahern return 1;
1961e659ba31SDavid Ahern }
1962e659ba31SDavid Ahern
rt6_update_exception_stamp_rt(struct rt6_info * rt)1963c0b220cfSDavid Ahern static void rt6_update_exception_stamp_rt(struct rt6_info *rt)
1964c0b220cfSDavid Ahern {
1965c0b220cfSDavid Ahern struct fib6_info *from;
1966e659ba31SDavid Ahern struct fib6_nh *fib6_nh;
1967c0b220cfSDavid Ahern
1968c0b220cfSDavid Ahern rcu_read_lock();
1969c0b220cfSDavid Ahern
1970c0b220cfSDavid Ahern from = rcu_dereference(rt->from);
1971c0b220cfSDavid Ahern if (!from || !(rt->rt6i_flags & RTF_CACHE))
1972c0b220cfSDavid Ahern goto unlock;
1973c0b220cfSDavid Ahern
1974e659ba31SDavid Ahern if (from->nh) {
1975e659ba31SDavid Ahern struct fib6_nh_match_arg arg = {
1976e659ba31SDavid Ahern .dev = rt->dst.dev,
1977e659ba31SDavid Ahern .gw = &rt->rt6i_gateway,
1978e659ba31SDavid Ahern };
1979e659ba31SDavid Ahern
1980e659ba31SDavid Ahern nexthop_for_each_fib6_nh(from->nh, fib6_nh_find_match, &arg);
1981e659ba31SDavid Ahern
1982e659ba31SDavid Ahern if (!arg.match)
1983cff6a327SDavid Ahern goto unlock;
1984e659ba31SDavid Ahern fib6_nh = arg.match;
1985e659ba31SDavid Ahern } else {
1986e659ba31SDavid Ahern fib6_nh = from->fib6_nh;
1987e659ba31SDavid Ahern }
1988e659ba31SDavid Ahern fib6_nh_update_exception(fib6_nh, from->fib6_src.plen, rt);
1989193f3685SPaolo Abeni unlock:
199035732d01SWei Wang rcu_read_unlock();
199135732d01SWei Wang }
199235732d01SWei Wang
rt6_mtu_change_route_allowed(struct inet6_dev * idev,struct rt6_info * rt,int mtu)1993e9fa1495SStefano Brivio static bool rt6_mtu_change_route_allowed(struct inet6_dev *idev,
1994e9fa1495SStefano Brivio struct rt6_info *rt, int mtu)
1995e9fa1495SStefano Brivio {
1996e9fa1495SStefano Brivio /* If the new MTU is lower than the route PMTU, this new MTU will be the
1997e9fa1495SStefano Brivio * lowest MTU in the path: always allow updating the route PMTU to
1998e9fa1495SStefano Brivio * reflect PMTU decreases.
1999e9fa1495SStefano Brivio *
2000e9fa1495SStefano Brivio * If the new MTU is higher, and the route PMTU is equal to the local
2001e9fa1495SStefano Brivio * MTU, this means the old MTU is the lowest in the path, so allow
2002e9fa1495SStefano Brivio * updating it: if other nodes now have lower MTUs, PMTU discovery will
2003e9fa1495SStefano Brivio * handle this.
2004e9fa1495SStefano Brivio */
2005e9fa1495SStefano Brivio
2006e9fa1495SStefano Brivio if (dst_mtu(&rt->dst) >= mtu)
2007e9fa1495SStefano Brivio return true;
2008e9fa1495SStefano Brivio
2009e9fa1495SStefano Brivio if (dst_mtu(&rt->dst) == idev->cnf.mtu6)
2010e9fa1495SStefano Brivio return true;
2011e9fa1495SStefano Brivio
2012e9fa1495SStefano Brivio return false;
2013e9fa1495SStefano Brivio }
2014e9fa1495SStefano Brivio
rt6_exceptions_update_pmtu(struct inet6_dev * idev,const struct fib6_nh * nh,int mtu)2015e9fa1495SStefano Brivio static void rt6_exceptions_update_pmtu(struct inet6_dev *idev,
2016cc5c073aSDavid Ahern const struct fib6_nh *nh, int mtu)
2017f5bbe7eeSWei Wang {
2018f5bbe7eeSWei Wang struct rt6_exception_bucket *bucket;
2019f5bbe7eeSWei Wang struct rt6_exception *rt6_ex;
2020f5bbe7eeSWei Wang int i;
2021f5bbe7eeSWei Wang
2022cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
2023e9fa1495SStefano Brivio if (!bucket)
2024e9fa1495SStefano Brivio return;
2025e9fa1495SStefano Brivio
2026f5bbe7eeSWei Wang for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
2027f5bbe7eeSWei Wang hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) {
2028f5bbe7eeSWei Wang struct rt6_info *entry = rt6_ex->rt6i;
2029e9fa1495SStefano Brivio
2030e9fa1495SStefano Brivio /* For RTF_CACHE with rt6i_pmtu == 0 (i.e. a redirected
2031d4ead6b3SDavid Ahern * route), the metrics of its rt->from have already
2032f5bbe7eeSWei Wang * been updated.
2033f5bbe7eeSWei Wang */
2034d4ead6b3SDavid Ahern if (dst_metric_raw(&entry->dst, RTAX_MTU) &&
2035e9fa1495SStefano Brivio rt6_mtu_change_route_allowed(idev, entry, mtu))
2036d4ead6b3SDavid Ahern dst_metric_set(&entry->dst, RTAX_MTU, mtu);
2037f5bbe7eeSWei Wang }
2038f5bbe7eeSWei Wang bucket++;
2039f5bbe7eeSWei Wang }
2040f5bbe7eeSWei Wang }
2041f5bbe7eeSWei Wang
2042b16cb459SWei Wang #define RTF_CACHE_GATEWAY (RTF_GATEWAY | RTF_CACHE)
2043b16cb459SWei Wang
fib6_nh_exceptions_clean_tohost(const struct fib6_nh * nh,const struct in6_addr * gateway)2044cc5c073aSDavid Ahern static void fib6_nh_exceptions_clean_tohost(const struct fib6_nh *nh,
2045cc5c073aSDavid Ahern const struct in6_addr *gateway)
2046b16cb459SWei Wang {
2047b16cb459SWei Wang struct rt6_exception_bucket *bucket;
2048b16cb459SWei Wang struct rt6_exception *rt6_ex;
2049b16cb459SWei Wang struct hlist_node *tmp;
2050b16cb459SWei Wang int i;
2051b16cb459SWei Wang
2052cc5c073aSDavid Ahern if (!rcu_access_pointer(nh->rt6i_exception_bucket))
2053b16cb459SWei Wang return;
2054b16cb459SWei Wang
2055b16cb459SWei Wang spin_lock_bh(&rt6_exception_lock);
2056cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
2057b16cb459SWei Wang if (bucket) {
2058b16cb459SWei Wang for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
2059b16cb459SWei Wang hlist_for_each_entry_safe(rt6_ex, tmp,
2060b16cb459SWei Wang &bucket->chain, hlist) {
2061b16cb459SWei Wang struct rt6_info *entry = rt6_ex->rt6i;
2062b16cb459SWei Wang
2063b16cb459SWei Wang if ((entry->rt6i_flags & RTF_CACHE_GATEWAY) ==
2064b16cb459SWei Wang RTF_CACHE_GATEWAY &&
2065b16cb459SWei Wang ipv6_addr_equal(gateway,
2066b16cb459SWei Wang &entry->rt6i_gateway)) {
2067b16cb459SWei Wang rt6_remove_exception(bucket, rt6_ex);
2068b16cb459SWei Wang }
2069b16cb459SWei Wang }
2070b16cb459SWei Wang bucket++;
2071b16cb459SWei Wang }
2072b16cb459SWei Wang }
2073b16cb459SWei Wang
2074b16cb459SWei Wang spin_unlock_bh(&rt6_exception_lock);
2075b16cb459SWei Wang }
2076b16cb459SWei Wang
rt6_age_examine_exception(struct rt6_exception_bucket * bucket,struct rt6_exception * rt6_ex,struct fib6_gc_args * gc_args,unsigned long now)2077c757faa8SWei Wang static void rt6_age_examine_exception(struct rt6_exception_bucket *bucket,
2078c757faa8SWei Wang struct rt6_exception *rt6_ex,
2079c757faa8SWei Wang struct fib6_gc_args *gc_args,
2080c757faa8SWei Wang unsigned long now)
2081c757faa8SWei Wang {
2082c757faa8SWei Wang struct rt6_info *rt = rt6_ex->rt6i;
2083c757faa8SWei Wang
20841859bac0SPaolo Abeni /* we are pruning and obsoleting aged-out and non gateway exceptions
20851859bac0SPaolo Abeni * even if others have still references to them, so that on next
20861859bac0SPaolo Abeni * dst_check() such references can be dropped.
20871859bac0SPaolo Abeni * EXPIRES exceptions - e.g. pmtu-generated ones are pruned when
20881859bac0SPaolo Abeni * expired, independently from their aging, as per RFC 8201 section 4
20891859bac0SPaolo Abeni */
209031afeb42SWei Wang if (!(rt->rt6i_flags & RTF_EXPIRES)) {
209131afeb42SWei Wang if (time_after_eq(now, rt->dst.lastuse + gc_args->timeout)) {
2092c757faa8SWei Wang RT6_TRACE("aging clone %p\n", rt);
2093c757faa8SWei Wang rt6_remove_exception(bucket, rt6_ex);
2094c757faa8SWei Wang return;
209531afeb42SWei Wang }
209631afeb42SWei Wang } else if (time_after(jiffies, rt->dst.expires)) {
209731afeb42SWei Wang RT6_TRACE("purging expired route %p\n", rt);
209831afeb42SWei Wang rt6_remove_exception(bucket, rt6_ex);
209931afeb42SWei Wang return;
210031afeb42SWei Wang }
210131afeb42SWei Wang
210231afeb42SWei Wang if (rt->rt6i_flags & RTF_GATEWAY) {
2103c757faa8SWei Wang struct neighbour *neigh;
2104c757faa8SWei Wang
21051bfa26ffSEric Dumazet neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway);
21061bfa26ffSEric Dumazet
2107b7a320c3SXu Jia if (!(neigh && (neigh->flags & NTF_ROUTER))) {
2108c757faa8SWei Wang RT6_TRACE("purging route %p via non-router but gateway\n",
2109c757faa8SWei Wang rt);
2110c757faa8SWei Wang rt6_remove_exception(bucket, rt6_ex);
2111c757faa8SWei Wang return;
2112c757faa8SWei Wang }
2113c757faa8SWei Wang }
211431afeb42SWei Wang
2115c757faa8SWei Wang gc_args->more++;
2116c757faa8SWei Wang }
2117c757faa8SWei Wang
fib6_nh_age_exceptions(const struct fib6_nh * nh,struct fib6_gc_args * gc_args,unsigned long now)2118cc5c073aSDavid Ahern static void fib6_nh_age_exceptions(const struct fib6_nh *nh,
2119c757faa8SWei Wang struct fib6_gc_args *gc_args,
2120c757faa8SWei Wang unsigned long now)
2121c757faa8SWei Wang {
2122c757faa8SWei Wang struct rt6_exception_bucket *bucket;
2123c757faa8SWei Wang struct rt6_exception *rt6_ex;
2124c757faa8SWei Wang struct hlist_node *tmp;
2125c757faa8SWei Wang int i;
2126c757faa8SWei Wang
2127cc5c073aSDavid Ahern if (!rcu_access_pointer(nh->rt6i_exception_bucket))
2128c757faa8SWei Wang return;
2129c757faa8SWei Wang
21301bfa26ffSEric Dumazet rcu_read_lock_bh();
21311bfa26ffSEric Dumazet spin_lock(&rt6_exception_lock);
2132cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
2133c757faa8SWei Wang if (bucket) {
2134c757faa8SWei Wang for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
2135c757faa8SWei Wang hlist_for_each_entry_safe(rt6_ex, tmp,
2136c757faa8SWei Wang &bucket->chain, hlist) {
2137c757faa8SWei Wang rt6_age_examine_exception(bucket, rt6_ex,
2138c757faa8SWei Wang gc_args, now);
2139c757faa8SWei Wang }
2140c757faa8SWei Wang bucket++;
2141c757faa8SWei Wang }
2142c757faa8SWei Wang }
21431bfa26ffSEric Dumazet spin_unlock(&rt6_exception_lock);
21441bfa26ffSEric Dumazet rcu_read_unlock_bh();
2145c757faa8SWei Wang }
2146c757faa8SWei Wang
2147e659ba31SDavid Ahern struct fib6_nh_age_excptn_arg {
2148e659ba31SDavid Ahern struct fib6_gc_args *gc_args;
2149e659ba31SDavid Ahern unsigned long now;
2150e659ba31SDavid Ahern };
2151e659ba31SDavid Ahern
rt6_nh_age_exceptions(struct fib6_nh * nh,void * _arg)2152e659ba31SDavid Ahern static int rt6_nh_age_exceptions(struct fib6_nh *nh, void *_arg)
2153e659ba31SDavid Ahern {
2154e659ba31SDavid Ahern struct fib6_nh_age_excptn_arg *arg = _arg;
2155e659ba31SDavid Ahern
2156e659ba31SDavid Ahern fib6_nh_age_exceptions(nh, arg->gc_args, arg->now);
2157e659ba31SDavid Ahern return 0;
2158e659ba31SDavid Ahern }
2159e659ba31SDavid Ahern
rt6_age_exceptions(struct fib6_info * f6i,struct fib6_gc_args * gc_args,unsigned long now)2160cc5c073aSDavid Ahern void rt6_age_exceptions(struct fib6_info *f6i,
2161c0b220cfSDavid Ahern struct fib6_gc_args *gc_args,
2162c0b220cfSDavid Ahern unsigned long now)
2163c0b220cfSDavid Ahern {
2164e659ba31SDavid Ahern if (f6i->nh) {
2165e659ba31SDavid Ahern struct fib6_nh_age_excptn_arg arg = {
2166e659ba31SDavid Ahern .gc_args = gc_args,
2167e659ba31SDavid Ahern .now = now
2168e659ba31SDavid Ahern };
2169e659ba31SDavid Ahern
2170e659ba31SDavid Ahern nexthop_for_each_fib6_nh(f6i->nh, rt6_nh_age_exceptions,
2171e659ba31SDavid Ahern &arg);
2172e659ba31SDavid Ahern } else {
21731cf844c7SDavid Ahern fib6_nh_age_exceptions(f6i->fib6_nh, gc_args, now);
2174c0b220cfSDavid Ahern }
2175e659ba31SDavid Ahern }
2176c0b220cfSDavid Ahern
21771d053da9SDavid Ahern /* must be called with rcu lock held */
fib6_table_lookup(struct net * net,struct fib6_table * table,int oif,struct flowi6 * fl6,struct fib6_result * res,int strict)2178effda4ddSDavid Ahern int fib6_table_lookup(struct net *net, struct fib6_table *table, int oif,
2179effda4ddSDavid Ahern struct flowi6 *fl6, struct fib6_result *res, int strict)
21801da177e4SLinus Torvalds {
2181367efcb9SMartin KaFai Lau struct fib6_node *fn, *saved_fn;
21821da177e4SLinus Torvalds
21836454743bSDavid Ahern fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
2184367efcb9SMartin KaFai Lau saved_fn = fn;
21851da177e4SLinus Torvalds
2186a3c00e46SMartin KaFai Lau redo_rt6_select:
2187effda4ddSDavid Ahern rt6_select(net, fn, oif, res, strict);
2188effda4ddSDavid Ahern if (res->f6i == net->ipv6.fib6_null_entry) {
2189a3c00e46SMartin KaFai Lau fn = fib6_backtrack(fn, &fl6->saddr);
2190a3c00e46SMartin KaFai Lau if (fn)
2191a3c00e46SMartin KaFai Lau goto redo_rt6_select;
2192367efcb9SMartin KaFai Lau else if (strict & RT6_LOOKUP_F_REACHABLE) {
2193367efcb9SMartin KaFai Lau /* also consider unreachable route */
2194367efcb9SMartin KaFai Lau strict &= ~RT6_LOOKUP_F_REACHABLE;
2195367efcb9SMartin KaFai Lau fn = saved_fn;
2196367efcb9SMartin KaFai Lau goto redo_rt6_select;
2197367efcb9SMartin KaFai Lau }
2198a3c00e46SMartin KaFai Lau }
2199a3c00e46SMartin KaFai Lau
2200effda4ddSDavid Ahern trace_fib6_table_lookup(net, res, table, fl6);
2201d52d3997SMartin KaFai Lau
2202effda4ddSDavid Ahern return 0;
22031d053da9SDavid Ahern }
22041d053da9SDavid Ahern
ip6_pol_route(struct net * net,struct fib6_table * table,int oif,struct flowi6 * fl6,const struct sk_buff * skb,int flags)22051d053da9SDavid Ahern struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
22061d053da9SDavid Ahern int oif, struct flowi6 *fl6,
22071d053da9SDavid Ahern const struct sk_buff *skb, int flags)
22081d053da9SDavid Ahern {
2209b1d40991SDavid Ahern struct fib6_result res = {};
22100e09edccSWei Wang struct rt6_info *rt = NULL;
22111d053da9SDavid Ahern int strict = 0;
22121d053da9SDavid Ahern
22130e09edccSWei Wang WARN_ON_ONCE((flags & RT6_LOOKUP_F_DST_NOREF) &&
22140e09edccSWei Wang !rcu_read_lock_held());
22150e09edccSWei Wang
22161d053da9SDavid Ahern strict |= flags & RT6_LOOKUP_F_IFACE;
22171d053da9SDavid Ahern strict |= flags & RT6_LOOKUP_F_IGNORE_LINKSTATE;
22181d053da9SDavid Ahern if (net->ipv6.devconf_all->forwarding == 0)
22191d053da9SDavid Ahern strict |= RT6_LOOKUP_F_REACHABLE;
22201d053da9SDavid Ahern
22211d053da9SDavid Ahern rcu_read_lock();
22221d053da9SDavid Ahern
2223effda4ddSDavid Ahern fib6_table_lookup(net, table, oif, fl6, &res, strict);
22240e09edccSWei Wang if (res.f6i == net->ipv6.fib6_null_entry)
22250e09edccSWei Wang goto out;
222623fb93a4SDavid Ahern
2227b1d40991SDavid Ahern fib6_select_path(net, &res, fl6, oif, false, skb, strict);
2228d83009d4SDavid Ahern
222923fb93a4SDavid Ahern /*Search through exception table */
22307e4b5128SDavid Ahern rt = rt6_find_cached_rt(&res, &fl6->daddr, &fl6->saddr);
223123fb93a4SDavid Ahern if (rt) {
22320e09edccSWei Wang goto out;
22333da59bd9SMartin KaFai Lau } else if (unlikely((fl6->flowi6_flags & FLOWI_FLAG_KNOWN_NH) &&
2234b1d40991SDavid Ahern !res.nh->fib_nh_gw_family)) {
22353da59bd9SMartin KaFai Lau /* Create a RTF_CACHE clone which will not be
22363da59bd9SMartin KaFai Lau * owned by the fib6 tree. It is for the special case where
22373da59bd9SMartin KaFai Lau * the daddr in the skb during the neighbor look-up is different
22383da59bd9SMartin KaFai Lau * from the fl6->daddr used to look-up route here.
22393da59bd9SMartin KaFai Lau */
22400e09edccSWei Wang rt = ip6_rt_cache_alloc(&res, &fl6->daddr, NULL);
22413da59bd9SMartin KaFai Lau
22420e09edccSWei Wang if (rt) {
22430e09edccSWei Wang /* 1 refcnt is taken during ip6_rt_cache_alloc().
22440e09edccSWei Wang * As rt6_uncached_list_add() does not consume refcnt,
22450e09edccSWei Wang * this refcnt is always returned to the caller even
22460e09edccSWei Wang * if caller sets RT6_LOOKUP_F_DST_NOREF flag.
22470e09edccSWei Wang */
22480e09edccSWei Wang rt6_uncached_list_add(rt);
22494d85cd0cSDavid Ahern rcu_read_unlock();
22503da59bd9SMartin KaFai Lau
22510e09edccSWei Wang return rt;
22521cfb71eeSWei Wang }
2253d52d3997SMartin KaFai Lau } else {
2254d52d3997SMartin KaFai Lau /* Get a percpu copy */
2255951f788aSEric Dumazet local_bh_disable();
22560e09edccSWei Wang rt = rt6_get_pcpu_route(&res);
2257d52d3997SMartin KaFai Lau
22580e09edccSWei Wang if (!rt)
22590e09edccSWei Wang rt = rt6_make_pcpu_route(net, &res);
226093531c67SDavid Ahern
2261951f788aSEric Dumazet local_bh_enable();
22620e09edccSWei Wang }
22630e09edccSWei Wang out:
22640e09edccSWei Wang if (!rt)
22650e09edccSWei Wang rt = net->ipv6.ip6_null_entry;
22660e09edccSWei Wang if (!(flags & RT6_LOOKUP_F_DST_NOREF))
22670e09edccSWei Wang ip6_hold_safe(net, &rt);
2268951f788aSEric Dumazet rcu_read_unlock();
2269d4bea421SDavid Ahern
22700e09edccSWei Wang return rt;
2271c71099acSThomas Graf }
22729ff74384SDavid Ahern EXPORT_SYMBOL_GPL(ip6_pol_route);
2273c71099acSThomas Graf
ip6_pol_route_input(struct net * net,struct fib6_table * table,struct flowi6 * fl6,const struct sk_buff * skb,int flags)227455cced4fSBrian Vazquez INDIRECT_CALLABLE_SCOPE struct rt6_info *ip6_pol_route_input(struct net *net,
2275b75cc8f9SDavid Ahern struct fib6_table *table,
2276b75cc8f9SDavid Ahern struct flowi6 *fl6,
2277b75cc8f9SDavid Ahern const struct sk_buff *skb,
2278b75cc8f9SDavid Ahern int flags)
22794acad72dSPavel Emelyanov {
2280b75cc8f9SDavid Ahern return ip6_pol_route(net, table, fl6->flowi6_iif, fl6, skb, flags);
22814acad72dSPavel Emelyanov }
22824acad72dSPavel Emelyanov
ip6_route_input_lookup(struct net * net,struct net_device * dev,struct flowi6 * fl6,const struct sk_buff * skb,int flags)2283d409b847SMahesh Bandewar struct dst_entry *ip6_route_input_lookup(struct net *net,
228472331bc0SShmulik Ladkani struct net_device *dev,
2285b75cc8f9SDavid Ahern struct flowi6 *fl6,
2286b75cc8f9SDavid Ahern const struct sk_buff *skb,
2287b75cc8f9SDavid Ahern int flags)
228872331bc0SShmulik Ladkani {
228972331bc0SShmulik Ladkani if (rt6_need_strict(&fl6->daddr) && dev->type != ARPHRD_PIMREG)
229072331bc0SShmulik Ladkani flags |= RT6_LOOKUP_F_IFACE;
229172331bc0SShmulik Ladkani
2292b75cc8f9SDavid Ahern return fib6_rule_lookup(net, fl6, skb, flags, ip6_pol_route_input);
229372331bc0SShmulik Ladkani }
2294d409b847SMahesh Bandewar EXPORT_SYMBOL_GPL(ip6_route_input_lookup);
229572331bc0SShmulik Ladkani
ip6_multipath_l3_keys(const struct sk_buff * skb,struct flow_keys * keys,struct flow_keys * flkeys)229623aebdacSJakub Sitnicki static void ip6_multipath_l3_keys(const struct sk_buff *skb,
22975e5d6fedSRoopa Prabhu struct flow_keys *keys,
22985e5d6fedSRoopa Prabhu struct flow_keys *flkeys)
229923aebdacSJakub Sitnicki {
230023aebdacSJakub Sitnicki const struct ipv6hdr *outer_iph = ipv6_hdr(skb);
230123aebdacSJakub Sitnicki const struct ipv6hdr *key_iph = outer_iph;
23025e5d6fedSRoopa Prabhu struct flow_keys *_flkeys = flkeys;
230323aebdacSJakub Sitnicki const struct ipv6hdr *inner_iph;
230423aebdacSJakub Sitnicki const struct icmp6hdr *icmph;
230523aebdacSJakub Sitnicki struct ipv6hdr _inner_iph;
2306cea67a2dSEric Dumazet struct icmp6hdr _icmph;
230723aebdacSJakub Sitnicki
230823aebdacSJakub Sitnicki if (likely(outer_iph->nexthdr != IPPROTO_ICMPV6))
230923aebdacSJakub Sitnicki goto out;
231023aebdacSJakub Sitnicki
2311cea67a2dSEric Dumazet icmph = skb_header_pointer(skb, skb_transport_offset(skb),
2312cea67a2dSEric Dumazet sizeof(_icmph), &_icmph);
2313cea67a2dSEric Dumazet if (!icmph)
2314cea67a2dSEric Dumazet goto out;
2315cea67a2dSEric Dumazet
231654074f1dSMatteo Croce if (!icmpv6_is_err(icmph->icmp6_type))
231723aebdacSJakub Sitnicki goto out;
231823aebdacSJakub Sitnicki
231923aebdacSJakub Sitnicki inner_iph = skb_header_pointer(skb,
232023aebdacSJakub Sitnicki skb_transport_offset(skb) + sizeof(*icmph),
232123aebdacSJakub Sitnicki sizeof(_inner_iph), &_inner_iph);
232223aebdacSJakub Sitnicki if (!inner_iph)
232323aebdacSJakub Sitnicki goto out;
232423aebdacSJakub Sitnicki
232523aebdacSJakub Sitnicki key_iph = inner_iph;
23265e5d6fedSRoopa Prabhu _flkeys = NULL;
232723aebdacSJakub Sitnicki out:
23285e5d6fedSRoopa Prabhu if (_flkeys) {
23295e5d6fedSRoopa Prabhu keys->addrs.v6addrs.src = _flkeys->addrs.v6addrs.src;
23305e5d6fedSRoopa Prabhu keys->addrs.v6addrs.dst = _flkeys->addrs.v6addrs.dst;
23315e5d6fedSRoopa Prabhu keys->tags.flow_label = _flkeys->tags.flow_label;
23325e5d6fedSRoopa Prabhu keys->basic.ip_proto = _flkeys->basic.ip_proto;
23335e5d6fedSRoopa Prabhu } else {
233423aebdacSJakub Sitnicki keys->addrs.v6addrs.src = key_iph->saddr;
233523aebdacSJakub Sitnicki keys->addrs.v6addrs.dst = key_iph->daddr;
2336fa1be7e0SMichal Kubecek keys->tags.flow_label = ip6_flowlabel(key_iph);
233723aebdacSJakub Sitnicki keys->basic.ip_proto = key_iph->nexthdr;
233823aebdacSJakub Sitnicki }
23395e5d6fedSRoopa Prabhu }
234023aebdacSJakub Sitnicki
rt6_multipath_custom_hash_outer(const struct net * net,const struct sk_buff * skb,bool * p_has_inner)234173c2c5cbSIdo Schimmel static u32 rt6_multipath_custom_hash_outer(const struct net *net,
234273c2c5cbSIdo Schimmel const struct sk_buff *skb,
234373c2c5cbSIdo Schimmel bool *p_has_inner)
234473c2c5cbSIdo Schimmel {
234573c2c5cbSIdo Schimmel u32 hash_fields = ip6_multipath_hash_fields(net);
234673c2c5cbSIdo Schimmel struct flow_keys keys, hash_keys;
234773c2c5cbSIdo Schimmel
234873c2c5cbSIdo Schimmel if (!(hash_fields & FIB_MULTIPATH_HASH_FIELD_OUTER_MASK))
234973c2c5cbSIdo Schimmel return 0;
235073c2c5cbSIdo Schimmel
235173c2c5cbSIdo Schimmel memset(&hash_keys, 0, sizeof(hash_keys));
235273c2c5cbSIdo Schimmel skb_flow_dissect_flow_keys(skb, &keys, FLOW_DISSECTOR_F_STOP_AT_ENCAP);
235373c2c5cbSIdo Schimmel
235473c2c5cbSIdo Schimmel hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
235573c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_IP)
235673c2c5cbSIdo Schimmel hash_keys.addrs.v6addrs.src = keys.addrs.v6addrs.src;
235773c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_IP)
235873c2c5cbSIdo Schimmel hash_keys.addrs.v6addrs.dst = keys.addrs.v6addrs.dst;
235973c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_IP_PROTO)
236073c2c5cbSIdo Schimmel hash_keys.basic.ip_proto = keys.basic.ip_proto;
236173c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_FLOWLABEL)
236273c2c5cbSIdo Schimmel hash_keys.tags.flow_label = keys.tags.flow_label;
236373c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_PORT)
236473c2c5cbSIdo Schimmel hash_keys.ports.src = keys.ports.src;
236573c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_PORT)
236673c2c5cbSIdo Schimmel hash_keys.ports.dst = keys.ports.dst;
236773c2c5cbSIdo Schimmel
236873c2c5cbSIdo Schimmel *p_has_inner = !!(keys.control.flags & FLOW_DIS_ENCAPSULATION);
236973c2c5cbSIdo Schimmel return flow_hash_from_keys(&hash_keys);
237073c2c5cbSIdo Schimmel }
237173c2c5cbSIdo Schimmel
rt6_multipath_custom_hash_inner(const struct net * net,const struct sk_buff * skb,bool has_inner)237273c2c5cbSIdo Schimmel static u32 rt6_multipath_custom_hash_inner(const struct net *net,
237373c2c5cbSIdo Schimmel const struct sk_buff *skb,
237473c2c5cbSIdo Schimmel bool has_inner)
237573c2c5cbSIdo Schimmel {
237673c2c5cbSIdo Schimmel u32 hash_fields = ip6_multipath_hash_fields(net);
237773c2c5cbSIdo Schimmel struct flow_keys keys, hash_keys;
237873c2c5cbSIdo Schimmel
237973c2c5cbSIdo Schimmel /* We assume the packet carries an encapsulation, but if none was
238073c2c5cbSIdo Schimmel * encountered during dissection of the outer flow, then there is no
238173c2c5cbSIdo Schimmel * point in calling the flow dissector again.
238273c2c5cbSIdo Schimmel */
238373c2c5cbSIdo Schimmel if (!has_inner)
238473c2c5cbSIdo Schimmel return 0;
238573c2c5cbSIdo Schimmel
238673c2c5cbSIdo Schimmel if (!(hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_MASK))
238773c2c5cbSIdo Schimmel return 0;
238873c2c5cbSIdo Schimmel
238973c2c5cbSIdo Schimmel memset(&hash_keys, 0, sizeof(hash_keys));
239073c2c5cbSIdo Schimmel skb_flow_dissect_flow_keys(skb, &keys, 0);
239173c2c5cbSIdo Schimmel
239273c2c5cbSIdo Schimmel if (!(keys.control.flags & FLOW_DIS_ENCAPSULATION))
239373c2c5cbSIdo Schimmel return 0;
239473c2c5cbSIdo Schimmel
239573c2c5cbSIdo Schimmel if (keys.control.addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
239673c2c5cbSIdo Schimmel hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
239773c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_SRC_IP)
239873c2c5cbSIdo Schimmel hash_keys.addrs.v4addrs.src = keys.addrs.v4addrs.src;
239973c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_DST_IP)
240073c2c5cbSIdo Schimmel hash_keys.addrs.v4addrs.dst = keys.addrs.v4addrs.dst;
240173c2c5cbSIdo Schimmel } else if (keys.control.addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
240273c2c5cbSIdo Schimmel hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
240373c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_SRC_IP)
240473c2c5cbSIdo Schimmel hash_keys.addrs.v6addrs.src = keys.addrs.v6addrs.src;
240573c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_DST_IP)
240673c2c5cbSIdo Schimmel hash_keys.addrs.v6addrs.dst = keys.addrs.v6addrs.dst;
240773c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_FLOWLABEL)
240873c2c5cbSIdo Schimmel hash_keys.tags.flow_label = keys.tags.flow_label;
240973c2c5cbSIdo Schimmel }
241073c2c5cbSIdo Schimmel
241173c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_IP_PROTO)
241273c2c5cbSIdo Schimmel hash_keys.basic.ip_proto = keys.basic.ip_proto;
241373c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_SRC_PORT)
241473c2c5cbSIdo Schimmel hash_keys.ports.src = keys.ports.src;
241573c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_DST_PORT)
241673c2c5cbSIdo Schimmel hash_keys.ports.dst = keys.ports.dst;
241773c2c5cbSIdo Schimmel
241873c2c5cbSIdo Schimmel return flow_hash_from_keys(&hash_keys);
241973c2c5cbSIdo Schimmel }
242073c2c5cbSIdo Schimmel
rt6_multipath_custom_hash_skb(const struct net * net,const struct sk_buff * skb)242173c2c5cbSIdo Schimmel static u32 rt6_multipath_custom_hash_skb(const struct net *net,
242273c2c5cbSIdo Schimmel const struct sk_buff *skb)
242373c2c5cbSIdo Schimmel {
242473c2c5cbSIdo Schimmel u32 mhash, mhash_inner;
242573c2c5cbSIdo Schimmel bool has_inner = true;
242673c2c5cbSIdo Schimmel
242773c2c5cbSIdo Schimmel mhash = rt6_multipath_custom_hash_outer(net, skb, &has_inner);
242873c2c5cbSIdo Schimmel mhash_inner = rt6_multipath_custom_hash_inner(net, skb, has_inner);
242973c2c5cbSIdo Schimmel
243073c2c5cbSIdo Schimmel return jhash_2words(mhash, mhash_inner, 0);
243173c2c5cbSIdo Schimmel }
243273c2c5cbSIdo Schimmel
rt6_multipath_custom_hash_fl6(const struct net * net,const struct flowi6 * fl6)243373c2c5cbSIdo Schimmel static u32 rt6_multipath_custom_hash_fl6(const struct net *net,
243473c2c5cbSIdo Schimmel const struct flowi6 *fl6)
243573c2c5cbSIdo Schimmel {
243673c2c5cbSIdo Schimmel u32 hash_fields = ip6_multipath_hash_fields(net);
243773c2c5cbSIdo Schimmel struct flow_keys hash_keys;
243873c2c5cbSIdo Schimmel
243973c2c5cbSIdo Schimmel if (!(hash_fields & FIB_MULTIPATH_HASH_FIELD_OUTER_MASK))
244073c2c5cbSIdo Schimmel return 0;
244173c2c5cbSIdo Schimmel
244273c2c5cbSIdo Schimmel memset(&hash_keys, 0, sizeof(hash_keys));
244373c2c5cbSIdo Schimmel hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
244473c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_IP)
244573c2c5cbSIdo Schimmel hash_keys.addrs.v6addrs.src = fl6->saddr;
244673c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_IP)
244773c2c5cbSIdo Schimmel hash_keys.addrs.v6addrs.dst = fl6->daddr;
244873c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_IP_PROTO)
244973c2c5cbSIdo Schimmel hash_keys.basic.ip_proto = fl6->flowi6_proto;
245073c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_FLOWLABEL)
245173c2c5cbSIdo Schimmel hash_keys.tags.flow_label = (__force u32)flowi6_get_flowlabel(fl6);
245273c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_PORT)
245373c2c5cbSIdo Schimmel hash_keys.ports.src = fl6->fl6_sport;
245473c2c5cbSIdo Schimmel if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_PORT)
245573c2c5cbSIdo Schimmel hash_keys.ports.dst = fl6->fl6_dport;
245673c2c5cbSIdo Schimmel
245773c2c5cbSIdo Schimmel return flow_hash_from_keys(&hash_keys);
245873c2c5cbSIdo Schimmel }
245973c2c5cbSIdo Schimmel
246023aebdacSJakub Sitnicki /* if skb is set it will be used and fl6 can be NULL */
rt6_multipath_hash(const struct net * net,const struct flowi6 * fl6,const struct sk_buff * skb,struct flow_keys * flkeys)2461b4bac172SDavid Ahern u32 rt6_multipath_hash(const struct net *net, const struct flowi6 *fl6,
2462b4bac172SDavid Ahern const struct sk_buff *skb, struct flow_keys *flkeys)
246323aebdacSJakub Sitnicki {
246423aebdacSJakub Sitnicki struct flow_keys hash_keys;
2465b95b6e07SIdo Schimmel u32 mhash = 0;
246623aebdacSJakub Sitnicki
2467bbfa047aSDavid S. Miller switch (ip6_multipath_hash_policy(net)) {
2468b4bac172SDavid Ahern case 0:
24696f74b6c2SDavid Ahern memset(&hash_keys, 0, sizeof(hash_keys));
24706f74b6c2SDavid Ahern hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
24719a2a537aSDavid Ahern if (skb) {
24725e5d6fedSRoopa Prabhu ip6_multipath_l3_keys(skb, &hash_keys, flkeys);
24739a2a537aSDavid Ahern } else {
24749a2a537aSDavid Ahern hash_keys.addrs.v6addrs.src = fl6->saddr;
24759a2a537aSDavid Ahern hash_keys.addrs.v6addrs.dst = fl6->daddr;
2476fa1be7e0SMichal Kubecek hash_keys.tags.flow_label = (__force u32)flowi6_get_flowlabel(fl6);
24779a2a537aSDavid Ahern hash_keys.basic.ip_proto = fl6->flowi6_proto;
247823aebdacSJakub Sitnicki }
2479b95b6e07SIdo Schimmel mhash = flow_hash_from_keys(&hash_keys);
2480b4bac172SDavid Ahern break;
2481b4bac172SDavid Ahern case 1:
2482b4bac172SDavid Ahern if (skb) {
2483b4bac172SDavid Ahern unsigned int flag = FLOW_DISSECTOR_F_STOP_AT_ENCAP;
2484b4bac172SDavid Ahern struct flow_keys keys;
2485b4bac172SDavid Ahern
2486b4bac172SDavid Ahern /* short-circuit if we already have L4 hash present */
2487b4bac172SDavid Ahern if (skb->l4_hash)
2488b4bac172SDavid Ahern return skb_get_hash_raw(skb) >> 1;
2489b4bac172SDavid Ahern
2490b4bac172SDavid Ahern memset(&hash_keys, 0, sizeof(hash_keys));
2491b4bac172SDavid Ahern
2492b4bac172SDavid Ahern if (!flkeys) {
2493b4bac172SDavid Ahern skb_flow_dissect_flow_keys(skb, &keys, flag);
2494b4bac172SDavid Ahern flkeys = &keys;
2495b4bac172SDavid Ahern }
2496b4bac172SDavid Ahern hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
2497b4bac172SDavid Ahern hash_keys.addrs.v6addrs.src = flkeys->addrs.v6addrs.src;
2498b4bac172SDavid Ahern hash_keys.addrs.v6addrs.dst = flkeys->addrs.v6addrs.dst;
2499b4bac172SDavid Ahern hash_keys.ports.src = flkeys->ports.src;
2500b4bac172SDavid Ahern hash_keys.ports.dst = flkeys->ports.dst;
2501b4bac172SDavid Ahern hash_keys.basic.ip_proto = flkeys->basic.ip_proto;
2502b4bac172SDavid Ahern } else {
2503b4bac172SDavid Ahern memset(&hash_keys, 0, sizeof(hash_keys));
2504b4bac172SDavid Ahern hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
2505b4bac172SDavid Ahern hash_keys.addrs.v6addrs.src = fl6->saddr;
2506b4bac172SDavid Ahern hash_keys.addrs.v6addrs.dst = fl6->daddr;
2507b4bac172SDavid Ahern hash_keys.ports.src = fl6->fl6_sport;
2508b4bac172SDavid Ahern hash_keys.ports.dst = fl6->fl6_dport;
2509b4bac172SDavid Ahern hash_keys.basic.ip_proto = fl6->flowi6_proto;
2510b4bac172SDavid Ahern }
2511b95b6e07SIdo Schimmel mhash = flow_hash_from_keys(&hash_keys);
2512b4bac172SDavid Ahern break;
2513d8f74f09SStephen Suryaputra case 2:
2514d8f74f09SStephen Suryaputra memset(&hash_keys, 0, sizeof(hash_keys));
2515d8f74f09SStephen Suryaputra hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
2516d8f74f09SStephen Suryaputra if (skb) {
2517d8f74f09SStephen Suryaputra struct flow_keys keys;
2518d8f74f09SStephen Suryaputra
2519d8f74f09SStephen Suryaputra if (!flkeys) {
2520d8f74f09SStephen Suryaputra skb_flow_dissect_flow_keys(skb, &keys, 0);
2521d8f74f09SStephen Suryaputra flkeys = &keys;
2522d8f74f09SStephen Suryaputra }
2523d8f74f09SStephen Suryaputra
2524d8f74f09SStephen Suryaputra /* Inner can be v4 or v6 */
2525d8f74f09SStephen Suryaputra if (flkeys->control.addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
2526d8f74f09SStephen Suryaputra hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
2527d8f74f09SStephen Suryaputra hash_keys.addrs.v4addrs.src = flkeys->addrs.v4addrs.src;
2528d8f74f09SStephen Suryaputra hash_keys.addrs.v4addrs.dst = flkeys->addrs.v4addrs.dst;
2529d8f74f09SStephen Suryaputra } else if (flkeys->control.addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
2530d8f74f09SStephen Suryaputra hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
2531d8f74f09SStephen Suryaputra hash_keys.addrs.v6addrs.src = flkeys->addrs.v6addrs.src;
2532d8f74f09SStephen Suryaputra hash_keys.addrs.v6addrs.dst = flkeys->addrs.v6addrs.dst;
2533d8f74f09SStephen Suryaputra hash_keys.tags.flow_label = flkeys->tags.flow_label;
2534d8f74f09SStephen Suryaputra hash_keys.basic.ip_proto = flkeys->basic.ip_proto;
2535d8f74f09SStephen Suryaputra } else {
2536d8f74f09SStephen Suryaputra /* Same as case 0 */
2537d8f74f09SStephen Suryaputra hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
2538d8f74f09SStephen Suryaputra ip6_multipath_l3_keys(skb, &hash_keys, flkeys);
2539d8f74f09SStephen Suryaputra }
2540d8f74f09SStephen Suryaputra } else {
2541d8f74f09SStephen Suryaputra /* Same as case 0 */
2542d8f74f09SStephen Suryaputra hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
2543d8f74f09SStephen Suryaputra hash_keys.addrs.v6addrs.src = fl6->saddr;
2544d8f74f09SStephen Suryaputra hash_keys.addrs.v6addrs.dst = fl6->daddr;
2545d8f74f09SStephen Suryaputra hash_keys.tags.flow_label = (__force u32)flowi6_get_flowlabel(fl6);
2546d8f74f09SStephen Suryaputra hash_keys.basic.ip_proto = fl6->flowi6_proto;
2547d8f74f09SStephen Suryaputra }
2548b95b6e07SIdo Schimmel mhash = flow_hash_from_keys(&hash_keys);
2549d8f74f09SStephen Suryaputra break;
255073c2c5cbSIdo Schimmel case 3:
255173c2c5cbSIdo Schimmel if (skb)
255273c2c5cbSIdo Schimmel mhash = rt6_multipath_custom_hash_skb(net, skb);
255373c2c5cbSIdo Schimmel else
255473c2c5cbSIdo Schimmel mhash = rt6_multipath_custom_hash_fl6(net, fl6);
255573c2c5cbSIdo Schimmel break;
2556b4bac172SDavid Ahern }
255723aebdacSJakub Sitnicki
25589a2a537aSDavid Ahern return mhash >> 1;
255923aebdacSJakub Sitnicki }
256023aebdacSJakub Sitnicki
256167f415ddSWei Wang /* Called with rcu held */
ip6_route_input(struct sk_buff * skb)2562c71099acSThomas Graf void ip6_route_input(struct sk_buff *skb)
2563c71099acSThomas Graf {
2564b71d1d42SEric Dumazet const struct ipv6hdr *iph = ipv6_hdr(skb);
2565c346dca1SYOSHIFUJI Hideaki struct net *net = dev_net(skb->dev);
256667f415ddSWei Wang int flags = RT6_LOOKUP_F_HAS_SADDR | RT6_LOOKUP_F_DST_NOREF;
2567904af04dSJiri Benc struct ip_tunnel_info *tun_info;
25684c9483b2SDavid S. Miller struct flowi6 fl6 = {
2569e0d56fddSDavid Ahern .flowi6_iif = skb->dev->ifindex,
25704c9483b2SDavid S. Miller .daddr = iph->daddr,
25714c9483b2SDavid S. Miller .saddr = iph->saddr,
25726502ca52SYOSHIFUJI Hideaki / 吉藤英明 .flowlabel = ip6_flowinfo(iph),
25734c9483b2SDavid S. Miller .flowi6_mark = skb->mark,
25744c9483b2SDavid S. Miller .flowi6_proto = iph->nexthdr,
2575c71099acSThomas Graf };
25765e5d6fedSRoopa Prabhu struct flow_keys *flkeys = NULL, _flkeys;
2577adaa70bbSThomas Graf
2578904af04dSJiri Benc tun_info = skb_tunnel_info(skb);
257946fa062aSJiri Benc if (tun_info && !(tun_info->mode & IP_TUNNEL_INFO_TX))
2580904af04dSJiri Benc fl6.flowi6_tun_key.tun_id = tun_info->key.tun_id;
25815e5d6fedSRoopa Prabhu
25825e5d6fedSRoopa Prabhu if (fib6_rules_early_flow_dissect(net, skb, &fl6, &_flkeys))
25835e5d6fedSRoopa Prabhu flkeys = &_flkeys;
25845e5d6fedSRoopa Prabhu
258523aebdacSJakub Sitnicki if (unlikely(fl6.flowi6_proto == IPPROTO_ICMPV6))
2586b4bac172SDavid Ahern fl6.mp_hash = rt6_multipath_hash(net, &fl6, skb, flkeys);
258706e9d040SJiri Benc skb_dst_drop(skb);
258867f415ddSWei Wang skb_dst_set_noref(skb, ip6_route_input_lookup(net, skb->dev,
258967f415ddSWei Wang &fl6, skb, flags));
2590c71099acSThomas Graf }
2591c71099acSThomas Graf
ip6_pol_route_output(struct net * net,struct fib6_table * table,struct flowi6 * fl6,const struct sk_buff * skb,int flags)259255cced4fSBrian Vazquez INDIRECT_CALLABLE_SCOPE struct rt6_info *ip6_pol_route_output(struct net *net,
2593b75cc8f9SDavid Ahern struct fib6_table *table,
2594b75cc8f9SDavid Ahern struct flowi6 *fl6,
2595b75cc8f9SDavid Ahern const struct sk_buff *skb,
2596b75cc8f9SDavid Ahern int flags)
2597c71099acSThomas Graf {
2598b75cc8f9SDavid Ahern return ip6_pol_route(net, table, fl6->flowi6_oif, fl6, skb, flags);
2599c71099acSThomas Graf }
2600c71099acSThomas Graf
ip6_route_output_flags_noref(struct net * net,const struct sock * sk,struct flowi6 * fl6,int flags)260190317bcdSGuillaume Nault static struct dst_entry *ip6_route_output_flags_noref(struct net *net,
26027d9e5f42SWei Wang const struct sock *sk,
260390317bcdSGuillaume Nault struct flowi6 *fl6,
260490317bcdSGuillaume Nault int flags)
2605c71099acSThomas Graf {
2606d46a9d67SDavid Ahern bool any_src;
2607c71099acSThomas Graf
26083ede0bbcSRobert Shearman if (ipv6_addr_type(&fl6->daddr) &
26093ede0bbcSRobert Shearman (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL)) {
26104c1feac5SDavid Ahern struct dst_entry *dst;
26114c1feac5SDavid Ahern
26127d9e5f42SWei Wang /* This function does not take refcnt on the dst */
26134c1feac5SDavid Ahern dst = l3mdev_link_scope_lookup(net, fl6);
2614ca254490SDavid Ahern if (dst)
2615ca254490SDavid Ahern return dst;
26164c1feac5SDavid Ahern }
2617ca254490SDavid Ahern
26181fb9489bSPavel Emelyanov fl6->flowi6_iif = LOOPBACK_IFINDEX;
26194dc27d1cSDavid McCullough
26207d9e5f42SWei Wang flags |= RT6_LOOKUP_F_DST_NOREF;
2621d46a9d67SDavid Ahern any_src = ipv6_addr_any(&fl6->saddr);
2622741a11d9SDavid Ahern if ((sk && sk->sk_bound_dev_if) || rt6_need_strict(&fl6->daddr) ||
2623d46a9d67SDavid Ahern (fl6->flowi6_oif && any_src))
262477d16f45SYOSHIFUJI Hideaki flags |= RT6_LOOKUP_F_IFACE;
2625c71099acSThomas Graf
2626d46a9d67SDavid Ahern if (!any_src)
2627adaa70bbSThomas Graf flags |= RT6_LOOKUP_F_HAS_SADDR;
26280c9a2ac1SYOSHIFUJI Hideaki / 吉藤英明 else if (sk)
26290c9a2ac1SYOSHIFUJI Hideaki / 吉藤英明 flags |= rt6_srcprefs2flags(inet6_sk(sk)->srcprefs);
2630adaa70bbSThomas Graf
2631b75cc8f9SDavid Ahern return fib6_rule_lookup(net, fl6, NULL, flags, ip6_pol_route_output);
26321da177e4SLinus Torvalds }
26337d9e5f42SWei Wang
ip6_route_output_flags(struct net * net,const struct sock * sk,struct flowi6 * fl6,int flags)26347d9e5f42SWei Wang struct dst_entry *ip6_route_output_flags(struct net *net,
26357d9e5f42SWei Wang const struct sock *sk,
26367d9e5f42SWei Wang struct flowi6 *fl6,
26377d9e5f42SWei Wang int flags)
26387d9e5f42SWei Wang {
26397d9e5f42SWei Wang struct dst_entry *dst;
26407d9e5f42SWei Wang struct rt6_info *rt6;
26417d9e5f42SWei Wang
26427d9e5f42SWei Wang rcu_read_lock();
26437d9e5f42SWei Wang dst = ip6_route_output_flags_noref(net, sk, fl6, flags);
2644797a4c1fSEric Dumazet rt6 = dst_rt6_info(dst);
26457d9e5f42SWei Wang /* For dst cached in uncached_list, refcnt is already taken. */
2646d288a162SWangyang Guo if (list_empty(&rt6->dst.rt_uncached) && !dst_hold_safe(dst)) {
26477d9e5f42SWei Wang dst = &net->ipv6.ip6_null_entry->dst;
26487d9e5f42SWei Wang dst_hold(dst);
26497d9e5f42SWei Wang }
26507d9e5f42SWei Wang rcu_read_unlock();
26517d9e5f42SWei Wang
26527d9e5f42SWei Wang return dst;
26537d9e5f42SWei Wang }
26546f21c96aSPaolo Abeni EXPORT_SYMBOL_GPL(ip6_route_output_flags);
26551da177e4SLinus Torvalds
ip6_blackhole_route(struct net * net,struct dst_entry * dst_orig)26562774c131SDavid S. Miller struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_orig)
265714e50e57SDavid S. Miller {
2658797a4c1fSEric Dumazet struct rt6_info *rt, *ort = dst_rt6_info(dst_orig);
26591dbe3252SWei Wang struct net_device *loopback_dev = net->loopback_dev;
266014e50e57SDavid S. Miller struct dst_entry *new = NULL;
266114e50e57SDavid S. Miller
26621dbe3252SWei Wang rt = dst_alloc(&ip6_dst_blackhole_ops, loopback_dev, 1,
266362cf27e5SSteffen Klassert DST_OBSOLETE_DEAD, 0);
266414e50e57SDavid S. Miller if (rt) {
26650a1f5962SMartin KaFai Lau rt6_info_init(rt);
266681eb8447SWei Wang atomic_inc(&net->ipv6.rt6_stats->fib_rt_alloc);
26670a1f5962SMartin KaFai Lau
2668d8d1f30bSChangli Gao new = &rt->dst;
266914e50e57SDavid S. Miller new->__use = 1;
2670352e512cSHerbert Xu new->input = dst_discard;
2671ede2059dSEric W. Biederman new->output = dst_discard_out;
267214e50e57SDavid S. Miller
2673defb3519SDavid S. Miller dst_copy_metrics(new, &ort->dst);
267414e50e57SDavid S. Miller
26751dbe3252SWei Wang rt->rt6i_idev = in6_dev_get(loopback_dev);
26764e3fd7a0SAlexey Dobriyan rt->rt6i_gateway = ort->rt6i_gateway;
26770a1f5962SMartin KaFai Lau rt->rt6i_flags = ort->rt6i_flags & ~RTF_PCPU;
267814e50e57SDavid S. Miller
267914e50e57SDavid S. Miller memcpy(&rt->rt6i_dst, &ort->rt6i_dst, sizeof(struct rt6key));
268014e50e57SDavid S. Miller #ifdef CONFIG_IPV6_SUBTREES
268114e50e57SDavid S. Miller memcpy(&rt->rt6i_src, &ort->rt6i_src, sizeof(struct rt6key));
268214e50e57SDavid S. Miller #endif
268314e50e57SDavid S. Miller }
268414e50e57SDavid S. Miller
268569ead7afSDavid S. Miller dst_release(dst_orig);
268669ead7afSDavid S. Miller return new ? new : ERR_PTR(-ENOMEM);
268714e50e57SDavid S. Miller }
268814e50e57SDavid S. Miller
26891da177e4SLinus Torvalds /*
26901da177e4SLinus Torvalds * Destination cache support functions
26911da177e4SLinus Torvalds */
26921da177e4SLinus Torvalds
fib6_check(struct fib6_info * f6i,u32 cookie)26938d1c802bSDavid Ahern static bool fib6_check(struct fib6_info *f6i, u32 cookie)
26943da59bd9SMartin KaFai Lau {
269536143645SSteffen Klassert u32 rt_cookie = 0;
2696c5cff856SWei Wang
26978ae86971SDavid Ahern if (!fib6_get_cookie_safe(f6i, &rt_cookie) || rt_cookie != cookie)
269893531c67SDavid Ahern return false;
269993531c67SDavid Ahern
270093531c67SDavid Ahern if (fib6_check_expired(f6i))
270193531c67SDavid Ahern return false;
270293531c67SDavid Ahern
270393531c67SDavid Ahern return true;
270493531c67SDavid Ahern }
270593531c67SDavid Ahern
rt6_check(struct rt6_info * rt,struct fib6_info * from,u32 cookie)2706a68886a6SDavid Ahern static struct dst_entry *rt6_check(struct rt6_info *rt,
2707a68886a6SDavid Ahern struct fib6_info *from,
2708a68886a6SDavid Ahern u32 cookie)
27093da59bd9SMartin KaFai Lau {
2710c5cff856SWei Wang u32 rt_cookie = 0;
2711c5cff856SWei Wang
271249d05fe2SDavid Ahern if (!from || !fib6_get_cookie_safe(from, &rt_cookie) ||
271393531c67SDavid Ahern rt_cookie != cookie)
27143da59bd9SMartin KaFai Lau return NULL;
27153da59bd9SMartin KaFai Lau
27163da59bd9SMartin KaFai Lau if (rt6_check_expired(rt))
27173da59bd9SMartin KaFai Lau return NULL;
27183da59bd9SMartin KaFai Lau
27193da59bd9SMartin KaFai Lau return &rt->dst;
27203da59bd9SMartin KaFai Lau }
27213da59bd9SMartin KaFai Lau
rt6_dst_from_check(struct rt6_info * rt,struct fib6_info * from,u32 cookie)2722a68886a6SDavid Ahern static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt,
2723a68886a6SDavid Ahern struct fib6_info *from,
2724a68886a6SDavid Ahern u32 cookie)
27253da59bd9SMartin KaFai Lau {
27265973fb1eSMartin KaFai Lau if (!__rt6_check_expired(rt) &&
27275973fb1eSMartin KaFai Lau rt->dst.obsolete == DST_OBSOLETE_FORCE_CHK &&
2728a68886a6SDavid Ahern fib6_check(from, cookie))
27293da59bd9SMartin KaFai Lau return &rt->dst;
27303da59bd9SMartin KaFai Lau else
27313da59bd9SMartin KaFai Lau return NULL;
27323da59bd9SMartin KaFai Lau }
27333da59bd9SMartin KaFai Lau
ip6_dst_check(struct dst_entry * dst,u32 cookie)2734bbd807dfSBrian Vazquez INDIRECT_CALLABLE_SCOPE struct dst_entry *ip6_dst_check(struct dst_entry *dst,
2735bbd807dfSBrian Vazquez u32 cookie)
27361da177e4SLinus Torvalds {
2737a87b7dc9SDavid Ahern struct dst_entry *dst_ret;
2738a68886a6SDavid Ahern struct fib6_info *from;
27391da177e4SLinus Torvalds struct rt6_info *rt;
27401da177e4SLinus Torvalds
2741797a4c1fSEric Dumazet rt = dst_rt6_info(dst);
2742a87b7dc9SDavid Ahern
27438f34e53bSDavid Ahern if (rt->sernum)
27448f34e53bSDavid Ahern return rt6_is_valid(rt) ? dst : NULL;
27458f34e53bSDavid Ahern
2746a87b7dc9SDavid Ahern rcu_read_lock();
27471da177e4SLinus Torvalds
27486f3118b5SNicolas Dichtel /* All IPV6 dsts are created with ->obsolete set to the value
27496f3118b5SNicolas Dichtel * DST_OBSOLETE_FORCE_CHK which forces validation calls down
27506f3118b5SNicolas Dichtel * into this function always.
27516f3118b5SNicolas Dichtel */
2752e3bc10bdSHannes Frederic Sowa
2753a68886a6SDavid Ahern from = rcu_dereference(rt->from);
27544b32b5adSMartin KaFai Lau
2755a68886a6SDavid Ahern if (from && (rt->rt6i_flags & RTF_PCPU ||
2756d288a162SWangyang Guo unlikely(!list_empty(&rt->dst.rt_uncached))))
2757a68886a6SDavid Ahern dst_ret = rt6_dst_from_check(rt, from, cookie);
27583da59bd9SMartin KaFai Lau else
2759a68886a6SDavid Ahern dst_ret = rt6_check(rt, from, cookie);
2760a87b7dc9SDavid Ahern
2761a87b7dc9SDavid Ahern rcu_read_unlock();
2762a87b7dc9SDavid Ahern
2763a87b7dc9SDavid Ahern return dst_ret;
27641da177e4SLinus Torvalds }
27659c97921aSBrian Vazquez EXPORT_INDIRECT_CALLABLE(ip6_dst_check);
27661da177e4SLinus Torvalds
ip6_negative_advice(struct sock * sk,struct dst_entry * dst)27675af198c3SEric Dumazet static void ip6_negative_advice(struct sock *sk,
27685af198c3SEric Dumazet struct dst_entry *dst)
27691da177e4SLinus Torvalds {
2770797a4c1fSEric Dumazet struct rt6_info *rt = dst_rt6_info(dst);
27711da177e4SLinus Torvalds
277254c1a859SYOSHIFUJI Hideaki / 吉藤英明 if (rt->rt6i_flags & RTF_CACHE) {
2773c3c14da0SDavid Ahern rcu_read_lock();
277454c1a859SYOSHIFUJI Hideaki / 吉藤英明 if (rt6_check_expired(rt)) {
2775*f43d12fdSJiri Wiesner /* rt/dst can not be destroyed yet,
2776*f43d12fdSJiri Wiesner * because of rcu_read_lock()
2777*f43d12fdSJiri Wiesner */
27785af198c3SEric Dumazet sk_dst_reset(sk);
277993531c67SDavid Ahern rt6_remove_exception_rt(rt);
27801da177e4SLinus Torvalds }
2781c3c14da0SDavid Ahern rcu_read_unlock();
27825af198c3SEric Dumazet return;
278354c1a859SYOSHIFUJI Hideaki / 吉藤英明 }
27845af198c3SEric Dumazet sk_dst_reset(sk);
27851da177e4SLinus Torvalds }
27861da177e4SLinus Torvalds
ip6_link_failure(struct sk_buff * skb)27871da177e4SLinus Torvalds static void ip6_link_failure(struct sk_buff *skb)
27881da177e4SLinus Torvalds {
27891da177e4SLinus Torvalds struct rt6_info *rt;
27901da177e4SLinus Torvalds
27913ffe533cSAlexey Dobriyan icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0);
27921da177e4SLinus Torvalds
2793797a4c1fSEric Dumazet rt = dst_rt6_info(skb_dst(skb));
27941da177e4SLinus Torvalds if (rt) {
27958a14e46fSDavid Ahern rcu_read_lock();
27961eb4f758SHannes Frederic Sowa if (rt->rt6i_flags & RTF_CACHE) {
279793531c67SDavid Ahern rt6_remove_exception_rt(rt);
2798c5cff856SWei Wang } else {
2799a68886a6SDavid Ahern struct fib6_info *from;
2800c5cff856SWei Wang struct fib6_node *fn;
2801c5cff856SWei Wang
2802a68886a6SDavid Ahern from = rcu_dereference(rt->from);
2803a68886a6SDavid Ahern if (from) {
2804a68886a6SDavid Ahern fn = rcu_dereference(from->fib6_node);
2805c5cff856SWei Wang if (fn && (rt->rt6i_flags & RTF_DEFAULT))
2806aafc2e32SEric Dumazet WRITE_ONCE(fn->fn_sernum, -1);
2807a68886a6SDavid Ahern }
28081da177e4SLinus Torvalds }
28091da177e4SLinus Torvalds rcu_read_unlock();
28101da177e4SLinus Torvalds }
28111da177e4SLinus Torvalds }
28121da177e4SLinus Torvalds
rt6_update_expires(struct rt6_info * rt0,int timeout)28136a3e030fSDavid Ahern static void rt6_update_expires(struct rt6_info *rt0, int timeout)
28146a3e030fSDavid Ahern {
2815a68886a6SDavid Ahern if (!(rt0->rt6i_flags & RTF_EXPIRES)) {
2816a68886a6SDavid Ahern struct fib6_info *from;
2817a68886a6SDavid Ahern
2818a68886a6SDavid Ahern rcu_read_lock();
2819a68886a6SDavid Ahern from = rcu_dereference(rt0->from);
2820a68886a6SDavid Ahern if (from)
2821a68886a6SDavid Ahern rt0->dst.expires = from->expires;
2822a68886a6SDavid Ahern rcu_read_unlock();
2823a68886a6SDavid Ahern }
28246a3e030fSDavid Ahern
28256a3e030fSDavid Ahern dst_set_expires(&rt0->dst, timeout);
28266a3e030fSDavid Ahern rt0->rt6i_flags |= RTF_EXPIRES;
28276700c270SDavid S. Miller }
28281da177e4SLinus Torvalds
rt6_do_update_pmtu(struct rt6_info * rt,u32 mtu)282945e4fd26SMartin KaFai Lau static void rt6_do_update_pmtu(struct rt6_info *rt, u32 mtu)
283045e4fd26SMartin KaFai Lau {
283145e4fd26SMartin KaFai Lau struct net *net = dev_net(rt->dst.dev);
283245e4fd26SMartin KaFai Lau
2833d4ead6b3SDavid Ahern dst_metric_set(&rt->dst, RTAX_MTU, mtu);
283445e4fd26SMartin KaFai Lau rt->rt6i_flags |= RTF_MODIFIED;
283545e4fd26SMartin KaFai Lau rt6_update_expires(rt, net->ipv6.sysctl.ip6_rt_mtu_expires);
283645e4fd26SMartin KaFai Lau }
283745e4fd26SMartin KaFai Lau
rt6_cache_allowed_for_pmtu(const struct rt6_info * rt)28380d3f6d29SMartin KaFai Lau static bool rt6_cache_allowed_for_pmtu(const struct rt6_info *rt)
28390d3f6d29SMartin KaFai Lau {
28400d3f6d29SMartin KaFai Lau return !(rt->rt6i_flags & RTF_CACHE) &&
28411490ed2aSPaolo Abeni (rt->rt6i_flags & RTF_PCPU || rcu_access_pointer(rt->from));
28420d3f6d29SMartin KaFai Lau }
28430d3f6d29SMartin KaFai Lau
__ip6_rt_update_pmtu(struct dst_entry * dst,const struct sock * sk,const struct ipv6hdr * iph,u32 mtu,bool confirm_neigh)284445e4fd26SMartin KaFai Lau static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
2845bd085ef6SHangbin Liu const struct ipv6hdr *iph, u32 mtu,
2846bd085ef6SHangbin Liu bool confirm_neigh)
28471da177e4SLinus Torvalds {
28480dec879fSJulian Anastasov const struct in6_addr *daddr, *saddr;
2849797a4c1fSEric Dumazet struct rt6_info *rt6 = dst_rt6_info(dst);
28501da177e4SLinus Torvalds
285109454fd0SMaciej Żenczykowski /* Note: do *NOT* check dst_metric_locked(dst, RTAX_MTU)
285209454fd0SMaciej Żenczykowski * IPv6 pmtu discovery isn't optional, so 'mtu lock' cannot disable it.
285309454fd0SMaciej Żenczykowski * [see also comment in rt6_mtu_change_route()]
285409454fd0SMaciej Żenczykowski */
285519bda36cSXin Long
285645e4fd26SMartin KaFai Lau if (iph) {
285745e4fd26SMartin KaFai Lau daddr = &iph->daddr;
285845e4fd26SMartin KaFai Lau saddr = &iph->saddr;
285945e4fd26SMartin KaFai Lau } else if (sk) {
286045e4fd26SMartin KaFai Lau daddr = &sk->sk_v6_daddr;
286145e4fd26SMartin KaFai Lau saddr = &inet6_sk(sk)->saddr;
286245e4fd26SMartin KaFai Lau } else {
28630dec879fSJulian Anastasov daddr = NULL;
28640dec879fSJulian Anastasov saddr = NULL;
28651da177e4SLinus Torvalds }
2866bd085ef6SHangbin Liu
2867bd085ef6SHangbin Liu if (confirm_neigh)
28680dec879fSJulian Anastasov dst_confirm_neigh(dst, daddr);
2869bd085ef6SHangbin Liu
28704a65dff8SGeorg Kohmann if (mtu < IPV6_MIN_MTU)
28714a65dff8SGeorg Kohmann return;
28720dec879fSJulian Anastasov if (mtu >= dst_mtu(dst))
28730dec879fSJulian Anastasov return;
28740dec879fSJulian Anastasov
28750dec879fSJulian Anastasov if (!rt6_cache_allowed_for_pmtu(rt6)) {
28760dec879fSJulian Anastasov rt6_do_update_pmtu(rt6, mtu);
28772b760fcfSWei Wang /* update rt6_ex->stamp for cache */
28782b760fcfSWei Wang if (rt6->rt6i_flags & RTF_CACHE)
28792b760fcfSWei Wang rt6_update_exception_stamp_rt(rt6);
28800dec879fSJulian Anastasov } else if (daddr) {
288185bd05deSDavid Ahern struct fib6_result res = {};
28820dec879fSJulian Anastasov struct rt6_info *nrt6;
28830dec879fSJulian Anastasov
28844d85cd0cSDavid Ahern rcu_read_lock();
288585bd05deSDavid Ahern res.f6i = rcu_dereference(rt6->from);
288643a4b60dSDavid Ahern if (!res.f6i)
288743a4b60dSDavid Ahern goto out_unlock;
288843a4b60dSDavid Ahern
28897d21fec9SDavid Ahern res.fib6_flags = res.f6i->fib6_flags;
28907d21fec9SDavid Ahern res.fib6_type = res.f6i->fib6_type;
28917d21fec9SDavid Ahern
28922d44234bSDavid Ahern if (res.f6i->nh) {
28932d44234bSDavid Ahern struct fib6_nh_match_arg arg = {
28942d44234bSDavid Ahern .dev = dst->dev,
28952d44234bSDavid Ahern .gw = &rt6->rt6i_gateway,
28962d44234bSDavid Ahern };
28972d44234bSDavid Ahern
28982d44234bSDavid Ahern nexthop_for_each_fib6_nh(res.f6i->nh,
28992d44234bSDavid Ahern fib6_nh_find_match, &arg);
29002d44234bSDavid Ahern
29012d44234bSDavid Ahern /* fib6_info uses a nexthop that does not have fib6_nh
29022d44234bSDavid Ahern * using the dst->dev + gw. Should be impossible.
29032d44234bSDavid Ahern */
290443a4b60dSDavid Ahern if (!arg.match)
290543a4b60dSDavid Ahern goto out_unlock;
29062d44234bSDavid Ahern
29072d44234bSDavid Ahern res.nh = arg.match;
29082d44234bSDavid Ahern } else {
29092d44234bSDavid Ahern res.nh = res.f6i->fib6_nh;
29102d44234bSDavid Ahern }
29112d44234bSDavid Ahern
291285bd05deSDavid Ahern nrt6 = ip6_rt_cache_alloc(&res, daddr, saddr);
291345e4fd26SMartin KaFai Lau if (nrt6) {
291445e4fd26SMartin KaFai Lau rt6_do_update_pmtu(nrt6, mtu);
29155012f0a5SDavid Ahern if (rt6_insert_exception(nrt6, &res))
29162b760fcfSWei Wang dst_release_immediate(&nrt6->dst);
291745e4fd26SMartin KaFai Lau }
291843a4b60dSDavid Ahern out_unlock:
2919a68886a6SDavid Ahern rcu_read_unlock();
292045e4fd26SMartin KaFai Lau }
292145e4fd26SMartin KaFai Lau }
292245e4fd26SMartin KaFai Lau
ip6_rt_update_pmtu(struct dst_entry * dst,struct sock * sk,struct sk_buff * skb,u32 mtu,bool confirm_neigh)292345e4fd26SMartin KaFai Lau static void ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
2924bd085ef6SHangbin Liu struct sk_buff *skb, u32 mtu,
2925bd085ef6SHangbin Liu bool confirm_neigh)
292645e4fd26SMartin KaFai Lau {
2927bd085ef6SHangbin Liu __ip6_rt_update_pmtu(dst, sk, skb ? ipv6_hdr(skb) : NULL, mtu,
2928bd085ef6SHangbin Liu confirm_neigh);
29291da177e4SLinus Torvalds }
29301da177e4SLinus Torvalds
ip6_update_pmtu(struct sk_buff * skb,struct net * net,__be32 mtu,int oif,u32 mark,kuid_t uid)293142ae66c8SDavid S. Miller void ip6_update_pmtu(struct sk_buff *skb, struct net *net, __be32 mtu,
2932e2d118a1SLorenzo Colitti int oif, u32 mark, kuid_t uid)
293381aded24SDavid S. Miller {
293481aded24SDavid S. Miller const struct ipv6hdr *iph = (struct ipv6hdr *) skb->data;
293581aded24SDavid S. Miller struct dst_entry *dst;
2936dc92095dSMaciej Żenczykowski struct flowi6 fl6 = {
2937dc92095dSMaciej Żenczykowski .flowi6_oif = oif,
2938dc92095dSMaciej Żenczykowski .flowi6_mark = mark ? mark : IP6_REPLY_MARK(net, skb->mark),
2939dc92095dSMaciej Żenczykowski .daddr = iph->daddr,
2940dc92095dSMaciej Żenczykowski .saddr = iph->saddr,
2941dc92095dSMaciej Żenczykowski .flowlabel = ip6_flowinfo(iph),
2942dc92095dSMaciej Żenczykowski .flowi6_uid = uid,
2943dc92095dSMaciej Żenczykowski };
294481aded24SDavid S. Miller
294581aded24SDavid S. Miller dst = ip6_route_output(net, NULL, &fl6);
294681aded24SDavid S. Miller if (!dst->error)
2947bd085ef6SHangbin Liu __ip6_rt_update_pmtu(dst, NULL, iph, ntohl(mtu), true);
294881aded24SDavid S. Miller dst_release(dst);
294981aded24SDavid S. Miller }
295081aded24SDavid S. Miller EXPORT_SYMBOL_GPL(ip6_update_pmtu);
295181aded24SDavid S. Miller
ip6_sk_update_pmtu(struct sk_buff * skb,struct sock * sk,__be32 mtu)295281aded24SDavid S. Miller void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, __be32 mtu)
295381aded24SDavid S. Miller {
29547ddacfa5SDavid Ahern int oif = sk->sk_bound_dev_if;
295533c162a9SMartin KaFai Lau struct dst_entry *dst;
295633c162a9SMartin KaFai Lau
29577ddacfa5SDavid Ahern if (!oif && skb->dev)
29587ddacfa5SDavid Ahern oif = l3mdev_master_ifindex(skb->dev);
29597ddacfa5SDavid Ahern
29603c5b4d69SEric Dumazet ip6_update_pmtu(skb, sock_net(sk), mtu, oif, READ_ONCE(sk->sk_mark),
29613c5b4d69SEric Dumazet sk->sk_uid);
296233c162a9SMartin KaFai Lau
296333c162a9SMartin KaFai Lau dst = __sk_dst_get(sk);
296433c162a9SMartin KaFai Lau if (!dst || !dst->obsolete ||
296533c162a9SMartin KaFai Lau dst->ops->check(dst, inet6_sk(sk)->dst_cookie))
296633c162a9SMartin KaFai Lau return;
296733c162a9SMartin KaFai Lau
296833c162a9SMartin KaFai Lau bh_lock_sock(sk);
296933c162a9SMartin KaFai Lau if (!sock_owned_by_user(sk) && !ipv6_addr_v4mapped(&sk->sk_v6_daddr))
297033c162a9SMartin KaFai Lau ip6_datagram_dst_update(sk, false);
297133c162a9SMartin KaFai Lau bh_unlock_sock(sk);
297281aded24SDavid S. Miller }
297381aded24SDavid S. Miller EXPORT_SYMBOL_GPL(ip6_sk_update_pmtu);
297481aded24SDavid S. Miller
ip6_sk_dst_store_flow(struct sock * sk,struct dst_entry * dst,const struct flowi6 * fl6)29757d6850f7SAlexey Kodanev void ip6_sk_dst_store_flow(struct sock *sk, struct dst_entry *dst,
29767d6850f7SAlexey Kodanev const struct flowi6 *fl6)
29777d6850f7SAlexey Kodanev {
29787d6850f7SAlexey Kodanev #ifdef CONFIG_IPV6_SUBTREES
29797d6850f7SAlexey Kodanev struct ipv6_pinfo *np = inet6_sk(sk);
29807d6850f7SAlexey Kodanev #endif
29817d6850f7SAlexey Kodanev
29827d6850f7SAlexey Kodanev ip6_dst_store(sk, dst,
29837d6850f7SAlexey Kodanev ipv6_addr_equal(&fl6->daddr, &sk->sk_v6_daddr) ?
29847d6850f7SAlexey Kodanev &sk->sk_v6_daddr : NULL,
29857d6850f7SAlexey Kodanev #ifdef CONFIG_IPV6_SUBTREES
29867d6850f7SAlexey Kodanev ipv6_addr_equal(&fl6->saddr, &np->saddr) ?
29877d6850f7SAlexey Kodanev &np->saddr :
29887d6850f7SAlexey Kodanev #endif
29897d6850f7SAlexey Kodanev NULL);
29907d6850f7SAlexey Kodanev }
29917d6850f7SAlexey Kodanev
ip6_redirect_nh_match(const struct fib6_result * res,struct flowi6 * fl6,const struct in6_addr * gw,struct rt6_info ** ret)29929b6b35abSDavid Ahern static bool ip6_redirect_nh_match(const struct fib6_result *res,
29930b34eb00SDavid Ahern struct flowi6 *fl6,
29940b34eb00SDavid Ahern const struct in6_addr *gw,
29950b34eb00SDavid Ahern struct rt6_info **ret)
29960b34eb00SDavid Ahern {
29979b6b35abSDavid Ahern const struct fib6_nh *nh = res->nh;
29989b6b35abSDavid Ahern
29990b34eb00SDavid Ahern if (nh->fib_nh_flags & RTNH_F_DEAD || !nh->fib_nh_gw_family ||
30000b34eb00SDavid Ahern fl6->flowi6_oif != nh->fib_nh_dev->ifindex)
30010b34eb00SDavid Ahern return false;
30020b34eb00SDavid Ahern
30030b34eb00SDavid Ahern /* rt_cache's gateway might be different from its 'parent'
30040b34eb00SDavid Ahern * in the case of an ip redirect.
30050b34eb00SDavid Ahern * So we keep searching in the exception table if the gateway
30060b34eb00SDavid Ahern * is different.
30070b34eb00SDavid Ahern */
30080b34eb00SDavid Ahern if (!ipv6_addr_equal(gw, &nh->fib_nh_gw6)) {
30090b34eb00SDavid Ahern struct rt6_info *rt_cache;
30100b34eb00SDavid Ahern
30119b6b35abSDavid Ahern rt_cache = rt6_find_cached_rt(res, &fl6->daddr, &fl6->saddr);
30120b34eb00SDavid Ahern if (rt_cache &&
30130b34eb00SDavid Ahern ipv6_addr_equal(gw, &rt_cache->rt6i_gateway)) {
30140b34eb00SDavid Ahern *ret = rt_cache;
30150b34eb00SDavid Ahern return true;
30160b34eb00SDavid Ahern }
30170b34eb00SDavid Ahern return false;
30180b34eb00SDavid Ahern }
30190b34eb00SDavid Ahern return true;
30200b34eb00SDavid Ahern }
30210b34eb00SDavid Ahern
3022c55c8988SDavid Ahern struct fib6_nh_rd_arg {
3023c55c8988SDavid Ahern struct fib6_result *res;
3024c55c8988SDavid Ahern struct flowi6 *fl6;
3025c55c8988SDavid Ahern const struct in6_addr *gw;
3026c55c8988SDavid Ahern struct rt6_info **ret;
3027c55c8988SDavid Ahern };
3028c55c8988SDavid Ahern
fib6_nh_redirect_match(struct fib6_nh * nh,void * _arg)3029c55c8988SDavid Ahern static int fib6_nh_redirect_match(struct fib6_nh *nh, void *_arg)
3030c55c8988SDavid Ahern {
3031c55c8988SDavid Ahern struct fib6_nh_rd_arg *arg = _arg;
3032c55c8988SDavid Ahern
3033c55c8988SDavid Ahern arg->res->nh = nh;
3034c55c8988SDavid Ahern return ip6_redirect_nh_match(arg->res, arg->fl6, arg->gw, arg->ret);
3035c55c8988SDavid Ahern }
3036c55c8988SDavid Ahern
3037b55b76b2SDuan Jiong /* Handle redirects */
3038b55b76b2SDuan Jiong struct ip6rd_flowi {
3039b55b76b2SDuan Jiong struct flowi6 fl6;
3040b55b76b2SDuan Jiong struct in6_addr gateway;
3041b55b76b2SDuan Jiong };
3042b55b76b2SDuan Jiong
__ip6_route_redirect(struct net * net,struct fib6_table * table,struct flowi6 * fl6,const struct sk_buff * skb,int flags)304355cced4fSBrian Vazquez INDIRECT_CALLABLE_SCOPE struct rt6_info *__ip6_route_redirect(struct net *net,
3044b55b76b2SDuan Jiong struct fib6_table *table,
3045b55b76b2SDuan Jiong struct flowi6 *fl6,
3046b75cc8f9SDavid Ahern const struct sk_buff *skb,
3047b55b76b2SDuan Jiong int flags)
3048b55b76b2SDuan Jiong {
3049b55b76b2SDuan Jiong struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6;
30500b34eb00SDavid Ahern struct rt6_info *ret = NULL;
30519b6b35abSDavid Ahern struct fib6_result res = {};
3052c55c8988SDavid Ahern struct fib6_nh_rd_arg arg = {
3053c55c8988SDavid Ahern .res = &res,
3054c55c8988SDavid Ahern .fl6 = fl6,
3055c55c8988SDavid Ahern .gw = &rdfl->gateway,
3056c55c8988SDavid Ahern .ret = &ret
3057c55c8988SDavid Ahern };
30588d1c802bSDavid Ahern struct fib6_info *rt;
3059b55b76b2SDuan Jiong struct fib6_node *fn;
3060b55b76b2SDuan Jiong
3061b55b76b2SDuan Jiong /* Get the "current" route for this destination and
306267c408cfSAlexander Alemayhu * check if the redirect has come from appropriate router.
3063b55b76b2SDuan Jiong *
3064b55b76b2SDuan Jiong * RFC 4861 specifies that redirects should only be
3065b55b76b2SDuan Jiong * accepted if they come from the nexthop to the target.
3066b55b76b2SDuan Jiong * Due to the way the routes are chosen, this notion
3067b55b76b2SDuan Jiong * is a bit fuzzy and one might need to check all possible
3068b55b76b2SDuan Jiong * routes.
3069b55b76b2SDuan Jiong */
3070b55b76b2SDuan Jiong
307166f5d6ceSWei Wang rcu_read_lock();
30726454743bSDavid Ahern fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
3073b55b76b2SDuan Jiong restart:
307466f5d6ceSWei Wang for_each_fib6_node_rt_rcu(fn) {
30759b6b35abSDavid Ahern res.f6i = rt;
307614895687SDavid Ahern if (fib6_check_expired(rt))
3077b55b76b2SDuan Jiong continue;
307893c2fb25SDavid Ahern if (rt->fib6_flags & RTF_REJECT)
3079b55b76b2SDuan Jiong break;
3080c55c8988SDavid Ahern if (unlikely(rt->nh)) {
3081c55c8988SDavid Ahern if (nexthop_is_blackhole(rt->nh))
3082c55c8988SDavid Ahern continue;
3083c55c8988SDavid Ahern /* on match, res->nh is filled in and potentially ret */
3084c55c8988SDavid Ahern if (nexthop_for_each_fib6_nh(rt->nh,
3085c55c8988SDavid Ahern fib6_nh_redirect_match,
3086c55c8988SDavid Ahern &arg))
30870b34eb00SDavid Ahern goto out;
3088c55c8988SDavid Ahern } else {
3089c55c8988SDavid Ahern res.nh = rt->fib6_nh;
3090c55c8988SDavid Ahern if (ip6_redirect_nh_match(&res, fl6, &rdfl->gateway,
3091c55c8988SDavid Ahern &ret))
3092c55c8988SDavid Ahern goto out;
3093c55c8988SDavid Ahern }
3094b55b76b2SDuan Jiong }
3095b55b76b2SDuan Jiong
3096b55b76b2SDuan Jiong if (!rt)
3097421842edSDavid Ahern rt = net->ipv6.fib6_null_entry;
309893c2fb25SDavid Ahern else if (rt->fib6_flags & RTF_REJECT) {
309923fb93a4SDavid Ahern ret = net->ipv6.ip6_null_entry;
3100b0a1ba59SMartin KaFai Lau goto out;
3101b0a1ba59SMartin KaFai Lau }
3102b0a1ba59SMartin KaFai Lau
3103421842edSDavid Ahern if (rt == net->ipv6.fib6_null_entry) {
3104a3c00e46SMartin KaFai Lau fn = fib6_backtrack(fn, &fl6->saddr);
3105a3c00e46SMartin KaFai Lau if (fn)
3106a3c00e46SMartin KaFai Lau goto restart;
3107b55b76b2SDuan Jiong }
3108a3c00e46SMartin KaFai Lau
31099b6b35abSDavid Ahern res.f6i = rt;
31101cf844c7SDavid Ahern res.nh = rt->fib6_nh;
3111b0a1ba59SMartin KaFai Lau out:
31127d21fec9SDavid Ahern if (ret) {
311310585b43SDavid Ahern ip6_hold_safe(net, &ret);
31147d21fec9SDavid Ahern } else {
31157d21fec9SDavid Ahern res.fib6_flags = res.f6i->fib6_flags;
31167d21fec9SDavid Ahern res.fib6_type = res.f6i->fib6_type;
31179b6b35abSDavid Ahern ret = ip6_create_rt_rcu(&res);
31187d21fec9SDavid Ahern }
3119b55b76b2SDuan Jiong
312066f5d6ceSWei Wang rcu_read_unlock();
3121b55b76b2SDuan Jiong
31228ff2e5b2SDavid Ahern trace_fib6_table_lookup(net, &res, table, fl6);
312323fb93a4SDavid Ahern return ret;
3124b55b76b2SDuan Jiong };
3125b55b76b2SDuan Jiong
ip6_route_redirect(struct net * net,const struct flowi6 * fl6,const struct sk_buff * skb,const struct in6_addr * gateway)3126b55b76b2SDuan Jiong static struct dst_entry *ip6_route_redirect(struct net *net,
3127b55b76b2SDuan Jiong const struct flowi6 *fl6,
3128b75cc8f9SDavid Ahern const struct sk_buff *skb,
3129b55b76b2SDuan Jiong const struct in6_addr *gateway)
3130b55b76b2SDuan Jiong {
3131b55b76b2SDuan Jiong int flags = RT6_LOOKUP_F_HAS_SADDR;
3132b55b76b2SDuan Jiong struct ip6rd_flowi rdfl;
3133b55b76b2SDuan Jiong
3134b55b76b2SDuan Jiong rdfl.fl6 = *fl6;
3135b55b76b2SDuan Jiong rdfl.gateway = *gateway;
3136b55b76b2SDuan Jiong
3137b75cc8f9SDavid Ahern return fib6_rule_lookup(net, &rdfl.fl6, skb,
3138b55b76b2SDuan Jiong flags, __ip6_route_redirect);
3139b55b76b2SDuan Jiong }
3140b55b76b2SDuan Jiong
ip6_redirect(struct sk_buff * skb,struct net * net,int oif,u32 mark,kuid_t uid)3141e2d118a1SLorenzo Colitti void ip6_redirect(struct sk_buff *skb, struct net *net, int oif, u32 mark,
3142e2d118a1SLorenzo Colitti kuid_t uid)
31433a5ad2eeSDavid S. Miller {
31443a5ad2eeSDavid S. Miller const struct ipv6hdr *iph = (struct ipv6hdr *) skb->data;
31453a5ad2eeSDavid S. Miller struct dst_entry *dst;
31461f7f10acSMaciej Żenczykowski struct flowi6 fl6 = {
31471f7f10acSMaciej Żenczykowski .flowi6_iif = LOOPBACK_IFINDEX,
31481f7f10acSMaciej Żenczykowski .flowi6_oif = oif,
31491f7f10acSMaciej Żenczykowski .flowi6_mark = mark,
31501f7f10acSMaciej Żenczykowski .daddr = iph->daddr,
31511f7f10acSMaciej Żenczykowski .saddr = iph->saddr,
31521f7f10acSMaciej Żenczykowski .flowlabel = ip6_flowinfo(iph),
31531f7f10acSMaciej Żenczykowski .flowi6_uid = uid,
31541f7f10acSMaciej Żenczykowski };
31553a5ad2eeSDavid S. Miller
3156b75cc8f9SDavid Ahern dst = ip6_route_redirect(net, &fl6, skb, &ipv6_hdr(skb)->saddr);
31576700c270SDavid S. Miller rt6_do_redirect(dst, NULL, skb);
31583a5ad2eeSDavid S. Miller dst_release(dst);
31593a5ad2eeSDavid S. Miller }
31603a5ad2eeSDavid S. Miller EXPORT_SYMBOL_GPL(ip6_redirect);
31613a5ad2eeSDavid S. Miller
ip6_redirect_no_header(struct sk_buff * skb,struct net * net,int oif)3162d456336dSMaciej Żenczykowski void ip6_redirect_no_header(struct sk_buff *skb, struct net *net, int oif)
3163c92a59ecSDuan Jiong {
3164c92a59ecSDuan Jiong const struct ipv6hdr *iph = ipv6_hdr(skb);
3165c92a59ecSDuan Jiong const struct rd_msg *msg = (struct rd_msg *)icmp6_hdr(skb);
3166c92a59ecSDuan Jiong struct dst_entry *dst;
31670b26fb17SMaciej Żenczykowski struct flowi6 fl6 = {
31680b26fb17SMaciej Żenczykowski .flowi6_iif = LOOPBACK_IFINDEX,
31690b26fb17SMaciej Żenczykowski .flowi6_oif = oif,
31700b26fb17SMaciej Żenczykowski .daddr = msg->dest,
31710b26fb17SMaciej Żenczykowski .saddr = iph->daddr,
31720b26fb17SMaciej Żenczykowski .flowi6_uid = sock_net_uid(net, NULL),
31730b26fb17SMaciej Żenczykowski };
3174c92a59ecSDuan Jiong
3175b75cc8f9SDavid Ahern dst = ip6_route_redirect(net, &fl6, skb, &iph->saddr);
3176c92a59ecSDuan Jiong rt6_do_redirect(dst, NULL, skb);
3177c92a59ecSDuan Jiong dst_release(dst);
3178c92a59ecSDuan Jiong }
3179c92a59ecSDuan Jiong
ip6_sk_redirect(struct sk_buff * skb,struct sock * sk)31803a5ad2eeSDavid S. Miller void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk)
31813a5ad2eeSDavid S. Miller {
31823c5b4d69SEric Dumazet ip6_redirect(skb, sock_net(sk), sk->sk_bound_dev_if,
31833c5b4d69SEric Dumazet READ_ONCE(sk->sk_mark), sk->sk_uid);
31843a5ad2eeSDavid S. Miller }
31853a5ad2eeSDavid S. Miller EXPORT_SYMBOL_GPL(ip6_sk_redirect);
31863a5ad2eeSDavid S. Miller
ip6_default_advmss(const struct dst_entry * dst)31870dbaee3bSDavid S. Miller static unsigned int ip6_default_advmss(const struct dst_entry *dst)
31881da177e4SLinus Torvalds {
31890dbaee3bSDavid S. Miller struct net_device *dev = dst->dev;
31900dbaee3bSDavid S. Miller unsigned int mtu = dst_mtu(dst);
31910dbaee3bSDavid S. Miller struct net *net = dev_net(dev);
31920dbaee3bSDavid S. Miller
31931da177e4SLinus Torvalds mtu -= sizeof(struct ipv6hdr) + sizeof(struct tcphdr);
31941da177e4SLinus Torvalds
31955578689aSDaniel Lezcano if (mtu < net->ipv6.sysctl.ip6_rt_min_advmss)
31965578689aSDaniel Lezcano mtu = net->ipv6.sysctl.ip6_rt_min_advmss;
31971da177e4SLinus Torvalds
31981da177e4SLinus Torvalds /*
31991da177e4SLinus Torvalds * Maximal non-jumbo IPv6 payload is IPV6_MAXPLEN and
32001da177e4SLinus Torvalds * corresponding MSS is IPV6_MAXPLEN - tcp_header_size.
32011da177e4SLinus Torvalds * IPV6_MAXPLEN is also valid and means: "any MSS,
32021da177e4SLinus Torvalds * rely only on pmtu discovery"
32031da177e4SLinus Torvalds */
32041da177e4SLinus Torvalds if (mtu > IPV6_MAXPLEN - sizeof(struct tcphdr))
32051da177e4SLinus Torvalds mtu = IPV6_MAXPLEN;
32061da177e4SLinus Torvalds return mtu;
32071da177e4SLinus Torvalds }
32081da177e4SLinus Torvalds
ip6_mtu(const struct dst_entry * dst)3209f67fbeaeSBrian Vazquez INDIRECT_CALLABLE_SCOPE unsigned int ip6_mtu(const struct dst_entry *dst)
3210d33e4553SDavid S. Miller {
3211427faee1SVadim Fedorenko return ip6_dst_mtu_maybe_forward(dst, false);
3212d33e4553SDavid S. Miller }
32139c97921aSBrian Vazquez EXPORT_INDIRECT_CALLABLE(ip6_mtu);
3214d33e4553SDavid S. Miller
3215901731b8SDavid Ahern /* MTU selection:
3216901731b8SDavid Ahern * 1. mtu on route is locked - use it
3217901731b8SDavid Ahern * 2. mtu from nexthop exception
3218901731b8SDavid Ahern * 3. mtu from egress device
3219901731b8SDavid Ahern *
3220901731b8SDavid Ahern * based on ip6_dst_mtu_forward and exception logic of
3221901731b8SDavid Ahern * rt6_find_cached_rt; called with rcu_read_lock
3222901731b8SDavid Ahern */
ip6_mtu_from_fib6(const struct fib6_result * res,const struct in6_addr * daddr,const struct in6_addr * saddr)3223b748f260SDavid Ahern u32 ip6_mtu_from_fib6(const struct fib6_result *res,
3224b748f260SDavid Ahern const struct in6_addr *daddr,
3225b748f260SDavid Ahern const struct in6_addr *saddr)
3226901731b8SDavid Ahern {
3227b748f260SDavid Ahern const struct fib6_nh *nh = res->nh;
3228b748f260SDavid Ahern struct fib6_info *f6i = res->f6i;
3229901731b8SDavid Ahern struct inet6_dev *idev;
3230510e2cedSWei Wang struct rt6_info *rt;
3231901731b8SDavid Ahern u32 mtu = 0;
3232901731b8SDavid Ahern
3233901731b8SDavid Ahern if (unlikely(fib6_metric_locked(f6i, RTAX_MTU))) {
3234901731b8SDavid Ahern mtu = f6i->fib6_pmtu;
3235901731b8SDavid Ahern if (mtu)
3236901731b8SDavid Ahern goto out;
3237901731b8SDavid Ahern }
3238901731b8SDavid Ahern
3239510e2cedSWei Wang rt = rt6_find_cached_rt(res, daddr, saddr);
3240510e2cedSWei Wang if (unlikely(rt)) {
3241510e2cedSWei Wang mtu = dst_metric_raw(&rt->dst, RTAX_MTU);
3242510e2cedSWei Wang } else {
3243b748f260SDavid Ahern struct net_device *dev = nh->fib_nh_dev;
3244901731b8SDavid Ahern
3245901731b8SDavid Ahern mtu = IPV6_MIN_MTU;
3246901731b8SDavid Ahern idev = __in6_dev_get(dev);
3247901731b8SDavid Ahern if (idev && idev->cnf.mtu6 > mtu)
3248901731b8SDavid Ahern mtu = idev->cnf.mtu6;
3249901731b8SDavid Ahern }
3250901731b8SDavid Ahern
3251901731b8SDavid Ahern mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);
3252901731b8SDavid Ahern out:
3253b748f260SDavid Ahern return mtu - lwtunnel_headroom(nh->fib_nh_lws, mtu);
3254901731b8SDavid Ahern }
3255901731b8SDavid Ahern
icmp6_dst_alloc(struct net_device * dev,struct flowi6 * fl6)32563b00944cSYOSHIFUJI Hideaki struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
325787a11578SDavid S. Miller struct flowi6 *fl6)
32581da177e4SLinus Torvalds {
325987a11578SDavid S. Miller struct dst_entry *dst;
32601da177e4SLinus Torvalds struct rt6_info *rt;
32611da177e4SLinus Torvalds struct inet6_dev *idev = in6_dev_get(dev);
3262c346dca1SYOSHIFUJI Hideaki struct net *net = dev_net(dev);
32631da177e4SLinus Torvalds
326438308473SDavid S. Miller if (unlikely(!idev))
3265122bdf67SEric Dumazet return ERR_PTR(-ENODEV);
32661da177e4SLinus Torvalds
3267ad706862SMartin KaFai Lau rt = ip6_dst_alloc(net, dev, 0);
326838308473SDavid S. Miller if (unlikely(!rt)) {
32691da177e4SLinus Torvalds in6_dev_put(idev);
327087a11578SDavid S. Miller dst = ERR_PTR(-ENOMEM);
32711da177e4SLinus Torvalds goto out;
32721da177e4SLinus Torvalds }
32731da177e4SLinus Torvalds
3274588753f1SBrendan McGrath rt->dst.input = ip6_input;
32758e2ec639SYan, Zheng rt->dst.output = ip6_output;
3276550bab42SJulian Anastasov rt->rt6i_gateway = fl6->daddr;
327787a11578SDavid S. Miller rt->rt6i_dst.addr = fl6->daddr;
32788e2ec639SYan, Zheng rt->rt6i_dst.plen = 128;
32798e2ec639SYan, Zheng rt->rt6i_idev = idev;
328014edd87dSLi RongQing dst_metric_set(&rt->dst, RTAX_HOPLIMIT, 0);
32811da177e4SLinus Torvalds
32824c981e28SIdo Schimmel /* Add this dst into uncached_list so that rt6_disable_ip() can
3283587fea74SWei Wang * do proper release of the net_device
3284587fea74SWei Wang */
3285587fea74SWei Wang rt6_uncached_list_add(rt);
32861da177e4SLinus Torvalds
328787a11578SDavid S. Miller dst = xfrm_lookup(net, &rt->dst, flowi6_to_flowi(fl6), NULL, 0);
328887a11578SDavid S. Miller
32891da177e4SLinus Torvalds out:
329087a11578SDavid S. Miller return dst;
32911da177e4SLinus Torvalds }
32921da177e4SLinus Torvalds
ip6_dst_gc(struct dst_ops * ops)3293af6d1034SJon Maxwell static void ip6_dst_gc(struct dst_ops *ops)
32941da177e4SLinus Torvalds {
329586393e52SAlexey Dobriyan struct net *net = container_of(ops, struct net, ipv6.ip6_dst_ops);
32967019b78eSDaniel Lezcano int rt_min_interval = net->ipv6.sysctl.ip6_rt_gc_min_interval;
32977019b78eSDaniel Lezcano int rt_elasticity = net->ipv6.sysctl.ip6_rt_gc_elasticity;
32987019b78eSDaniel Lezcano int rt_gc_timeout = net->ipv6.sysctl.ip6_rt_gc_timeout;
32997019b78eSDaniel Lezcano unsigned long rt_last_gc = net->ipv6.ip6_rt_last_gc;
33009cb7c013SEric Dumazet unsigned int val;
3301fc66f95cSEric Dumazet int entries;
33021da177e4SLinus Torvalds
3303af6d1034SJon Maxwell if (time_after(rt_last_gc + rt_min_interval, jiffies))
33041da177e4SLinus Torvalds goto out;
33051da177e4SLinus Torvalds
33069cb7c013SEric Dumazet fib6_run_gc(atomic_inc_return(&net->ipv6.ip6_rt_gc_expire), net, true);
3307fc66f95cSEric Dumazet entries = dst_entries_get_slow(ops);
3308fc66f95cSEric Dumazet if (entries < ops->gc_thresh)
33099cb7c013SEric Dumazet atomic_set(&net->ipv6.ip6_rt_gc_expire, rt_gc_timeout >> 1);
33101da177e4SLinus Torvalds out:
33119cb7c013SEric Dumazet val = atomic_read(&net->ipv6.ip6_rt_gc_expire);
33129cb7c013SEric Dumazet atomic_set(&net->ipv6.ip6_rt_gc_expire, val - (val >> rt_elasticity));
33131da177e4SLinus Torvalds }
33141da177e4SLinus Torvalds
ip6_nh_lookup_table(struct net * net,struct fib6_config * cfg,const struct in6_addr * gw_addr,u32 tbid,int flags,struct fib6_result * res)3315b2c709ccSDavid Ahern static int ip6_nh_lookup_table(struct net *net, struct fib6_config *cfg,
3316b2c709ccSDavid Ahern const struct in6_addr *gw_addr, u32 tbid,
3317b2c709ccSDavid Ahern int flags, struct fib6_result *res)
33188c14586fSDavid Ahern {
33198c14586fSDavid Ahern struct flowi6 fl6 = {
33208c14586fSDavid Ahern .flowi6_oif = cfg->fc_ifindex,
33218c14586fSDavid Ahern .daddr = *gw_addr,
33228c14586fSDavid Ahern .saddr = cfg->fc_prefsrc,
33238c14586fSDavid Ahern };
33248c14586fSDavid Ahern struct fib6_table *table;
3325b2c709ccSDavid Ahern int err;
33268c14586fSDavid Ahern
3327f4797b33SDavid Ahern table = fib6_get_table(net, tbid);
33288c14586fSDavid Ahern if (!table)
3329b2c709ccSDavid Ahern return -EINVAL;
33308c14586fSDavid Ahern
33318c14586fSDavid Ahern if (!ipv6_addr_any(&cfg->fc_prefsrc))
33328c14586fSDavid Ahern flags |= RT6_LOOKUP_F_HAS_SADDR;
33338c14586fSDavid Ahern
3334f4797b33SDavid Ahern flags |= RT6_LOOKUP_F_IGNORE_LINKSTATE;
33358c14586fSDavid Ahern
3336b2c709ccSDavid Ahern err = fib6_table_lookup(net, table, cfg->fc_ifindex, &fl6, res, flags);
3337b2c709ccSDavid Ahern if (!err && res->f6i != net->ipv6.fib6_null_entry)
3338b2c709ccSDavid Ahern fib6_select_path(net, res, &fl6, cfg->fc_ifindex,
3339b2c709ccSDavid Ahern cfg->fc_ifindex != 0, NULL, flags);
33408c14586fSDavid Ahern
3341b2c709ccSDavid Ahern return err;
33428c14586fSDavid Ahern }
33438c14586fSDavid Ahern
ip6_route_check_nh_onlink(struct net * net,struct fib6_config * cfg,const struct net_device * dev,struct netlink_ext_ack * extack)3344fc1e64e1SDavid Ahern static int ip6_route_check_nh_onlink(struct net *net,
3345fc1e64e1SDavid Ahern struct fib6_config *cfg,
33469fbb704cSDavid Ahern const struct net_device *dev,
3347fc1e64e1SDavid Ahern struct netlink_ext_ack *extack)
3348fc1e64e1SDavid Ahern {
3349b2c709ccSDavid Ahern u32 tbid = l3mdev_fib_table_rcu(dev) ? : RT_TABLE_MAIN;
3350fc1e64e1SDavid Ahern const struct in6_addr *gw_addr = &cfg->fc_gateway;
3351b2c709ccSDavid Ahern struct fib6_result res = {};
3352fc1e64e1SDavid Ahern int err;
3353fc1e64e1SDavid Ahern
3354b2c709ccSDavid Ahern err = ip6_nh_lookup_table(net, cfg, gw_addr, tbid, 0, &res);
3355b2c709ccSDavid Ahern if (!err && !(res.fib6_flags & RTF_REJECT) &&
33564ed591c8SDavid Ahern /* ignore match if it is the default route */
3357b2c709ccSDavid Ahern !ipv6_addr_any(&res.f6i->fib6_dst.addr) &&
3358b2c709ccSDavid Ahern (res.fib6_type != RTN_UNICAST || dev != res.nh->fib_nh_dev)) {
335944750f84SDavid Ahern NL_SET_ERR_MSG(extack,
336044750f84SDavid Ahern "Nexthop has invalid gateway or device mismatch");
3361fc1e64e1SDavid Ahern err = -EINVAL;
3362fc1e64e1SDavid Ahern }
3363fc1e64e1SDavid Ahern
3364fc1e64e1SDavid Ahern return err;
3365fc1e64e1SDavid Ahern }
3366fc1e64e1SDavid Ahern
ip6_route_check_nh(struct net * net,struct fib6_config * cfg,struct net_device ** _dev,netdevice_tracker * dev_tracker,struct inet6_dev ** idev)33671edce99fSDavid Ahern static int ip6_route_check_nh(struct net *net,
33681edce99fSDavid Ahern struct fib6_config *cfg,
33691edce99fSDavid Ahern struct net_device **_dev,
33703515440dSEric Dumazet netdevice_tracker *dev_tracker,
33711edce99fSDavid Ahern struct inet6_dev **idev)
33721edce99fSDavid Ahern {
33731edce99fSDavid Ahern const struct in6_addr *gw_addr = &cfg->fc_gateway;
33741edce99fSDavid Ahern struct net_device *dev = _dev ? *_dev : NULL;
3375b2c709ccSDavid Ahern int flags = RT6_LOOKUP_F_IFACE;
3376b2c709ccSDavid Ahern struct fib6_result res = {};
33771edce99fSDavid Ahern int err = -EHOSTUNREACH;
33781edce99fSDavid Ahern
33791edce99fSDavid Ahern if (cfg->fc_table) {
3380b2c709ccSDavid Ahern err = ip6_nh_lookup_table(net, cfg, gw_addr,
3381b2c709ccSDavid Ahern cfg->fc_table, flags, &res);
3382b2c709ccSDavid Ahern /* gw_addr can not require a gateway or resolve to a reject
3383b2c709ccSDavid Ahern * route. If a device is given, it must match the result.
3384b2c709ccSDavid Ahern */
3385b2c709ccSDavid Ahern if (err || res.fib6_flags & RTF_REJECT ||
3386b2c709ccSDavid Ahern res.nh->fib_nh_gw_family ||
3387b2c709ccSDavid Ahern (dev && dev != res.nh->fib_nh_dev))
3388b2c709ccSDavid Ahern err = -EHOSTUNREACH;
33891edce99fSDavid Ahern }
33901edce99fSDavid Ahern
3391b2c709ccSDavid Ahern if (err < 0) {
3392b2c709ccSDavid Ahern struct flowi6 fl6 = {
3393b2c709ccSDavid Ahern .flowi6_oif = cfg->fc_ifindex,
3394b2c709ccSDavid Ahern .daddr = *gw_addr,
3395b2c709ccSDavid Ahern };
33961edce99fSDavid Ahern
3397b2c709ccSDavid Ahern err = fib6_lookup(net, cfg->fc_ifindex, &fl6, &res, flags);
3398b2c709ccSDavid Ahern if (err || res.fib6_flags & RTF_REJECT ||
3399b2c709ccSDavid Ahern res.nh->fib_nh_gw_family)
3400b2c709ccSDavid Ahern err = -EHOSTUNREACH;
34011edce99fSDavid Ahern
3402b2c709ccSDavid Ahern if (err)
3403b2c709ccSDavid Ahern return err;
3404b2c709ccSDavid Ahern
3405b2c709ccSDavid Ahern fib6_select_path(net, &res, &fl6, cfg->fc_ifindex,
3406b2c709ccSDavid Ahern cfg->fc_ifindex != 0, NULL, flags);
34071edce99fSDavid Ahern }
34081edce99fSDavid Ahern
34091edce99fSDavid Ahern err = 0;
3410b2c709ccSDavid Ahern if (dev) {
3411b2c709ccSDavid Ahern if (dev != res.nh->fib_nh_dev)
3412b2c709ccSDavid Ahern err = -EHOSTUNREACH;
3413b2c709ccSDavid Ahern } else {
3414b2c709ccSDavid Ahern *_dev = dev = res.nh->fib_nh_dev;
34153515440dSEric Dumazet netdev_hold(dev, dev_tracker, GFP_ATOMIC);
3416b2c709ccSDavid Ahern *idev = in6_dev_get(dev);
3417b2c709ccSDavid Ahern }
34181edce99fSDavid Ahern
34191edce99fSDavid Ahern return err;
34201edce99fSDavid Ahern }
34211edce99fSDavid Ahern
ip6_validate_gw(struct net * net,struct fib6_config * cfg,struct net_device ** _dev,netdevice_tracker * dev_tracker,struct inet6_dev ** idev,struct netlink_ext_ack * extack)34229fbb704cSDavid Ahern static int ip6_validate_gw(struct net *net, struct fib6_config *cfg,
34233515440dSEric Dumazet struct net_device **_dev,
34243515440dSEric Dumazet netdevice_tracker *dev_tracker,
34253515440dSEric Dumazet struct inet6_dev **idev,
34269fbb704cSDavid Ahern struct netlink_ext_ack *extack)
34279fbb704cSDavid Ahern {
34289fbb704cSDavid Ahern const struct in6_addr *gw_addr = &cfg->fc_gateway;
34299fbb704cSDavid Ahern int gwa_type = ipv6_addr_type(gw_addr);
3430232378e8SDavid Ahern bool skip_dev = gwa_type & IPV6_ADDR_LINKLOCAL ? false : true;
34319fbb704cSDavid Ahern const struct net_device *dev = *_dev;
3432232378e8SDavid Ahern bool need_addr_check = !dev;
34339fbb704cSDavid Ahern int err = -EINVAL;
34349fbb704cSDavid Ahern
34359fbb704cSDavid Ahern /* if gw_addr is local we will fail to detect this in case
34369fbb704cSDavid Ahern * address is still TENTATIVE (DAD in progress). rt6_lookup()
34379fbb704cSDavid Ahern * will return already-added prefix route via interface that
34389fbb704cSDavid Ahern * prefix route was assigned to, which might be non-loopback.
34399fbb704cSDavid Ahern */
3440232378e8SDavid Ahern if (dev &&
3441232378e8SDavid Ahern ipv6_chk_addr_and_flags(net, gw_addr, dev, skip_dev, 0, 0)) {
3442232378e8SDavid Ahern NL_SET_ERR_MSG(extack, "Gateway can not be a local address");
34439fbb704cSDavid Ahern goto out;
34449fbb704cSDavid Ahern }
34459fbb704cSDavid Ahern
34469fbb704cSDavid Ahern if (gwa_type != (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_UNICAST)) {
34479fbb704cSDavid Ahern /* IPv6 strictly inhibits using not link-local
34489fbb704cSDavid Ahern * addresses as nexthop address.
34499fbb704cSDavid Ahern * Otherwise, router will not able to send redirects.
34509fbb704cSDavid Ahern * It is very good, but in some (rare!) circumstances
34519fbb704cSDavid Ahern * (SIT, PtP, NBMA NOARP links) it is handy to allow
34529fbb704cSDavid Ahern * some exceptions. --ANK
34539fbb704cSDavid Ahern * We allow IPv4-mapped nexthops to support RFC4798-type
34549fbb704cSDavid Ahern * addressing
34559fbb704cSDavid Ahern */
34569fbb704cSDavid Ahern if (!(gwa_type & (IPV6_ADDR_UNICAST | IPV6_ADDR_MAPPED))) {
34579fbb704cSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid gateway address");
34589fbb704cSDavid Ahern goto out;
34599fbb704cSDavid Ahern }
34609fbb704cSDavid Ahern
3461b2c709ccSDavid Ahern rcu_read_lock();
3462b2c709ccSDavid Ahern
34639fbb704cSDavid Ahern if (cfg->fc_flags & RTNH_F_ONLINK)
34649fbb704cSDavid Ahern err = ip6_route_check_nh_onlink(net, cfg, dev, extack);
34659fbb704cSDavid Ahern else
34663515440dSEric Dumazet err = ip6_route_check_nh(net, cfg, _dev, dev_tracker,
34673515440dSEric Dumazet idev);
34689fbb704cSDavid Ahern
3469b2c709ccSDavid Ahern rcu_read_unlock();
3470b2c709ccSDavid Ahern
34719fbb704cSDavid Ahern if (err)
34729fbb704cSDavid Ahern goto out;
34739fbb704cSDavid Ahern }
34749fbb704cSDavid Ahern
34759fbb704cSDavid Ahern /* reload in case device was changed */
34769fbb704cSDavid Ahern dev = *_dev;
34779fbb704cSDavid Ahern
34789fbb704cSDavid Ahern err = -EINVAL;
34799fbb704cSDavid Ahern if (!dev) {
34809fbb704cSDavid Ahern NL_SET_ERR_MSG(extack, "Egress device not specified");
34819fbb704cSDavid Ahern goto out;
34829fbb704cSDavid Ahern } else if (dev->flags & IFF_LOOPBACK) {
34839fbb704cSDavid Ahern NL_SET_ERR_MSG(extack,
34849fbb704cSDavid Ahern "Egress device can not be loopback device for this route");
34859fbb704cSDavid Ahern goto out;
34869fbb704cSDavid Ahern }
3487232378e8SDavid Ahern
3488232378e8SDavid Ahern /* if we did not check gw_addr above, do so now that the
3489232378e8SDavid Ahern * egress device has been resolved.
3490232378e8SDavid Ahern */
3491232378e8SDavid Ahern if (need_addr_check &&
3492232378e8SDavid Ahern ipv6_chk_addr_and_flags(net, gw_addr, dev, skip_dev, 0, 0)) {
3493232378e8SDavid Ahern NL_SET_ERR_MSG(extack, "Gateway can not be a local address");
3494232378e8SDavid Ahern goto out;
3495232378e8SDavid Ahern }
3496232378e8SDavid Ahern
34979fbb704cSDavid Ahern err = 0;
34989fbb704cSDavid Ahern out:
34999fbb704cSDavid Ahern return err;
35009fbb704cSDavid Ahern }
35019fbb704cSDavid Ahern
fib6_is_reject(u32 flags,struct net_device * dev,int addr_type)350283c44251SDavid Ahern static bool fib6_is_reject(u32 flags, struct net_device *dev, int addr_type)
350383c44251SDavid Ahern {
350483c44251SDavid Ahern if ((flags & RTF_REJECT) ||
350583c44251SDavid Ahern (dev && (dev->flags & IFF_LOOPBACK) &&
350683c44251SDavid Ahern !(addr_type & IPV6_ADDR_LOOPBACK) &&
3507aea23c32SDavid Ahern !(flags & (RTF_ANYCAST | RTF_LOCAL))))
350883c44251SDavid Ahern return true;
350983c44251SDavid Ahern
351083c44251SDavid Ahern return false;
351183c44251SDavid Ahern }
351283c44251SDavid Ahern
fib6_nh_init(struct net * net,struct fib6_nh * fib6_nh,struct fib6_config * cfg,gfp_t gfp_flags,struct netlink_ext_ack * extack)351383c44251SDavid Ahern int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
351483c44251SDavid Ahern struct fib6_config *cfg, gfp_t gfp_flags,
351583c44251SDavid Ahern struct netlink_ext_ack *extack)
351683c44251SDavid Ahern {
351770f7457aSJakub Kicinski netdevice_tracker *dev_tracker = &fib6_nh->fib_nh_dev_tracker;
351883c44251SDavid Ahern struct net_device *dev = NULL;
351983c44251SDavid Ahern struct inet6_dev *idev = NULL;
352083c44251SDavid Ahern int addr_type;
352183c44251SDavid Ahern int err;
352283c44251SDavid Ahern
3523f1741730SDavid Ahern fib6_nh->fib_nh_family = AF_INET6;
35241bef4c22SEric Dumazet #ifdef CONFIG_IPV6_ROUTER_PREF
35251bef4c22SEric Dumazet fib6_nh->last_probe = jiffies;
35261bef4c22SEric Dumazet #endif
352738428d68SRoopa Prabhu if (cfg->fc_is_fdb) {
352838428d68SRoopa Prabhu fib6_nh->fib_nh_gw6 = cfg->fc_gateway;
352938428d68SRoopa Prabhu fib6_nh->fib_nh_gw_family = AF_INET6;
353038428d68SRoopa Prabhu return 0;
353138428d68SRoopa Prabhu }
3532f1741730SDavid Ahern
353383c44251SDavid Ahern err = -ENODEV;
353483c44251SDavid Ahern if (cfg->fc_ifindex) {
353570f7457aSJakub Kicinski dev = netdev_get_by_index(net, cfg->fc_ifindex,
353670f7457aSJakub Kicinski dev_tracker, gfp_flags);
353783c44251SDavid Ahern if (!dev)
353883c44251SDavid Ahern goto out;
353983c44251SDavid Ahern idev = in6_dev_get(dev);
354083c44251SDavid Ahern if (!idev)
354183c44251SDavid Ahern goto out;
354283c44251SDavid Ahern }
354383c44251SDavid Ahern
354483c44251SDavid Ahern if (cfg->fc_flags & RTNH_F_ONLINK) {
354583c44251SDavid Ahern if (!dev) {
354683c44251SDavid Ahern NL_SET_ERR_MSG(extack,
354783c44251SDavid Ahern "Nexthop device required for onlink");
354883c44251SDavid Ahern goto out;
354983c44251SDavid Ahern }
355083c44251SDavid Ahern
355183c44251SDavid Ahern if (!(dev->flags & IFF_UP)) {
355283c44251SDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop device is not up");
355383c44251SDavid Ahern err = -ENETDOWN;
355483c44251SDavid Ahern goto out;
355583c44251SDavid Ahern }
355683c44251SDavid Ahern
3557ad1601aeSDavid Ahern fib6_nh->fib_nh_flags |= RTNH_F_ONLINK;
355883c44251SDavid Ahern }
355983c44251SDavid Ahern
3560ad1601aeSDavid Ahern fib6_nh->fib_nh_weight = 1;
356183c44251SDavid Ahern
356283c44251SDavid Ahern /* We cannot add true routes via loopback here,
356383c44251SDavid Ahern * they would result in kernel looping; promote them to reject routes
356483c44251SDavid Ahern */
356583c44251SDavid Ahern addr_type = ipv6_addr_type(&cfg->fc_dst);
356683c44251SDavid Ahern if (fib6_is_reject(cfg->fc_flags, dev, addr_type)) {
356783c44251SDavid Ahern /* hold loopback dev/idev if we haven't done so. */
356883c44251SDavid Ahern if (dev != net->loopback_dev) {
356983c44251SDavid Ahern if (dev) {
357070f7457aSJakub Kicinski netdev_put(dev, dev_tracker);
357183c44251SDavid Ahern in6_dev_put(idev);
357283c44251SDavid Ahern }
357383c44251SDavid Ahern dev = net->loopback_dev;
357470f7457aSJakub Kicinski netdev_hold(dev, dev_tracker, gfp_flags);
357583c44251SDavid Ahern idev = in6_dev_get(dev);
357683c44251SDavid Ahern if (!idev) {
357783c44251SDavid Ahern err = -ENODEV;
357883c44251SDavid Ahern goto out;
357983c44251SDavid Ahern }
358083c44251SDavid Ahern }
35817dd73168SDavid Ahern goto pcpu_alloc;
358283c44251SDavid Ahern }
358383c44251SDavid Ahern
358483c44251SDavid Ahern if (cfg->fc_flags & RTF_GATEWAY) {
35853515440dSEric Dumazet err = ip6_validate_gw(net, cfg, &dev, dev_tracker,
35863515440dSEric Dumazet &idev, extack);
358783c44251SDavid Ahern if (err)
358883c44251SDavid Ahern goto out;
358983c44251SDavid Ahern
3590ad1601aeSDavid Ahern fib6_nh->fib_nh_gw6 = cfg->fc_gateway;
3591bdf00467SDavid Ahern fib6_nh->fib_nh_gw_family = AF_INET6;
359283c44251SDavid Ahern }
359383c44251SDavid Ahern
359483c44251SDavid Ahern err = -ENODEV;
359583c44251SDavid Ahern if (!dev)
359683c44251SDavid Ahern goto out;
359783c44251SDavid Ahern
3598b6947723SEric Dumazet if (!idev || idev->cnf.disable_ipv6) {
359983c44251SDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 is disabled on nexthop device");
360083c44251SDavid Ahern err = -EACCES;
360183c44251SDavid Ahern goto out;
360283c44251SDavid Ahern }
360383c44251SDavid Ahern
360483c44251SDavid Ahern if (!(dev->flags & IFF_UP) && !cfg->fc_ignore_dev_down) {
360583c44251SDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop device is not up");
360683c44251SDavid Ahern err = -ENETDOWN;
360783c44251SDavid Ahern goto out;
360883c44251SDavid Ahern }
360983c44251SDavid Ahern
361083c44251SDavid Ahern if (!(cfg->fc_flags & (RTF_LOCAL | RTF_ANYCAST)) &&
361183c44251SDavid Ahern !netif_carrier_ok(dev))
3612ad1601aeSDavid Ahern fib6_nh->fib_nh_flags |= RTNH_F_LINKDOWN;
361383c44251SDavid Ahern
3614faee6769SAlexander Aring err = fib_nh_common_init(net, &fib6_nh->nh_common, cfg->fc_encap,
36157dd73168SDavid Ahern cfg->fc_encap_type, cfg, gfp_flags, extack);
36167dd73168SDavid Ahern if (err)
36177dd73168SDavid Ahern goto out;
36187dd73168SDavid Ahern
36197dd73168SDavid Ahern pcpu_alloc:
3620f40b6ae2SDavid Ahern fib6_nh->rt6i_pcpu = alloc_percpu_gfp(struct rt6_info *, gfp_flags);
3621f40b6ae2SDavid Ahern if (!fib6_nh->rt6i_pcpu) {
3622f40b6ae2SDavid Ahern err = -ENOMEM;
3623f40b6ae2SDavid Ahern goto out;
3624f40b6ae2SDavid Ahern }
3625f40b6ae2SDavid Ahern
3626ad1601aeSDavid Ahern fib6_nh->fib_nh_dev = dev;
3627f1741730SDavid Ahern fib6_nh->fib_nh_oif = dev->ifindex;
362883c44251SDavid Ahern err = 0;
362983c44251SDavid Ahern out:
363083c44251SDavid Ahern if (idev)
363183c44251SDavid Ahern in6_dev_put(idev);
363283c44251SDavid Ahern
363383c44251SDavid Ahern if (err) {
3634ad1601aeSDavid Ahern lwtstate_put(fib6_nh->fib_nh_lws);
3635ad1601aeSDavid Ahern fib6_nh->fib_nh_lws = NULL;
363670f7457aSJakub Kicinski netdev_put(dev, dev_tracker);
363783c44251SDavid Ahern }
363883c44251SDavid Ahern
363983c44251SDavid Ahern return err;
364083c44251SDavid Ahern }
364183c44251SDavid Ahern
fib6_nh_release(struct fib6_nh * fib6_nh)3642dac7d0f2SDavid Ahern void fib6_nh_release(struct fib6_nh *fib6_nh)
3643dac7d0f2SDavid Ahern {
3644cc5c073aSDavid Ahern struct rt6_exception_bucket *bucket;
3645cc5c073aSDavid Ahern
3646cc5c073aSDavid Ahern rcu_read_lock();
3647cc5c073aSDavid Ahern
3648cc5c073aSDavid Ahern fib6_nh_flush_exceptions(fib6_nh, NULL);
3649cc5c073aSDavid Ahern bucket = fib6_nh_get_excptn_bucket(fib6_nh, NULL);
3650cc5c073aSDavid Ahern if (bucket) {
3651cc5c073aSDavid Ahern rcu_assign_pointer(fib6_nh->rt6i_exception_bucket, NULL);
3652cc5c073aSDavid Ahern kfree(bucket);
3653cc5c073aSDavid Ahern }
3654cc5c073aSDavid Ahern
3655cc5c073aSDavid Ahern rcu_read_unlock();
3656cc5c073aSDavid Ahern
365761308050SNikolay Aleksandrov fib6_nh_release_dsts(fib6_nh);
3658f40b6ae2SDavid Ahern free_percpu(fib6_nh->rt6i_pcpu);
3659f40b6ae2SDavid Ahern
3660979e276eSDavid Ahern fib_nh_common_release(&fib6_nh->nh_common);
3661dac7d0f2SDavid Ahern }
3662dac7d0f2SDavid Ahern
fib6_nh_release_dsts(struct fib6_nh * fib6_nh)36638837cbbfSNikolay Aleksandrov void fib6_nh_release_dsts(struct fib6_nh *fib6_nh)
36648837cbbfSNikolay Aleksandrov {
36658837cbbfSNikolay Aleksandrov int cpu;
36668837cbbfSNikolay Aleksandrov
36678837cbbfSNikolay Aleksandrov if (!fib6_nh->rt6i_pcpu)
36688837cbbfSNikolay Aleksandrov return;
36698837cbbfSNikolay Aleksandrov
36708837cbbfSNikolay Aleksandrov for_each_possible_cpu(cpu) {
36718837cbbfSNikolay Aleksandrov struct rt6_info *pcpu_rt, **ppcpu_rt;
36728837cbbfSNikolay Aleksandrov
36738837cbbfSNikolay Aleksandrov ppcpu_rt = per_cpu_ptr(fib6_nh->rt6i_pcpu, cpu);
36748837cbbfSNikolay Aleksandrov pcpu_rt = xchg(ppcpu_rt, NULL);
36758837cbbfSNikolay Aleksandrov if (pcpu_rt) {
36768837cbbfSNikolay Aleksandrov dst_dev_put(&pcpu_rt->dst);
36778837cbbfSNikolay Aleksandrov dst_release(&pcpu_rt->dst);
36788837cbbfSNikolay Aleksandrov }
36798837cbbfSNikolay Aleksandrov }
36808837cbbfSNikolay Aleksandrov }
36818837cbbfSNikolay Aleksandrov
ip6_route_info_create(struct fib6_config * cfg,gfp_t gfp_flags,struct netlink_ext_ack * extack)36828d1c802bSDavid Ahern static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
3683acb54e3cSDavid Ahern gfp_t gfp_flags,
3684333c4301SDavid Ahern struct netlink_ext_ack *extack)
36851da177e4SLinus Torvalds {
36865578689aSDaniel Lezcano struct net *net = cfg->fc_nlinfo.nl_net;
36878d1c802bSDavid Ahern struct fib6_info *rt = NULL;
3688f88d8ea6SDavid Ahern struct nexthop *nh = NULL;
3689c71099acSThomas Graf struct fib6_table *table;
3690f88d8ea6SDavid Ahern struct fib6_nh *fib6_nh;
36918c5b83f0SRoopa Prabhu int err = -EINVAL;
369283c44251SDavid Ahern int addr_type;
36931da177e4SLinus Torvalds
3694557c44beSDavid Ahern /* RTF_PCPU is an internal flag; can not be set by userspace */
3695d5d531cbSDavid Ahern if (cfg->fc_flags & RTF_PCPU) {
3696d5d531cbSDavid Ahern NL_SET_ERR_MSG(extack, "Userspace can not set RTF_PCPU");
3697557c44beSDavid Ahern goto out;
3698d5d531cbSDavid Ahern }
3699557c44beSDavid Ahern
37002ea2352eSWei Wang /* RTF_CACHE is an internal flag; can not be set by userspace */
37012ea2352eSWei Wang if (cfg->fc_flags & RTF_CACHE) {
37022ea2352eSWei Wang NL_SET_ERR_MSG(extack, "Userspace can not set RTF_CACHE");
37032ea2352eSWei Wang goto out;
37042ea2352eSWei Wang }
37052ea2352eSWei Wang
3706e8478e80SDavid Ahern if (cfg->fc_type > RTN_MAX) {
3707e8478e80SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid route type");
3708e8478e80SDavid Ahern goto out;
3709e8478e80SDavid Ahern }
3710e8478e80SDavid Ahern
3711d5d531cbSDavid Ahern if (cfg->fc_dst_len > 128) {
3712d5d531cbSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid prefix length");
37138c5b83f0SRoopa Prabhu goto out;
3714d5d531cbSDavid Ahern }
3715d5d531cbSDavid Ahern if (cfg->fc_src_len > 128) {
3716d5d531cbSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid source address length");
3717d5d531cbSDavid Ahern goto out;
3718d5d531cbSDavid Ahern }
37191da177e4SLinus Torvalds #ifndef CONFIG_IPV6_SUBTREES
3720d5d531cbSDavid Ahern if (cfg->fc_src_len) {
3721d5d531cbSDavid Ahern NL_SET_ERR_MSG(extack,
3722d5d531cbSDavid Ahern "Specifying source address requires IPV6_SUBTREES to be enabled");
37238c5b83f0SRoopa Prabhu goto out;
3724d5d531cbSDavid Ahern }
37251da177e4SLinus Torvalds #endif
37265b98324eSDavid Ahern if (cfg->fc_nh_id) {
37275b98324eSDavid Ahern nh = nexthop_find_by_id(net, cfg->fc_nh_id);
37285b98324eSDavid Ahern if (!nh) {
37295b98324eSDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop id does not exist");
37305b98324eSDavid Ahern goto out;
37315b98324eSDavid Ahern }
37325b98324eSDavid Ahern err = fib6_check_nexthop(nh, cfg, extack);
37335b98324eSDavid Ahern if (err)
37345b98324eSDavid Ahern goto out;
37355b98324eSDavid Ahern }
3736fc1e64e1SDavid Ahern
3737c71099acSThomas Graf err = -ENOBUFS;
373838308473SDavid S. Miller if (cfg->fc_nlinfo.nlh &&
3739d71314b4SMatti Vaittinen !(cfg->fc_nlinfo.nlh->nlmsg_flags & NLM_F_CREATE)) {
3740d71314b4SMatti Vaittinen table = fib6_get_table(net, cfg->fc_table);
374138308473SDavid S. Miller if (!table) {
3742f3213831SJoe Perches pr_warn("NLM_F_CREATE should be specified when creating new route\n");
3743d71314b4SMatti Vaittinen table = fib6_new_table(net, cfg->fc_table);
3744d71314b4SMatti Vaittinen }
3745d71314b4SMatti Vaittinen } else {
3746d71314b4SMatti Vaittinen table = fib6_new_table(net, cfg->fc_table);
3747d71314b4SMatti Vaittinen }
374838308473SDavid S. Miller
374938308473SDavid S. Miller if (!table)
3750c71099acSThomas Graf goto out;
3751c71099acSThomas Graf
37521da177e4SLinus Torvalds err = -ENOMEM;
3753f88d8ea6SDavid Ahern rt = fib6_info_alloc(gfp_flags, !nh);
375493531c67SDavid Ahern if (!rt)
37551da177e4SLinus Torvalds goto out;
375693531c67SDavid Ahern
375769f397e6SJason Xing rt->fib6_metrics = ip_fib_metrics_init(cfg->fc_mx, cfg->fc_mx_len,
3758d7e774f3SDavid Ahern extack);
3759767a2217SDavid Ahern if (IS_ERR(rt->fib6_metrics)) {
3760767a2217SDavid Ahern err = PTR_ERR(rt->fib6_metrics);
3761fda21d46SEric Dumazet /* Do not leave garbage there. */
3762fda21d46SEric Dumazet rt->fib6_metrics = (struct dst_metrics *)&dst_default_metrics;
37638fb4792fSPaolo Abeni goto out_free;
3764767a2217SDavid Ahern }
3765767a2217SDavid Ahern
376693531c67SDavid Ahern if (cfg->fc_flags & RTF_ADDRCONF)
376793531c67SDavid Ahern rt->dst_nocount = true;
37681da177e4SLinus Torvalds
37691716a961SGao feng if (cfg->fc_flags & RTF_EXPIRES)
3770b577b9aaSDavid Ahern fib6_set_expires(rt, jiffies +
37711716a961SGao feng clock_t_to_jiffies(cfg->fc_expires));
37721716a961SGao feng else
3773b577b9aaSDavid Ahern fib6_clean_expires(rt);
37741da177e4SLinus Torvalds
377586872cb5SThomas Graf if (cfg->fc_protocol == RTPROT_UNSPEC)
377686872cb5SThomas Graf cfg->fc_protocol = RTPROT_BOOT;
377793c2fb25SDavid Ahern rt->fib6_protocol = cfg->fc_protocol;
377886872cb5SThomas Graf
377983c44251SDavid Ahern rt->fib6_table = table;
378083c44251SDavid Ahern rt->fib6_metric = cfg->fc_metric;
3781c7036d97SDavid Ahern rt->fib6_type = cfg->fc_type ? : RTN_UNICAST;
37822b2450caSDavid Ahern rt->fib6_flags = cfg->fc_flags & ~RTF_GATEWAY;
378319e42e45SRoopa Prabhu
378493c2fb25SDavid Ahern ipv6_addr_prefix(&rt->fib6_dst.addr, &cfg->fc_dst, cfg->fc_dst_len);
378593c2fb25SDavid Ahern rt->fib6_dst.plen = cfg->fc_dst_len;
37861da177e4SLinus Torvalds
37871da177e4SLinus Torvalds #ifdef CONFIG_IPV6_SUBTREES
378893c2fb25SDavid Ahern ipv6_addr_prefix(&rt->fib6_src.addr, &cfg->fc_src, cfg->fc_src_len);
378993c2fb25SDavid Ahern rt->fib6_src.plen = cfg->fc_src_len;
37901da177e4SLinus Torvalds #endif
3791f88d8ea6SDavid Ahern if (nh) {
3792f88d8ea6SDavid Ahern if (rt->fib6_src.plen) {
37934daa95afSColin Ian King NL_SET_ERR_MSG(extack, "Nexthops can not be used with source routing");
3794821bbf79SCoco Li goto out_free;
3795f88d8ea6SDavid Ahern }
3796706ec919SXiyu Yang if (!nexthop_get(nh)) {
3797706ec919SXiyu Yang NL_SET_ERR_MSG(extack, "Nexthop has been deleted");
3798821bbf79SCoco Li goto out_free;
3799706ec919SXiyu Yang }
3800f88d8ea6SDavid Ahern rt->nh = nh;
3801f88d8ea6SDavid Ahern fib6_nh = nexthop_fib6_nh(rt->nh);
3802f88d8ea6SDavid Ahern } else {
38031cf844c7SDavid Ahern err = fib6_nh_init(net, rt->fib6_nh, cfg, gfp_flags, extack);
38041da177e4SLinus Torvalds if (err)
38051da177e4SLinus Torvalds goto out;
38069fbb704cSDavid Ahern
3807f88d8ea6SDavid Ahern fib6_nh = rt->fib6_nh;
3808f88d8ea6SDavid Ahern
3809f88d8ea6SDavid Ahern /* We cannot add true routes via loopback here, they would
3810f88d8ea6SDavid Ahern * result in kernel looping; promote them to reject routes
381183c44251SDavid Ahern */
381283c44251SDavid Ahern addr_type = ipv6_addr_type(&cfg->fc_dst);
3813f88d8ea6SDavid Ahern if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh->fib_nh_dev,
3814f88d8ea6SDavid Ahern addr_type))
381583c44251SDavid Ahern rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP;
3816f88d8ea6SDavid Ahern }
3817955ec4cbSDavid Ahern
3818c3968a85SDaniel Walter if (!ipv6_addr_any(&cfg->fc_prefsrc)) {
3819f88d8ea6SDavid Ahern struct net_device *dev = fib6_nh->fib_nh_dev;
382083c44251SDavid Ahern
3821c3968a85SDaniel Walter if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) {
3822d5d531cbSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid source address");
3823c3968a85SDaniel Walter err = -EINVAL;
3824c3968a85SDaniel Walter goto out;
3825c3968a85SDaniel Walter }
382693c2fb25SDavid Ahern rt->fib6_prefsrc.addr = cfg->fc_prefsrc;
382793c2fb25SDavid Ahern rt->fib6_prefsrc.plen = 128;
3828c3968a85SDaniel Walter } else
382993c2fb25SDavid Ahern rt->fib6_prefsrc.plen = 0;
3830c3968a85SDaniel Walter
38318c5b83f0SRoopa Prabhu return rt;
38321da177e4SLinus Torvalds out:
383393531c67SDavid Ahern fib6_info_release(rt);
38348c5b83f0SRoopa Prabhu return ERR_PTR(err);
3835821bbf79SCoco Li out_free:
3836821bbf79SCoco Li ip_fib_metrics_put(rt->fib6_metrics);
3837821bbf79SCoco Li kfree(rt);
3838821bbf79SCoco Li return ERR_PTR(err);
38396b9ea5a6SRoopa Prabhu }
38406b9ea5a6SRoopa Prabhu
ip6_route_add(struct fib6_config * cfg,gfp_t gfp_flags,struct netlink_ext_ack * extack)3841acb54e3cSDavid Ahern int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,
3842333c4301SDavid Ahern struct netlink_ext_ack *extack)
38436b9ea5a6SRoopa Prabhu {
38448d1c802bSDavid Ahern struct fib6_info *rt;
38456b9ea5a6SRoopa Prabhu int err;
38466b9ea5a6SRoopa Prabhu
3847acb54e3cSDavid Ahern rt = ip6_route_info_create(cfg, gfp_flags, extack);
3848d4ead6b3SDavid Ahern if (IS_ERR(rt))
3849d4ead6b3SDavid Ahern return PTR_ERR(rt);
38506b9ea5a6SRoopa Prabhu
3851d4ead6b3SDavid Ahern err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, extack);
385293531c67SDavid Ahern fib6_info_release(rt);
38536b9ea5a6SRoopa Prabhu
38541da177e4SLinus Torvalds return err;
38551da177e4SLinus Torvalds }
38561da177e4SLinus Torvalds
__ip6_del_rt(struct fib6_info * rt,struct nl_info * info)38578d1c802bSDavid Ahern static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)
38581da177e4SLinus Torvalds {
3859afb1d4b5SDavid Ahern struct net *net = info->nl_net;
3860c71099acSThomas Graf struct fib6_table *table;
3861afb1d4b5SDavid Ahern int err;
38621da177e4SLinus Torvalds
3863421842edSDavid Ahern if (rt == net->ipv6.fib6_null_entry) {
38646825a26cSGao feng err = -ENOENT;
38656825a26cSGao feng goto out;
38666825a26cSGao feng }
38676c813a72SPatrick McHardy
386893c2fb25SDavid Ahern table = rt->fib6_table;
386966f5d6ceSWei Wang spin_lock_bh(&table->tb6_lock);
387086872cb5SThomas Graf err = fib6_del(rt, info);
387166f5d6ceSWei Wang spin_unlock_bh(&table->tb6_lock);
38721da177e4SLinus Torvalds
38736825a26cSGao feng out:
387493531c67SDavid Ahern fib6_info_release(rt);
38751da177e4SLinus Torvalds return err;
38761da177e4SLinus Torvalds }
38771da177e4SLinus Torvalds
ip6_del_rt(struct net * net,struct fib6_info * rt,bool skip_notify)387811dd74b3SRoopa Prabhu int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)
3879e0a1ad73SThomas Graf {
388011dd74b3SRoopa Prabhu struct nl_info info = {
388111dd74b3SRoopa Prabhu .nl_net = net,
388211dd74b3SRoopa Prabhu .skip_notify = skip_notify
388311dd74b3SRoopa Prabhu };
3884afb1d4b5SDavid Ahern
3885528c4cebSDenis V. Lunev return __ip6_del_rt(rt, &info);
3886e0a1ad73SThomas Graf }
3887e0a1ad73SThomas Graf
__ip6_del_rt_siblings(struct fib6_info * rt,struct fib6_config * cfg)38888d1c802bSDavid Ahern static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
38890ae81335SDavid Ahern {
38900ae81335SDavid Ahern struct nl_info *info = &cfg->fc_nlinfo;
3891e3330039SWANG Cong struct net *net = info->nl_net;
389216a16cd3SDavid Ahern struct sk_buff *skb = NULL;
38930ae81335SDavid Ahern struct fib6_table *table;
3894e3330039SWANG Cong int err = -ENOENT;
38950ae81335SDavid Ahern
3896421842edSDavid Ahern if (rt == net->ipv6.fib6_null_entry)
3897e3330039SWANG Cong goto out_put;
389893c2fb25SDavid Ahern table = rt->fib6_table;
389966f5d6ceSWei Wang spin_lock_bh(&table->tb6_lock);
39000ae81335SDavid Ahern
390193c2fb25SDavid Ahern if (rt->fib6_nsiblings && cfg->fc_delete_all_nh) {
39028d1c802bSDavid Ahern struct fib6_info *sibling, *next_sibling;
39030284696bSIdo Schimmel struct fib6_node *fn;
39040ae81335SDavid Ahern
390516a16cd3SDavid Ahern /* prefer to send a single notification with all hops */
390616a16cd3SDavid Ahern skb = nlmsg_new(rt6_nlmsg_size(rt), gfp_any());
390716a16cd3SDavid Ahern if (skb) {
390816a16cd3SDavid Ahern u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
390916a16cd3SDavid Ahern
3910d4ead6b3SDavid Ahern if (rt6_fill_node(net, skb, rt, NULL,
391116a16cd3SDavid Ahern NULL, NULL, 0, RTM_DELROUTE,
391216a16cd3SDavid Ahern info->portid, seq, 0) < 0) {
391316a16cd3SDavid Ahern kfree_skb(skb);
391416a16cd3SDavid Ahern skb = NULL;
391516a16cd3SDavid Ahern } else
391616a16cd3SDavid Ahern info->skip_notify = 1;
391716a16cd3SDavid Ahern }
391816a16cd3SDavid Ahern
39190284696bSIdo Schimmel /* 'rt' points to the first sibling route. If it is not the
39200284696bSIdo Schimmel * leaf, then we do not need to send a notification. Otherwise,
39210284696bSIdo Schimmel * we need to check if the last sibling has a next route or not
39220284696bSIdo Schimmel * and emit a replace or delete notification, respectively.
39230284696bSIdo Schimmel */
39242881fd61SIdo Schimmel info->skip_notify_kernel = 1;
39250284696bSIdo Schimmel fn = rcu_dereference_protected(rt->fib6_node,
39260284696bSIdo Schimmel lockdep_is_held(&table->tb6_lock));
39270284696bSIdo Schimmel if (rcu_access_pointer(fn->leaf) == rt) {
39280284696bSIdo Schimmel struct fib6_info *last_sibling, *replace_rt;
39290284696bSIdo Schimmel
39300284696bSIdo Schimmel last_sibling = list_last_entry(&rt->fib6_siblings,
39310284696bSIdo Schimmel struct fib6_info,
39320284696bSIdo Schimmel fib6_siblings);
39330284696bSIdo Schimmel replace_rt = rcu_dereference_protected(
39340284696bSIdo Schimmel last_sibling->fib6_next,
39350284696bSIdo Schimmel lockdep_is_held(&table->tb6_lock));
39360284696bSIdo Schimmel if (replace_rt)
39370284696bSIdo Schimmel call_fib6_entry_notifiers_replace(net,
39380284696bSIdo Schimmel replace_rt);
39390284696bSIdo Schimmel else
39400284696bSIdo Schimmel call_fib6_multipath_entry_notifiers(net,
3941caafb250SIdo Schimmel FIB_EVENT_ENTRY_DEL,
39420284696bSIdo Schimmel rt, rt->fib6_nsiblings,
39430284696bSIdo Schimmel NULL);
39440284696bSIdo Schimmel }
39450ae81335SDavid Ahern list_for_each_entry_safe(sibling, next_sibling,
394693c2fb25SDavid Ahern &rt->fib6_siblings,
394793c2fb25SDavid Ahern fib6_siblings) {
39480ae81335SDavid Ahern err = fib6_del(sibling, info);
39490ae81335SDavid Ahern if (err)
3950e3330039SWANG Cong goto out_unlock;
39510ae81335SDavid Ahern }
39520ae81335SDavid Ahern }
39530ae81335SDavid Ahern
39540ae81335SDavid Ahern err = fib6_del(rt, info);
3955e3330039SWANG Cong out_unlock:
395666f5d6ceSWei Wang spin_unlock_bh(&table->tb6_lock);
3957e3330039SWANG Cong out_put:
395893531c67SDavid Ahern fib6_info_release(rt);
395916a16cd3SDavid Ahern
396016a16cd3SDavid Ahern if (skb) {
3961e3330039SWANG Cong rtnl_notify(skb, net, info->portid, RTNLGRP_IPV6_ROUTE,
396216a16cd3SDavid Ahern info->nlh, gfp_any());
396316a16cd3SDavid Ahern }
39640ae81335SDavid Ahern return err;
39650ae81335SDavid Ahern }
39660ae81335SDavid Ahern
__ip6_del_cached_rt(struct rt6_info * rt,struct fib6_config * cfg)39670fa6efc5SDavid Ahern static int __ip6_del_cached_rt(struct rt6_info *rt, struct fib6_config *cfg)
396823fb93a4SDavid Ahern {
396923fb93a4SDavid Ahern int rc = -ESRCH;
397023fb93a4SDavid Ahern
397123fb93a4SDavid Ahern if (cfg->fc_ifindex && rt->dst.dev->ifindex != cfg->fc_ifindex)
397223fb93a4SDavid Ahern goto out;
397323fb93a4SDavid Ahern
397423fb93a4SDavid Ahern if (cfg->fc_flags & RTF_GATEWAY &&
397523fb93a4SDavid Ahern !ipv6_addr_equal(&cfg->fc_gateway, &rt->rt6i_gateway))
397623fb93a4SDavid Ahern goto out;
3977761f6026SXin Long
397823fb93a4SDavid Ahern rc = rt6_remove_exception_rt(rt);
397923fb93a4SDavid Ahern out:
398023fb93a4SDavid Ahern return rc;
398123fb93a4SDavid Ahern }
398223fb93a4SDavid Ahern
ip6_del_cached_rt(struct fib6_config * cfg,struct fib6_info * rt,struct fib6_nh * nh)39830fa6efc5SDavid Ahern static int ip6_del_cached_rt(struct fib6_config *cfg, struct fib6_info *rt,
39840fa6efc5SDavid Ahern struct fib6_nh *nh)
39850fa6efc5SDavid Ahern {
39860fa6efc5SDavid Ahern struct fib6_result res = {
39870fa6efc5SDavid Ahern .f6i = rt,
39880fa6efc5SDavid Ahern .nh = nh,
39890fa6efc5SDavid Ahern };
39900fa6efc5SDavid Ahern struct rt6_info *rt_cache;
39910fa6efc5SDavid Ahern
39920fa6efc5SDavid Ahern rt_cache = rt6_find_cached_rt(&res, &cfg->fc_dst, &cfg->fc_src);
39930fa6efc5SDavid Ahern if (rt_cache)
39940fa6efc5SDavid Ahern return __ip6_del_cached_rt(rt_cache, cfg);
39950fa6efc5SDavid Ahern
39960fa6efc5SDavid Ahern return 0;
39970fa6efc5SDavid Ahern }
39980fa6efc5SDavid Ahern
39995b98324eSDavid Ahern struct fib6_nh_del_cached_rt_arg {
40005b98324eSDavid Ahern struct fib6_config *cfg;
40015b98324eSDavid Ahern struct fib6_info *f6i;
40025b98324eSDavid Ahern };
40035b98324eSDavid Ahern
fib6_nh_del_cached_rt(struct fib6_nh * nh,void * _arg)40045b98324eSDavid Ahern static int fib6_nh_del_cached_rt(struct fib6_nh *nh, void *_arg)
40055b98324eSDavid Ahern {
40065b98324eSDavid Ahern struct fib6_nh_del_cached_rt_arg *arg = _arg;
40075b98324eSDavid Ahern int rc;
40085b98324eSDavid Ahern
40095b98324eSDavid Ahern rc = ip6_del_cached_rt(arg->cfg, arg->f6i, nh);
40105b98324eSDavid Ahern return rc != -ESRCH ? rc : 0;
40115b98324eSDavid Ahern }
40125b98324eSDavid Ahern
ip6_del_cached_rt_nh(struct fib6_config * cfg,struct fib6_info * f6i)40135b98324eSDavid Ahern static int ip6_del_cached_rt_nh(struct fib6_config *cfg, struct fib6_info *f6i)
40145b98324eSDavid Ahern {
40155b98324eSDavid Ahern struct fib6_nh_del_cached_rt_arg arg = {
40165b98324eSDavid Ahern .cfg = cfg,
40175b98324eSDavid Ahern .f6i = f6i
40185b98324eSDavid Ahern };
40195b98324eSDavid Ahern
40205b98324eSDavid Ahern return nexthop_for_each_fib6_nh(f6i->nh, fib6_nh_del_cached_rt, &arg);
40215b98324eSDavid Ahern }
40225b98324eSDavid Ahern
ip6_route_del(struct fib6_config * cfg,struct netlink_ext_ack * extack)4023333c4301SDavid Ahern static int ip6_route_del(struct fib6_config *cfg,
4024333c4301SDavid Ahern struct netlink_ext_ack *extack)
40251da177e4SLinus Torvalds {
4026c71099acSThomas Graf struct fib6_table *table;
40278d1c802bSDavid Ahern struct fib6_info *rt;
40281da177e4SLinus Torvalds struct fib6_node *fn;
40291da177e4SLinus Torvalds int err = -ESRCH;
40301da177e4SLinus Torvalds
40315578689aSDaniel Lezcano table = fib6_get_table(cfg->fc_nlinfo.nl_net, cfg->fc_table);
4032d5d531cbSDavid Ahern if (!table) {
4033d5d531cbSDavid Ahern NL_SET_ERR_MSG(extack, "FIB table does not exist");
4034c71099acSThomas Graf return err;
4035d5d531cbSDavid Ahern }
40361da177e4SLinus Torvalds
403766f5d6ceSWei Wang rcu_read_lock();
4038c71099acSThomas Graf
4039c71099acSThomas Graf fn = fib6_locate(&table->tb6_root,
404086872cb5SThomas Graf &cfg->fc_dst, cfg->fc_dst_len,
404138fbeeeeSWei Wang &cfg->fc_src, cfg->fc_src_len,
40422b760fcfSWei Wang !(cfg->fc_flags & RTF_CACHE));
40431da177e4SLinus Torvalds
40441da177e4SLinus Torvalds if (fn) {
404566f5d6ceSWei Wang for_each_fib6_node_rt_rcu(fn) {
4046ad1601aeSDavid Ahern struct fib6_nh *nh;
4047ad1601aeSDavid Ahern
40483401bfb1SStefano Brivio if (rt->nh && cfg->fc_nh_id &&
40493401bfb1SStefano Brivio rt->nh->id != cfg->fc_nh_id)
40505b98324eSDavid Ahern continue;
405123fb93a4SDavid Ahern
40525b98324eSDavid Ahern if (cfg->fc_flags & RTF_CACHE) {
40535b98324eSDavid Ahern int rc = 0;
40545b98324eSDavid Ahern
40555b98324eSDavid Ahern if (rt->nh) {
40565b98324eSDavid Ahern rc = ip6_del_cached_rt_nh(cfg, rt);
40575b98324eSDavid Ahern } else if (cfg->fc_nh_id) {
40585b98324eSDavid Ahern continue;
40595b98324eSDavid Ahern } else {
40605b98324eSDavid Ahern nh = rt->fib6_nh;
40610fa6efc5SDavid Ahern rc = ip6_del_cached_rt(cfg, rt, nh);
40625b98324eSDavid Ahern }
40639e575010SEric Dumazet if (rc != -ESRCH) {
40649e575010SEric Dumazet rcu_read_unlock();
406523fb93a4SDavid Ahern return rc;
406623fb93a4SDavid Ahern }
40671f56a01fSMartin KaFai Lau continue;
40682b760fcfSWei Wang }
4069ad1601aeSDavid Ahern
40705b98324eSDavid Ahern if (cfg->fc_metric && cfg->fc_metric != rt->fib6_metric)
40715b98324eSDavid Ahern continue;
40725b98324eSDavid Ahern if (cfg->fc_protocol &&
40735b98324eSDavid Ahern cfg->fc_protocol != rt->fib6_protocol)
40745b98324eSDavid Ahern continue;
40755b98324eSDavid Ahern
40765b98324eSDavid Ahern if (rt->nh) {
40775b98324eSDavid Ahern if (!fib6_info_hold_safe(rt))
40785b98324eSDavid Ahern continue;
40795b98324eSDavid Ahern rcu_read_unlock();
40805b98324eSDavid Ahern
40815b98324eSDavid Ahern return __ip6_del_rt(rt, &cfg->fc_nlinfo);
40825b98324eSDavid Ahern }
40835b98324eSDavid Ahern if (cfg->fc_nh_id)
40845b98324eSDavid Ahern continue;
40855b98324eSDavid Ahern
40865b98324eSDavid Ahern nh = rt->fib6_nh;
408786872cb5SThomas Graf if (cfg->fc_ifindex &&
4088ad1601aeSDavid Ahern (!nh->fib_nh_dev ||
4089ad1601aeSDavid Ahern nh->fib_nh_dev->ifindex != cfg->fc_ifindex))
40901da177e4SLinus Torvalds continue;
409186872cb5SThomas Graf if (cfg->fc_flags & RTF_GATEWAY &&
4092ad1601aeSDavid Ahern !ipv6_addr_equal(&cfg->fc_gateway, &nh->fib_nh_gw6))
40931da177e4SLinus Torvalds continue;
4094e873e4b9SWei Wang if (!fib6_info_hold_safe(rt))
4095e873e4b9SWei Wang continue;
409666f5d6ceSWei Wang rcu_read_unlock();
40971da177e4SLinus Torvalds
40980ae81335SDavid Ahern /* if gateway was specified only delete the one hop */
40990ae81335SDavid Ahern if (cfg->fc_flags & RTF_GATEWAY)
410086872cb5SThomas Graf return __ip6_del_rt(rt, &cfg->fc_nlinfo);
41010ae81335SDavid Ahern
41020ae81335SDavid Ahern return __ip6_del_rt_siblings(rt, cfg);
41031da177e4SLinus Torvalds }
41041da177e4SLinus Torvalds }
410566f5d6ceSWei Wang rcu_read_unlock();
41061da177e4SLinus Torvalds
41071da177e4SLinus Torvalds return err;
41081da177e4SLinus Torvalds }
41091da177e4SLinus Torvalds
rt6_do_redirect(struct dst_entry * dst,struct sock * sk,struct sk_buff * skb)41106700c270SDavid S. Miller static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb)
4111a6279458SYOSHIFUJI Hideaki {
4112a6279458SYOSHIFUJI Hideaki struct netevent_redirect netevent;
4113e8599ff4SDavid S. Miller struct rt6_info *rt, *nrt = NULL;
411485bd05deSDavid Ahern struct fib6_result res = {};
4115e8599ff4SDavid S. Miller struct ndisc_options ndopts;
4116e8599ff4SDavid S. Miller struct inet6_dev *in6_dev;
4117e8599ff4SDavid S. Miller struct neighbour *neigh;
411871bcdba0SYOSHIFUJI Hideaki / 吉藤英明 struct rd_msg *msg;
41196e157b6aSDavid S. Miller int optlen, on_link;
41206e157b6aSDavid S. Miller u8 *lladdr;
4121e8599ff4SDavid S. Miller
412229a3cad5SSimon Horman optlen = skb_tail_pointer(skb) - skb_transport_header(skb);
412371bcdba0SYOSHIFUJI Hideaki / 吉藤英明 optlen -= sizeof(*msg);
4124e8599ff4SDavid S. Miller
4125e8599ff4SDavid S. Miller if (optlen < 0) {
41266e157b6aSDavid S. Miller net_dbg_ratelimited("rt6_do_redirect: packet too short\n");
4127e8599ff4SDavid S. Miller return;
4128e8599ff4SDavid S. Miller }
4129e8599ff4SDavid S. Miller
413071bcdba0SYOSHIFUJI Hideaki / 吉藤英明 msg = (struct rd_msg *)icmp6_hdr(skb);
4131e8599ff4SDavid S. Miller
413271bcdba0SYOSHIFUJI Hideaki / 吉藤英明 if (ipv6_addr_is_multicast(&msg->dest)) {
41336e157b6aSDavid S. Miller net_dbg_ratelimited("rt6_do_redirect: destination address is multicast\n");
4134e8599ff4SDavid S. Miller return;
4135e8599ff4SDavid S. Miller }
4136e8599ff4SDavid S. Miller
41376e157b6aSDavid S. Miller on_link = 0;
413871bcdba0SYOSHIFUJI Hideaki / 吉藤英明 if (ipv6_addr_equal(&msg->dest, &msg->target)) {
4139e8599ff4SDavid S. Miller on_link = 1;
414071bcdba0SYOSHIFUJI Hideaki / 吉藤英明 } else if (ipv6_addr_type(&msg->target) !=
4141e8599ff4SDavid S. Miller (IPV6_ADDR_UNICAST|IPV6_ADDR_LINKLOCAL)) {
41426e157b6aSDavid S. Miller net_dbg_ratelimited("rt6_do_redirect: target address is not link-local unicast\n");
4143e8599ff4SDavid S. Miller return;
4144e8599ff4SDavid S. Miller }
4145e8599ff4SDavid S. Miller
4146e8599ff4SDavid S. Miller in6_dev = __in6_dev_get(skb->dev);
4147e8599ff4SDavid S. Miller if (!in6_dev)
4148e8599ff4SDavid S. Miller return;
4149e8599ff4SDavid S. Miller if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects)
4150e8599ff4SDavid S. Miller return;
4151e8599ff4SDavid S. Miller
4152e8599ff4SDavid S. Miller /* RFC2461 8.1:
4153e8599ff4SDavid S. Miller * The IP source address of the Redirect MUST be the same as the current
4154e8599ff4SDavid S. Miller * first-hop router for the specified ICMP Destination Address.
4155e8599ff4SDavid S. Miller */
4156e8599ff4SDavid S. Miller
4157f997c55cSAlexander Aring if (!ndisc_parse_options(skb->dev, msg->opt, optlen, &ndopts)) {
4158e8599ff4SDavid S. Miller net_dbg_ratelimited("rt6_redirect: invalid ND options\n");
4159e8599ff4SDavid S. Miller return;
4160e8599ff4SDavid S. Miller }
41616e157b6aSDavid S. Miller
41626e157b6aSDavid S. Miller lladdr = NULL;
4163e8599ff4SDavid S. Miller if (ndopts.nd_opts_tgt_lladdr) {
4164e8599ff4SDavid S. Miller lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr,
4165e8599ff4SDavid S. Miller skb->dev);
4166e8599ff4SDavid S. Miller if (!lladdr) {
4167e8599ff4SDavid S. Miller net_dbg_ratelimited("rt6_redirect: invalid link-layer address length\n");
4168e8599ff4SDavid S. Miller return;
4169e8599ff4SDavid S. Miller }
4170e8599ff4SDavid S. Miller }
4171e8599ff4SDavid S. Miller
4172797a4c1fSEric Dumazet rt = dst_rt6_info(dst);
4173ec13ad1dSMatthias Schiffer if (rt->rt6i_flags & RTF_REJECT) {
41746e157b6aSDavid S. Miller net_dbg_ratelimited("rt6_redirect: source isn't a valid nexthop for redirect target\n");
41756e157b6aSDavid S. Miller return;
41766e157b6aSDavid S. Miller }
41776e157b6aSDavid S. Miller
41786e157b6aSDavid S. Miller /* Redirect received -> path was valid.
41796e157b6aSDavid S. Miller * Look, redirects are sent only in response to data packets,
41806e157b6aSDavid S. Miller * so that this nexthop apparently is reachable. --ANK
41816e157b6aSDavid S. Miller */
41820dec879fSJulian Anastasov dst_confirm_neigh(&rt->dst, &ipv6_hdr(skb)->saddr);
41836e157b6aSDavid S. Miller
418471bcdba0SYOSHIFUJI Hideaki / 吉藤英明 neigh = __neigh_lookup(&nd_tbl, &msg->target, skb->dev, 1);
4185e8599ff4SDavid S. Miller if (!neigh)
4186e8599ff4SDavid S. Miller return;
4187e8599ff4SDavid S. Miller
41881da177e4SLinus Torvalds /*
41891da177e4SLinus Torvalds * We have finally decided to accept it.
41901da177e4SLinus Torvalds */
41911da177e4SLinus Torvalds
4192f997c55cSAlexander Aring ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
41931da177e4SLinus Torvalds NEIGH_UPDATE_F_WEAK_OVERRIDE|
41941da177e4SLinus Torvalds NEIGH_UPDATE_F_OVERRIDE|
41951da177e4SLinus Torvalds (on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
4196f997c55cSAlexander Aring NEIGH_UPDATE_F_ISROUTER)),
4197f997c55cSAlexander Aring NDISC_REDIRECT, &ndopts);
41981da177e4SLinus Torvalds
41994d85cd0cSDavid Ahern rcu_read_lock();
420085bd05deSDavid Ahern res.f6i = rcu_dereference(rt->from);
4201ff24e498SDavid S. Miller if (!res.f6i)
4202886b7a50SMartin KaFai Lau goto out;
42038a14e46fSDavid Ahern
420449d5b8efSDavid Ahern if (res.f6i->nh) {
420549d5b8efSDavid Ahern struct fib6_nh_match_arg arg = {
420649d5b8efSDavid Ahern .dev = dst->dev,
420749d5b8efSDavid Ahern .gw = &rt->rt6i_gateway,
420849d5b8efSDavid Ahern };
420949d5b8efSDavid Ahern
421049d5b8efSDavid Ahern nexthop_for_each_fib6_nh(res.f6i->nh,
421149d5b8efSDavid Ahern fib6_nh_find_match, &arg);
421249d5b8efSDavid Ahern
421349d5b8efSDavid Ahern /* fib6_info uses a nexthop that does not have fib6_nh
421449d5b8efSDavid Ahern * using the dst->dev. Should be impossible
421549d5b8efSDavid Ahern */
421649d5b8efSDavid Ahern if (!arg.match)
421749d5b8efSDavid Ahern goto out;
421849d5b8efSDavid Ahern res.nh = arg.match;
421949d5b8efSDavid Ahern } else {
42201cf844c7SDavid Ahern res.nh = res.f6i->fib6_nh;
422149d5b8efSDavid Ahern }
422249d5b8efSDavid Ahern
42237d21fec9SDavid Ahern res.fib6_flags = res.f6i->fib6_flags;
42247d21fec9SDavid Ahern res.fib6_type = res.f6i->fib6_type;
422585bd05deSDavid Ahern nrt = ip6_rt_cache_alloc(&res, &msg->dest, NULL);
422638308473SDavid S. Miller if (!nrt)
42271da177e4SLinus Torvalds goto out;
42281da177e4SLinus Torvalds
42291da177e4SLinus Torvalds nrt->rt6i_flags = RTF_GATEWAY|RTF_UP|RTF_DYNAMIC|RTF_CACHE;
42301da177e4SLinus Torvalds if (on_link)
42311da177e4SLinus Torvalds nrt->rt6i_flags &= ~RTF_GATEWAY;
42321da177e4SLinus Torvalds
42334e3fd7a0SAlexey Dobriyan nrt->rt6i_gateway = *(struct in6_addr *)neigh->primary_key;
42341da177e4SLinus Torvalds
4235886b7a50SMartin KaFai Lau /* rt6_insert_exception() will take care of duplicated exceptions */
42365012f0a5SDavid Ahern if (rt6_insert_exception(nrt, &res)) {
42372b760fcfSWei Wang dst_release_immediate(&nrt->dst);
42382b760fcfSWei Wang goto out;
42392b760fcfSWei Wang }
42401da177e4SLinus Torvalds
4241d8d1f30bSChangli Gao netevent.old = &rt->dst;
4242d8d1f30bSChangli Gao netevent.new = &nrt->dst;
424371bcdba0SYOSHIFUJI Hideaki / 吉藤英明 netevent.daddr = &msg->dest;
424460592833SYOSHIFUJI Hideaki / 吉藤英明 netevent.neigh = neigh;
42458d71740cSTom Tucker call_netevent_notifiers(NETEVENT_REDIRECT, &netevent);
42468d71740cSTom Tucker
42471da177e4SLinus Torvalds out:
4248886b7a50SMartin KaFai Lau rcu_read_unlock();
4249e8599ff4SDavid S. Miller neigh_release(neigh);
42506e157b6aSDavid S. Miller }
42516e157b6aSDavid S. Miller
425270ceb4f5SYOSHIFUJI Hideaki #ifdef CONFIG_IPV6_ROUTE_INFO
rt6_get_route_info(struct net * net,const struct in6_addr * prefix,int prefixlen,const struct in6_addr * gwaddr,struct net_device * dev)42538d1c802bSDavid Ahern static struct fib6_info *rt6_get_route_info(struct net *net,
4254b71d1d42SEric Dumazet const struct in6_addr *prefix, int prefixlen,
4255830218c1SDavid Ahern const struct in6_addr *gwaddr,
4256830218c1SDavid Ahern struct net_device *dev)
425770ceb4f5SYOSHIFUJI Hideaki {
4258830218c1SDavid Ahern u32 tb_id = l3mdev_fib_table(dev) ? : RT6_TABLE_INFO;
4259830218c1SDavid Ahern int ifindex = dev->ifindex;
426070ceb4f5SYOSHIFUJI Hideaki struct fib6_node *fn;
42618d1c802bSDavid Ahern struct fib6_info *rt = NULL;
4262c71099acSThomas Graf struct fib6_table *table;
426370ceb4f5SYOSHIFUJI Hideaki
4264830218c1SDavid Ahern table = fib6_get_table(net, tb_id);
426538308473SDavid S. Miller if (!table)
4266c71099acSThomas Graf return NULL;
4267c71099acSThomas Graf
426866f5d6ceSWei Wang rcu_read_lock();
426938fbeeeeSWei Wang fn = fib6_locate(&table->tb6_root, prefix, prefixlen, NULL, 0, true);
427070ceb4f5SYOSHIFUJI Hideaki if (!fn)
427170ceb4f5SYOSHIFUJI Hideaki goto out;
427270ceb4f5SYOSHIFUJI Hideaki
427366f5d6ceSWei Wang for_each_fib6_node_rt_rcu(fn) {
4274f88d8ea6SDavid Ahern /* these routes do not use nexthops */
4275f88d8ea6SDavid Ahern if (rt->nh)
4276f88d8ea6SDavid Ahern continue;
42771cf844c7SDavid Ahern if (rt->fib6_nh->fib_nh_dev->ifindex != ifindex)
427870ceb4f5SYOSHIFUJI Hideaki continue;
42792b2450caSDavid Ahern if (!(rt->fib6_flags & RTF_ROUTEINFO) ||
42801cf844c7SDavid Ahern !rt->fib6_nh->fib_nh_gw_family)
428170ceb4f5SYOSHIFUJI Hideaki continue;
42821cf844c7SDavid Ahern if (!ipv6_addr_equal(&rt->fib6_nh->fib_nh_gw6, gwaddr))
428370ceb4f5SYOSHIFUJI Hideaki continue;
4284e873e4b9SWei Wang if (!fib6_info_hold_safe(rt))
4285e873e4b9SWei Wang continue;
428670ceb4f5SYOSHIFUJI Hideaki break;
428770ceb4f5SYOSHIFUJI Hideaki }
428870ceb4f5SYOSHIFUJI Hideaki out:
428966f5d6ceSWei Wang rcu_read_unlock();
429070ceb4f5SYOSHIFUJI Hideaki return rt;
429170ceb4f5SYOSHIFUJI Hideaki }
429270ceb4f5SYOSHIFUJI Hideaki
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)42938d1c802bSDavid Ahern static struct fib6_info *rt6_add_route_info(struct net *net,
4294b71d1d42SEric Dumazet const struct in6_addr *prefix, int prefixlen,
4295830218c1SDavid Ahern const struct in6_addr *gwaddr,
4296830218c1SDavid Ahern struct net_device *dev,
429795c96174SEric Dumazet unsigned int pref)
429870ceb4f5SYOSHIFUJI Hideaki {
429986872cb5SThomas Graf struct fib6_config cfg = {
4300238fc7eaSRami Rosen .fc_metric = IP6_RT_PRIO_USER,
4301830218c1SDavid Ahern .fc_ifindex = dev->ifindex,
430286872cb5SThomas Graf .fc_dst_len = prefixlen,
430386872cb5SThomas Graf .fc_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_ROUTEINFO |
430486872cb5SThomas Graf RTF_UP | RTF_PREF(pref),
4305b91d5329SXin Long .fc_protocol = RTPROT_RA,
4306e8478e80SDavid Ahern .fc_type = RTN_UNICAST,
430715e47304SEric W. Biederman .fc_nlinfo.portid = 0,
4308efa2cea0SDaniel Lezcano .fc_nlinfo.nlh = NULL,
4309efa2cea0SDaniel Lezcano .fc_nlinfo.nl_net = net,
431086872cb5SThomas Graf };
431170ceb4f5SYOSHIFUJI Hideaki
431291b2c9a0SXu Wang cfg.fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_INFO;
43134e3fd7a0SAlexey Dobriyan cfg.fc_dst = *prefix;
43144e3fd7a0SAlexey Dobriyan cfg.fc_gateway = *gwaddr;
431586872cb5SThomas Graf
4316e317da96SYOSHIFUJI Hideaki /* We should treat it as a default route if prefix length is 0. */
4317e317da96SYOSHIFUJI Hideaki if (!prefixlen)
431886872cb5SThomas Graf cfg.fc_flags |= RTF_DEFAULT;
431970ceb4f5SYOSHIFUJI Hideaki
4320acb54e3cSDavid Ahern ip6_route_add(&cfg, GFP_ATOMIC, NULL);
432170ceb4f5SYOSHIFUJI Hideaki
4322830218c1SDavid Ahern return rt6_get_route_info(net, prefix, prefixlen, gwaddr, dev);
432370ceb4f5SYOSHIFUJI Hideaki }
432470ceb4f5SYOSHIFUJI Hideaki #endif
432570ceb4f5SYOSHIFUJI Hideaki
rt6_get_dflt_router(struct net * net,const struct in6_addr * addr,struct net_device * dev)43268d1c802bSDavid Ahern struct fib6_info *rt6_get_dflt_router(struct net *net,
4327afb1d4b5SDavid Ahern const struct in6_addr *addr,
4328afb1d4b5SDavid Ahern struct net_device *dev)
43291da177e4SLinus Torvalds {
4330830218c1SDavid Ahern u32 tb_id = l3mdev_fib_table(dev) ? : RT6_TABLE_DFLT;
43318d1c802bSDavid Ahern struct fib6_info *rt;
4332c71099acSThomas Graf struct fib6_table *table;
43331da177e4SLinus Torvalds
4334afb1d4b5SDavid Ahern table = fib6_get_table(net, tb_id);
433538308473SDavid S. Miller if (!table)
4336c71099acSThomas Graf return NULL;
43371da177e4SLinus Torvalds
433866f5d6ceSWei Wang rcu_read_lock();
433966f5d6ceSWei Wang for_each_fib6_node_rt_rcu(&table->tb6_root) {
4340f88d8ea6SDavid Ahern struct fib6_nh *nh;
4341ad1601aeSDavid Ahern
4342f88d8ea6SDavid Ahern /* RA routes do not use nexthops */
4343f88d8ea6SDavid Ahern if (rt->nh)
4344f88d8ea6SDavid Ahern continue;
4345f88d8ea6SDavid Ahern
4346f88d8ea6SDavid Ahern nh = rt->fib6_nh;
4347ad1601aeSDavid Ahern if (dev == nh->fib_nh_dev &&
434893c2fb25SDavid Ahern ((rt->fib6_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) &&
4349ad1601aeSDavid Ahern ipv6_addr_equal(&nh->fib_nh_gw6, addr))
43501da177e4SLinus Torvalds break;
43511da177e4SLinus Torvalds }
4352e873e4b9SWei Wang if (rt && !fib6_info_hold_safe(rt))
4353e873e4b9SWei Wang rt = NULL;
435466f5d6ceSWei Wang rcu_read_unlock();
43551da177e4SLinus Torvalds return rt;
43561da177e4SLinus Torvalds }
43571da177e4SLinus Torvalds
rt6_add_dflt_router(struct net * net,const struct in6_addr * gwaddr,struct net_device * dev,unsigned int pref,u32 defrtr_usr_metric)43588d1c802bSDavid Ahern struct fib6_info *rt6_add_dflt_router(struct net *net,
4359afb1d4b5SDavid Ahern const struct in6_addr *gwaddr,
4360ebacaaa0SYOSHIFUJI Hideaki struct net_device *dev,
43616b2e04bcSPraveen Chaudhary unsigned int pref,
43626b2e04bcSPraveen Chaudhary u32 defrtr_usr_metric)
43631da177e4SLinus Torvalds {
436486872cb5SThomas Graf struct fib6_config cfg = {
4365ca254490SDavid Ahern .fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_DFLT,
43666b2e04bcSPraveen Chaudhary .fc_metric = defrtr_usr_metric,
436786872cb5SThomas Graf .fc_ifindex = dev->ifindex,
436886872cb5SThomas Graf .fc_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT |
436986872cb5SThomas Graf RTF_UP | RTF_EXPIRES | RTF_PREF(pref),
4370b91d5329SXin Long .fc_protocol = RTPROT_RA,
4371e8478e80SDavid Ahern .fc_type = RTN_UNICAST,
437215e47304SEric W. Biederman .fc_nlinfo.portid = 0,
43735578689aSDaniel Lezcano .fc_nlinfo.nlh = NULL,
4374afb1d4b5SDavid Ahern .fc_nlinfo.nl_net = net,
437586872cb5SThomas Graf };
43761da177e4SLinus Torvalds
43774e3fd7a0SAlexey Dobriyan cfg.fc_gateway = *gwaddr;
43781da177e4SLinus Torvalds
4379acb54e3cSDavid Ahern if (!ip6_route_add(&cfg, GFP_ATOMIC, NULL)) {
4380830218c1SDavid Ahern struct fib6_table *table;
4381830218c1SDavid Ahern
4382830218c1SDavid Ahern table = fib6_get_table(dev_net(dev), cfg.fc_table);
4383830218c1SDavid Ahern if (table)
4384830218c1SDavid Ahern table->flags |= RT6_TABLE_HAS_DFLT_ROUTER;
4385830218c1SDavid Ahern }
43861da177e4SLinus Torvalds
4387afb1d4b5SDavid Ahern return rt6_get_dflt_router(net, gwaddr, dev);
43881da177e4SLinus Torvalds }
43891da177e4SLinus Torvalds
__rt6_purge_dflt_routers(struct net * net,struct fib6_table * table)4390afb1d4b5SDavid Ahern static void __rt6_purge_dflt_routers(struct net *net,
4391afb1d4b5SDavid Ahern struct fib6_table *table)
43921da177e4SLinus Torvalds {
43938d1c802bSDavid Ahern struct fib6_info *rt;
43941da177e4SLinus Torvalds
43951da177e4SLinus Torvalds restart:
439666f5d6ceSWei Wang rcu_read_lock();
439766f5d6ceSWei Wang for_each_fib6_node_rt_rcu(&table->tb6_root) {
4398dcd1f572SDavid Ahern struct net_device *dev = fib6_info_nh_dev(rt);
4399dcd1f572SDavid Ahern struct inet6_dev *idev = dev ? __in6_dev_get(dev) : NULL;
4400dcd1f572SDavid Ahern
440193c2fb25SDavid Ahern if (rt->fib6_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
4402e873e4b9SWei Wang (!idev || idev->cnf.accept_ra != 2) &&
4403e873e4b9SWei Wang fib6_info_hold_safe(rt)) {
440466f5d6ceSWei Wang rcu_read_unlock();
440511dd74b3SRoopa Prabhu ip6_del_rt(net, rt, false);
44061da177e4SLinus Torvalds goto restart;
44071da177e4SLinus Torvalds }
44081da177e4SLinus Torvalds }
440966f5d6ceSWei Wang rcu_read_unlock();
4410830218c1SDavid Ahern
4411830218c1SDavid Ahern table->flags &= ~RT6_TABLE_HAS_DFLT_ROUTER;
4412830218c1SDavid Ahern }
4413830218c1SDavid Ahern
rt6_purge_dflt_routers(struct net * net)4414830218c1SDavid Ahern void rt6_purge_dflt_routers(struct net *net)
4415830218c1SDavid Ahern {
4416830218c1SDavid Ahern struct fib6_table *table;
4417830218c1SDavid Ahern struct hlist_head *head;
4418830218c1SDavid Ahern unsigned int h;
4419830218c1SDavid Ahern
4420830218c1SDavid Ahern rcu_read_lock();
4421830218c1SDavid Ahern
4422830218c1SDavid Ahern for (h = 0; h < FIB6_TABLE_HASHSZ; h++) {
4423830218c1SDavid Ahern head = &net->ipv6.fib_table_hash[h];
4424830218c1SDavid Ahern hlist_for_each_entry_rcu(table, head, tb6_hlist) {
4425830218c1SDavid Ahern if (table->flags & RT6_TABLE_HAS_DFLT_ROUTER)
4426afb1d4b5SDavid Ahern __rt6_purge_dflt_routers(net, table);
4427830218c1SDavid Ahern }
4428830218c1SDavid Ahern }
4429830218c1SDavid Ahern
4430830218c1SDavid Ahern rcu_read_unlock();
44311da177e4SLinus Torvalds }
44321da177e4SLinus Torvalds
rtmsg_to_fib6_config(struct net * net,struct in6_rtmsg * rtmsg,struct fib6_config * cfg)44335578689aSDaniel Lezcano static void rtmsg_to_fib6_config(struct net *net,
44345578689aSDaniel Lezcano struct in6_rtmsg *rtmsg,
443586872cb5SThomas Graf struct fib6_config *cfg)
443686872cb5SThomas Graf {
44378823a3acSMaciej Żenczykowski *cfg = (struct fib6_config){
44388823a3acSMaciej Żenczykowski .fc_table = l3mdev_fib_table_by_index(net, rtmsg->rtmsg_ifindex) ?
44398823a3acSMaciej Żenczykowski : RT6_TABLE_MAIN,
44408823a3acSMaciej Żenczykowski .fc_ifindex = rtmsg->rtmsg_ifindex,
4441400b8fb6Sxu xin .fc_metric = rtmsg->rtmsg_metric,
44428823a3acSMaciej Żenczykowski .fc_expires = rtmsg->rtmsg_info,
44438823a3acSMaciej Żenczykowski .fc_dst_len = rtmsg->rtmsg_dst_len,
44448823a3acSMaciej Żenczykowski .fc_src_len = rtmsg->rtmsg_src_len,
44458823a3acSMaciej Żenczykowski .fc_flags = rtmsg->rtmsg_flags,
44468823a3acSMaciej Żenczykowski .fc_type = rtmsg->rtmsg_type,
444786872cb5SThomas Graf
44488823a3acSMaciej Żenczykowski .fc_nlinfo.nl_net = net,
444986872cb5SThomas Graf
44508823a3acSMaciej Żenczykowski .fc_dst = rtmsg->rtmsg_dst,
44518823a3acSMaciej Żenczykowski .fc_src = rtmsg->rtmsg_src,
44528823a3acSMaciej Żenczykowski .fc_gateway = rtmsg->rtmsg_gateway,
44538823a3acSMaciej Żenczykowski };
445486872cb5SThomas Graf }
445586872cb5SThomas Graf
ipv6_route_ioctl(struct net * net,unsigned int cmd,struct in6_rtmsg * rtmsg)44567c1552daSChristoph Hellwig int ipv6_route_ioctl(struct net *net, unsigned int cmd, struct in6_rtmsg *rtmsg)
44571da177e4SLinus Torvalds {
445886872cb5SThomas Graf struct fib6_config cfg;
44591da177e4SLinus Torvalds int err;
44601da177e4SLinus Torvalds
44617c1552daSChristoph Hellwig if (cmd != SIOCADDRT && cmd != SIOCDELRT)
44627c1552daSChristoph Hellwig return -EINVAL;
4463af31f412SEric W. Biederman if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
44641da177e4SLinus Torvalds return -EPERM;
44651da177e4SLinus Torvalds
44667c1552daSChristoph Hellwig rtmsg_to_fib6_config(net, rtmsg, &cfg);
446786872cb5SThomas Graf
44681da177e4SLinus Torvalds rtnl_lock();
44691da177e4SLinus Torvalds switch (cmd) {
44701da177e4SLinus Torvalds case SIOCADDRT:
4471400b8fb6Sxu xin /* Only do the default setting of fc_metric in route adding */
4472400b8fb6Sxu xin if (cfg.fc_metric == 0)
4473400b8fb6Sxu xin cfg.fc_metric = IP6_RT_PRIO_USER;
4474acb54e3cSDavid Ahern err = ip6_route_add(&cfg, GFP_KERNEL, NULL);
44751da177e4SLinus Torvalds break;
44761da177e4SLinus Torvalds case SIOCDELRT:
4477333c4301SDavid Ahern err = ip6_route_del(&cfg, NULL);
44781da177e4SLinus Torvalds break;
44791da177e4SLinus Torvalds }
44801da177e4SLinus Torvalds rtnl_unlock();
44811da177e4SLinus Torvalds return err;
44823ff50b79SStephen Hemminger }
44831da177e4SLinus Torvalds
44841da177e4SLinus Torvalds /*
44851da177e4SLinus Torvalds * Drop the packet on the floor
44861da177e4SLinus Torvalds */
44871da177e4SLinus Torvalds
ip6_pkt_drop(struct sk_buff * skb,u8 code,int ipstats_mib_noroutes)4488d5fdd6baSBrian Haley static int ip6_pkt_drop(struct sk_buff *skb, u8 code, int ipstats_mib_noroutes)
44891da177e4SLinus Torvalds {
4490adf30907SEric Dumazet struct dst_entry *dst = skb_dst(skb);
44911d3fd8a1SStephen Suryaputra struct net *net = dev_net(dst->dev);
44921d3fd8a1SStephen Suryaputra struct inet6_dev *idev;
44933ae42cc8SMenglong Dong SKB_DR(reason);
44941d3fd8a1SStephen Suryaputra int type;
44951d3fd8a1SStephen Suryaputra
44961158f79fSDavid Ahern if (netif_is_l3_master(skb->dev) ||
44971d3fd8a1SStephen Suryaputra dst->dev == net->loopback_dev)
44981d3fd8a1SStephen Suryaputra idev = __in6_dev_get_safely(dev_get_by_index_rcu(net, IP6CB(skb)->iif));
44991d3fd8a1SStephen Suryaputra else
45001d3fd8a1SStephen Suryaputra idev = ip6_dst_idev(dst);
45011d3fd8a1SStephen Suryaputra
4502612f09e8SYOSHIFUJI Hideaki switch (ipstats_mib_noroutes) {
4503612f09e8SYOSHIFUJI Hideaki case IPSTATS_MIB_INNOROUTES:
45040660e03fSArnaldo Carvalho de Melo type = ipv6_addr_type(&ipv6_hdr(skb)->daddr);
450545bb0060SUlrich Weber if (type == IPV6_ADDR_ANY) {
45063ae42cc8SMenglong Dong SKB_DR_SET(reason, IP_INADDRERRORS);
45071d3fd8a1SStephen Suryaputra IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS);
4508612f09e8SYOSHIFUJI Hideaki break;
4509612f09e8SYOSHIFUJI Hideaki }
45103ae42cc8SMenglong Dong SKB_DR_SET(reason, IP_INNOROUTES);
4511a8eceea8SJoe Perches fallthrough;
4512612f09e8SYOSHIFUJI Hideaki case IPSTATS_MIB_OUTNOROUTES:
45133ae42cc8SMenglong Dong SKB_DR_OR(reason, IP_OUTNOROUTES);
45141d3fd8a1SStephen Suryaputra IP6_INC_STATS(net, idev, ipstats_mib_noroutes);
4515612f09e8SYOSHIFUJI Hideaki break;
4516612f09e8SYOSHIFUJI Hideaki }
45171d3fd8a1SStephen Suryaputra
45181d3fd8a1SStephen Suryaputra /* Start over by dropping the dst for l3mdev case */
45191d3fd8a1SStephen Suryaputra if (netif_is_l3_master(skb->dev))
45201d3fd8a1SStephen Suryaputra skb_dst_drop(skb);
45211d3fd8a1SStephen Suryaputra
45223ffe533cSAlexey Dobriyan icmpv6_send(skb, ICMPV6_DEST_UNREACH, code, 0);
45233ae42cc8SMenglong Dong kfree_skb_reason(skb, reason);
45241da177e4SLinus Torvalds return 0;
45251da177e4SLinus Torvalds }
45261da177e4SLinus Torvalds
ip6_pkt_discard(struct sk_buff * skb)45279ce8ade0SThomas Graf static int ip6_pkt_discard(struct sk_buff *skb)
45289ce8ade0SThomas Graf {
4529612f09e8SYOSHIFUJI Hideaki return ip6_pkt_drop(skb, ICMPV6_NOROUTE, IPSTATS_MIB_INNOROUTES);
45309ce8ade0SThomas Graf }
45319ce8ade0SThomas Graf
ip6_pkt_discard_out(struct net * net,struct sock * sk,struct sk_buff * skb)4532ede2059dSEric W. Biederman static int ip6_pkt_discard_out(struct net *net, struct sock *sk, struct sk_buff *skb)
45331da177e4SLinus Torvalds {
4534adf30907SEric Dumazet skb->dev = skb_dst(skb)->dev;
4535612f09e8SYOSHIFUJI Hideaki return ip6_pkt_drop(skb, ICMPV6_NOROUTE, IPSTATS_MIB_OUTNOROUTES);
45361da177e4SLinus Torvalds }
45371da177e4SLinus Torvalds
ip6_pkt_prohibit(struct sk_buff * skb)45389ce8ade0SThomas Graf static int ip6_pkt_prohibit(struct sk_buff *skb)
45399ce8ade0SThomas Graf {
4540612f09e8SYOSHIFUJI Hideaki return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED, IPSTATS_MIB_INNOROUTES);
45419ce8ade0SThomas Graf }
45429ce8ade0SThomas Graf
ip6_pkt_prohibit_out(struct net * net,struct sock * sk,struct sk_buff * skb)4543ede2059dSEric W. Biederman static int ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb)
45449ce8ade0SThomas Graf {
4545adf30907SEric Dumazet skb->dev = skb_dst(skb)->dev;
4546612f09e8SYOSHIFUJI Hideaki return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED, IPSTATS_MIB_OUTNOROUTES);
45479ce8ade0SThomas Graf }
45489ce8ade0SThomas Graf
45491da177e4SLinus Torvalds /*
45501da177e4SLinus Torvalds * Allocate a dst for local (unicast / anycast) address.
45511da177e4SLinus Torvalds */
45521da177e4SLinus Torvalds
addrconf_f6i_alloc(struct net * net,struct inet6_dev * idev,const struct in6_addr * addr,bool anycast,gfp_t gfp_flags,struct netlink_ext_ack * extack)4553360a9887SDavid Ahern struct fib6_info *addrconf_f6i_alloc(struct net *net,
4554afb1d4b5SDavid Ahern struct inet6_dev *idev,
45551da177e4SLinus Torvalds const struct in6_addr *addr,
45567f6c4039SHangbin Liu bool anycast, gfp_t gfp_flags,
45577f6c4039SHangbin Liu struct netlink_ext_ack *extack)
45581da177e4SLinus Torvalds {
4559c7a1ce39SDavid Ahern struct fib6_config cfg = {
4560c7a1ce39SDavid Ahern .fc_table = l3mdev_fib_table(idev->dev) ? : RT6_TABLE_LOCAL,
4561c7a1ce39SDavid Ahern .fc_ifindex = idev->dev->ifindex,
4562d55a2e37SMaciej Żenczykowski .fc_flags = RTF_UP | RTF_NONEXTHOP,
4563c7a1ce39SDavid Ahern .fc_dst = *addr,
4564c7a1ce39SDavid Ahern .fc_dst_len = 128,
4565c7a1ce39SDavid Ahern .fc_protocol = RTPROT_KERNEL,
4566c7a1ce39SDavid Ahern .fc_nlinfo.nl_net = net,
4567c7a1ce39SDavid Ahern .fc_ignore_dev_down = true,
4568c7a1ce39SDavid Ahern };
4569d55a2e37SMaciej Żenczykowski struct fib6_info *f6i;
45705f02ce24SDavid Ahern
4571e8478e80SDavid Ahern if (anycast) {
4572c7a1ce39SDavid Ahern cfg.fc_type = RTN_ANYCAST;
4573c7a1ce39SDavid Ahern cfg.fc_flags |= RTF_ANYCAST;
4574e8478e80SDavid Ahern } else {
4575c7a1ce39SDavid Ahern cfg.fc_type = RTN_LOCAL;
4576c7a1ce39SDavid Ahern cfg.fc_flags |= RTF_LOCAL;
4577e8478e80SDavid Ahern }
45781da177e4SLinus Torvalds
45797f6c4039SHangbin Liu f6i = ip6_route_info_create(&cfg, gfp_flags, extack);
45803b0dc529SNicolas Dichtel if (!IS_ERR(f6i)) {
4581d55a2e37SMaciej Żenczykowski f6i->dst_nocount = true;
45823b0dc529SNicolas Dichtel
45833b0dc529SNicolas Dichtel if (!anycast &&
45843b0dc529SNicolas Dichtel (net->ipv6.devconf_all->disable_policy ||
45853b0dc529SNicolas Dichtel idev->cnf.disable_policy))
45863b0dc529SNicolas Dichtel f6i->dst_nopolicy = true;
45873b0dc529SNicolas Dichtel }
45883b0dc529SNicolas Dichtel
4589d55a2e37SMaciej Żenczykowski return f6i;
45901da177e4SLinus Torvalds }
45911da177e4SLinus Torvalds
4592c3968a85SDaniel Walter /* remove deleted ip from prefsrc entries */
4593c3968a85SDaniel Walter struct arg_dev_net_ip {
4594c3968a85SDaniel Walter struct net *net;
4595c3968a85SDaniel Walter struct in6_addr *addr;
4596c3968a85SDaniel Walter };
4597c3968a85SDaniel Walter
fib6_remove_prefsrc(struct fib6_info * rt,void * arg)45988d1c802bSDavid Ahern static int fib6_remove_prefsrc(struct fib6_info *rt, void *arg)
4599c3968a85SDaniel Walter {
4600c3968a85SDaniel Walter struct net *net = ((struct arg_dev_net_ip *)arg)->net;
4601c3968a85SDaniel Walter struct in6_addr *addr = ((struct arg_dev_net_ip *)arg)->addr;
4602c3968a85SDaniel Walter
4603f88d8ea6SDavid Ahern if (!rt->nh &&
4604421842edSDavid Ahern rt != net->ipv6.fib6_null_entry &&
4605b358f57fSHangbin Liu ipv6_addr_equal(addr, &rt->fib6_prefsrc.addr) &&
4606b358f57fSHangbin Liu !ipv6_chk_addr(net, addr, rt->fib6_nh->fib_nh_dev, 0)) {
460760006a48SWei Wang spin_lock_bh(&rt6_exception_lock);
4608c3968a85SDaniel Walter /* remove prefsrc entry */
460993c2fb25SDavid Ahern rt->fib6_prefsrc.plen = 0;
461060006a48SWei Wang spin_unlock_bh(&rt6_exception_lock);
4611c3968a85SDaniel Walter }
4612c3968a85SDaniel Walter return 0;
4613c3968a85SDaniel Walter }
4614c3968a85SDaniel Walter
rt6_remove_prefsrc(struct inet6_ifaddr * ifp)4615c3968a85SDaniel Walter void rt6_remove_prefsrc(struct inet6_ifaddr *ifp)
4616c3968a85SDaniel Walter {
4617c3968a85SDaniel Walter struct net *net = dev_net(ifp->idev->dev);
4618c3968a85SDaniel Walter struct arg_dev_net_ip adni = {
4619c3968a85SDaniel Walter .net = net,
4620c3968a85SDaniel Walter .addr = &ifp->addr,
4621c3968a85SDaniel Walter };
46220c3584d5SLi RongQing fib6_clean_all(net, fib6_remove_prefsrc, &adni);
4623c3968a85SDaniel Walter }
4624c3968a85SDaniel Walter
46252b2450caSDavid Ahern #define RTF_RA_ROUTER (RTF_ADDRCONF | RTF_DEFAULT)
4626be7a010dSDuan Jiong
4627be7a010dSDuan Jiong /* Remove routers and update dst entries when gateway turn into host. */
fib6_clean_tohost(struct fib6_info * rt,void * arg)46288d1c802bSDavid Ahern static int fib6_clean_tohost(struct fib6_info *rt, void *arg)
4629be7a010dSDuan Jiong {
4630be7a010dSDuan Jiong struct in6_addr *gateway = (struct in6_addr *)arg;
4631f88d8ea6SDavid Ahern struct fib6_nh *nh;
4632be7a010dSDuan Jiong
4633f88d8ea6SDavid Ahern /* RA routes do not use nexthops */
4634f88d8ea6SDavid Ahern if (rt->nh)
4635f88d8ea6SDavid Ahern return 0;
4636f88d8ea6SDavid Ahern
4637f88d8ea6SDavid Ahern nh = rt->fib6_nh;
463893c2fb25SDavid Ahern if (((rt->fib6_flags & RTF_RA_ROUTER) == RTF_RA_ROUTER) &&
4639cc5c073aSDavid Ahern nh->fib_nh_gw_family && ipv6_addr_equal(gateway, &nh->fib_nh_gw6))
4640be7a010dSDuan Jiong return -1;
4641b16cb459SWei Wang
4642b16cb459SWei Wang /* Further clean up cached routes in exception table.
4643b16cb459SWei Wang * This is needed because cached route may have a different
4644b16cb459SWei Wang * gateway than its 'parent' in the case of an ip redirect.
4645b16cb459SWei Wang */
4646cc5c073aSDavid Ahern fib6_nh_exceptions_clean_tohost(nh, gateway);
4647b16cb459SWei Wang
4648be7a010dSDuan Jiong return 0;
4649be7a010dSDuan Jiong }
4650be7a010dSDuan Jiong
rt6_clean_tohost(struct net * net,struct in6_addr * gateway)4651be7a010dSDuan Jiong void rt6_clean_tohost(struct net *net, struct in6_addr *gateway)
4652be7a010dSDuan Jiong {
4653be7a010dSDuan Jiong fib6_clean_all(net, fib6_clean_tohost, gateway);
4654be7a010dSDuan Jiong }
4655be7a010dSDuan Jiong
46562127d95aSIdo Schimmel struct arg_netdev_event {
46572127d95aSIdo Schimmel const struct net_device *dev;
46584c981e28SIdo Schimmel union {
4659ecc5663cSDavid Ahern unsigned char nh_flags;
46604c981e28SIdo Schimmel unsigned long event;
46614c981e28SIdo Schimmel };
46622127d95aSIdo Schimmel };
46632127d95aSIdo Schimmel
rt6_multipath_first_sibling(const struct fib6_info * rt)46648d1c802bSDavid Ahern static struct fib6_info *rt6_multipath_first_sibling(const struct fib6_info *rt)
4665d7dedee1SIdo Schimmel {
46668d1c802bSDavid Ahern struct fib6_info *iter;
4667d7dedee1SIdo Schimmel struct fib6_node *fn;
4668d7dedee1SIdo Schimmel
466993c2fb25SDavid Ahern fn = rcu_dereference_protected(rt->fib6_node,
467093c2fb25SDavid Ahern lockdep_is_held(&rt->fib6_table->tb6_lock));
4671d7dedee1SIdo Schimmel iter = rcu_dereference_protected(fn->leaf,
467293c2fb25SDavid Ahern lockdep_is_held(&rt->fib6_table->tb6_lock));
4673d7dedee1SIdo Schimmel while (iter) {
467493c2fb25SDavid Ahern if (iter->fib6_metric == rt->fib6_metric &&
467533bd5ac5SDavid Ahern rt6_qualify_for_ecmp(iter))
4676d7dedee1SIdo Schimmel return iter;
46778fb11a9aSDavid Ahern iter = rcu_dereference_protected(iter->fib6_next,
467893c2fb25SDavid Ahern lockdep_is_held(&rt->fib6_table->tb6_lock));
4679d7dedee1SIdo Schimmel }
4680d7dedee1SIdo Schimmel
4681d7dedee1SIdo Schimmel return NULL;
4682d7dedee1SIdo Schimmel }
4683d7dedee1SIdo Schimmel
4684f88d8ea6SDavid Ahern /* only called for fib entries with builtin fib6_nh */
rt6_is_dead(const struct fib6_info * rt)46858d1c802bSDavid Ahern static bool rt6_is_dead(const struct fib6_info *rt)
4686d7dedee1SIdo Schimmel {
46871cf844c7SDavid Ahern if (rt->fib6_nh->fib_nh_flags & RTNH_F_DEAD ||
46881cf844c7SDavid Ahern (rt->fib6_nh->fib_nh_flags & RTNH_F_LINKDOWN &&
46891cf844c7SDavid Ahern ip6_ignore_linkdown(rt->fib6_nh->fib_nh_dev)))
4690d7dedee1SIdo Schimmel return true;
4691d7dedee1SIdo Schimmel
4692d7dedee1SIdo Schimmel return false;
4693d7dedee1SIdo Schimmel }
4694d7dedee1SIdo Schimmel
rt6_multipath_total_weight(const struct fib6_info * rt)46958d1c802bSDavid Ahern static int rt6_multipath_total_weight(const struct fib6_info *rt)
4696d7dedee1SIdo Schimmel {
46978d1c802bSDavid Ahern struct fib6_info *iter;
4698d7dedee1SIdo Schimmel int total = 0;
4699d7dedee1SIdo Schimmel
4700d7dedee1SIdo Schimmel if (!rt6_is_dead(rt))
47011cf844c7SDavid Ahern total += rt->fib6_nh->fib_nh_weight;
4702d7dedee1SIdo Schimmel
470393c2fb25SDavid Ahern list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) {
4704d7dedee1SIdo Schimmel if (!rt6_is_dead(iter))
47051cf844c7SDavid Ahern total += iter->fib6_nh->fib_nh_weight;
4706d7dedee1SIdo Schimmel }
4707d7dedee1SIdo Schimmel
4708d7dedee1SIdo Schimmel return total;
4709d7dedee1SIdo Schimmel }
4710d7dedee1SIdo Schimmel
rt6_upper_bound_set(struct fib6_info * rt,int * weight,int total)47118d1c802bSDavid Ahern static void rt6_upper_bound_set(struct fib6_info *rt, int *weight, int total)
4712d7dedee1SIdo Schimmel {
4713d7dedee1SIdo Schimmel int upper_bound = -1;
4714d7dedee1SIdo Schimmel
4715d7dedee1SIdo Schimmel if (!rt6_is_dead(rt)) {
47161cf844c7SDavid Ahern *weight += rt->fib6_nh->fib_nh_weight;
4717d7dedee1SIdo Schimmel upper_bound = DIV_ROUND_CLOSEST_ULL((u64) (*weight) << 31,
4718d7dedee1SIdo Schimmel total) - 1;
4719d7dedee1SIdo Schimmel }
47201cf844c7SDavid Ahern atomic_set(&rt->fib6_nh->fib_nh_upper_bound, upper_bound);
4721d7dedee1SIdo Schimmel }
4722d7dedee1SIdo Schimmel
rt6_multipath_upper_bound_set(struct fib6_info * rt,int total)47238d1c802bSDavid Ahern static void rt6_multipath_upper_bound_set(struct fib6_info *rt, int total)
4724d7dedee1SIdo Schimmel {
47258d1c802bSDavid Ahern struct fib6_info *iter;
4726d7dedee1SIdo Schimmel int weight = 0;
4727d7dedee1SIdo Schimmel
4728d7dedee1SIdo Schimmel rt6_upper_bound_set(rt, &weight, total);
4729d7dedee1SIdo Schimmel
473093c2fb25SDavid Ahern list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
4731d7dedee1SIdo Schimmel rt6_upper_bound_set(iter, &weight, total);
4732d7dedee1SIdo Schimmel }
4733d7dedee1SIdo Schimmel
rt6_multipath_rebalance(struct fib6_info * rt)47348d1c802bSDavid Ahern void rt6_multipath_rebalance(struct fib6_info *rt)
4735d7dedee1SIdo Schimmel {
47368d1c802bSDavid Ahern struct fib6_info *first;
4737d7dedee1SIdo Schimmel int total;
4738d7dedee1SIdo Schimmel
4739d7dedee1SIdo Schimmel /* In case the entire multipath route was marked for flushing,
4740d7dedee1SIdo Schimmel * then there is no need to rebalance upon the removal of every
4741d7dedee1SIdo Schimmel * sibling route.
4742d7dedee1SIdo Schimmel */
474393c2fb25SDavid Ahern if (!rt->fib6_nsiblings || rt->should_flush)
4744d7dedee1SIdo Schimmel return;
4745d7dedee1SIdo Schimmel
4746d7dedee1SIdo Schimmel /* During lookup routes are evaluated in order, so we need to
4747d7dedee1SIdo Schimmel * make sure upper bounds are assigned from the first sibling
4748d7dedee1SIdo Schimmel * onwards.
4749d7dedee1SIdo Schimmel */
4750d7dedee1SIdo Schimmel first = rt6_multipath_first_sibling(rt);
4751d7dedee1SIdo Schimmel if (WARN_ON_ONCE(!first))
4752d7dedee1SIdo Schimmel return;
4753d7dedee1SIdo Schimmel
4754d7dedee1SIdo Schimmel total = rt6_multipath_total_weight(first);
4755d7dedee1SIdo Schimmel rt6_multipath_upper_bound_set(first, total);
4756d7dedee1SIdo Schimmel }
4757d7dedee1SIdo Schimmel
fib6_ifup(struct fib6_info * rt,void * p_arg)47588d1c802bSDavid Ahern static int fib6_ifup(struct fib6_info *rt, void *p_arg)
47592127d95aSIdo Schimmel {
47602127d95aSIdo Schimmel const struct arg_netdev_event *arg = p_arg;
47617aef6859SDavid Ahern struct net *net = dev_net(arg->dev);
47622127d95aSIdo Schimmel
4763f88d8ea6SDavid Ahern if (rt != net->ipv6.fib6_null_entry && !rt->nh &&
47641cf844c7SDavid Ahern rt->fib6_nh->fib_nh_dev == arg->dev) {
47651cf844c7SDavid Ahern rt->fib6_nh->fib_nh_flags &= ~arg->nh_flags;
47667aef6859SDavid Ahern fib6_update_sernum_upto_root(net, rt);
4767d7dedee1SIdo Schimmel rt6_multipath_rebalance(rt);
47681de178edSIdo Schimmel }
47692127d95aSIdo Schimmel
47702127d95aSIdo Schimmel return 0;
47712127d95aSIdo Schimmel }
47722127d95aSIdo Schimmel
rt6_sync_up(struct net_device * dev,unsigned char nh_flags)4773ecc5663cSDavid Ahern void rt6_sync_up(struct net_device *dev, unsigned char nh_flags)
47742127d95aSIdo Schimmel {
47752127d95aSIdo Schimmel struct arg_netdev_event arg = {
47762127d95aSIdo Schimmel .dev = dev,
47776802f3adSIdo Schimmel {
47782127d95aSIdo Schimmel .nh_flags = nh_flags,
47796802f3adSIdo Schimmel },
47802127d95aSIdo Schimmel };
47812127d95aSIdo Schimmel
47822127d95aSIdo Schimmel if (nh_flags & RTNH_F_DEAD && netif_carrier_ok(dev))
47832127d95aSIdo Schimmel arg.nh_flags |= RTNH_F_LINKDOWN;
47842127d95aSIdo Schimmel
47852127d95aSIdo Schimmel fib6_clean_all(dev_net(dev), fib6_ifup, &arg);
47862127d95aSIdo Schimmel }
47872127d95aSIdo Schimmel
4788f88d8ea6SDavid Ahern /* only called for fib entries with inline fib6_nh */
rt6_multipath_uses_dev(const struct fib6_info * rt,const struct net_device * dev)47898d1c802bSDavid Ahern static bool rt6_multipath_uses_dev(const struct fib6_info *rt,
47901de178edSIdo Schimmel const struct net_device *dev)
47911de178edSIdo Schimmel {
47928d1c802bSDavid Ahern struct fib6_info *iter;
47931de178edSIdo Schimmel
47941cf844c7SDavid Ahern if (rt->fib6_nh->fib_nh_dev == dev)
47951de178edSIdo Schimmel return true;
479693c2fb25SDavid Ahern list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
47971cf844c7SDavid Ahern if (iter->fib6_nh->fib_nh_dev == dev)
47981de178edSIdo Schimmel return true;
47991de178edSIdo Schimmel
48001de178edSIdo Schimmel return false;
48011de178edSIdo Schimmel }
48021de178edSIdo Schimmel
rt6_multipath_flush(struct fib6_info * rt)48038d1c802bSDavid Ahern static void rt6_multipath_flush(struct fib6_info *rt)
48041de178edSIdo Schimmel {
48058d1c802bSDavid Ahern struct fib6_info *iter;
48061de178edSIdo Schimmel
48071de178edSIdo Schimmel rt->should_flush = 1;
480893c2fb25SDavid Ahern list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
48091de178edSIdo Schimmel iter->should_flush = 1;
48101de178edSIdo Schimmel }
48111de178edSIdo Schimmel
rt6_multipath_dead_count(const struct fib6_info * rt,const struct net_device * down_dev)48128d1c802bSDavid Ahern static unsigned int rt6_multipath_dead_count(const struct fib6_info *rt,
48131de178edSIdo Schimmel const struct net_device *down_dev)
48141de178edSIdo Schimmel {
48158d1c802bSDavid Ahern struct fib6_info *iter;
48161de178edSIdo Schimmel unsigned int dead = 0;
48171de178edSIdo Schimmel
48181cf844c7SDavid Ahern if (rt->fib6_nh->fib_nh_dev == down_dev ||
48191cf844c7SDavid Ahern rt->fib6_nh->fib_nh_flags & RTNH_F_DEAD)
48201de178edSIdo Schimmel dead++;
482193c2fb25SDavid Ahern list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
48221cf844c7SDavid Ahern if (iter->fib6_nh->fib_nh_dev == down_dev ||
48231cf844c7SDavid Ahern iter->fib6_nh->fib_nh_flags & RTNH_F_DEAD)
48241de178edSIdo Schimmel dead++;
48251de178edSIdo Schimmel
48261de178edSIdo Schimmel return dead;
48271de178edSIdo Schimmel }
48281de178edSIdo Schimmel
rt6_multipath_nh_flags_set(struct fib6_info * rt,const struct net_device * dev,unsigned char nh_flags)48298d1c802bSDavid Ahern static void rt6_multipath_nh_flags_set(struct fib6_info *rt,
48301de178edSIdo Schimmel const struct net_device *dev,
4831ecc5663cSDavid Ahern unsigned char nh_flags)
48321de178edSIdo Schimmel {
48338d1c802bSDavid Ahern struct fib6_info *iter;
48341de178edSIdo Schimmel
48351cf844c7SDavid Ahern if (rt->fib6_nh->fib_nh_dev == dev)
48361cf844c7SDavid Ahern rt->fib6_nh->fib_nh_flags |= nh_flags;
483793c2fb25SDavid Ahern list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
48381cf844c7SDavid Ahern if (iter->fib6_nh->fib_nh_dev == dev)
48391cf844c7SDavid Ahern iter->fib6_nh->fib_nh_flags |= nh_flags;
48401de178edSIdo Schimmel }
48411de178edSIdo Schimmel
4842a1a22c12SDavid Ahern /* called with write lock held for table with rt */
fib6_ifdown(struct fib6_info * rt,void * p_arg)48438d1c802bSDavid Ahern static int fib6_ifdown(struct fib6_info *rt, void *p_arg)
48441da177e4SLinus Torvalds {
48454c981e28SIdo Schimmel const struct arg_netdev_event *arg = p_arg;
48464c981e28SIdo Schimmel const struct net_device *dev = arg->dev;
48477aef6859SDavid Ahern struct net *net = dev_net(dev);
48488ed67789SDaniel Lezcano
4849f88d8ea6SDavid Ahern if (rt == net->ipv6.fib6_null_entry || rt->nh)
485027c6fa73SIdo Schimmel return 0;
485127c6fa73SIdo Schimmel
485227c6fa73SIdo Schimmel switch (arg->event) {
485327c6fa73SIdo Schimmel case NETDEV_UNREGISTER:
48541cf844c7SDavid Ahern return rt->fib6_nh->fib_nh_dev == dev ? -1 : 0;
485527c6fa73SIdo Schimmel case NETDEV_DOWN:
48561de178edSIdo Schimmel if (rt->should_flush)
485727c6fa73SIdo Schimmel return -1;
485893c2fb25SDavid Ahern if (!rt->fib6_nsiblings)
48591cf844c7SDavid Ahern return rt->fib6_nh->fib_nh_dev == dev ? -1 : 0;
48601de178edSIdo Schimmel if (rt6_multipath_uses_dev(rt, dev)) {
48611de178edSIdo Schimmel unsigned int count;
48621de178edSIdo Schimmel
48631de178edSIdo Schimmel count = rt6_multipath_dead_count(rt, dev);
486493c2fb25SDavid Ahern if (rt->fib6_nsiblings + 1 == count) {
48651de178edSIdo Schimmel rt6_multipath_flush(rt);
48661de178edSIdo Schimmel return -1;
48671de178edSIdo Schimmel }
48681de178edSIdo Schimmel rt6_multipath_nh_flags_set(rt, dev, RTNH_F_DEAD |
48691de178edSIdo Schimmel RTNH_F_LINKDOWN);
48707aef6859SDavid Ahern fib6_update_sernum(net, rt);
4871d7dedee1SIdo Schimmel rt6_multipath_rebalance(rt);
48721de178edSIdo Schimmel }
48731de178edSIdo Schimmel return -2;
487427c6fa73SIdo Schimmel case NETDEV_CHANGE:
48751cf844c7SDavid Ahern if (rt->fib6_nh->fib_nh_dev != dev ||
487693c2fb25SDavid Ahern rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST))
487727c6fa73SIdo Schimmel break;
48781cf844c7SDavid Ahern rt->fib6_nh->fib_nh_flags |= RTNH_F_LINKDOWN;
4879d7dedee1SIdo Schimmel rt6_multipath_rebalance(rt);
488027c6fa73SIdo Schimmel break;
48812b241361SIdo Schimmel }
4882c159d30cSDavid S. Miller
48831da177e4SLinus Torvalds return 0;
48841da177e4SLinus Torvalds }
48851da177e4SLinus Torvalds
rt6_sync_down_dev(struct net_device * dev,unsigned long event)488627c6fa73SIdo Schimmel void rt6_sync_down_dev(struct net_device *dev, unsigned long event)
48871da177e4SLinus Torvalds {
48884c981e28SIdo Schimmel struct arg_netdev_event arg = {
48898ed67789SDaniel Lezcano .dev = dev,
48906802f3adSIdo Schimmel {
48914c981e28SIdo Schimmel .event = event,
48926802f3adSIdo Schimmel },
48938ed67789SDaniel Lezcano };
48947c6bb7d2SDavid Ahern struct net *net = dev_net(dev);
48958ed67789SDaniel Lezcano
48967c6bb7d2SDavid Ahern if (net->ipv6.sysctl.skip_notify_on_dev_down)
48977c6bb7d2SDavid Ahern fib6_clean_all_skip_notify(net, fib6_ifdown, &arg);
48987c6bb7d2SDavid Ahern else
48997c6bb7d2SDavid Ahern fib6_clean_all(net, fib6_ifdown, &arg);
49004c981e28SIdo Schimmel }
49014c981e28SIdo Schimmel
rt6_disable_ip(struct net_device * dev,unsigned long event)49024c981e28SIdo Schimmel void rt6_disable_ip(struct net_device *dev, unsigned long event)
49034c981e28SIdo Schimmel {
49044c981e28SIdo Schimmel rt6_sync_down_dev(dev, event);
4905e5f80fcfSEric Dumazet rt6_uncached_list_flush_dev(dev);
49064c981e28SIdo Schimmel neigh_ifdown(&nd_tbl, dev);
49071da177e4SLinus Torvalds }
49081da177e4SLinus Torvalds
490995c96174SEric Dumazet struct rt6_mtu_change_arg {
49101da177e4SLinus Torvalds struct net_device *dev;
491195c96174SEric Dumazet unsigned int mtu;
4912c0b220cfSDavid Ahern struct fib6_info *f6i;
49131da177e4SLinus Torvalds };
49141da177e4SLinus Torvalds
fib6_nh_mtu_change(struct fib6_nh * nh,void * _arg)4915cc5c073aSDavid Ahern static int fib6_nh_mtu_change(struct fib6_nh *nh, void *_arg)
4916c0b220cfSDavid Ahern {
4917c0b220cfSDavid Ahern struct rt6_mtu_change_arg *arg = (struct rt6_mtu_change_arg *)_arg;
4918cc5c073aSDavid Ahern struct fib6_info *f6i = arg->f6i;
4919c0b220cfSDavid Ahern
4920c0b220cfSDavid Ahern /* For administrative MTU increase, there is no way to discover
4921c0b220cfSDavid Ahern * IPv6 PMTU increase, so PMTU increase should be updated here.
4922c0b220cfSDavid Ahern * Since RFC 1981 doesn't include administrative MTU increase
4923c0b220cfSDavid Ahern * update PMTU increase is a MUST. (i.e. jumbo frame)
4924c0b220cfSDavid Ahern */
4925c0b220cfSDavid Ahern if (nh->fib_nh_dev == arg->dev) {
4926c0b220cfSDavid Ahern struct inet6_dev *idev = __in6_dev_get(arg->dev);
4927c0b220cfSDavid Ahern u32 mtu = f6i->fib6_pmtu;
4928c0b220cfSDavid Ahern
4929c0b220cfSDavid Ahern if (mtu >= arg->mtu ||
4930c0b220cfSDavid Ahern (mtu < arg->mtu && mtu == idev->cnf.mtu6))
4931c0b220cfSDavid Ahern fib6_metric_set(f6i, RTAX_MTU, arg->mtu);
4932c0b220cfSDavid Ahern
4933c0b220cfSDavid Ahern spin_lock_bh(&rt6_exception_lock);
4934cc5c073aSDavid Ahern rt6_exceptions_update_pmtu(idev, nh, arg->mtu);
4935c0b220cfSDavid Ahern spin_unlock_bh(&rt6_exception_lock);
4936c0b220cfSDavid Ahern }
4937c0b220cfSDavid Ahern
4938c0b220cfSDavid Ahern return 0;
4939c0b220cfSDavid Ahern }
4940c0b220cfSDavid Ahern
rt6_mtu_change_route(struct fib6_info * f6i,void * p_arg)4941c0b220cfSDavid Ahern static int rt6_mtu_change_route(struct fib6_info *f6i, void *p_arg)
49421da177e4SLinus Torvalds {
49431da177e4SLinus Torvalds struct rt6_mtu_change_arg *arg = (struct rt6_mtu_change_arg *) p_arg;
49441da177e4SLinus Torvalds struct inet6_dev *idev;
49451da177e4SLinus Torvalds
49461da177e4SLinus Torvalds /* In IPv6 pmtu discovery is not optional,
49471da177e4SLinus Torvalds so that RTAX_MTU lock cannot disable it.
49481da177e4SLinus Torvalds We still use this lock to block changes
49491da177e4SLinus Torvalds caused by addrconf/ndisc.
49501da177e4SLinus Torvalds */
49511da177e4SLinus Torvalds
49521da177e4SLinus Torvalds idev = __in6_dev_get(arg->dev);
495338308473SDavid S. Miller if (!idev)
49541da177e4SLinus Torvalds return 0;
49551da177e4SLinus Torvalds
4956c0b220cfSDavid Ahern if (fib6_metric_locked(f6i, RTAX_MTU))
49571da177e4SLinus Torvalds return 0;
4958c0b220cfSDavid Ahern
4959c0b220cfSDavid Ahern arg->f6i = f6i;
49602d44234bSDavid Ahern if (f6i->nh) {
49612d44234bSDavid Ahern /* fib6_nh_mtu_change only returns 0, so this is safe */
49622d44234bSDavid Ahern return nexthop_for_each_fib6_nh(f6i->nh, fib6_nh_mtu_change,
49632d44234bSDavid Ahern arg);
49642d44234bSDavid Ahern }
49652d44234bSDavid Ahern
49661cf844c7SDavid Ahern return fib6_nh_mtu_change(f6i->fib6_nh, arg);
49671da177e4SLinus Torvalds }
49681da177e4SLinus Torvalds
rt6_mtu_change(struct net_device * dev,unsigned int mtu)496995c96174SEric Dumazet void rt6_mtu_change(struct net_device *dev, unsigned int mtu)
49701da177e4SLinus Torvalds {
4971c71099acSThomas Graf struct rt6_mtu_change_arg arg = {
4972c71099acSThomas Graf .dev = dev,
4973c71099acSThomas Graf .mtu = mtu,
4974c71099acSThomas Graf };
49751da177e4SLinus Torvalds
49760c3584d5SLi RongQing fib6_clean_all(dev_net(dev), rt6_mtu_change_route, &arg);
49771da177e4SLinus Torvalds }
49781da177e4SLinus Torvalds
4979ef7c79edSPatrick McHardy static const struct nla_policy rtm_ipv6_policy[RTA_MAX+1] = {
498075425657SDavid Ahern [RTA_UNSPEC] = { .strict_start_type = RTA_DPORT + 1 },
49815176f91eSThomas Graf [RTA_GATEWAY] = { .len = sizeof(struct in6_addr) },
4982aa8f8778SEric Dumazet [RTA_PREFSRC] = { .len = sizeof(struct in6_addr) },
498386872cb5SThomas Graf [RTA_OIF] = { .type = NLA_U32 },
4984ab364a6fSThomas Graf [RTA_IIF] = { .type = NLA_U32 },
498586872cb5SThomas Graf [RTA_PRIORITY] = { .type = NLA_U32 },
498686872cb5SThomas Graf [RTA_METRICS] = { .type = NLA_NESTED },
498751ebd318SNicolas Dichtel [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) },
4988c78ba6d6SLubomir Rintel [RTA_PREF] = { .type = NLA_U8 },
498919e42e45SRoopa Prabhu [RTA_ENCAP_TYPE] = { .type = NLA_U16 },
499019e42e45SRoopa Prabhu [RTA_ENCAP] = { .type = NLA_NESTED },
499132bc201eSXin Long [RTA_EXPIRES] = { .type = NLA_U32 },
4992622ec2c9SLorenzo Colitti [RTA_UID] = { .type = NLA_U32 },
49933b45a410SLiping Zhang [RTA_MARK] = { .type = NLA_U32 },
4994aa8f8778SEric Dumazet [RTA_TABLE] = { .type = NLA_U32 },
4995eacb9384SRoopa Prabhu [RTA_IP_PROTO] = { .type = NLA_U8 },
4996eacb9384SRoopa Prabhu [RTA_SPORT] = { .type = NLA_U16 },
4997eacb9384SRoopa Prabhu [RTA_DPORT] = { .type = NLA_U16 },
49985b98324eSDavid Ahern [RTA_NH_ID] = { .type = NLA_U32 },
499986872cb5SThomas Graf };
500086872cb5SThomas Graf
rtm_to_fib6_config(struct sk_buff * skb,struct nlmsghdr * nlh,struct fib6_config * cfg,struct netlink_ext_ack * extack)500186872cb5SThomas Graf static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
5002333c4301SDavid Ahern struct fib6_config *cfg,
5003333c4301SDavid Ahern struct netlink_ext_ack *extack)
50041da177e4SLinus Torvalds {
500586872cb5SThomas Graf struct rtmsg *rtm;
500686872cb5SThomas Graf struct nlattr *tb[RTA_MAX+1];
5007c78ba6d6SLubomir Rintel unsigned int pref;
500886872cb5SThomas Graf int err;
50091da177e4SLinus Torvalds
50108cb08174SJohannes Berg err = nlmsg_parse_deprecated(nlh, sizeof(*rtm), tb, RTA_MAX,
50118cb08174SJohannes Berg rtm_ipv6_policy, extack);
501286872cb5SThomas Graf if (err < 0)
501386872cb5SThomas Graf goto errout;
50141da177e4SLinus Torvalds
501586872cb5SThomas Graf err = -EINVAL;
501686872cb5SThomas Graf rtm = nlmsg_data(nlh);
501786872cb5SThomas Graf
5018b9605161SGuillaume Nault if (rtm->rtm_tos) {
5019b9605161SGuillaume Nault NL_SET_ERR_MSG(extack,
5020b9605161SGuillaume Nault "Invalid dsfield (tos): option not available for IPv6");
5021b9605161SGuillaume Nault goto errout;
5022b9605161SGuillaume Nault }
5023b9605161SGuillaume Nault
502484db8407SMaciej Żenczykowski *cfg = (struct fib6_config){
502584db8407SMaciej Żenczykowski .fc_table = rtm->rtm_table,
502684db8407SMaciej Żenczykowski .fc_dst_len = rtm->rtm_dst_len,
502784db8407SMaciej Żenczykowski .fc_src_len = rtm->rtm_src_len,
502884db8407SMaciej Żenczykowski .fc_flags = RTF_UP,
502984db8407SMaciej Żenczykowski .fc_protocol = rtm->rtm_protocol,
503084db8407SMaciej Żenczykowski .fc_type = rtm->rtm_type,
503184db8407SMaciej Żenczykowski
503284db8407SMaciej Żenczykowski .fc_nlinfo.portid = NETLINK_CB(skb).portid,
503384db8407SMaciej Żenczykowski .fc_nlinfo.nlh = nlh,
503484db8407SMaciej Żenczykowski .fc_nlinfo.nl_net = sock_net(skb->sk),
503584db8407SMaciej Żenczykowski };
503686872cb5SThomas Graf
5037ef2c7d7bSNicolas Dichtel if (rtm->rtm_type == RTN_UNREACHABLE ||
5038ef2c7d7bSNicolas Dichtel rtm->rtm_type == RTN_BLACKHOLE ||
5039b4949ab2SNicolas Dichtel rtm->rtm_type == RTN_PROHIBIT ||
5040b4949ab2SNicolas Dichtel rtm->rtm_type == RTN_THROW)
504186872cb5SThomas Graf cfg->fc_flags |= RTF_REJECT;
504286872cb5SThomas Graf
5043ab79ad14SMaciej Żenczykowski if (rtm->rtm_type == RTN_LOCAL)
5044ab79ad14SMaciej Żenczykowski cfg->fc_flags |= RTF_LOCAL;
5045ab79ad14SMaciej Żenczykowski
50461f56a01fSMartin KaFai Lau if (rtm->rtm_flags & RTM_F_CLONED)
50471f56a01fSMartin KaFai Lau cfg->fc_flags |= RTF_CACHE;
50481f56a01fSMartin KaFai Lau
5049fc1e64e1SDavid Ahern cfg->fc_flags |= (rtm->rtm_flags & RTNH_F_ONLINK);
5050fc1e64e1SDavid Ahern
50515b98324eSDavid Ahern if (tb[RTA_NH_ID]) {
50525b98324eSDavid Ahern if (tb[RTA_GATEWAY] || tb[RTA_OIF] ||
50535b98324eSDavid Ahern tb[RTA_MULTIPATH] || tb[RTA_ENCAP]) {
50545b98324eSDavid Ahern NL_SET_ERR_MSG(extack,
50555b98324eSDavid Ahern "Nexthop specification and nexthop id are mutually exclusive");
50565b98324eSDavid Ahern goto errout;
50575b98324eSDavid Ahern }
50585b98324eSDavid Ahern cfg->fc_nh_id = nla_get_u32(tb[RTA_NH_ID]);
50595b98324eSDavid Ahern }
50605b98324eSDavid Ahern
506186872cb5SThomas Graf if (tb[RTA_GATEWAY]) {
506267b61f6cSJiri Benc cfg->fc_gateway = nla_get_in6_addr(tb[RTA_GATEWAY]);
506386872cb5SThomas Graf cfg->fc_flags |= RTF_GATEWAY;
50641da177e4SLinus Torvalds }
5065e3818541SDavid Ahern if (tb[RTA_VIA]) {
5066e3818541SDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 does not support RTA_VIA attribute");
5067e3818541SDavid Ahern goto errout;
5068e3818541SDavid Ahern }
506986872cb5SThomas Graf
507086872cb5SThomas Graf if (tb[RTA_DST]) {
507186872cb5SThomas Graf int plen = (rtm->rtm_dst_len + 7) >> 3;
507286872cb5SThomas Graf
507386872cb5SThomas Graf if (nla_len(tb[RTA_DST]) < plen)
507486872cb5SThomas Graf goto errout;
507586872cb5SThomas Graf
507686872cb5SThomas Graf nla_memcpy(&cfg->fc_dst, tb[RTA_DST], plen);
50771da177e4SLinus Torvalds }
507886872cb5SThomas Graf
507986872cb5SThomas Graf if (tb[RTA_SRC]) {
508086872cb5SThomas Graf int plen = (rtm->rtm_src_len + 7) >> 3;
508186872cb5SThomas Graf
508286872cb5SThomas Graf if (nla_len(tb[RTA_SRC]) < plen)
508386872cb5SThomas Graf goto errout;
508486872cb5SThomas Graf
508586872cb5SThomas Graf nla_memcpy(&cfg->fc_src, tb[RTA_SRC], plen);
50861da177e4SLinus Torvalds }
508786872cb5SThomas Graf
5088c3968a85SDaniel Walter if (tb[RTA_PREFSRC])
508967b61f6cSJiri Benc cfg->fc_prefsrc = nla_get_in6_addr(tb[RTA_PREFSRC]);
5090c3968a85SDaniel Walter
509186872cb5SThomas Graf if (tb[RTA_OIF])
509286872cb5SThomas Graf cfg->fc_ifindex = nla_get_u32(tb[RTA_OIF]);
509386872cb5SThomas Graf
509486872cb5SThomas Graf if (tb[RTA_PRIORITY])
509586872cb5SThomas Graf cfg->fc_metric = nla_get_u32(tb[RTA_PRIORITY]);
509686872cb5SThomas Graf
509786872cb5SThomas Graf if (tb[RTA_METRICS]) {
509886872cb5SThomas Graf cfg->fc_mx = nla_data(tb[RTA_METRICS]);
509986872cb5SThomas Graf cfg->fc_mx_len = nla_len(tb[RTA_METRICS]);
51001da177e4SLinus Torvalds }
510186872cb5SThomas Graf
510286872cb5SThomas Graf if (tb[RTA_TABLE])
510386872cb5SThomas Graf cfg->fc_table = nla_get_u32(tb[RTA_TABLE]);
510486872cb5SThomas Graf
510551ebd318SNicolas Dichtel if (tb[RTA_MULTIPATH]) {
510651ebd318SNicolas Dichtel cfg->fc_mp = nla_data(tb[RTA_MULTIPATH]);
510751ebd318SNicolas Dichtel cfg->fc_mp_len = nla_len(tb[RTA_MULTIPATH]);
51089ed59592SDavid Ahern
51099ed59592SDavid Ahern err = lwtunnel_valid_encap_type_attr(cfg->fc_mp,
5110c255bd68SDavid Ahern cfg->fc_mp_len, extack);
51119ed59592SDavid Ahern if (err < 0)
51129ed59592SDavid Ahern goto errout;
511351ebd318SNicolas Dichtel }
511451ebd318SNicolas Dichtel
5115c78ba6d6SLubomir Rintel if (tb[RTA_PREF]) {
5116c78ba6d6SLubomir Rintel pref = nla_get_u8(tb[RTA_PREF]);
5117c78ba6d6SLubomir Rintel if (pref != ICMPV6_ROUTER_PREF_LOW &&
5118c78ba6d6SLubomir Rintel pref != ICMPV6_ROUTER_PREF_HIGH)
5119c78ba6d6SLubomir Rintel pref = ICMPV6_ROUTER_PREF_MEDIUM;
5120c78ba6d6SLubomir Rintel cfg->fc_flags |= RTF_PREF(pref);
5121c78ba6d6SLubomir Rintel }
5122c78ba6d6SLubomir Rintel
512319e42e45SRoopa Prabhu if (tb[RTA_ENCAP])
512419e42e45SRoopa Prabhu cfg->fc_encap = tb[RTA_ENCAP];
512519e42e45SRoopa Prabhu
51269ed59592SDavid Ahern if (tb[RTA_ENCAP_TYPE]) {
512719e42e45SRoopa Prabhu cfg->fc_encap_type = nla_get_u16(tb[RTA_ENCAP_TYPE]);
512819e42e45SRoopa Prabhu
5129c255bd68SDavid Ahern err = lwtunnel_valid_encap_type(cfg->fc_encap_type, extack);
51309ed59592SDavid Ahern if (err < 0)
51319ed59592SDavid Ahern goto errout;
51329ed59592SDavid Ahern }
51339ed59592SDavid Ahern
513432bc201eSXin Long if (tb[RTA_EXPIRES]) {
513532bc201eSXin Long unsigned long timeout = addrconf_timeout_fixup(nla_get_u32(tb[RTA_EXPIRES]), HZ);
513632bc201eSXin Long
513732bc201eSXin Long if (addrconf_finite_timeout(timeout)) {
513832bc201eSXin Long cfg->fc_expires = jiffies_to_clock_t(timeout * HZ);
513932bc201eSXin Long cfg->fc_flags |= RTF_EXPIRES;
514032bc201eSXin Long }
514132bc201eSXin Long }
514232bc201eSXin Long
514386872cb5SThomas Graf err = 0;
514486872cb5SThomas Graf errout:
514586872cb5SThomas Graf return err;
51461da177e4SLinus Torvalds }
51471da177e4SLinus Torvalds
51486b9ea5a6SRoopa Prabhu struct rt6_nh {
51498d1c802bSDavid Ahern struct fib6_info *fib6_info;
51506b9ea5a6SRoopa Prabhu struct fib6_config r_cfg;
51516b9ea5a6SRoopa Prabhu struct list_head next;
51526b9ea5a6SRoopa Prabhu };
51536b9ea5a6SRoopa Prabhu
ip6_route_info_append(struct net * net,struct list_head * rt6_nh_list,struct fib6_info * rt,struct fib6_config * r_cfg)5154d4ead6b3SDavid Ahern static int ip6_route_info_append(struct net *net,
5155d4ead6b3SDavid Ahern struct list_head *rt6_nh_list,
51568d1c802bSDavid Ahern struct fib6_info *rt,
51578d1c802bSDavid Ahern struct fib6_config *r_cfg)
51586b9ea5a6SRoopa Prabhu {
51596b9ea5a6SRoopa Prabhu struct rt6_nh *nh;
51606b9ea5a6SRoopa Prabhu int err = -EEXIST;
51616b9ea5a6SRoopa Prabhu
51626b9ea5a6SRoopa Prabhu list_for_each_entry(nh, rt6_nh_list, next) {
51638d1c802bSDavid Ahern /* check if fib6_info already exists */
51648d1c802bSDavid Ahern if (rt6_duplicate_nexthop(nh->fib6_info, rt))
51656b9ea5a6SRoopa Prabhu return err;
51666b9ea5a6SRoopa Prabhu }
51676b9ea5a6SRoopa Prabhu
51686b9ea5a6SRoopa Prabhu nh = kzalloc(sizeof(*nh), GFP_KERNEL);
51696b9ea5a6SRoopa Prabhu if (!nh)
51706b9ea5a6SRoopa Prabhu return -ENOMEM;
51718d1c802bSDavid Ahern nh->fib6_info = rt;
51726b9ea5a6SRoopa Prabhu memcpy(&nh->r_cfg, r_cfg, sizeof(*r_cfg));
51736b9ea5a6SRoopa Prabhu list_add_tail(&nh->next, rt6_nh_list);
51746b9ea5a6SRoopa Prabhu
51756b9ea5a6SRoopa Prabhu return 0;
51766b9ea5a6SRoopa Prabhu }
51776b9ea5a6SRoopa Prabhu
ip6_route_mpath_notify(struct fib6_info * rt,struct fib6_info * rt_last,struct nl_info * info,__u16 nlflags)51788d1c802bSDavid Ahern static void ip6_route_mpath_notify(struct fib6_info *rt,
51798d1c802bSDavid Ahern struct fib6_info *rt_last,
51803b1137feSDavid Ahern struct nl_info *info,
51813b1137feSDavid Ahern __u16 nlflags)
51823b1137feSDavid Ahern {
51833b1137feSDavid Ahern /* if this is an APPEND route, then rt points to the first route
51843b1137feSDavid Ahern * inserted and rt_last points to last route inserted. Userspace
51853b1137feSDavid Ahern * wants a consistent dump of the route which starts at the first
51863b1137feSDavid Ahern * nexthop. Since sibling routes are always added at the end of
51873b1137feSDavid Ahern * the list, find the first sibling of the last route appended
51883b1137feSDavid Ahern */
518993c2fb25SDavid Ahern if ((nlflags & NLM_F_APPEND) && rt_last && rt_last->fib6_nsiblings) {
519093c2fb25SDavid Ahern rt = list_first_entry(&rt_last->fib6_siblings,
51918d1c802bSDavid Ahern struct fib6_info,
519293c2fb25SDavid Ahern fib6_siblings);
51933b1137feSDavid Ahern }
51943b1137feSDavid Ahern
51953b1137feSDavid Ahern if (rt)
51963b1137feSDavid Ahern inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags);
51973b1137feSDavid Ahern }
51983b1137feSDavid Ahern
ip6_route_mpath_should_notify(const struct fib6_info * rt)51990ee0f47cSIdo Schimmel static bool ip6_route_mpath_should_notify(const struct fib6_info *rt)
52000ee0f47cSIdo Schimmel {
52010ee0f47cSIdo Schimmel bool rt_can_ecmp = rt6_qualify_for_ecmp(rt);
52020ee0f47cSIdo Schimmel bool should_notify = false;
52030ee0f47cSIdo Schimmel struct fib6_info *leaf;
52040ee0f47cSIdo Schimmel struct fib6_node *fn;
52050ee0f47cSIdo Schimmel
52060ee0f47cSIdo Schimmel rcu_read_lock();
52070ee0f47cSIdo Schimmel fn = rcu_dereference(rt->fib6_node);
52080ee0f47cSIdo Schimmel if (!fn)
52090ee0f47cSIdo Schimmel goto out;
52100ee0f47cSIdo Schimmel
52110ee0f47cSIdo Schimmel leaf = rcu_dereference(fn->leaf);
52120ee0f47cSIdo Schimmel if (!leaf)
52130ee0f47cSIdo Schimmel goto out;
52140ee0f47cSIdo Schimmel
52150ee0f47cSIdo Schimmel if (rt == leaf ||
52160ee0f47cSIdo Schimmel (rt_can_ecmp && rt->fib6_metric == leaf->fib6_metric &&
52170ee0f47cSIdo Schimmel rt6_qualify_for_ecmp(leaf)))
52180ee0f47cSIdo Schimmel should_notify = true;
52190ee0f47cSIdo Schimmel out:
52200ee0f47cSIdo Schimmel rcu_read_unlock();
52210ee0f47cSIdo Schimmel
52220ee0f47cSIdo Schimmel return should_notify;
52230ee0f47cSIdo Schimmel }
52240ee0f47cSIdo Schimmel
fib6_gw_from_attr(struct in6_addr * gw,struct nlattr * nla,struct netlink_ext_ack * extack)52254619bcf9SDavid Ahern static int fib6_gw_from_attr(struct in6_addr *gw, struct nlattr *nla,
52264619bcf9SDavid Ahern struct netlink_ext_ack *extack)
52274619bcf9SDavid Ahern {
52284619bcf9SDavid Ahern if (nla_len(nla) < sizeof(*gw)) {
52294619bcf9SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid IPv6 address in RTA_GATEWAY");
52304619bcf9SDavid Ahern return -EINVAL;
52314619bcf9SDavid Ahern }
52324619bcf9SDavid Ahern
52334619bcf9SDavid Ahern *gw = nla_get_in6_addr(nla);
52344619bcf9SDavid Ahern
52354619bcf9SDavid Ahern return 0;
52364619bcf9SDavid Ahern }
52374619bcf9SDavid Ahern
ip6_route_multipath_add(struct fib6_config * cfg,struct netlink_ext_ack * extack)5238333c4301SDavid Ahern static int ip6_route_multipath_add(struct fib6_config *cfg,
5239333c4301SDavid Ahern struct netlink_ext_ack *extack)
524051ebd318SNicolas Dichtel {
52418d1c802bSDavid Ahern struct fib6_info *rt_notif = NULL, *rt_last = NULL;
52423b1137feSDavid Ahern struct nl_info *info = &cfg->fc_nlinfo;
524351ebd318SNicolas Dichtel struct fib6_config r_cfg;
524451ebd318SNicolas Dichtel struct rtnexthop *rtnh;
52458d1c802bSDavid Ahern struct fib6_info *rt;
52466b9ea5a6SRoopa Prabhu struct rt6_nh *err_nh;
52476b9ea5a6SRoopa Prabhu struct rt6_nh *nh, *nh_safe;
52483b1137feSDavid Ahern __u16 nlflags;
524951ebd318SNicolas Dichtel int remaining;
525051ebd318SNicolas Dichtel int attrlen;
52516b9ea5a6SRoopa Prabhu int err = 1;
52526b9ea5a6SRoopa Prabhu int nhn = 0;
52536b9ea5a6SRoopa Prabhu int replace = (cfg->fc_nlinfo.nlh &&
52546b9ea5a6SRoopa Prabhu (cfg->fc_nlinfo.nlh->nlmsg_flags & NLM_F_REPLACE));
52556b9ea5a6SRoopa Prabhu LIST_HEAD(rt6_nh_list);
525651ebd318SNicolas Dichtel
52573b1137feSDavid Ahern nlflags = replace ? NLM_F_REPLACE : NLM_F_CREATE;
52583b1137feSDavid Ahern if (info->nlh && info->nlh->nlmsg_flags & NLM_F_APPEND)
52593b1137feSDavid Ahern nlflags |= NLM_F_APPEND;
52603b1137feSDavid Ahern
526135f1b4e9SMichal Kubeček remaining = cfg->fc_mp_len;
526251ebd318SNicolas Dichtel rtnh = (struct rtnexthop *)cfg->fc_mp;
526351ebd318SNicolas Dichtel
52646b9ea5a6SRoopa Prabhu /* Parse a Multipath Entry and build a list (rt6_nh_list) of
52658d1c802bSDavid Ahern * fib6_info structs per nexthop
52666b9ea5a6SRoopa Prabhu */
526751ebd318SNicolas Dichtel while (rtnh_ok(rtnh, remaining)) {
526851ebd318SNicolas Dichtel memcpy(&r_cfg, cfg, sizeof(*cfg));
526951ebd318SNicolas Dichtel if (rtnh->rtnh_ifindex)
527051ebd318SNicolas Dichtel r_cfg.fc_ifindex = rtnh->rtnh_ifindex;
527151ebd318SNicolas Dichtel
527251ebd318SNicolas Dichtel attrlen = rtnh_attrlen(rtnh);
527351ebd318SNicolas Dichtel if (attrlen > 0) {
527451ebd318SNicolas Dichtel struct nlattr *nla, *attrs = rtnh_attrs(rtnh);
527551ebd318SNicolas Dichtel
527651ebd318SNicolas Dichtel nla = nla_find(attrs, attrlen, RTA_GATEWAY);
527751ebd318SNicolas Dichtel if (nla) {
527895bdba23SDavid Ahern err = fib6_gw_from_attr(&r_cfg.fc_gateway, nla,
52794619bcf9SDavid Ahern extack);
528095bdba23SDavid Ahern if (err)
528195bdba23SDavid Ahern goto cleanup;
52824619bcf9SDavid Ahern
528351ebd318SNicolas Dichtel r_cfg.fc_flags |= RTF_GATEWAY;
528451ebd318SNicolas Dichtel }
528519e42e45SRoopa Prabhu r_cfg.fc_encap = nla_find(attrs, attrlen, RTA_ENCAP);
52868bda81a4SDavid Ahern
52878bda81a4SDavid Ahern /* RTA_ENCAP_TYPE length checked in
52888bda81a4SDavid Ahern * lwtunnel_valid_encap_type_attr
52898bda81a4SDavid Ahern */
529019e42e45SRoopa Prabhu nla = nla_find(attrs, attrlen, RTA_ENCAP_TYPE);
529119e42e45SRoopa Prabhu if (nla)
529219e42e45SRoopa Prabhu r_cfg.fc_encap_type = nla_get_u16(nla);
529351ebd318SNicolas Dichtel }
52946b9ea5a6SRoopa Prabhu
529568e2ffdeSDavid Ahern r_cfg.fc_flags |= (rtnh->rtnh_flags & RTNH_F_ONLINK);
5296acb54e3cSDavid Ahern rt = ip6_route_info_create(&r_cfg, GFP_KERNEL, extack);
52978c5b83f0SRoopa Prabhu if (IS_ERR(rt)) {
52988c5b83f0SRoopa Prabhu err = PTR_ERR(rt);
52998c5b83f0SRoopa Prabhu rt = NULL;
53006b9ea5a6SRoopa Prabhu goto cleanup;
53018c5b83f0SRoopa Prabhu }
5302b5d2d75eSDavid Ahern if (!rt6_qualify_for_ecmp(rt)) {
5303b5d2d75eSDavid Ahern err = -EINVAL;
5304b5d2d75eSDavid Ahern NL_SET_ERR_MSG(extack,
5305b5d2d75eSDavid Ahern "Device only routes can not be added for IPv6 using the multipath API.");
5306b5d2d75eSDavid Ahern fib6_info_release(rt);
5307b5d2d75eSDavid Ahern goto cleanup;
5308b5d2d75eSDavid Ahern }
53096b9ea5a6SRoopa Prabhu
53101cf844c7SDavid Ahern rt->fib6_nh->fib_nh_weight = rtnh->rtnh_hops + 1;
5311398958aeSIdo Schimmel
5312d4ead6b3SDavid Ahern err = ip6_route_info_append(info->nl_net, &rt6_nh_list,
5313d4ead6b3SDavid Ahern rt, &r_cfg);
531451ebd318SNicolas Dichtel if (err) {
531593531c67SDavid Ahern fib6_info_release(rt);
53166b9ea5a6SRoopa Prabhu goto cleanup;
531751ebd318SNicolas Dichtel }
53186b9ea5a6SRoopa Prabhu
53196b9ea5a6SRoopa Prabhu rtnh = rtnh_next(rtnh, &remaining);
532051ebd318SNicolas Dichtel }
53216b9ea5a6SRoopa Prabhu
53229eee3b49SIdo Schimmel if (list_empty(&rt6_nh_list)) {
53239eee3b49SIdo Schimmel NL_SET_ERR_MSG(extack,
53249eee3b49SIdo Schimmel "Invalid nexthop configuration - no valid nexthops");
53259eee3b49SIdo Schimmel return -EINVAL;
53269eee3b49SIdo Schimmel }
53279eee3b49SIdo Schimmel
53283b1137feSDavid Ahern /* for add and replace send one notification with all nexthops.
53293b1137feSDavid Ahern * Skip the notification in fib6_add_rt2node and send one with
53303b1137feSDavid Ahern * the full route when done
53313b1137feSDavid Ahern */
53323b1137feSDavid Ahern info->skip_notify = 1;
53333b1137feSDavid Ahern
5334ebee3cadSIdo Schimmel /* For add and replace, send one notification with all nexthops. For
5335ebee3cadSIdo Schimmel * append, send one notification with all appended nexthops.
5336ebee3cadSIdo Schimmel */
5337ebee3cadSIdo Schimmel info->skip_notify_kernel = 1;
5338ebee3cadSIdo Schimmel
53396b9ea5a6SRoopa Prabhu err_nh = NULL;
53406b9ea5a6SRoopa Prabhu list_for_each_entry(nh, &rt6_nh_list, next) {
53418d1c802bSDavid Ahern err = __ip6_ins_rt(nh->fib6_info, info, extack);
53423b1137feSDavid Ahern
53436b9ea5a6SRoopa Prabhu if (err) {
53446b9ea5a6SRoopa Prabhu if (replace && nhn)
5345a5a82d84SJakub Kicinski NL_SET_ERR_MSG_MOD(extack,
5346a5a82d84SJakub Kicinski "multipath route replace failed (check consistency of installed routes)");
53476b9ea5a6SRoopa Prabhu err_nh = nh;
53486b9ea5a6SRoopa Prabhu goto add_errout;
53496b9ea5a6SRoopa Prabhu }
5350ed883060SEric Dumazet /* save reference to last route successfully inserted */
5351ed883060SEric Dumazet rt_last = nh->fib6_info;
5352ed883060SEric Dumazet
5353ed883060SEric Dumazet /* save reference to first route for notification */
5354ed883060SEric Dumazet if (!rt_notif)
5355ed883060SEric Dumazet rt_notif = nh->fib6_info;
53566b9ea5a6SRoopa Prabhu
53571a72418bSNicolas Dichtel /* Because each route is added like a single route we remove
535827596472SMichal Kubeček * these flags after the first nexthop: if there is a collision,
535927596472SMichal Kubeček * we have already failed to add the first nexthop:
536027596472SMichal Kubeček * fib6_add_rt2node() has rejected it; when replacing, old
536127596472SMichal Kubeček * nexthops have been replaced by first new, the rest should
536227596472SMichal Kubeček * be added to it.
53631a72418bSNicolas Dichtel */
5364864db232SMuhammad Usama Anjum if (cfg->fc_nlinfo.nlh) {
536527596472SMichal Kubeček cfg->fc_nlinfo.nlh->nlmsg_flags &= ~(NLM_F_EXCL |
536627596472SMichal Kubeček NLM_F_REPLACE);
5367afecdb37SBenjamin Poirier cfg->fc_nlinfo.nlh->nlmsg_flags |= NLM_F_CREATE;
5368864db232SMuhammad Usama Anjum }
53696b9ea5a6SRoopa Prabhu nhn++;
53706b9ea5a6SRoopa Prabhu }
53716b9ea5a6SRoopa Prabhu
53720ee0f47cSIdo Schimmel /* An in-kernel notification should only be sent in case the new
53730ee0f47cSIdo Schimmel * multipath route is added as the first route in the node, or if
53740ee0f47cSIdo Schimmel * it was appended to it. We pass 'rt_notif' since it is the first
53750ee0f47cSIdo Schimmel * sibling and might allow us to skip some checks in the replace case.
53760ee0f47cSIdo Schimmel */
53770ee0f47cSIdo Schimmel if (ip6_route_mpath_should_notify(rt_notif)) {
53780ee0f47cSIdo Schimmel enum fib_event_type fib_event;
53790ee0f47cSIdo Schimmel
53800ee0f47cSIdo Schimmel if (rt_notif->fib6_nsiblings != nhn - 1)
53810ee0f47cSIdo Schimmel fib_event = FIB_EVENT_ENTRY_APPEND;
53820ee0f47cSIdo Schimmel else
5383caafb250SIdo Schimmel fib_event = FIB_EVENT_ENTRY_REPLACE;
53840ee0f47cSIdo Schimmel
53850ee0f47cSIdo Schimmel err = call_fib6_multipath_entry_notifiers(info->nl_net,
53860ee0f47cSIdo Schimmel fib_event, rt_notif,
53870ee0f47cSIdo Schimmel nhn - 1, extack);
53880ee0f47cSIdo Schimmel if (err) {
53890ee0f47cSIdo Schimmel /* Delete all the siblings that were just added */
53900ee0f47cSIdo Schimmel err_nh = NULL;
53910ee0f47cSIdo Schimmel goto add_errout;
53920ee0f47cSIdo Schimmel }
53930ee0f47cSIdo Schimmel }
5394ebee3cadSIdo Schimmel
53953b1137feSDavid Ahern /* success ... tell user about new route */
53963b1137feSDavid Ahern ip6_route_mpath_notify(rt_notif, rt_last, info, nlflags);
53976b9ea5a6SRoopa Prabhu goto cleanup;
53986b9ea5a6SRoopa Prabhu
53996b9ea5a6SRoopa Prabhu add_errout:
54003b1137feSDavid Ahern /* send notification for routes that were added so that
54013b1137feSDavid Ahern * the delete notifications sent by ip6_route_del are
54023b1137feSDavid Ahern * coherent
54033b1137feSDavid Ahern */
54043b1137feSDavid Ahern if (rt_notif)
54053b1137feSDavid Ahern ip6_route_mpath_notify(rt_notif, rt_last, info, nlflags);
54063b1137feSDavid Ahern
54076b9ea5a6SRoopa Prabhu /* Delete routes that were already added */
54086b9ea5a6SRoopa Prabhu list_for_each_entry(nh, &rt6_nh_list, next) {
54096b9ea5a6SRoopa Prabhu if (err_nh == nh)
54106b9ea5a6SRoopa Prabhu break;
5411333c4301SDavid Ahern ip6_route_del(&nh->r_cfg, extack);
54126b9ea5a6SRoopa Prabhu }
54136b9ea5a6SRoopa Prabhu
54146b9ea5a6SRoopa Prabhu cleanup:
54156b9ea5a6SRoopa Prabhu list_for_each_entry_safe(nh, nh_safe, &rt6_nh_list, next) {
54168d1c802bSDavid Ahern fib6_info_release(nh->fib6_info);
54176b9ea5a6SRoopa Prabhu list_del(&nh->next);
54186b9ea5a6SRoopa Prabhu kfree(nh);
54196b9ea5a6SRoopa Prabhu }
54206b9ea5a6SRoopa Prabhu
54216b9ea5a6SRoopa Prabhu return err;
54226b9ea5a6SRoopa Prabhu }
54236b9ea5a6SRoopa Prabhu
ip6_route_multipath_del(struct fib6_config * cfg,struct netlink_ext_ack * extack)5424333c4301SDavid Ahern static int ip6_route_multipath_del(struct fib6_config *cfg,
5425333c4301SDavid Ahern struct netlink_ext_ack *extack)
54266b9ea5a6SRoopa Prabhu {
54276b9ea5a6SRoopa Prabhu struct fib6_config r_cfg;
54286b9ea5a6SRoopa Prabhu struct rtnexthop *rtnh;
54292291267eSColin Ian King int last_err = 0;
54306b9ea5a6SRoopa Prabhu int remaining;
54316b9ea5a6SRoopa Prabhu int attrlen;
54322291267eSColin Ian King int err;
54336b9ea5a6SRoopa Prabhu
54346b9ea5a6SRoopa Prabhu remaining = cfg->fc_mp_len;
54356b9ea5a6SRoopa Prabhu rtnh = (struct rtnexthop *)cfg->fc_mp;
54366b9ea5a6SRoopa Prabhu
54376b9ea5a6SRoopa Prabhu /* Parse a Multipath Entry */
54386b9ea5a6SRoopa Prabhu while (rtnh_ok(rtnh, remaining)) {
54396b9ea5a6SRoopa Prabhu memcpy(&r_cfg, cfg, sizeof(*cfg));
54406b9ea5a6SRoopa Prabhu if (rtnh->rtnh_ifindex)
54416b9ea5a6SRoopa Prabhu r_cfg.fc_ifindex = rtnh->rtnh_ifindex;
54426b9ea5a6SRoopa Prabhu
54436b9ea5a6SRoopa Prabhu attrlen = rtnh_attrlen(rtnh);
54446b9ea5a6SRoopa Prabhu if (attrlen > 0) {
54456b9ea5a6SRoopa Prabhu struct nlattr *nla, *attrs = rtnh_attrs(rtnh);
54466b9ea5a6SRoopa Prabhu
54476b9ea5a6SRoopa Prabhu nla = nla_find(attrs, attrlen, RTA_GATEWAY);
54486b9ea5a6SRoopa Prabhu if (nla) {
54491ff15a71SDavid Ahern err = fib6_gw_from_attr(&r_cfg.fc_gateway, nla,
54501ff15a71SDavid Ahern extack);
5451e30a845bSDavid Ahern if (err) {
5452e30a845bSDavid Ahern last_err = err;
5453e30a845bSDavid Ahern goto next_rtnh;
5454e30a845bSDavid Ahern }
54551ff15a71SDavid Ahern
54566b9ea5a6SRoopa Prabhu r_cfg.fc_flags |= RTF_GATEWAY;
54576b9ea5a6SRoopa Prabhu }
54586b9ea5a6SRoopa Prabhu }
5459333c4301SDavid Ahern err = ip6_route_del(&r_cfg, extack);
54606b9ea5a6SRoopa Prabhu if (err)
54616b9ea5a6SRoopa Prabhu last_err = err;
54626b9ea5a6SRoopa Prabhu
5463e30a845bSDavid Ahern next_rtnh:
546451ebd318SNicolas Dichtel rtnh = rtnh_next(rtnh, &remaining);
546551ebd318SNicolas Dichtel }
546651ebd318SNicolas Dichtel
546751ebd318SNicolas Dichtel return last_err;
546851ebd318SNicolas Dichtel }
546951ebd318SNicolas Dichtel
inet6_rtm_delroute(struct sk_buff * skb,struct nlmsghdr * nlh,struct netlink_ext_ack * extack)5470c21ef3e3SDavid Ahern static int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh,
5471c21ef3e3SDavid Ahern struct netlink_ext_ack *extack)
54721da177e4SLinus Torvalds {
547386872cb5SThomas Graf struct fib6_config cfg;
547486872cb5SThomas Graf int err;
54751da177e4SLinus Torvalds
5476333c4301SDavid Ahern err = rtm_to_fib6_config(skb, nlh, &cfg, extack);
547786872cb5SThomas Graf if (err < 0)
547886872cb5SThomas Graf return err;
547986872cb5SThomas Graf
54805b98324eSDavid Ahern if (cfg.fc_nh_id &&
54815b98324eSDavid Ahern !nexthop_find_by_id(sock_net(skb->sk), cfg.fc_nh_id)) {
54825b98324eSDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop id does not exist");
54835b98324eSDavid Ahern return -EINVAL;
54845b98324eSDavid Ahern }
54855b98324eSDavid Ahern
548651ebd318SNicolas Dichtel if (cfg.fc_mp)
5487333c4301SDavid Ahern return ip6_route_multipath_del(&cfg, extack);
54880ae81335SDavid Ahern else {
54890ae81335SDavid Ahern cfg.fc_delete_all_nh = 1;
5490333c4301SDavid Ahern return ip6_route_del(&cfg, extack);
54911da177e4SLinus Torvalds }
54920ae81335SDavid Ahern }
54931da177e4SLinus Torvalds
inet6_rtm_newroute(struct sk_buff * skb,struct nlmsghdr * nlh,struct netlink_ext_ack * extack)5494c21ef3e3SDavid Ahern static int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh,
5495c21ef3e3SDavid Ahern struct netlink_ext_ack *extack)
54961da177e4SLinus Torvalds {
549786872cb5SThomas Graf struct fib6_config cfg;
549886872cb5SThomas Graf int err;
54991da177e4SLinus Torvalds
5500333c4301SDavid Ahern err = rtm_to_fib6_config(skb, nlh, &cfg, extack);
550186872cb5SThomas Graf if (err < 0)
550286872cb5SThomas Graf return err;
550386872cb5SThomas Graf
550467f69513SDavid Ahern if (cfg.fc_metric == 0)
550567f69513SDavid Ahern cfg.fc_metric = IP6_RT_PRIO_USER;
550667f69513SDavid Ahern
550751ebd318SNicolas Dichtel if (cfg.fc_mp)
5508333c4301SDavid Ahern return ip6_route_multipath_add(&cfg, extack);
550951ebd318SNicolas Dichtel else
5510acb54e3cSDavid Ahern return ip6_route_add(&cfg, GFP_KERNEL, extack);
55111da177e4SLinus Torvalds }
55121da177e4SLinus Torvalds
5513a1b7a1f0SDavid Ahern /* add the overhead of this fib6_nh to nexthop_len */
rt6_nh_nlmsg_size(struct fib6_nh * nh,void * arg)5514a1b7a1f0SDavid Ahern static int rt6_nh_nlmsg_size(struct fib6_nh *nh, void *arg)
5515339bf98fSThomas Graf {
5516a1b7a1f0SDavid Ahern int *nexthop_len = arg;
5517beb1afacSDavid Ahern
5518a1b7a1f0SDavid Ahern *nexthop_len += nla_total_size(0) /* RTA_MULTIPATH */
5519a1b7a1f0SDavid Ahern + NLA_ALIGN(sizeof(struct rtnexthop))
5520a1b7a1f0SDavid Ahern + nla_total_size(16); /* RTA_GATEWAY */
5521f88d8ea6SDavid Ahern
5522a1b7a1f0SDavid Ahern if (nh->fib_nh_lws) {
5523a1b7a1f0SDavid Ahern /* RTA_ENCAP_TYPE */
5524a1b7a1f0SDavid Ahern *nexthop_len += lwtunnel_get_encap_size(nh->fib_nh_lws);
5525a1b7a1f0SDavid Ahern /* RTA_ENCAP */
5526a1b7a1f0SDavid Ahern *nexthop_len += nla_total_size(2);
5527a1b7a1f0SDavid Ahern }
5528a1b7a1f0SDavid Ahern
5529a1b7a1f0SDavid Ahern return 0;
5530a1b7a1f0SDavid Ahern }
5531a1b7a1f0SDavid Ahern
rt6_nlmsg_size(struct fib6_info * f6i)5532a1b7a1f0SDavid Ahern static size_t rt6_nlmsg_size(struct fib6_info *f6i)
5533a1b7a1f0SDavid Ahern {
5534a1b7a1f0SDavid Ahern int nexthop_len;
5535a1b7a1f0SDavid Ahern
5536a1b7a1f0SDavid Ahern if (f6i->nh) {
5537a1b7a1f0SDavid Ahern nexthop_len = nla_total_size(4); /* RTA_NH_ID */
5538a1b7a1f0SDavid Ahern nexthop_for_each_fib6_nh(f6i->nh, rt6_nh_nlmsg_size,
5539a1b7a1f0SDavid Ahern &nexthop_len);
5540a1b7a1f0SDavid Ahern } else {
55414cc59f38SLu Wei struct fib6_info *sibling, *next_sibling;
5542a1b7a1f0SDavid Ahern struct fib6_nh *nh = f6i->fib6_nh;
5543a1b7a1f0SDavid Ahern
5544a1b7a1f0SDavid Ahern nexthop_len = 0;
5545a1b7a1f0SDavid Ahern if (f6i->fib6_nsiblings) {
55464cc59f38SLu Wei rt6_nh_nlmsg_size(nh, &nexthop_len);
5547beb1afacSDavid Ahern
55484cc59f38SLu Wei list_for_each_entry_safe(sibling, next_sibling,
55494cc59f38SLu Wei &f6i->fib6_siblings, fib6_siblings) {
55504cc59f38SLu Wei rt6_nh_nlmsg_size(sibling->fib6_nh, &nexthop_len);
55514cc59f38SLu Wei }
5552a1b7a1f0SDavid Ahern }
5553a1b7a1f0SDavid Ahern nexthop_len += lwtunnel_get_encap_size(nh->fib_nh_lws);
5554beb1afacSDavid Ahern }
5555beb1afacSDavid Ahern
5556339bf98fSThomas Graf return NLMSG_ALIGN(sizeof(struct rtmsg))
5557339bf98fSThomas Graf + nla_total_size(16) /* RTA_SRC */
5558339bf98fSThomas Graf + nla_total_size(16) /* RTA_DST */
5559339bf98fSThomas Graf + nla_total_size(16) /* RTA_GATEWAY */
5560339bf98fSThomas Graf + nla_total_size(16) /* RTA_PREFSRC */
5561339bf98fSThomas Graf + nla_total_size(4) /* RTA_TABLE */
5562339bf98fSThomas Graf + nla_total_size(4) /* RTA_IIF */
5563339bf98fSThomas Graf + nla_total_size(4) /* RTA_OIF */
5564339bf98fSThomas Graf + nla_total_size(4) /* RTA_PRIORITY */
55656a2b9ce0SNoriaki TAKAMIYA + RTAX_MAX * nla_total_size(4) /* RTA_METRICS */
5566ea697639SDaniel Borkmann + nla_total_size(sizeof(struct rta_cacheinfo))
5567c78ba6d6SLubomir Rintel + nla_total_size(TCP_CA_NAME_MAX) /* RTAX_CC_ALGO */
556819e42e45SRoopa Prabhu + nla_total_size(1) /* RTA_PREF */
5569beb1afacSDavid Ahern + nexthop_len;
5570beb1afacSDavid Ahern }
5571beb1afacSDavid Ahern
rt6_fill_node_nexthop(struct sk_buff * skb,struct nexthop * nh,unsigned char * flags)5572f88d8ea6SDavid Ahern static int rt6_fill_node_nexthop(struct sk_buff *skb, struct nexthop *nh,
5573f88d8ea6SDavid Ahern unsigned char *flags)
5574f88d8ea6SDavid Ahern {
5575f88d8ea6SDavid Ahern if (nexthop_is_multipath(nh)) {
5576f88d8ea6SDavid Ahern struct nlattr *mp;
5577f88d8ea6SDavid Ahern
55784255ff05SDavid Ahern mp = nla_nest_start_noflag(skb, RTA_MULTIPATH);
5579f88d8ea6SDavid Ahern if (!mp)
5580f88d8ea6SDavid Ahern goto nla_put_failure;
5581f88d8ea6SDavid Ahern
55827bdf4de1SDonald Sharp if (nexthop_mpath_fill_node(skb, nh, AF_INET6))
5583f88d8ea6SDavid Ahern goto nla_put_failure;
5584f88d8ea6SDavid Ahern
5585f88d8ea6SDavid Ahern nla_nest_end(skb, mp);
5586f88d8ea6SDavid Ahern } else {
5587f88d8ea6SDavid Ahern struct fib6_nh *fib6_nh;
5588f88d8ea6SDavid Ahern
5589f88d8ea6SDavid Ahern fib6_nh = nexthop_fib6_nh(nh);
55907bdf4de1SDonald Sharp if (fib_nexthop_info(skb, &fib6_nh->nh_common, AF_INET6,
5591f88d8ea6SDavid Ahern flags, false) < 0)
5592f88d8ea6SDavid Ahern goto nla_put_failure;
5593f88d8ea6SDavid Ahern }
5594f88d8ea6SDavid Ahern
5595f88d8ea6SDavid Ahern return 0;
5596f88d8ea6SDavid Ahern
5597f88d8ea6SDavid Ahern nla_put_failure:
5598f88d8ea6SDavid Ahern return -EMSGSIZE;
5599f88d8ea6SDavid Ahern }
5600f88d8ea6SDavid Ahern
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)5601d4ead6b3SDavid Ahern static int rt6_fill_node(struct net *net, struct sk_buff *skb,
56028d1c802bSDavid Ahern struct fib6_info *rt, struct dst_entry *dst,
5603d4ead6b3SDavid Ahern struct in6_addr *dest, struct in6_addr *src,
560415e47304SEric W. Biederman int iif, int type, u32 portid, u32 seq,
5605f8cfe2ceSDavid Ahern unsigned int flags)
56061da177e4SLinus Torvalds {
5607797a4c1fSEric Dumazet struct rt6_info *rt6 = dst_rt6_info(dst);
560822d0bd82SXin Long struct rt6key *rt6_dst, *rt6_src;
560922d0bd82SXin Long u32 *pmetrics, table, rt6_flags;
5610f88d8ea6SDavid Ahern unsigned char nh_flags = 0;
56111da177e4SLinus Torvalds struct nlmsghdr *nlh;
561222d0bd82SXin Long struct rtmsg *rtm;
5613d4ead6b3SDavid Ahern long expires = 0;
56141da177e4SLinus Torvalds
561515e47304SEric W. Biederman nlh = nlmsg_put(skb, portid, seq, type, sizeof(*rtm), flags);
561638308473SDavid S. Miller if (!nlh)
561726932566SPatrick McHardy return -EMSGSIZE;
56182d7202bfSThomas Graf
561922d0bd82SXin Long if (rt6) {
562022d0bd82SXin Long rt6_dst = &rt6->rt6i_dst;
562122d0bd82SXin Long rt6_src = &rt6->rt6i_src;
562222d0bd82SXin Long rt6_flags = rt6->rt6i_flags;
562322d0bd82SXin Long } else {
562422d0bd82SXin Long rt6_dst = &rt->fib6_dst;
562522d0bd82SXin Long rt6_src = &rt->fib6_src;
562622d0bd82SXin Long rt6_flags = rt->fib6_flags;
562722d0bd82SXin Long }
562822d0bd82SXin Long
56292d7202bfSThomas Graf rtm = nlmsg_data(nlh);
56301da177e4SLinus Torvalds rtm->rtm_family = AF_INET6;
563122d0bd82SXin Long rtm->rtm_dst_len = rt6_dst->plen;
563222d0bd82SXin Long rtm->rtm_src_len = rt6_src->plen;
56331da177e4SLinus Torvalds rtm->rtm_tos = 0;
563493c2fb25SDavid Ahern if (rt->fib6_table)
563593c2fb25SDavid Ahern table = rt->fib6_table->tb6_id;
5636c71099acSThomas Graf else
56379e762a4aSPatrick McHardy table = RT6_TABLE_UNSPEC;
563897f0082aSKalash Nainwal rtm->rtm_table = table < 256 ? table : RT_TABLE_COMPAT;
5639c78679e8SDavid S. Miller if (nla_put_u32(skb, RTA_TABLE, table))
5640c78679e8SDavid S. Miller goto nla_put_failure;
5641e8478e80SDavid Ahern
5642e8478e80SDavid Ahern rtm->rtm_type = rt->fib6_type;
56431da177e4SLinus Torvalds rtm->rtm_flags = 0;
56441da177e4SLinus Torvalds rtm->rtm_scope = RT_SCOPE_UNIVERSE;
564593c2fb25SDavid Ahern rtm->rtm_protocol = rt->fib6_protocol;
56461da177e4SLinus Torvalds
564722d0bd82SXin Long if (rt6_flags & RTF_CACHE)
56481da177e4SLinus Torvalds rtm->rtm_flags |= RTM_F_CLONED;
56491da177e4SLinus Torvalds
5650d4ead6b3SDavid Ahern if (dest) {
5651d4ead6b3SDavid Ahern if (nla_put_in6_addr(skb, RTA_DST, dest))
5652c78679e8SDavid S. Miller goto nla_put_failure;
56531da177e4SLinus Torvalds rtm->rtm_dst_len = 128;
56541da177e4SLinus Torvalds } else if (rtm->rtm_dst_len)
565522d0bd82SXin Long if (nla_put_in6_addr(skb, RTA_DST, &rt6_dst->addr))
5656c78679e8SDavid S. Miller goto nla_put_failure;
56571da177e4SLinus Torvalds #ifdef CONFIG_IPV6_SUBTREES
56581da177e4SLinus Torvalds if (src) {
5659930345eaSJiri Benc if (nla_put_in6_addr(skb, RTA_SRC, src))
5660c78679e8SDavid S. Miller goto nla_put_failure;
56611da177e4SLinus Torvalds rtm->rtm_src_len = 128;
5662c78679e8SDavid S. Miller } else if (rtm->rtm_src_len &&
566322d0bd82SXin Long nla_put_in6_addr(skb, RTA_SRC, &rt6_src->addr))
5664c78679e8SDavid S. Miller goto nla_put_failure;
56651da177e4SLinus Torvalds #endif
56667bc570c8SYOSHIFUJI Hideaki if (iif) {
56677bc570c8SYOSHIFUJI Hideaki #ifdef CONFIG_IPV6_MROUTE
566822d0bd82SXin Long if (ipv6_addr_is_multicast(&rt6_dst->addr)) {
5669fd61c6baSDavid Ahern int err = ip6mr_get_route(net, skb, rtm, portid);
56702cf75070SNikolay Aleksandrov
56717bc570c8SYOSHIFUJI Hideaki if (err == 0)
56727bc570c8SYOSHIFUJI Hideaki return 0;
5673fd61c6baSDavid Ahern if (err < 0)
56747bc570c8SYOSHIFUJI Hideaki goto nla_put_failure;
56757bc570c8SYOSHIFUJI Hideaki } else
56767bc570c8SYOSHIFUJI Hideaki #endif
5677c78679e8SDavid S. Miller if (nla_put_u32(skb, RTA_IIF, iif))
5678c78679e8SDavid S. Miller goto nla_put_failure;
5679d4ead6b3SDavid Ahern } else if (dest) {
56801da177e4SLinus Torvalds struct in6_addr saddr_buf;
5681e7f3e5fbSNicolas Dichtel if (ip6_route_get_saddr(net, rt, dest, 0, 0, &saddr_buf) == 0 &&
5682930345eaSJiri Benc nla_put_in6_addr(skb, RTA_PREFSRC, &saddr_buf))
5683c78679e8SDavid S. Miller goto nla_put_failure;
5684c3968a85SDaniel Walter }
5685c3968a85SDaniel Walter
568693c2fb25SDavid Ahern if (rt->fib6_prefsrc.plen) {
5687c3968a85SDaniel Walter struct in6_addr saddr_buf;
568893c2fb25SDavid Ahern saddr_buf = rt->fib6_prefsrc.addr;
5689930345eaSJiri Benc if (nla_put_in6_addr(skb, RTA_PREFSRC, &saddr_buf))
5690c78679e8SDavid S. Miller goto nla_put_failure;
56911da177e4SLinus Torvalds }
56922d7202bfSThomas Graf
5693d4ead6b3SDavid Ahern pmetrics = dst ? dst_metrics_ptr(dst) : rt->fib6_metrics->metrics;
5694d4ead6b3SDavid Ahern if (rtnetlink_put_metrics(skb, pmetrics) < 0)
56952d7202bfSThomas Graf goto nla_put_failure;
56962d7202bfSThomas Graf
569793c2fb25SDavid Ahern if (nla_put_u32(skb, RTA_PRIORITY, rt->fib6_metric))
5698beb1afacSDavid Ahern goto nla_put_failure;
5699beb1afacSDavid Ahern
5700beb1afacSDavid Ahern /* For multipath routes, walk the siblings list and add
5701beb1afacSDavid Ahern * each as a nexthop within RTA_MULTIPATH.
5702beb1afacSDavid Ahern */
570322d0bd82SXin Long if (rt6) {
570422d0bd82SXin Long if (rt6_flags & RTF_GATEWAY &&
570522d0bd82SXin Long nla_put_in6_addr(skb, RTA_GATEWAY, &rt6->rt6i_gateway))
570622d0bd82SXin Long goto nla_put_failure;
570722d0bd82SXin Long
570822d0bd82SXin Long if (dst->dev && nla_put_u32(skb, RTA_OIF, dst->dev->ifindex))
570922d0bd82SXin Long goto nla_put_failure;
57106b13d8f7SOliver Herms
57116b13d8f7SOliver Herms if (dst->lwtstate &&
57126b13d8f7SOliver Herms lwtunnel_fill_encap(skb, dst->lwtstate, RTA_ENCAP, RTA_ENCAP_TYPE) < 0)
57136b13d8f7SOliver Herms goto nla_put_failure;
571422d0bd82SXin Long } else if (rt->fib6_nsiblings) {
57158d1c802bSDavid Ahern struct fib6_info *sibling, *next_sibling;
5716beb1afacSDavid Ahern struct nlattr *mp;
5717beb1afacSDavid Ahern
5718ae0be8deSMichal Kubecek mp = nla_nest_start_noflag(skb, RTA_MULTIPATH);
5719beb1afacSDavid Ahern if (!mp)
5720beb1afacSDavid Ahern goto nla_put_failure;
5721beb1afacSDavid Ahern
57221cf844c7SDavid Ahern if (fib_add_nexthop(skb, &rt->fib6_nh->nh_common,
5723597aa16cSXiao Liang rt->fib6_nh->fib_nh_weight, AF_INET6,
5724597aa16cSXiao Liang 0) < 0)
5725beb1afacSDavid Ahern goto nla_put_failure;
5726beb1afacSDavid Ahern
5727beb1afacSDavid Ahern list_for_each_entry_safe(sibling, next_sibling,
572893c2fb25SDavid Ahern &rt->fib6_siblings, fib6_siblings) {
57291cf844c7SDavid Ahern if (fib_add_nexthop(skb, &sibling->fib6_nh->nh_common,
57307bdf4de1SDonald Sharp sibling->fib6_nh->fib_nh_weight,
5731597aa16cSXiao Liang AF_INET6, 0) < 0)
573294f826b8SEric Dumazet goto nla_put_failure;
573394f826b8SEric Dumazet }
57342d7202bfSThomas Graf
5735beb1afacSDavid Ahern nla_nest_end(skb, mp);
5736f88d8ea6SDavid Ahern } else if (rt->nh) {
5737f88d8ea6SDavid Ahern if (nla_put_u32(skb, RTA_NH_ID, rt->nh->id))
5738f88d8ea6SDavid Ahern goto nla_put_failure;
5739ecc5663cSDavid Ahern
5740f88d8ea6SDavid Ahern if (nexthop_is_blackhole(rt->nh))
5741f88d8ea6SDavid Ahern rtm->rtm_type = RTN_BLACKHOLE;
5742f88d8ea6SDavid Ahern
5743bdf00bf2SKuniyuki Iwashima if (READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode) &&
57444f80116dSRoopa Prabhu rt6_fill_node_nexthop(skb, rt->nh, &nh_flags) < 0)
5745f88d8ea6SDavid Ahern goto nla_put_failure;
5746f88d8ea6SDavid Ahern
5747f88d8ea6SDavid Ahern rtm->rtm_flags |= nh_flags;
5748f88d8ea6SDavid Ahern } else {
57497bdf4de1SDonald Sharp if (fib_nexthop_info(skb, &rt->fib6_nh->nh_common, AF_INET6,
5750ecc5663cSDavid Ahern &nh_flags, false) < 0)
5751c78679e8SDavid S. Miller goto nla_put_failure;
5752ecc5663cSDavid Ahern
5753ecc5663cSDavid Ahern rtm->rtm_flags |= nh_flags;
5754beb1afacSDavid Ahern }
57558253947eSLi Wei
575622d0bd82SXin Long if (rt6_flags & RTF_EXPIRES) {
575714895687SDavid Ahern expires = dst ? dst->expires : rt->expires;
575814895687SDavid Ahern expires -= jiffies;
575914895687SDavid Ahern }
576069cdf8f9SYOSHIFUJI Hideaki
5761bb3c4ab9SIdo Schimmel if (!dst) {
5762d95d6320SEric Dumazet if (READ_ONCE(rt->offload))
5763bb3c4ab9SIdo Schimmel rtm->rtm_flags |= RTM_F_OFFLOAD;
5764d95d6320SEric Dumazet if (READ_ONCE(rt->trap))
5765bb3c4ab9SIdo Schimmel rtm->rtm_flags |= RTM_F_TRAP;
5766d95d6320SEric Dumazet if (READ_ONCE(rt->offload_failed))
57670c5fcf9eSAmit Cohen rtm->rtm_flags |= RTM_F_OFFLOAD_FAILED;
5768bb3c4ab9SIdo Schimmel }
5769bb3c4ab9SIdo Schimmel
5770d4ead6b3SDavid Ahern if (rtnl_put_cacheinfo(skb, dst, 0, expires, dst ? dst->error : 0) < 0)
5771e3703b3dSThomas Graf goto nla_put_failure;
57721da177e4SLinus Torvalds
577322d0bd82SXin Long if (nla_put_u8(skb, RTA_PREF, IPV6_EXTRACT_PREF(rt6_flags)))
5774c78ba6d6SLubomir Rintel goto nla_put_failure;
5775c78ba6d6SLubomir Rintel
577619e42e45SRoopa Prabhu
5777053c095aSJohannes Berg nlmsg_end(skb, nlh);
5778053c095aSJohannes Berg return 0;
57792d7202bfSThomas Graf
57802d7202bfSThomas Graf nla_put_failure:
578126932566SPatrick McHardy nlmsg_cancel(skb, nlh);
578226932566SPatrick McHardy return -EMSGSIZE;
57831da177e4SLinus Torvalds }
57841da177e4SLinus Torvalds
fib6_info_nh_uses_dev(struct fib6_nh * nh,void * arg)57852c170e07SDavid Ahern static int fib6_info_nh_uses_dev(struct fib6_nh *nh, void *arg)
57862c170e07SDavid Ahern {
57872c170e07SDavid Ahern const struct net_device *dev = arg;
57882c170e07SDavid Ahern
57892c170e07SDavid Ahern if (nh->fib_nh_dev == dev)
57902c170e07SDavid Ahern return 1;
57912c170e07SDavid Ahern
57922c170e07SDavid Ahern return 0;
57932c170e07SDavid Ahern }
57942c170e07SDavid Ahern
fib6_info_uses_dev(const struct fib6_info * f6i,const struct net_device * dev)579513e38901SDavid Ahern static bool fib6_info_uses_dev(const struct fib6_info *f6i,
579613e38901SDavid Ahern const struct net_device *dev)
579713e38901SDavid Ahern {
57982c170e07SDavid Ahern if (f6i->nh) {
57992c170e07SDavid Ahern struct net_device *_dev = (struct net_device *)dev;
58002c170e07SDavid Ahern
58012c170e07SDavid Ahern return !!nexthop_for_each_fib6_nh(f6i->nh,
58022c170e07SDavid Ahern fib6_info_nh_uses_dev,
58032c170e07SDavid Ahern _dev);
58042c170e07SDavid Ahern }
58052c170e07SDavid Ahern
58061cf844c7SDavid Ahern if (f6i->fib6_nh->fib_nh_dev == dev)
580713e38901SDavid Ahern return true;
580813e38901SDavid Ahern
580913e38901SDavid Ahern if (f6i->fib6_nsiblings) {
581013e38901SDavid Ahern struct fib6_info *sibling, *next_sibling;
581113e38901SDavid Ahern
581213e38901SDavid Ahern list_for_each_entry_safe(sibling, next_sibling,
581313e38901SDavid Ahern &f6i->fib6_siblings, fib6_siblings) {
58141cf844c7SDavid Ahern if (sibling->fib6_nh->fib_nh_dev == dev)
581513e38901SDavid Ahern return true;
581613e38901SDavid Ahern }
581713e38901SDavid Ahern }
581813e38901SDavid Ahern
581913e38901SDavid Ahern return false;
582013e38901SDavid Ahern }
582113e38901SDavid Ahern
58221e47b483SStefano Brivio struct fib6_nh_exception_dump_walker {
58231e47b483SStefano Brivio struct rt6_rtnl_dump_arg *dump;
58241e47b483SStefano Brivio struct fib6_info *rt;
58251e47b483SStefano Brivio unsigned int flags;
58261e47b483SStefano Brivio unsigned int skip;
58271e47b483SStefano Brivio unsigned int count;
58281e47b483SStefano Brivio };
58291e47b483SStefano Brivio
rt6_nh_dump_exceptions(struct fib6_nh * nh,void * arg)58301e47b483SStefano Brivio static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg)
58311e47b483SStefano Brivio {
58321e47b483SStefano Brivio struct fib6_nh_exception_dump_walker *w = arg;
58331e47b483SStefano Brivio struct rt6_rtnl_dump_arg *dump = w->dump;
58341e47b483SStefano Brivio struct rt6_exception_bucket *bucket;
58351e47b483SStefano Brivio struct rt6_exception *rt6_ex;
58361e47b483SStefano Brivio int i, err;
58371e47b483SStefano Brivio
58381e47b483SStefano Brivio bucket = fib6_nh_get_excptn_bucket(nh, NULL);
58391e47b483SStefano Brivio if (!bucket)
58401e47b483SStefano Brivio return 0;
58411e47b483SStefano Brivio
58421e47b483SStefano Brivio for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
58431e47b483SStefano Brivio hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) {
58441e47b483SStefano Brivio if (w->skip) {
58451e47b483SStefano Brivio w->skip--;
58461e47b483SStefano Brivio continue;
58471e47b483SStefano Brivio }
58481e47b483SStefano Brivio
58491e47b483SStefano Brivio /* Expiration of entries doesn't bump sernum, insertion
58501e47b483SStefano Brivio * does. Removal is triggered by insertion, so we can
58511e47b483SStefano Brivio * rely on the fact that if entries change between two
58521e47b483SStefano Brivio * partial dumps, this node is scanned again completely,
58531e47b483SStefano Brivio * see rt6_insert_exception() and fib6_dump_table().
58541e47b483SStefano Brivio *
58551e47b483SStefano Brivio * Count expired entries we go through as handled
58561e47b483SStefano Brivio * entries that we'll skip next time, in case of partial
58571e47b483SStefano Brivio * node dump. Otherwise, if entries expire meanwhile,
58581e47b483SStefano Brivio * we'll skip the wrong amount.
58591e47b483SStefano Brivio */
58601e47b483SStefano Brivio if (rt6_check_expired(rt6_ex->rt6i)) {
58611e47b483SStefano Brivio w->count++;
58621e47b483SStefano Brivio continue;
58631e47b483SStefano Brivio }
58641e47b483SStefano Brivio
58651e47b483SStefano Brivio err = rt6_fill_node(dump->net, dump->skb, w->rt,
58661e47b483SStefano Brivio &rt6_ex->rt6i->dst, NULL, NULL, 0,
58671e47b483SStefano Brivio RTM_NEWROUTE,
58681e47b483SStefano Brivio NETLINK_CB(dump->cb->skb).portid,
58691e47b483SStefano Brivio dump->cb->nlh->nlmsg_seq, w->flags);
58701e47b483SStefano Brivio if (err)
58711e47b483SStefano Brivio return err;
58721e47b483SStefano Brivio
58731e47b483SStefano Brivio w->count++;
58741e47b483SStefano Brivio }
58751e47b483SStefano Brivio bucket++;
58761e47b483SStefano Brivio }
58771e47b483SStefano Brivio
58781e47b483SStefano Brivio return 0;
58791e47b483SStefano Brivio }
58801e47b483SStefano Brivio
5881bf9a8a06SStefano Brivio /* Return -1 if done with node, number of handled routes on partial dump */
rt6_dump_route(struct fib6_info * rt,void * p_arg,unsigned int skip)58821e47b483SStefano Brivio int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip)
58831da177e4SLinus Torvalds {
58841da177e4SLinus Torvalds struct rt6_rtnl_dump_arg *arg = (struct rt6_rtnl_dump_arg *) p_arg;
588513e38901SDavid Ahern struct fib_dump_filter *filter = &arg->filter;
588613e38901SDavid Ahern unsigned int flags = NLM_F_MULTI;
58871f17e2f2SDavid Ahern struct net *net = arg->net;
58881e47b483SStefano Brivio int count = 0;
58891f17e2f2SDavid Ahern
5890421842edSDavid Ahern if (rt == net->ipv6.fib6_null_entry)
5891bf9a8a06SStefano Brivio return -1;
58921da177e4SLinus Torvalds
589313e38901SDavid Ahern if ((filter->flags & RTM_F_PREFIX) &&
589493c2fb25SDavid Ahern !(rt->fib6_flags & RTF_PREFIX_RT)) {
5895f8cfe2ceSDavid Ahern /* success since this is not a prefix route */
5896bf9a8a06SStefano Brivio return -1;
5897f8cfe2ceSDavid Ahern }
58981e47b483SStefano Brivio if (filter->filter_set &&
58991e47b483SStefano Brivio ((filter->rt_type && rt->fib6_type != filter->rt_type) ||
590013e38901SDavid Ahern (filter->dev && !fib6_info_uses_dev(rt, filter->dev)) ||
59011e47b483SStefano Brivio (filter->protocol && rt->fib6_protocol != filter->protocol))) {
5902bf9a8a06SStefano Brivio return -1;
590313e38901SDavid Ahern }
59041e47b483SStefano Brivio
59051e47b483SStefano Brivio if (filter->filter_set ||
59061e47b483SStefano Brivio !filter->dump_routes || !filter->dump_exceptions) {
590713e38901SDavid Ahern flags |= NLM_F_DUMP_FILTERED;
5908f8cfe2ceSDavid Ahern }
59091da177e4SLinus Torvalds
59101e47b483SStefano Brivio if (filter->dump_routes) {
59111e47b483SStefano Brivio if (skip) {
59121e47b483SStefano Brivio skip--;
59131e47b483SStefano Brivio } else {
59141e47b483SStefano Brivio if (rt6_fill_node(net, arg->skb, rt, NULL, NULL, NULL,
59151e47b483SStefano Brivio 0, RTM_NEWROUTE,
5916bf9a8a06SStefano Brivio NETLINK_CB(arg->cb->skb).portid,
59171e47b483SStefano Brivio arg->cb->nlh->nlmsg_seq, flags)) {
5918bf9a8a06SStefano Brivio return 0;
59191e47b483SStefano Brivio }
59201e47b483SStefano Brivio count++;
59211e47b483SStefano Brivio }
59221e47b483SStefano Brivio }
59231e47b483SStefano Brivio
59241e47b483SStefano Brivio if (filter->dump_exceptions) {
59251e47b483SStefano Brivio struct fib6_nh_exception_dump_walker w = { .dump = arg,
59261e47b483SStefano Brivio .rt = rt,
59271e47b483SStefano Brivio .flags = flags,
59281e47b483SStefano Brivio .skip = skip,
59291e47b483SStefano Brivio .count = 0 };
59301e47b483SStefano Brivio int err;
59311e47b483SStefano Brivio
59323b525691SEric Dumazet rcu_read_lock();
59331e47b483SStefano Brivio if (rt->nh) {
59341e47b483SStefano Brivio err = nexthop_for_each_fib6_nh(rt->nh,
59351e47b483SStefano Brivio rt6_nh_dump_exceptions,
59361e47b483SStefano Brivio &w);
59371e47b483SStefano Brivio } else {
59381e47b483SStefano Brivio err = rt6_nh_dump_exceptions(rt->fib6_nh, &w);
59391e47b483SStefano Brivio }
59403b525691SEric Dumazet rcu_read_unlock();
59411e47b483SStefano Brivio
59421e47b483SStefano Brivio if (err)
594374fd304fSColin Ian King return count + w.count;
59441e47b483SStefano Brivio }
5945bf9a8a06SStefano Brivio
5946bf9a8a06SStefano Brivio return -1;
59471da177e4SLinus Torvalds }
59481da177e4SLinus Torvalds
inet6_rtm_valid_getroute_req(struct sk_buff * skb,const struct nlmsghdr * nlh,struct nlattr ** tb,struct netlink_ext_ack * extack)59490eff0a27SJakub Kicinski static int inet6_rtm_valid_getroute_req(struct sk_buff *skb,
59500eff0a27SJakub Kicinski const struct nlmsghdr *nlh,
59510eff0a27SJakub Kicinski struct nlattr **tb,
59520eff0a27SJakub Kicinski struct netlink_ext_ack *extack)
59530eff0a27SJakub Kicinski {
59540eff0a27SJakub Kicinski struct rtmsg *rtm;
59550eff0a27SJakub Kicinski int i, err;
59560eff0a27SJakub Kicinski
59570eff0a27SJakub Kicinski if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*rtm))) {
59580eff0a27SJakub Kicinski NL_SET_ERR_MSG_MOD(extack,
59590eff0a27SJakub Kicinski "Invalid header for get route request");
59600eff0a27SJakub Kicinski return -EINVAL;
59610eff0a27SJakub Kicinski }
59620eff0a27SJakub Kicinski
59630eff0a27SJakub Kicinski if (!netlink_strict_get_check(skb))
59648cb08174SJohannes Berg return nlmsg_parse_deprecated(nlh, sizeof(*rtm), tb, RTA_MAX,
59650eff0a27SJakub Kicinski rtm_ipv6_policy, extack);
59660eff0a27SJakub Kicinski
59670eff0a27SJakub Kicinski rtm = nlmsg_data(nlh);
59680eff0a27SJakub Kicinski if ((rtm->rtm_src_len && rtm->rtm_src_len != 128) ||
59690eff0a27SJakub Kicinski (rtm->rtm_dst_len && rtm->rtm_dst_len != 128) ||
59700eff0a27SJakub Kicinski rtm->rtm_table || rtm->rtm_protocol || rtm->rtm_scope ||
59710eff0a27SJakub Kicinski rtm->rtm_type) {
59720eff0a27SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "Invalid values in header for get route request");
59730eff0a27SJakub Kicinski return -EINVAL;
59740eff0a27SJakub Kicinski }
59750eff0a27SJakub Kicinski if (rtm->rtm_flags & ~RTM_F_FIB_MATCH) {
59760eff0a27SJakub Kicinski NL_SET_ERR_MSG_MOD(extack,
59770eff0a27SJakub Kicinski "Invalid flags for get route request");
59780eff0a27SJakub Kicinski return -EINVAL;
59790eff0a27SJakub Kicinski }
59800eff0a27SJakub Kicinski
59818cb08174SJohannes Berg err = nlmsg_parse_deprecated_strict(nlh, sizeof(*rtm), tb, RTA_MAX,
59820eff0a27SJakub Kicinski rtm_ipv6_policy, extack);
59830eff0a27SJakub Kicinski if (err)
59840eff0a27SJakub Kicinski return err;
59850eff0a27SJakub Kicinski
59860eff0a27SJakub Kicinski if ((tb[RTA_SRC] && !rtm->rtm_src_len) ||
59870eff0a27SJakub Kicinski (tb[RTA_DST] && !rtm->rtm_dst_len)) {
59880eff0a27SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "rtm_src_len and rtm_dst_len must be 128 for IPv6");
59890eff0a27SJakub Kicinski return -EINVAL;
59900eff0a27SJakub Kicinski }
59910eff0a27SJakub Kicinski
59920eff0a27SJakub Kicinski for (i = 0; i <= RTA_MAX; i++) {
59930eff0a27SJakub Kicinski if (!tb[i])
59940eff0a27SJakub Kicinski continue;
59950eff0a27SJakub Kicinski
59960eff0a27SJakub Kicinski switch (i) {
59970eff0a27SJakub Kicinski case RTA_SRC:
59980eff0a27SJakub Kicinski case RTA_DST:
59990eff0a27SJakub Kicinski case RTA_IIF:
60000eff0a27SJakub Kicinski case RTA_OIF:
60010eff0a27SJakub Kicinski case RTA_MARK:
60020eff0a27SJakub Kicinski case RTA_UID:
60030eff0a27SJakub Kicinski case RTA_SPORT:
60040eff0a27SJakub Kicinski case RTA_DPORT:
60050eff0a27SJakub Kicinski case RTA_IP_PROTO:
60060eff0a27SJakub Kicinski break;
60070eff0a27SJakub Kicinski default:
60080eff0a27SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "Unsupported attribute in get route request");
60090eff0a27SJakub Kicinski return -EINVAL;
60100eff0a27SJakub Kicinski }
60110eff0a27SJakub Kicinski }
60120eff0a27SJakub Kicinski
60130eff0a27SJakub Kicinski return 0;
60140eff0a27SJakub Kicinski }
60150eff0a27SJakub Kicinski
inet6_rtm_getroute(struct sk_buff * in_skb,struct nlmsghdr * nlh,struct netlink_ext_ack * extack)6016c21ef3e3SDavid Ahern static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
6017c21ef3e3SDavid Ahern struct netlink_ext_ack *extack)
60181da177e4SLinus Torvalds {
60193b1e0a65SYOSHIFUJI Hideaki struct net *net = sock_net(in_skb->sk);
6020ab364a6fSThomas Graf struct nlattr *tb[RTA_MAX+1];
602118c3a61cSRoopa Prabhu int err, iif = 0, oif = 0;
6022a68886a6SDavid Ahern struct fib6_info *from;
602318c3a61cSRoopa Prabhu struct dst_entry *dst;
60241da177e4SLinus Torvalds struct rt6_info *rt;
6025ab364a6fSThomas Graf struct sk_buff *skb;
6026ab364a6fSThomas Graf struct rtmsg *rtm;
6027744486d4SMaciej Żenczykowski struct flowi6 fl6 = {};
602818c3a61cSRoopa Prabhu bool fibmatch;
6029ab364a6fSThomas Graf
60300eff0a27SJakub Kicinski err = inet6_rtm_valid_getroute_req(in_skb, nlh, tb, extack);
6031ab364a6fSThomas Graf if (err < 0)
6032ab364a6fSThomas Graf goto errout;
6033ab364a6fSThomas Graf
6034ab364a6fSThomas Graf err = -EINVAL;
603538b7097bSHannes Frederic Sowa rtm = nlmsg_data(nlh);
603638b7097bSHannes Frederic Sowa fl6.flowlabel = ip6_make_flowinfo(rtm->rtm_tos, 0);
603718c3a61cSRoopa Prabhu fibmatch = !!(rtm->rtm_flags & RTM_F_FIB_MATCH);
6038ab364a6fSThomas Graf
6039ab364a6fSThomas Graf if (tb[RTA_SRC]) {
6040ab364a6fSThomas Graf if (nla_len(tb[RTA_SRC]) < sizeof(struct in6_addr))
6041ab364a6fSThomas Graf goto errout;
6042ab364a6fSThomas Graf
60434e3fd7a0SAlexey Dobriyan fl6.saddr = *(struct in6_addr *)nla_data(tb[RTA_SRC]);
6044ab364a6fSThomas Graf }
6045ab364a6fSThomas Graf
6046ab364a6fSThomas Graf if (tb[RTA_DST]) {
6047ab364a6fSThomas Graf if (nla_len(tb[RTA_DST]) < sizeof(struct in6_addr))
6048ab364a6fSThomas Graf goto errout;
6049ab364a6fSThomas Graf
60504e3fd7a0SAlexey Dobriyan fl6.daddr = *(struct in6_addr *)nla_data(tb[RTA_DST]);
6051ab364a6fSThomas Graf }
6052ab364a6fSThomas Graf
6053ab364a6fSThomas Graf if (tb[RTA_IIF])
6054ab364a6fSThomas Graf iif = nla_get_u32(tb[RTA_IIF]);
6055ab364a6fSThomas Graf
6056ab364a6fSThomas Graf if (tb[RTA_OIF])
605772331bc0SShmulik Ladkani oif = nla_get_u32(tb[RTA_OIF]);
6058ab364a6fSThomas Graf
60592e47b291SLorenzo Colitti if (tb[RTA_MARK])
60602e47b291SLorenzo Colitti fl6.flowi6_mark = nla_get_u32(tb[RTA_MARK]);
60612e47b291SLorenzo Colitti
6062622ec2c9SLorenzo Colitti if (tb[RTA_UID])
6063622ec2c9SLorenzo Colitti fl6.flowi6_uid = make_kuid(current_user_ns(),
6064622ec2c9SLorenzo Colitti nla_get_u32(tb[RTA_UID]));
6065622ec2c9SLorenzo Colitti else
6066622ec2c9SLorenzo Colitti fl6.flowi6_uid = iif ? INVALID_UID : current_uid();
6067622ec2c9SLorenzo Colitti
6068eacb9384SRoopa Prabhu if (tb[RTA_SPORT])
6069eacb9384SRoopa Prabhu fl6.fl6_sport = nla_get_be16(tb[RTA_SPORT]);
6070eacb9384SRoopa Prabhu
6071eacb9384SRoopa Prabhu if (tb[RTA_DPORT])
6072eacb9384SRoopa Prabhu fl6.fl6_dport = nla_get_be16(tb[RTA_DPORT]);
6073eacb9384SRoopa Prabhu
6074eacb9384SRoopa Prabhu if (tb[RTA_IP_PROTO]) {
6075eacb9384SRoopa Prabhu err = rtm_getroute_parse_ip_proto(tb[RTA_IP_PROTO],
60765e1a99eaSHangbin Liu &fl6.flowi6_proto, AF_INET6,
60775e1a99eaSHangbin Liu extack);
6078eacb9384SRoopa Prabhu if (err)
6079eacb9384SRoopa Prabhu goto errout;
6080eacb9384SRoopa Prabhu }
6081eacb9384SRoopa Prabhu
6082ab364a6fSThomas Graf if (iif) {
6083ab364a6fSThomas Graf struct net_device *dev;
608472331bc0SShmulik Ladkani int flags = 0;
608572331bc0SShmulik Ladkani
6086121622dbSFlorian Westphal rcu_read_lock();
6087121622dbSFlorian Westphal
6088121622dbSFlorian Westphal dev = dev_get_by_index_rcu(net, iif);
6089ab364a6fSThomas Graf if (!dev) {
6090121622dbSFlorian Westphal rcu_read_unlock();
6091ab364a6fSThomas Graf err = -ENODEV;
6092ab364a6fSThomas Graf goto errout;
6093ab364a6fSThomas Graf }
609472331bc0SShmulik Ladkani
609572331bc0SShmulik Ladkani fl6.flowi6_iif = iif;
609672331bc0SShmulik Ladkani
609772331bc0SShmulik Ladkani if (!ipv6_addr_any(&fl6.saddr))
609872331bc0SShmulik Ladkani flags |= RT6_LOOKUP_F_HAS_SADDR;
609972331bc0SShmulik Ladkani
6100b75cc8f9SDavid Ahern dst = ip6_route_input_lookup(net, dev, &fl6, NULL, flags);
6101121622dbSFlorian Westphal
6102121622dbSFlorian Westphal rcu_read_unlock();
610372331bc0SShmulik Ladkani } else {
610472331bc0SShmulik Ladkani fl6.flowi6_oif = oif;
610572331bc0SShmulik Ladkani
610618c3a61cSRoopa Prabhu dst = ip6_route_output(net, NULL, &fl6);
610718c3a61cSRoopa Prabhu }
610818c3a61cSRoopa Prabhu
610918c3a61cSRoopa Prabhu
6110797a4c1fSEric Dumazet rt = dst_rt6_info(dst);
611118c3a61cSRoopa Prabhu if (rt->dst.error) {
611218c3a61cSRoopa Prabhu err = rt->dst.error;
611318c3a61cSRoopa Prabhu ip6_rt_put(rt);
611418c3a61cSRoopa Prabhu goto errout;
6115ab364a6fSThomas Graf }
61161da177e4SLinus Torvalds
61179d6acb3bSWANG Cong if (rt == net->ipv6.ip6_null_entry) {
61189d6acb3bSWANG Cong err = rt->dst.error;
61199d6acb3bSWANG Cong ip6_rt_put(rt);
61209d6acb3bSWANG Cong goto errout;
61219d6acb3bSWANG Cong }
61229d6acb3bSWANG Cong
61231da177e4SLinus Torvalds skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
612438308473SDavid S. Miller if (!skb) {
612594e187c0SAmerigo Wang ip6_rt_put(rt);
6126ab364a6fSThomas Graf err = -ENOBUFS;
6127ab364a6fSThomas Graf goto errout;
6128ab364a6fSThomas Graf }
61291da177e4SLinus Torvalds
6130d8d1f30bSChangli Gao skb_dst_set(skb, &rt->dst);
6131a68886a6SDavid Ahern
6132a68886a6SDavid Ahern rcu_read_lock();
6133a68886a6SDavid Ahern from = rcu_dereference(rt->from);
6134886b7a50SMartin KaFai Lau if (from) {
613518c3a61cSRoopa Prabhu if (fibmatch)
6136886b7a50SMartin KaFai Lau err = rt6_fill_node(net, skb, from, NULL, NULL, NULL,
6137886b7a50SMartin KaFai Lau iif, RTM_NEWROUTE,
6138886b7a50SMartin KaFai Lau NETLINK_CB(in_skb).portid,
613918c3a61cSRoopa Prabhu nlh->nlmsg_seq, 0);
614018c3a61cSRoopa Prabhu else
6141a68886a6SDavid Ahern err = rt6_fill_node(net, skb, from, dst, &fl6.daddr,
6142a68886a6SDavid Ahern &fl6.saddr, iif, RTM_NEWROUTE,
6143886b7a50SMartin KaFai Lau NETLINK_CB(in_skb).portid,
6144886b7a50SMartin KaFai Lau nlh->nlmsg_seq, 0);
6145886b7a50SMartin KaFai Lau } else {
6146886b7a50SMartin KaFai Lau err = -ENETUNREACH;
6147886b7a50SMartin KaFai Lau }
6148a68886a6SDavid Ahern rcu_read_unlock();
6149a68886a6SDavid Ahern
61501da177e4SLinus Torvalds if (err < 0) {
6151ab364a6fSThomas Graf kfree_skb(skb);
6152ab364a6fSThomas Graf goto errout;
61531da177e4SLinus Torvalds }
61541da177e4SLinus Torvalds
615515e47304SEric W. Biederman err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
6156ab364a6fSThomas Graf errout:
61571da177e4SLinus Torvalds return err;
61581da177e4SLinus Torvalds }
61591da177e4SLinus Torvalds
inet6_rt_notify(int event,struct fib6_info * rt,struct nl_info * info,unsigned int nlm_flags)61608d1c802bSDavid Ahern void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
616137a1d361SRoopa Prabhu unsigned int nlm_flags)
61621da177e4SLinus Torvalds {
61631da177e4SLinus Torvalds struct sk_buff *skb;
61645578689aSDaniel Lezcano struct net *net = info->nl_net;
6165528c4cebSDenis V. Lunev u32 seq;
6166528c4cebSDenis V. Lunev int err;
61670d51aa80SJamal Hadi Salim
6168528c4cebSDenis V. Lunev err = -ENOBUFS;
616938308473SDavid S. Miller seq = info->nlh ? info->nlh->nlmsg_seq : 0;
617086872cb5SThomas Graf
617119e42e45SRoopa Prabhu skb = nlmsg_new(rt6_nlmsg_size(rt), gfp_any());
617238308473SDavid S. Miller if (!skb)
617321713ebcSThomas Graf goto errout;
61741da177e4SLinus Torvalds
6175d4ead6b3SDavid Ahern err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,
6176f8cfe2ceSDavid Ahern event, info->portid, seq, nlm_flags);
617726932566SPatrick McHardy if (err < 0) {
617826932566SPatrick McHardy /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
617926932566SPatrick McHardy WARN_ON(err == -EMSGSIZE);
618026932566SPatrick McHardy kfree_skb(skb);
618126932566SPatrick McHardy goto errout;
618226932566SPatrick McHardy }
618315e47304SEric W. Biederman rtnl_notify(skb, net, info->portid, RTNLGRP_IPV6_ROUTE,
61845578689aSDaniel Lezcano info->nlh, gfp_any());
61851ce85fe4SPablo Neira Ayuso return;
618621713ebcSThomas Graf errout:
618721713ebcSThomas Graf if (err < 0)
61885578689aSDaniel Lezcano rtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err);
61891da177e4SLinus Torvalds }
61901da177e4SLinus Torvalds
fib6_rt_update(struct net * net,struct fib6_info * rt,struct nl_info * info)619119a3b7eeSDavid Ahern void fib6_rt_update(struct net *net, struct fib6_info *rt,
619219a3b7eeSDavid Ahern struct nl_info *info)
619319a3b7eeSDavid Ahern {
619419a3b7eeSDavid Ahern u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
619519a3b7eeSDavid Ahern struct sk_buff *skb;
619619a3b7eeSDavid Ahern int err = -ENOBUFS;
619719a3b7eeSDavid Ahern
619819a3b7eeSDavid Ahern skb = nlmsg_new(rt6_nlmsg_size(rt), gfp_any());
619919a3b7eeSDavid Ahern if (!skb)
620019a3b7eeSDavid Ahern goto errout;
620119a3b7eeSDavid Ahern
620219a3b7eeSDavid Ahern err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,
620319a3b7eeSDavid Ahern RTM_NEWROUTE, info->portid, seq, NLM_F_REPLACE);
620419a3b7eeSDavid Ahern if (err < 0) {
620519a3b7eeSDavid Ahern /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
620619a3b7eeSDavid Ahern WARN_ON(err == -EMSGSIZE);
620719a3b7eeSDavid Ahern kfree_skb(skb);
620819a3b7eeSDavid Ahern goto errout;
620919a3b7eeSDavid Ahern }
621019a3b7eeSDavid Ahern rtnl_notify(skb, net, info->portid, RTNLGRP_IPV6_ROUTE,
621119a3b7eeSDavid Ahern info->nlh, gfp_any());
621219a3b7eeSDavid Ahern return;
621319a3b7eeSDavid Ahern errout:
621419a3b7eeSDavid Ahern if (err < 0)
621519a3b7eeSDavid Ahern rtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err);
621619a3b7eeSDavid Ahern }
621719a3b7eeSDavid Ahern
fib6_info_hw_flags_set(struct net * net,struct fib6_info * f6i,bool offload,bool trap,bool offload_failed)6218907eea48SAmit Cohen void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i,
62190c5fcf9eSAmit Cohen bool offload, bool trap, bool offload_failed)
6220907eea48SAmit Cohen {
6221907eea48SAmit Cohen struct sk_buff *skb;
6222907eea48SAmit Cohen int err;
6223907eea48SAmit Cohen
6224d95d6320SEric Dumazet if (READ_ONCE(f6i->offload) == offload &&
6225d95d6320SEric Dumazet READ_ONCE(f6i->trap) == trap &&
6226d95d6320SEric Dumazet READ_ONCE(f6i->offload_failed) == offload_failed)
6227907eea48SAmit Cohen return;
6228907eea48SAmit Cohen
6229d95d6320SEric Dumazet WRITE_ONCE(f6i->offload, offload);
6230d95d6320SEric Dumazet WRITE_ONCE(f6i->trap, trap);
62316fad361aSAmit Cohen
62326fad361aSAmit Cohen /* 2 means send notifications only if offload_failed was changed. */
62336fad361aSAmit Cohen if (net->ipv6.sysctl.fib_notify_on_flag_change == 2 &&
6234d95d6320SEric Dumazet READ_ONCE(f6i->offload_failed) == offload_failed)
62356fad361aSAmit Cohen return;
62366fad361aSAmit Cohen
6237d95d6320SEric Dumazet WRITE_ONCE(f6i->offload_failed, offload_failed);
6238907eea48SAmit Cohen
6239907eea48SAmit Cohen if (!rcu_access_pointer(f6i->fib6_node))
6240907eea48SAmit Cohen /* The route was removed from the tree, do not send
624189e8347fSBhaskar Chowdhury * notification.
6242907eea48SAmit Cohen */
6243907eea48SAmit Cohen return;
6244907eea48SAmit Cohen
6245907eea48SAmit Cohen if (!net->ipv6.sysctl.fib_notify_on_flag_change)
6246907eea48SAmit Cohen return;
6247907eea48SAmit Cohen
6248907eea48SAmit Cohen skb = nlmsg_new(rt6_nlmsg_size(f6i), GFP_KERNEL);
6249907eea48SAmit Cohen if (!skb) {
6250907eea48SAmit Cohen err = -ENOBUFS;
6251907eea48SAmit Cohen goto errout;
6252907eea48SAmit Cohen }
6253907eea48SAmit Cohen
6254907eea48SAmit Cohen err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0,
6255907eea48SAmit Cohen 0, 0);
6256907eea48SAmit Cohen if (err < 0) {
6257907eea48SAmit Cohen /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
6258907eea48SAmit Cohen WARN_ON(err == -EMSGSIZE);
6259907eea48SAmit Cohen kfree_skb(skb);
6260907eea48SAmit Cohen goto errout;
6261907eea48SAmit Cohen }
6262907eea48SAmit Cohen
6263907eea48SAmit Cohen rtnl_notify(skb, net, 0, RTNLGRP_IPV6_ROUTE, NULL, GFP_KERNEL);
6264907eea48SAmit Cohen return;
6265907eea48SAmit Cohen
6266907eea48SAmit Cohen errout:
6267907eea48SAmit Cohen rtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err);
6268907eea48SAmit Cohen }
6269907eea48SAmit Cohen EXPORT_SYMBOL(fib6_info_hw_flags_set);
6270907eea48SAmit Cohen
ip6_route_dev_notify(struct notifier_block * this,unsigned long event,void * ptr)62718ed67789SDaniel Lezcano static int ip6_route_dev_notify(struct notifier_block *this,
6272351638e7SJiri Pirko unsigned long event, void *ptr)
62738ed67789SDaniel Lezcano {
6274351638e7SJiri Pirko struct net_device *dev = netdev_notifier_info_to_dev(ptr);
6275c346dca1SYOSHIFUJI Hideaki struct net *net = dev_net(dev);
62768ed67789SDaniel Lezcano
6277242d3a49SWANG Cong if (!(dev->flags & IFF_LOOPBACK))
6278242d3a49SWANG Cong return NOTIFY_OK;
6279242d3a49SWANG Cong
6280242d3a49SWANG Cong if (event == NETDEV_REGISTER) {
62811cf844c7SDavid Ahern net->ipv6.fib6_null_entry->fib6_nh->fib_nh_dev = dev;
6282d8d1f30bSChangli Gao net->ipv6.ip6_null_entry->dst.dev = dev;
62838ed67789SDaniel Lezcano net->ipv6.ip6_null_entry->rt6i_idev = in6_dev_get(dev);
62848ed67789SDaniel Lezcano #ifdef CONFIG_IPV6_MULTIPLE_TABLES
6285d8d1f30bSChangli Gao net->ipv6.ip6_prohibit_entry->dst.dev = dev;
62868ed67789SDaniel Lezcano net->ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(dev);
6287d8d1f30bSChangli Gao net->ipv6.ip6_blk_hole_entry->dst.dev = dev;
62888ed67789SDaniel Lezcano net->ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(dev);
62898ed67789SDaniel Lezcano #endif
629076da0704SWANG Cong } else if (event == NETDEV_UNREGISTER &&
629176da0704SWANG Cong dev->reg_state != NETREG_UNREGISTERED) {
629276da0704SWANG Cong /* NETDEV_UNREGISTER could be fired for multiple times by
629376da0704SWANG Cong * netdev_wait_allrefs(). Make sure we only call this once.
629476da0704SWANG Cong */
629512d94a80SEric Dumazet in6_dev_put_clear(&net->ipv6.ip6_null_entry->rt6i_idev);
6296242d3a49SWANG Cong #ifdef CONFIG_IPV6_MULTIPLE_TABLES
629712d94a80SEric Dumazet in6_dev_put_clear(&net->ipv6.ip6_prohibit_entry->rt6i_idev);
629812d94a80SEric Dumazet in6_dev_put_clear(&net->ipv6.ip6_blk_hole_entry->rt6i_idev);
6299242d3a49SWANG Cong #endif
63008ed67789SDaniel Lezcano }
63018ed67789SDaniel Lezcano
63028ed67789SDaniel Lezcano return NOTIFY_OK;
63038ed67789SDaniel Lezcano }
63048ed67789SDaniel Lezcano
63051da177e4SLinus Torvalds /*
63061da177e4SLinus Torvalds * /proc
63071da177e4SLinus Torvalds */
63081da177e4SLinus Torvalds
63091da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
rt6_stats_seq_show(struct seq_file * seq,void * v)63101da177e4SLinus Torvalds static int rt6_stats_seq_show(struct seq_file *seq, void *v)
63111da177e4SLinus Torvalds {
631269ddb805SDaniel Lezcano struct net *net = (struct net *)seq->private;
63131da177e4SLinus Torvalds seq_printf(seq, "%04x %04x %04x %04x %04x %04x %04x\n",
631469ddb805SDaniel Lezcano net->ipv6.rt6_stats->fib_nodes,
631569ddb805SDaniel Lezcano net->ipv6.rt6_stats->fib_route_nodes,
631681eb8447SWei Wang atomic_read(&net->ipv6.rt6_stats->fib_rt_alloc),
631769ddb805SDaniel Lezcano net->ipv6.rt6_stats->fib_rt_entries,
631869ddb805SDaniel Lezcano net->ipv6.rt6_stats->fib_rt_cache,
6319fc66f95cSEric Dumazet dst_entries_get_slow(&net->ipv6.ip6_dst_ops),
632069ddb805SDaniel Lezcano net->ipv6.rt6_stats->fib_discarded_routes);
63211da177e4SLinus Torvalds
63221da177e4SLinus Torvalds return 0;
63231da177e4SLinus Torvalds }
63241da177e4SLinus Torvalds #endif /* CONFIG_PROC_FS */
63251da177e4SLinus Torvalds
63261da177e4SLinus Torvalds #ifdef CONFIG_SYSCTL
63271da177e4SLinus Torvalds
ipv6_sysctl_rtcache_flush(struct ctl_table * ctl,int write,void * buffer,size_t * lenp,loff_t * ppos)632832927393SChristoph Hellwig static int ipv6_sysctl_rtcache_flush(struct ctl_table *ctl, int write,
632932927393SChristoph Hellwig void *buffer, size_t *lenp, loff_t *ppos)
63301da177e4SLinus Torvalds {
6331c486da34SLucian Adrian Grijincu struct net *net;
6332c486da34SLucian Adrian Grijincu int delay;
6333f0fb9b28SAditya Pakki int ret;
6334c486da34SLucian Adrian Grijincu if (!write)
6335c486da34SLucian Adrian Grijincu return -EINVAL;
6336c486da34SLucian Adrian Grijincu
6337f0fb9b28SAditya Pakki ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
6338f0fb9b28SAditya Pakki if (ret)
6339f0fb9b28SAditya Pakki return ret;
6340f0fb9b28SAditya Pakki
634196d3265fSPetr Pavlu net = (struct net *)ctl->extra1;
634296d3265fSPetr Pavlu delay = net->ipv6.sysctl.flush_delay;
63432ac3ac8fSMichal Kubeček fib6_run_gc(delay <= 0 ? 0 : (unsigned long)delay, net, delay > 0);
63441da177e4SLinus Torvalds return 0;
63451da177e4SLinus Torvalds }
63461da177e4SLinus Torvalds
6347ed792e28SDavid Ahern static struct ctl_table ipv6_route_table_template[] = {
63481da177e4SLinus Torvalds {
634906e6c88fSAlexander Kuznetsov .procname = "max_size",
635006e6c88fSAlexander Kuznetsov .data = &init_net.ipv6.sysctl.ip6_rt_max_size,
63511da177e4SLinus Torvalds .maxlen = sizeof(int),
635206e6c88fSAlexander Kuznetsov .mode = 0644,
635306e6c88fSAlexander Kuznetsov .proc_handler = proc_dointvec,
63541da177e4SLinus Torvalds },
63551da177e4SLinus Torvalds {
63561da177e4SLinus Torvalds .procname = "gc_thresh",
63579a7ec3a9SDaniel Lezcano .data = &ip6_dst_ops_template.gc_thresh,
63581da177e4SLinus Torvalds .maxlen = sizeof(int),
63591da177e4SLinus Torvalds .mode = 0644,
63606d9f239aSAlexey Dobriyan .proc_handler = proc_dointvec,
63611da177e4SLinus Torvalds },
63621da177e4SLinus Torvalds {
636306e6c88fSAlexander Kuznetsov .procname = "flush",
636406e6c88fSAlexander Kuznetsov .data = &init_net.ipv6.sysctl.flush_delay,
63651da177e4SLinus Torvalds .maxlen = sizeof(int),
636606e6c88fSAlexander Kuznetsov .mode = 0200,
636706e6c88fSAlexander Kuznetsov .proc_handler = ipv6_sysctl_rtcache_flush
63681da177e4SLinus Torvalds },
63691da177e4SLinus Torvalds {
63701da177e4SLinus Torvalds .procname = "gc_min_interval",
63714990509fSDaniel Lezcano .data = &init_net.ipv6.sysctl.ip6_rt_gc_min_interval,
63721da177e4SLinus Torvalds .maxlen = sizeof(int),
63731da177e4SLinus Torvalds .mode = 0644,
63746d9f239aSAlexey Dobriyan .proc_handler = proc_dointvec_jiffies,
63751da177e4SLinus Torvalds },
63761da177e4SLinus Torvalds {
63771da177e4SLinus Torvalds .procname = "gc_timeout",
63784990509fSDaniel Lezcano .data = &init_net.ipv6.sysctl.ip6_rt_gc_timeout,
63791da177e4SLinus Torvalds .maxlen = sizeof(int),
63801da177e4SLinus Torvalds .mode = 0644,
63816d9f239aSAlexey Dobriyan .proc_handler = proc_dointvec_jiffies,
63821da177e4SLinus Torvalds },
63831da177e4SLinus Torvalds {
63841da177e4SLinus Torvalds .procname = "gc_interval",
63854990509fSDaniel Lezcano .data = &init_net.ipv6.sysctl.ip6_rt_gc_interval,
63861da177e4SLinus Torvalds .maxlen = sizeof(int),
63871da177e4SLinus Torvalds .mode = 0644,
63886d9f239aSAlexey Dobriyan .proc_handler = proc_dointvec_jiffies,
63891da177e4SLinus Torvalds },
63901da177e4SLinus Torvalds {
63911da177e4SLinus Torvalds .procname = "gc_elasticity",
63924990509fSDaniel Lezcano .data = &init_net.ipv6.sysctl.ip6_rt_gc_elasticity,
63931da177e4SLinus Torvalds .maxlen = sizeof(int),
63941da177e4SLinus Torvalds .mode = 0644,
6395f3d3f616SMin Zhang .proc_handler = proc_dointvec,
63961da177e4SLinus Torvalds },
63971da177e4SLinus Torvalds {
63981da177e4SLinus Torvalds .procname = "mtu_expires",
63994990509fSDaniel Lezcano .data = &init_net.ipv6.sysctl.ip6_rt_mtu_expires,
64001da177e4SLinus Torvalds .maxlen = sizeof(int),
64011da177e4SLinus Torvalds .mode = 0644,
64026d9f239aSAlexey Dobriyan .proc_handler = proc_dointvec_jiffies,
64031da177e4SLinus Torvalds },
64041da177e4SLinus Torvalds {
64051da177e4SLinus Torvalds .procname = "min_adv_mss",
64064990509fSDaniel Lezcano .data = &init_net.ipv6.sysctl.ip6_rt_min_advmss,
64071da177e4SLinus Torvalds .maxlen = sizeof(int),
64081da177e4SLinus Torvalds .mode = 0644,
6409f3d3f616SMin Zhang .proc_handler = proc_dointvec,
64101da177e4SLinus Torvalds },
64111da177e4SLinus Torvalds {
64121da177e4SLinus Torvalds .procname = "gc_min_interval_ms",
64134990509fSDaniel Lezcano .data = &init_net.ipv6.sysctl.ip6_rt_gc_min_interval,
64141da177e4SLinus Torvalds .maxlen = sizeof(int),
64151da177e4SLinus Torvalds .mode = 0644,
64166d9f239aSAlexey Dobriyan .proc_handler = proc_dointvec_ms_jiffies,
64171da177e4SLinus Torvalds },
64187c6bb7d2SDavid Ahern {
64197c6bb7d2SDavid Ahern .procname = "skip_notify_on_dev_down",
64207c6bb7d2SDavid Ahern .data = &init_net.ipv6.sysctl.skip_notify_on_dev_down,
6421ef62c0aeSEric Dumazet .maxlen = sizeof(u8),
64227c6bb7d2SDavid Ahern .mode = 0644,
6423ef62c0aeSEric Dumazet .proc_handler = proc_dou8vec_minmax,
6424eec4844fSMatteo Croce .extra1 = SYSCTL_ZERO,
6425eec4844fSMatteo Croce .extra2 = SYSCTL_ONE,
64267c6bb7d2SDavid Ahern },
6427f8572d8fSEric W. Biederman { }
64281da177e4SLinus Torvalds };
64291da177e4SLinus Torvalds
ipv6_route_sysctl_init(struct net * net)64302c8c1e72SAlexey Dobriyan struct ctl_table * __net_init ipv6_route_sysctl_init(struct net *net)
6431760f2d01SDaniel Lezcano {
6432760f2d01SDaniel Lezcano struct ctl_table *table;
6433760f2d01SDaniel Lezcano
6434760f2d01SDaniel Lezcano table = kmemdup(ipv6_route_table_template,
6435760f2d01SDaniel Lezcano sizeof(ipv6_route_table_template),
6436760f2d01SDaniel Lezcano GFP_KERNEL);
64375ee09105SYOSHIFUJI Hideaki
64385ee09105SYOSHIFUJI Hideaki if (table) {
643906e6c88fSAlexander Kuznetsov table[0].data = &net->ipv6.sysctl.ip6_rt_max_size;
644086393e52SAlexey Dobriyan table[1].data = &net->ipv6.ip6_dst_ops.gc_thresh;
644106e6c88fSAlexander Kuznetsov table[2].data = &net->ipv6.sysctl.flush_delay;
644206e6c88fSAlexander Kuznetsov table[2].extra1 = net;
64435ee09105SYOSHIFUJI Hideaki table[3].data = &net->ipv6.sysctl.ip6_rt_gc_min_interval;
64445ee09105SYOSHIFUJI Hideaki table[4].data = &net->ipv6.sysctl.ip6_rt_gc_timeout;
64455ee09105SYOSHIFUJI Hideaki table[5].data = &net->ipv6.sysctl.ip6_rt_gc_interval;
64465ee09105SYOSHIFUJI Hideaki table[6].data = &net->ipv6.sysctl.ip6_rt_gc_elasticity;
64475ee09105SYOSHIFUJI Hideaki table[7].data = &net->ipv6.sysctl.ip6_rt_mtu_expires;
64485ee09105SYOSHIFUJI Hideaki table[8].data = &net->ipv6.sysctl.ip6_rt_min_advmss;
64499c69fabeSAlexey Dobriyan table[9].data = &net->ipv6.sysctl.ip6_rt_gc_min_interval;
64507c6bb7d2SDavid Ahern table[10].data = &net->ipv6.sysctl.skip_notify_on_dev_down;
6451464dc801SEric W. Biederman
6452464dc801SEric W. Biederman /* Don't export sysctls to unprivileged users */
6453464dc801SEric W. Biederman if (net->user_ns != &init_user_ns)
645406e6c88fSAlexander Kuznetsov table[1].procname = NULL;
64555ee09105SYOSHIFUJI Hideaki }
64565ee09105SYOSHIFUJI Hideaki
6457760f2d01SDaniel Lezcano return table;
6458760f2d01SDaniel Lezcano }
6459c899710fSJoel Granados
ipv6_route_sysctl_table_size(struct net * net)6460c899710fSJoel Granados size_t ipv6_route_sysctl_table_size(struct net *net)
6461c899710fSJoel Granados {
6462c899710fSJoel Granados /* Don't export sysctls to unprivileged users */
6463c899710fSJoel Granados if (net->user_ns != &init_user_ns)
6464c899710fSJoel Granados return 1;
6465c899710fSJoel Granados
6466c899710fSJoel Granados return ARRAY_SIZE(ipv6_route_table_template);
6467c899710fSJoel Granados }
64681da177e4SLinus Torvalds #endif
64691da177e4SLinus Torvalds
ip6_route_net_init(struct net * net)64702c8c1e72SAlexey Dobriyan static int __net_init ip6_route_net_init(struct net *net)
6471cdb18761SDaniel Lezcano {
6472633d424bSPavel Emelyanov int ret = -ENOMEM;
64738ed67789SDaniel Lezcano
647486393e52SAlexey Dobriyan memcpy(&net->ipv6.ip6_dst_ops, &ip6_dst_ops_template,
647586393e52SAlexey Dobriyan sizeof(net->ipv6.ip6_dst_ops));
6476f2fc6a54SBenjamin Thery
6477fc66f95cSEric Dumazet if (dst_entries_init(&net->ipv6.ip6_dst_ops) < 0)
6478fc66f95cSEric Dumazet goto out_ip6_dst_ops;
6479fc66f95cSEric Dumazet
64801cf844c7SDavid Ahern net->ipv6.fib6_null_entry = fib6_info_alloc(GFP_KERNEL, true);
6481421842edSDavid Ahern if (!net->ipv6.fib6_null_entry)
6482421842edSDavid Ahern goto out_ip6_dst_entries;
64831cf844c7SDavid Ahern memcpy(net->ipv6.fib6_null_entry, &fib6_null_entry_template,
64841cf844c7SDavid Ahern sizeof(*net->ipv6.fib6_null_entry));
6485421842edSDavid Ahern
64868ed67789SDaniel Lezcano net->ipv6.ip6_null_entry = kmemdup(&ip6_null_entry_template,
64878ed67789SDaniel Lezcano sizeof(*net->ipv6.ip6_null_entry),
64888ed67789SDaniel Lezcano GFP_KERNEL);
64898ed67789SDaniel Lezcano if (!net->ipv6.ip6_null_entry)
6490421842edSDavid Ahern goto out_fib6_null_entry;
6491d8d1f30bSChangli Gao net->ipv6.ip6_null_entry->dst.ops = &net->ipv6.ip6_dst_ops;
649262fa8a84SDavid S. Miller dst_init_metrics(&net->ipv6.ip6_null_entry->dst,
649362fa8a84SDavid S. Miller ip6_template_metrics, true);
6494d288a162SWangyang Guo INIT_LIST_HEAD(&net->ipv6.ip6_null_entry->dst.rt_uncached);
64958ed67789SDaniel Lezcano
64968ed67789SDaniel Lezcano #ifdef CONFIG_IPV6_MULTIPLE_TABLES
6497feca7d8cSVincent Bernat net->ipv6.fib6_has_custom_rules = false;
64988ed67789SDaniel Lezcano net->ipv6.ip6_prohibit_entry = kmemdup(&ip6_prohibit_entry_template,
64998ed67789SDaniel Lezcano sizeof(*net->ipv6.ip6_prohibit_entry),
65008ed67789SDaniel Lezcano GFP_KERNEL);
650168fffc67SPeter Zijlstra if (!net->ipv6.ip6_prohibit_entry)
650268fffc67SPeter Zijlstra goto out_ip6_null_entry;
6503d8d1f30bSChangli Gao net->ipv6.ip6_prohibit_entry->dst.ops = &net->ipv6.ip6_dst_ops;
650462fa8a84SDavid S. Miller dst_init_metrics(&net->ipv6.ip6_prohibit_entry->dst,
650562fa8a84SDavid S. Miller ip6_template_metrics, true);
6506d288a162SWangyang Guo INIT_LIST_HEAD(&net->ipv6.ip6_prohibit_entry->dst.rt_uncached);
65078ed67789SDaniel Lezcano
65088ed67789SDaniel Lezcano net->ipv6.ip6_blk_hole_entry = kmemdup(&ip6_blk_hole_entry_template,
65098ed67789SDaniel Lezcano sizeof(*net->ipv6.ip6_blk_hole_entry),
65108ed67789SDaniel Lezcano GFP_KERNEL);
651168fffc67SPeter Zijlstra if (!net->ipv6.ip6_blk_hole_entry)
651268fffc67SPeter Zijlstra goto out_ip6_prohibit_entry;
6513d8d1f30bSChangli Gao net->ipv6.ip6_blk_hole_entry->dst.ops = &net->ipv6.ip6_dst_ops;
651462fa8a84SDavid S. Miller dst_init_metrics(&net->ipv6.ip6_blk_hole_entry->dst,
651562fa8a84SDavid S. Miller ip6_template_metrics, true);
6516d288a162SWangyang Guo INIT_LIST_HEAD(&net->ipv6.ip6_blk_hole_entry->dst.rt_uncached);
6517b9b33e7cSPaolo Abeni #ifdef CONFIG_IPV6_SUBTREES
6518b9b33e7cSPaolo Abeni net->ipv6.fib6_routes_require_src = 0;
6519b9b33e7cSPaolo Abeni #endif
65208ed67789SDaniel Lezcano #endif
65218ed67789SDaniel Lezcano
6522b339a47cSPeter Zijlstra net->ipv6.sysctl.flush_delay = 0;
6523af6d1034SJon Maxwell net->ipv6.sysctl.ip6_rt_max_size = INT_MAX;
6524b339a47cSPeter Zijlstra net->ipv6.sysctl.ip6_rt_gc_min_interval = HZ / 2;
6525b339a47cSPeter Zijlstra net->ipv6.sysctl.ip6_rt_gc_timeout = 60*HZ;
6526b339a47cSPeter Zijlstra net->ipv6.sysctl.ip6_rt_gc_interval = 30*HZ;
6527b339a47cSPeter Zijlstra net->ipv6.sysctl.ip6_rt_gc_elasticity = 9;
6528b339a47cSPeter Zijlstra net->ipv6.sysctl.ip6_rt_mtu_expires = 10*60*HZ;
6529b339a47cSPeter Zijlstra net->ipv6.sysctl.ip6_rt_min_advmss = IPV6_MIN_MTU - 20 - 40;
65307c6bb7d2SDavid Ahern net->ipv6.sysctl.skip_notify_on_dev_down = 0;
6531b339a47cSPeter Zijlstra
65329cb7c013SEric Dumazet atomic_set(&net->ipv6.ip6_rt_gc_expire, 30*HZ);
65336891a346SBenjamin Thery
65348ed67789SDaniel Lezcano ret = 0;
65358ed67789SDaniel Lezcano out:
65368ed67789SDaniel Lezcano return ret;
6537f2fc6a54SBenjamin Thery
653868fffc67SPeter Zijlstra #ifdef CONFIG_IPV6_MULTIPLE_TABLES
653968fffc67SPeter Zijlstra out_ip6_prohibit_entry:
654068fffc67SPeter Zijlstra kfree(net->ipv6.ip6_prohibit_entry);
654168fffc67SPeter Zijlstra out_ip6_null_entry:
654268fffc67SPeter Zijlstra kfree(net->ipv6.ip6_null_entry);
654368fffc67SPeter Zijlstra #endif
6544421842edSDavid Ahern out_fib6_null_entry:
6545421842edSDavid Ahern kfree(net->ipv6.fib6_null_entry);
6546fc66f95cSEric Dumazet out_ip6_dst_entries:
6547fc66f95cSEric Dumazet dst_entries_destroy(&net->ipv6.ip6_dst_ops);
6548f2fc6a54SBenjamin Thery out_ip6_dst_ops:
6549f2fc6a54SBenjamin Thery goto out;
6550cdb18761SDaniel Lezcano }
6551cdb18761SDaniel Lezcano
ip6_route_net_exit(struct net * net)65522c8c1e72SAlexey Dobriyan static void __net_exit ip6_route_net_exit(struct net *net)
6553cdb18761SDaniel Lezcano {
6554421842edSDavid Ahern kfree(net->ipv6.fib6_null_entry);
65558ed67789SDaniel Lezcano kfree(net->ipv6.ip6_null_entry);
65568ed67789SDaniel Lezcano #ifdef CONFIG_IPV6_MULTIPLE_TABLES
65578ed67789SDaniel Lezcano kfree(net->ipv6.ip6_prohibit_entry);
65588ed67789SDaniel Lezcano kfree(net->ipv6.ip6_blk_hole_entry);
65598ed67789SDaniel Lezcano #endif
656041bb78b4SXiaotian Feng dst_entries_destroy(&net->ipv6.ip6_dst_ops);
6561cdb18761SDaniel Lezcano }
6562cdb18761SDaniel Lezcano
ip6_route_net_init_late(struct net * net)6563d189634eSThomas Graf static int __net_init ip6_route_net_init_late(struct net *net)
6564d189634eSThomas Graf {
6565d189634eSThomas Graf #ifdef CONFIG_PROC_FS
6566768b3c74SZhengchao Shao if (!proc_create_net("ipv6_route", 0, net->proc_net,
6567768b3c74SZhengchao Shao &ipv6_route_seq_ops,
6568768b3c74SZhengchao Shao sizeof(struct ipv6_route_iter)))
6569768b3c74SZhengchao Shao return -ENOMEM;
6570768b3c74SZhengchao Shao
6571768b3c74SZhengchao Shao if (!proc_create_net_single("rt6_stats", 0444, net->proc_net,
6572768b3c74SZhengchao Shao rt6_stats_seq_show, NULL)) {
6573768b3c74SZhengchao Shao remove_proc_entry("ipv6_route", net->proc_net);
6574768b3c74SZhengchao Shao return -ENOMEM;
6575768b3c74SZhengchao Shao }
6576d189634eSThomas Graf #endif
6577d189634eSThomas Graf return 0;
6578d189634eSThomas Graf }
6579d189634eSThomas Graf
ip6_route_net_exit_late(struct net * net)6580d189634eSThomas Graf static void __net_exit ip6_route_net_exit_late(struct net *net)
6581d189634eSThomas Graf {
6582d189634eSThomas Graf #ifdef CONFIG_PROC_FS
6583ece31ffdSGao feng remove_proc_entry("ipv6_route", net->proc_net);
6584ece31ffdSGao feng remove_proc_entry("rt6_stats", net->proc_net);
6585d189634eSThomas Graf #endif
6586d189634eSThomas Graf }
6587d189634eSThomas Graf
6588cdb18761SDaniel Lezcano static struct pernet_operations ip6_route_net_ops = {
6589cdb18761SDaniel Lezcano .init = ip6_route_net_init,
6590cdb18761SDaniel Lezcano .exit = ip6_route_net_exit,
6591cdb18761SDaniel Lezcano };
6592cdb18761SDaniel Lezcano
ipv6_inetpeer_init(struct net * net)6593c3426b47SDavid S. Miller static int __net_init ipv6_inetpeer_init(struct net *net)
6594c3426b47SDavid S. Miller {
6595c3426b47SDavid S. Miller struct inet_peer_base *bp = kmalloc(sizeof(*bp), GFP_KERNEL);
6596c3426b47SDavid S. Miller
6597c3426b47SDavid S. Miller if (!bp)
6598c3426b47SDavid S. Miller return -ENOMEM;
6599c3426b47SDavid S. Miller inet_peer_base_init(bp);
6600c3426b47SDavid S. Miller net->ipv6.peers = bp;
6601c3426b47SDavid S. Miller return 0;
6602c3426b47SDavid S. Miller }
6603c3426b47SDavid S. Miller
ipv6_inetpeer_exit(struct net * net)6604c3426b47SDavid S. Miller static void __net_exit ipv6_inetpeer_exit(struct net *net)
6605c3426b47SDavid S. Miller {
6606c3426b47SDavid S. Miller struct inet_peer_base *bp = net->ipv6.peers;
6607c3426b47SDavid S. Miller
6608c3426b47SDavid S. Miller net->ipv6.peers = NULL;
660956a6b248SDavid S. Miller inetpeer_invalidate_tree(bp);
6610c3426b47SDavid S. Miller kfree(bp);
6611c3426b47SDavid S. Miller }
6612c3426b47SDavid S. Miller
66132b823f72SDavid S. Miller static struct pernet_operations ipv6_inetpeer_ops = {
6614c3426b47SDavid S. Miller .init = ipv6_inetpeer_init,
6615c3426b47SDavid S. Miller .exit = ipv6_inetpeer_exit,
6616c3426b47SDavid S. Miller };
6617c3426b47SDavid S. Miller
6618d189634eSThomas Graf static struct pernet_operations ip6_route_net_late_ops = {
6619d189634eSThomas Graf .init = ip6_route_net_init_late,
6620d189634eSThomas Graf .exit = ip6_route_net_exit_late,
6621d189634eSThomas Graf };
6622d189634eSThomas Graf
66238ed67789SDaniel Lezcano static struct notifier_block ip6_route_dev_notifier = {
66248ed67789SDaniel Lezcano .notifier_call = ip6_route_dev_notify,
6625242d3a49SWANG Cong .priority = ADDRCONF_NOTIFY_PRIORITY - 10,
66268ed67789SDaniel Lezcano };
66278ed67789SDaniel Lezcano
ip6_route_init_special_entries(void)66282f460933SWANG Cong void __init ip6_route_init_special_entries(void)
66292f460933SWANG Cong {
66302f460933SWANG Cong /* Registering of the loopback is done before this portion of code,
66312f460933SWANG Cong * the loopback reference in rt6_info will not be taken, do it
66322f460933SWANG Cong * manually for init_net */
66331cf844c7SDavid Ahern init_net.ipv6.fib6_null_entry->fib6_nh->fib_nh_dev = init_net.loopback_dev;
66342f460933SWANG Cong init_net.ipv6.ip6_null_entry->dst.dev = init_net.loopback_dev;
66352f460933SWANG Cong init_net.ipv6.ip6_null_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
66362f460933SWANG Cong #ifdef CONFIG_IPV6_MULTIPLE_TABLES
66372f460933SWANG Cong init_net.ipv6.ip6_prohibit_entry->dst.dev = init_net.loopback_dev;
66382f460933SWANG Cong init_net.ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
66392f460933SWANG Cong init_net.ipv6.ip6_blk_hole_entry->dst.dev = init_net.loopback_dev;
66402f460933SWANG Cong init_net.ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
66412f460933SWANG Cong #endif
66422f460933SWANG Cong }
66432f460933SWANG Cong
6644138d0be3SYonghong Song #if IS_BUILTIN(CONFIG_IPV6)
6645138d0be3SYonghong Song #if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
6646138d0be3SYonghong Song DEFINE_BPF_ITER_FUNC(ipv6_route, struct bpf_iter_meta *meta, struct fib6_info *rt)
6647138d0be3SYonghong Song
6648951cf368SYonghong Song BTF_ID_LIST(btf_fib6_info_id)
6649951cf368SYonghong Song BTF_ID(struct, fib6_info)
6650951cf368SYonghong Song
665114fc6bd6SYonghong Song static const struct bpf_iter_seq_info ipv6_route_seq_info = {
6652138d0be3SYonghong Song .seq_ops = &ipv6_route_seq_ops,
6653138d0be3SYonghong Song .init_seq_private = bpf_iter_init_seq_net,
6654138d0be3SYonghong Song .fini_seq_private = bpf_iter_fini_seq_net,
6655138d0be3SYonghong Song .seq_priv_size = sizeof(struct ipv6_route_iter),
665614fc6bd6SYonghong Song };
665714fc6bd6SYonghong Song
665814fc6bd6SYonghong Song static struct bpf_iter_reg ipv6_route_reg_info = {
665914fc6bd6SYonghong Song .target = "ipv6_route",
66603c32cc1bSYonghong Song .ctx_arg_info_size = 1,
66613c32cc1bSYonghong Song .ctx_arg_info = {
66623c32cc1bSYonghong Song { offsetof(struct bpf_iter__ipv6_route, rt),
66633c32cc1bSYonghong Song PTR_TO_BTF_ID_OR_NULL },
66643c32cc1bSYonghong Song },
666514fc6bd6SYonghong Song .seq_info = &ipv6_route_seq_info,
6666138d0be3SYonghong Song };
6667138d0be3SYonghong Song
bpf_iter_register(void)666815172a46SYonghong Song static int __init bpf_iter_register(void)
666915172a46SYonghong Song {
6670951cf368SYonghong Song ipv6_route_reg_info.ctx_arg_info[0].btf_id = *btf_fib6_info_id;
667115172a46SYonghong Song return bpf_iter_reg_target(&ipv6_route_reg_info);
6672138d0be3SYonghong Song }
6673138d0be3SYonghong Song
bpf_iter_unregister(void)6674138d0be3SYonghong Song static void bpf_iter_unregister(void)
6675138d0be3SYonghong Song {
6676ab2ee4fcSYonghong Song bpf_iter_unreg_target(&ipv6_route_reg_info);
6677138d0be3SYonghong Song }
6678138d0be3SYonghong Song #endif
6679138d0be3SYonghong Song #endif
6680138d0be3SYonghong Song
ip6_route_init(void)6681433d49c3SDaniel Lezcano int __init ip6_route_init(void)
66821da177e4SLinus Torvalds {
6683433d49c3SDaniel Lezcano int ret;
66848d0b94afSMartin KaFai Lau int cpu;
6685433d49c3SDaniel Lezcano
66869a7ec3a9SDaniel Lezcano ret = -ENOMEM;
66879a7ec3a9SDaniel Lezcano ip6_dst_ops_template.kmem_cachep =
66889a7ec3a9SDaniel Lezcano kmem_cache_create("ip6_dst_cache", sizeof(struct rt6_info), 0,
66896126891cSVasily Averin SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT, NULL);
66909a7ec3a9SDaniel Lezcano if (!ip6_dst_ops_template.kmem_cachep)
6691c19a28e1SFernando Carrijo goto out;
669214e50e57SDavid S. Miller
6693fc66f95cSEric Dumazet ret = dst_entries_init(&ip6_dst_blackhole_ops);
66948ed67789SDaniel Lezcano if (ret)
6695bdb3289fSDaniel Lezcano goto out_kmem_cache;
6696bdb3289fSDaniel Lezcano
6697c3426b47SDavid S. Miller ret = register_pernet_subsys(&ipv6_inetpeer_ops);
6698c3426b47SDavid S. Miller if (ret)
6699e8803b6cSDavid S. Miller goto out_dst_entries;
67002a0c451aSThomas Graf
67017e52b33bSDavid S. Miller ret = register_pernet_subsys(&ip6_route_net_ops);
67027e52b33bSDavid S. Miller if (ret)
67037e52b33bSDavid S. Miller goto out_register_inetpeer;
6704c3426b47SDavid S. Miller
67055dc121e9SArnaud Ebalard ip6_dst_blackhole_ops.kmem_cachep = ip6_dst_ops_template.kmem_cachep;
67065dc121e9SArnaud Ebalard
6707e8803b6cSDavid S. Miller ret = fib6_init();
6708433d49c3SDaniel Lezcano if (ret)
67098ed67789SDaniel Lezcano goto out_register_subsys;
6710433d49c3SDaniel Lezcano
6711433d49c3SDaniel Lezcano ret = xfrm6_init();
6712433d49c3SDaniel Lezcano if (ret)
6713e8803b6cSDavid S. Miller goto out_fib6_init;
6714c35b7e72SDaniel Lezcano
6715433d49c3SDaniel Lezcano ret = fib6_rules_init();
6716433d49c3SDaniel Lezcano if (ret)
6717433d49c3SDaniel Lezcano goto xfrm6_init;
67187e5449c2SDaniel Lezcano
6719d189634eSThomas Graf ret = register_pernet_subsys(&ip6_route_net_late_ops);
6720d189634eSThomas Graf if (ret)
6721d189634eSThomas Graf goto fib6_rules_init;
6722d189634eSThomas Graf
672316feebcfSFlorian Westphal ret = rtnl_register_module(THIS_MODULE, PF_INET6, RTM_NEWROUTE,
672416feebcfSFlorian Westphal inet6_rtm_newroute, NULL, 0);
672516feebcfSFlorian Westphal if (ret < 0)
672616feebcfSFlorian Westphal goto out_register_late_subsys;
672716feebcfSFlorian Westphal
672816feebcfSFlorian Westphal ret = rtnl_register_module(THIS_MODULE, PF_INET6, RTM_DELROUTE,
672916feebcfSFlorian Westphal inet6_rtm_delroute, NULL, 0);
673016feebcfSFlorian Westphal if (ret < 0)
673116feebcfSFlorian Westphal goto out_register_late_subsys;
673216feebcfSFlorian Westphal
673316feebcfSFlorian Westphal ret = rtnl_register_module(THIS_MODULE, PF_INET6, RTM_GETROUTE,
673416feebcfSFlorian Westphal inet6_rtm_getroute, NULL,
673516feebcfSFlorian Westphal RTNL_FLAG_DOIT_UNLOCKED);
673616feebcfSFlorian Westphal if (ret < 0)
6737d189634eSThomas Graf goto out_register_late_subsys;
6738433d49c3SDaniel Lezcano
67398ed67789SDaniel Lezcano ret = register_netdevice_notifier(&ip6_route_dev_notifier);
6740cdb18761SDaniel Lezcano if (ret)
6741d189634eSThomas Graf goto out_register_late_subsys;
67428ed67789SDaniel Lezcano
6743138d0be3SYonghong Song #if IS_BUILTIN(CONFIG_IPV6)
6744138d0be3SYonghong Song #if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
6745138d0be3SYonghong Song ret = bpf_iter_register();
6746138d0be3SYonghong Song if (ret)
6747138d0be3SYonghong Song goto out_register_late_subsys;
6748138d0be3SYonghong Song #endif
6749138d0be3SYonghong Song #endif
6750138d0be3SYonghong Song
67518d0b94afSMartin KaFai Lau for_each_possible_cpu(cpu) {
67528d0b94afSMartin KaFai Lau struct uncached_list *ul = per_cpu_ptr(&rt6_uncached_list, cpu);
67538d0b94afSMartin KaFai Lau
67548d0b94afSMartin KaFai Lau INIT_LIST_HEAD(&ul->head);
6755ba55ef81SEric Dumazet INIT_LIST_HEAD(&ul->quarantine);
67568d0b94afSMartin KaFai Lau spin_lock_init(&ul->lock);
67578d0b94afSMartin KaFai Lau }
67588d0b94afSMartin KaFai Lau
6759433d49c3SDaniel Lezcano out:
6760433d49c3SDaniel Lezcano return ret;
6761433d49c3SDaniel Lezcano
6762d189634eSThomas Graf out_register_late_subsys:
676316feebcfSFlorian Westphal rtnl_unregister_all(PF_INET6);
6764d189634eSThomas Graf unregister_pernet_subsys(&ip6_route_net_late_ops);
6765433d49c3SDaniel Lezcano fib6_rules_init:
6766433d49c3SDaniel Lezcano fib6_rules_cleanup();
6767433d49c3SDaniel Lezcano xfrm6_init:
6768433d49c3SDaniel Lezcano xfrm6_fini();
67692a0c451aSThomas Graf out_fib6_init:
67702a0c451aSThomas Graf fib6_gc_cleanup();
67718ed67789SDaniel Lezcano out_register_subsys:
67728ed67789SDaniel Lezcano unregister_pernet_subsys(&ip6_route_net_ops);
67737e52b33bSDavid S. Miller out_register_inetpeer:
67747e52b33bSDavid S. Miller unregister_pernet_subsys(&ipv6_inetpeer_ops);
6775fc66f95cSEric Dumazet out_dst_entries:
6776fc66f95cSEric Dumazet dst_entries_destroy(&ip6_dst_blackhole_ops);
6777433d49c3SDaniel Lezcano out_kmem_cache:
6778f2fc6a54SBenjamin Thery kmem_cache_destroy(ip6_dst_ops_template.kmem_cachep);
6779433d49c3SDaniel Lezcano goto out;
67801da177e4SLinus Torvalds }
67811da177e4SLinus Torvalds
ip6_route_cleanup(void)67821da177e4SLinus Torvalds void ip6_route_cleanup(void)
67831da177e4SLinus Torvalds {
6784138d0be3SYonghong Song #if IS_BUILTIN(CONFIG_IPV6)
6785138d0be3SYonghong Song #if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
6786138d0be3SYonghong Song bpf_iter_unregister();
6787138d0be3SYonghong Song #endif
6788138d0be3SYonghong Song #endif
67898ed67789SDaniel Lezcano unregister_netdevice_notifier(&ip6_route_dev_notifier);
6790d189634eSThomas Graf unregister_pernet_subsys(&ip6_route_net_late_ops);
6791101367c2SThomas Graf fib6_rules_cleanup();
67921da177e4SLinus Torvalds xfrm6_fini();
67931da177e4SLinus Torvalds fib6_gc_cleanup();
6794c3426b47SDavid S. Miller unregister_pernet_subsys(&ipv6_inetpeer_ops);
67958ed67789SDaniel Lezcano unregister_pernet_subsys(&ip6_route_net_ops);
679641bb78b4SXiaotian Feng dst_entries_destroy(&ip6_dst_blackhole_ops);
6797f2fc6a54SBenjamin Thery kmem_cache_destroy(ip6_dst_ops_template.kmem_cachep);
67981da177e4SLinus Torvalds }
6799