Commit cea29a70 authored by David S. Miller's avatar David S. Miller

Merge branch 'ipv6-Use-fib6_result-for-fib_lookups'

David Ahern says:

====================
ipv6: Use fib6_result for fib_lookups

Add fib6_result as a single data structure to hold results from a fib
lookup. IPv6 currently has everything in 1 data structure - a fib6_info,
but with nexthop objects the fib6_nh can be in a nexthop or a nexthop
can be a blackhole which affects the fib6_type and flags (REJECT).

v2
- fixed 2 bugs in patch12:
  i. checking return from fib6_table_lookup in fib6_lookup
  ii. call to fib6_rule_saddr in fib6_rule_action_alt should use res->nh
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 6b0a7f84 7d21fec9
...@@ -190,6 +190,13 @@ struct rt6_info { ...@@ -190,6 +190,13 @@ struct rt6_info {
unsigned short rt6i_nfheader_len; unsigned short rt6i_nfheader_len;
}; };
struct fib6_result {
struct fib6_nh *nh;
struct fib6_info *f6i;
u32 fib6_flags;
u8 fib6_type;
};
#define for_each_fib6_node_rt_rcu(fn) \ #define for_each_fib6_node_rt_rcu(fn) \
for (rt = rcu_dereference((fn)->leaf); rt; \ for (rt = rcu_dereference((fn)->leaf); rt; \
rt = rcu_dereference(rt->fib6_next)) rt = rcu_dereference(rt->fib6_next))
...@@ -384,18 +391,17 @@ struct dst_entry *fib6_rule_lookup(struct net *net, struct flowi6 *fl6, ...@@ -384,18 +391,17 @@ struct dst_entry *fib6_rule_lookup(struct net *net, struct flowi6 *fl6,
/* called with rcu lock held; can return error pointer /* called with rcu lock held; can return error pointer
* caller needs to select path * caller needs to select path
*/ */
struct fib6_info *fib6_lookup(struct net *net, int oif, struct flowi6 *fl6, int fib6_lookup(struct net *net, int oif, struct flowi6 *fl6,
int flags); struct fib6_result *res, int flags);
/* called with rcu lock held; caller needs to select path */ /* called with rcu lock held; caller needs to select path */
struct fib6_info *fib6_table_lookup(struct net *net, struct fib6_table *table, int fib6_table_lookup(struct net *net, struct fib6_table *table,
int oif, struct flowi6 *fl6, int strict); int oif, struct flowi6 *fl6, struct fib6_result *res,
int strict);
struct fib6_info *fib6_multipath_select(const struct net *net,
struct fib6_info *match,
struct flowi6 *fl6, int oif,
const struct sk_buff *skb, int strict);
void fib6_select_path(const struct net *net, struct fib6_result *res,
struct flowi6 *fl6, int oif, bool have_oif_match,
const struct sk_buff *skb, int strict);
struct fib6_node *fib6_node_lookup(struct fib6_node *root, struct fib6_node *fib6_node_lookup(struct fib6_node *root,
const struct in6_addr *daddr, const struct in6_addr *daddr,
const struct in6_addr *saddr); const struct in6_addr *saddr);
......
...@@ -302,8 +302,9 @@ static inline unsigned int ip6_dst_mtu_forward(const struct dst_entry *dst) ...@@ -302,8 +302,9 @@ static inline unsigned int ip6_dst_mtu_forward(const struct dst_entry *dst)
return mtu; return mtu;
} }
u32 ip6_mtu_from_fib6(struct fib6_info *f6i, struct in6_addr *daddr, u32 ip6_mtu_from_fib6(const struct fib6_result *res,
struct in6_addr *saddr); const struct in6_addr *daddr,
const struct in6_addr *saddr);
struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw, struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,
struct net_device *dev, struct sk_buff *skb, struct net_device *dev, struct sk_buff *skb,
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
struct fib6_info; struct fib6_info;
struct fib6_nh; struct fib6_nh;
struct fib6_config; struct fib6_config;
struct fib6_result;
/* This is ugly, ideally these symbols should be built /* This is ugly, ideally these symbols should be built
* into the core kernel. * into the core kernel.
...@@ -28,19 +29,17 @@ struct ipv6_stub { ...@@ -28,19 +29,17 @@ struct ipv6_stub {
int (*ipv6_route_input)(struct sk_buff *skb); int (*ipv6_route_input)(struct sk_buff *skb);
struct fib6_table *(*fib6_get_table)(struct net *net, u32 id); struct fib6_table *(*fib6_get_table)(struct net *net, u32 id);
struct fib6_info *(*fib6_lookup)(struct net *net, int oif, int (*fib6_lookup)(struct net *net, int oif, struct flowi6 *fl6,
struct flowi6 *fl6, int flags); struct fib6_result *res, int flags);
struct fib6_info *(*fib6_table_lookup)(struct net *net, int (*fib6_table_lookup)(struct net *net, struct fib6_table *table,
struct fib6_table *table, int oif, struct flowi6 *fl6,
int oif, struct flowi6 *fl6, struct fib6_result *res, int flags);
int flags); void (*fib6_select_path)(const struct net *net, struct fib6_result *res,
struct fib6_info *(*fib6_multipath_select)(const struct net *net, struct flowi6 *fl6, int oif, bool oif_match,
struct fib6_info *f6i, const struct sk_buff *skb, int strict);
struct flowi6 *fl6, int oif, u32 (*ip6_mtu_from_fib6)(const struct fib6_result *res,
const struct sk_buff *skb, const struct in6_addr *daddr,
int strict); const struct in6_addr *saddr);
u32 (*ip6_mtu_from_fib6)(struct fib6_info *f6i, struct in6_addr *daddr,
struct in6_addr *saddr);
int (*fib6_nh_init)(struct net *net, struct fib6_nh *fib6_nh, int (*fib6_nh_init)(struct net *net, struct fib6_nh *fib6_nh,
struct fib6_config *cfg, gfp_t gfp_flags, struct fib6_config *cfg, gfp_t gfp_flags,
......
...@@ -12,10 +12,10 @@ ...@@ -12,10 +12,10 @@
TRACE_EVENT(fib6_table_lookup, TRACE_EVENT(fib6_table_lookup,
TP_PROTO(const struct net *net, const struct fib6_info *f6i, TP_PROTO(const struct net *net, const struct fib6_result *res,
struct fib6_table *table, const struct flowi6 *flp), struct fib6_table *table, const struct flowi6 *flp),
TP_ARGS(net, f6i, table, flp), TP_ARGS(net, res, table, flp),
TP_STRUCT__entry( TP_STRUCT__entry(
__field( u32, tb_id ) __field( u32, tb_id )
...@@ -39,7 +39,7 @@ TRACE_EVENT(fib6_table_lookup, ...@@ -39,7 +39,7 @@ TRACE_EVENT(fib6_table_lookup,
struct in6_addr *in6; struct in6_addr *in6;
__entry->tb_id = table->tb6_id; __entry->tb_id = table->tb6_id;
__entry->err = ip6_rt_type_to_error(f6i->fib6_type); __entry->err = ip6_rt_type_to_error(res->fib6_type);
__entry->oif = flp->flowi6_oif; __entry->oif = flp->flowi6_oif;
__entry->iif = flp->flowi6_iif; __entry->iif = flp->flowi6_iif;
__entry->tos = ip6_tclass(flp->flowlabel); __entry->tos = ip6_tclass(flp->flowlabel);
...@@ -62,20 +62,20 @@ TRACE_EVENT(fib6_table_lookup, ...@@ -62,20 +62,20 @@ TRACE_EVENT(fib6_table_lookup,
__entry->dport = 0; __entry->dport = 0;
} }
if (f6i->fib6_nh.fib_nh_dev) { if (res->nh && res->nh->fib_nh_dev) {
__assign_str(name, f6i->fib6_nh.fib_nh_dev); __assign_str(name, res->nh->fib_nh_dev);
} else { } else {
__assign_str(name, "-"); __assign_str(name, "-");
} }
if (f6i == net->ipv6.fib6_null_entry) { if (res->f6i == net->ipv6.fib6_null_entry) {
struct in6_addr in6_zero = {}; struct in6_addr in6_zero = {};
in6 = (struct in6_addr *)__entry->gw; in6 = (struct in6_addr *)__entry->gw;
*in6 = in6_zero; *in6 = in6_zero;
} else if (f6i) { } else if (res->nh) {
in6 = (struct in6_addr *)__entry->gw; in6 = (struct in6_addr *)__entry->gw;
*in6 = f6i->fib6_nh.fib_nh_gw6; *in6 = res->nh->fib_nh_gw6;
} }
), ),
......
...@@ -4679,12 +4679,12 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params, ...@@ -4679,12 +4679,12 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
struct in6_addr *src = (struct in6_addr *) params->ipv6_src; struct in6_addr *src = (struct in6_addr *) params->ipv6_src;
struct in6_addr *dst = (struct in6_addr *) params->ipv6_dst; struct in6_addr *dst = (struct in6_addr *) params->ipv6_dst;
struct neighbour *neigh; struct neighbour *neigh;
struct fib6_result res;
struct net_device *dev; struct net_device *dev;
struct inet6_dev *idev; struct inet6_dev *idev;
struct fib6_info *f6i;
struct flowi6 fl6; struct flowi6 fl6;
int strict = 0; int strict = 0;
int oif; int oif, err;
u32 mtu; u32 mtu;
/* link local addresses are never forwarded */ /* link local addresses are never forwarded */
...@@ -4726,54 +4726,52 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params, ...@@ -4726,54 +4726,52 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
if (unlikely(!tb)) if (unlikely(!tb))
return BPF_FIB_LKUP_RET_NOT_FWDED; return BPF_FIB_LKUP_RET_NOT_FWDED;
f6i = ipv6_stub->fib6_table_lookup(net, tb, oif, &fl6, strict); err = ipv6_stub->fib6_table_lookup(net, tb, oif, &fl6, &res,
strict);
} else { } else {
fl6.flowi6_mark = 0; fl6.flowi6_mark = 0;
fl6.flowi6_secid = 0; fl6.flowi6_secid = 0;
fl6.flowi6_tun_key.tun_id = 0; fl6.flowi6_tun_key.tun_id = 0;
fl6.flowi6_uid = sock_net_uid(net, NULL); fl6.flowi6_uid = sock_net_uid(net, NULL);
f6i = ipv6_stub->fib6_lookup(net, oif, &fl6, strict); err = ipv6_stub->fib6_lookup(net, oif, &fl6, &res, strict);
} }
if (unlikely(IS_ERR_OR_NULL(f6i) || f6i == net->ipv6.fib6_null_entry)) if (unlikely(err || IS_ERR_OR_NULL(res.f6i) ||
res.f6i == net->ipv6.fib6_null_entry))
return BPF_FIB_LKUP_RET_NOT_FWDED; return BPF_FIB_LKUP_RET_NOT_FWDED;
if (unlikely(f6i->fib6_flags & RTF_REJECT)) { switch (res.fib6_type) {
switch (f6i->fib6_type) { /* only unicast is forwarded */
case RTN_BLACKHOLE: case RTN_UNICAST:
return BPF_FIB_LKUP_RET_BLACKHOLE; break;
case RTN_UNREACHABLE: case RTN_BLACKHOLE:
return BPF_FIB_LKUP_RET_UNREACHABLE; return BPF_FIB_LKUP_RET_BLACKHOLE;
case RTN_PROHIBIT: case RTN_UNREACHABLE:
return BPF_FIB_LKUP_RET_PROHIBIT; return BPF_FIB_LKUP_RET_UNREACHABLE;
default: case RTN_PROHIBIT:
return BPF_FIB_LKUP_RET_NOT_FWDED; return BPF_FIB_LKUP_RET_PROHIBIT;
} default:
}
if (f6i->fib6_type != RTN_UNICAST)
return BPF_FIB_LKUP_RET_NOT_FWDED; return BPF_FIB_LKUP_RET_NOT_FWDED;
}
if (f6i->fib6_nsiblings && fl6.flowi6_oif == 0) ipv6_stub->fib6_select_path(net, &res, &fl6, fl6.flowi6_oif,
f6i = ipv6_stub->fib6_multipath_select(net, f6i, &fl6, fl6.flowi6_oif != 0, NULL, strict);
fl6.flowi6_oif, NULL,
strict);
if (check_mtu) { if (check_mtu) {
mtu = ipv6_stub->ip6_mtu_from_fib6(f6i, dst, src); mtu = ipv6_stub->ip6_mtu_from_fib6(&res, dst, src);
if (params->tot_len > mtu) if (params->tot_len > mtu)
return BPF_FIB_LKUP_RET_FRAG_NEEDED; return BPF_FIB_LKUP_RET_FRAG_NEEDED;
} }
if (f6i->fib6_nh.fib_nh_lws) if (res.nh->fib_nh_lws)
return BPF_FIB_LKUP_RET_UNSUPP_LWT; return BPF_FIB_LKUP_RET_UNSUPP_LWT;
if (f6i->fib6_nh.fib_nh_gw_family) if (res.nh->fib_nh_gw_family)
*dst = f6i->fib6_nh.fib_nh_gw6; *dst = res.nh->fib_nh_gw6;
dev = f6i->fib6_nh.fib_nh_dev; dev = res.nh->fib_nh_dev;
params->rt_metric = f6i->fib6_metric; params->rt_metric = res.f6i->fib6_metric;
/* xdp and cls_bpf programs are run in RCU-bh so rcu_read_lock_bh is /* xdp and cls_bpf programs are run in RCU-bh so rcu_read_lock_bh is
* not needed here. * not needed here.
......
...@@ -144,31 +144,32 @@ static struct fib6_table *eafnosupport_fib6_get_table(struct net *net, u32 id) ...@@ -144,31 +144,32 @@ static struct fib6_table *eafnosupport_fib6_get_table(struct net *net, u32 id)
return NULL; return NULL;
} }
static struct fib6_info * static int
eafnosupport_fib6_table_lookup(struct net *net, struct fib6_table *table, eafnosupport_fib6_table_lookup(struct net *net, struct fib6_table *table,
int oif, struct flowi6 *fl6, int flags) int oif, struct flowi6 *fl6,
struct fib6_result *res, int flags)
{ {
return NULL; return -EAFNOSUPPORT;
} }
static struct fib6_info * static int
eafnosupport_fib6_lookup(struct net *net, int oif, struct flowi6 *fl6, eafnosupport_fib6_lookup(struct net *net, int oif, struct flowi6 *fl6,
int flags) struct fib6_result *res, int flags)
{ {
return NULL; return -EAFNOSUPPORT;
} }
static struct fib6_info * static void
eafnosupport_fib6_multipath_select(const struct net *net, struct fib6_info *f6i, eafnosupport_fib6_select_path(const struct net *net, struct fib6_result *res,
struct flowi6 *fl6, int oif, struct flowi6 *fl6, int oif, bool have_oif_match,
const struct sk_buff *skb, int strict) const struct sk_buff *skb, int strict)
{ {
return f6i;
} }
static u32 static u32
eafnosupport_ip6_mtu_from_fib6(struct fib6_info *f6i, struct in6_addr *daddr, eafnosupport_ip6_mtu_from_fib6(const struct fib6_result *res,
struct in6_addr *saddr) const struct in6_addr *daddr,
const struct in6_addr *saddr)
{ {
return 0; return 0;
} }
...@@ -187,7 +188,7 @@ const struct ipv6_stub *ipv6_stub __read_mostly = &(struct ipv6_stub) { ...@@ -187,7 +188,7 @@ const struct ipv6_stub *ipv6_stub __read_mostly = &(struct ipv6_stub) {
.fib6_get_table = eafnosupport_fib6_get_table, .fib6_get_table = eafnosupport_fib6_get_table,
.fib6_table_lookup = eafnosupport_fib6_table_lookup, .fib6_table_lookup = eafnosupport_fib6_table_lookup,
.fib6_lookup = eafnosupport_fib6_lookup, .fib6_lookup = eafnosupport_fib6_lookup,
.fib6_multipath_select = eafnosupport_fib6_multipath_select, .fib6_select_path = eafnosupport_fib6_select_path,
.ip6_mtu_from_fib6 = eafnosupport_ip6_mtu_from_fib6, .ip6_mtu_from_fib6 = eafnosupport_ip6_mtu_from_fib6,
.fib6_nh_init = eafnosupport_fib6_nh_init, .fib6_nh_init = eafnosupport_fib6_nh_init,
}; };
......
...@@ -917,7 +917,7 @@ static const struct ipv6_stub ipv6_stub_impl = { ...@@ -917,7 +917,7 @@ static const struct ipv6_stub ipv6_stub_impl = {
.fib6_get_table = fib6_get_table, .fib6_get_table = fib6_get_table,
.fib6_table_lookup = fib6_table_lookup, .fib6_table_lookup = fib6_table_lookup,
.fib6_lookup = fib6_lookup, .fib6_lookup = fib6_lookup,
.fib6_multipath_select = fib6_multipath_select, .fib6_select_path = fib6_select_path,
.ip6_mtu_from_fib6 = ip6_mtu_from_fib6, .ip6_mtu_from_fib6 = ip6_mtu_from_fib6,
.fib6_nh_init = fib6_nh_init, .fib6_nh_init = fib6_nh_init,
.fib6_nh_release = fib6_nh_release, .fib6_nh_release = fib6_nh_release,
......
...@@ -61,16 +61,16 @@ unsigned int fib6_rules_seq_read(struct net *net) ...@@ -61,16 +61,16 @@ unsigned int fib6_rules_seq_read(struct net *net)
} }
/* called with rcu lock held; no reference taken on fib6_info */ /* called with rcu lock held; no reference taken on fib6_info */
struct fib6_info *fib6_lookup(struct net *net, int oif, struct flowi6 *fl6, int fib6_lookup(struct net *net, int oif, struct flowi6 *fl6,
int flags) struct fib6_result *res, int flags)
{ {
struct fib6_info *f6i;
int err; int err;
if (net->ipv6.fib6_has_custom_rules) { if (net->ipv6.fib6_has_custom_rules) {
struct fib_lookup_arg arg = { struct fib_lookup_arg arg = {
.lookup_ptr = fib6_table_lookup, .lookup_ptr = fib6_table_lookup,
.lookup_data = &oif, .lookup_data = &oif,
.result = res,
.flags = FIB_LOOKUP_NOREF, .flags = FIB_LOOKUP_NOREF,
}; };
...@@ -78,19 +78,15 @@ struct fib6_info *fib6_lookup(struct net *net, int oif, struct flowi6 *fl6, ...@@ -78,19 +78,15 @@ struct fib6_info *fib6_lookup(struct net *net, int oif, struct flowi6 *fl6,
err = fib_rules_lookup(net->ipv6.fib6_rules_ops, err = fib_rules_lookup(net->ipv6.fib6_rules_ops,
flowi6_to_flowi(fl6), flags, &arg); flowi6_to_flowi(fl6), flags, &arg);
if (err)
return ERR_PTR(err);
f6i = arg.result ? : net->ipv6.fib6_null_entry;
} else { } else {
f6i = fib6_table_lookup(net, net->ipv6.fib6_local_tbl, err = fib6_table_lookup(net, net->ipv6.fib6_local_tbl, oif,
oif, fl6, flags); fl6, res, flags);
if (!f6i || f6i == net->ipv6.fib6_null_entry) if (err || res->f6i == net->ipv6.fib6_null_entry)
f6i = fib6_table_lookup(net, net->ipv6.fib6_main_tbl, err = fib6_table_lookup(net, net->ipv6.fib6_main_tbl,
oif, fl6, flags); oif, fl6, res, flags);
} }
return f6i; return err;
} }
struct dst_entry *fib6_rule_lookup(struct net *net, struct flowi6 *fl6, struct dst_entry *fib6_rule_lookup(struct net *net, struct flowi6 *fl6,
...@@ -157,10 +153,10 @@ static int fib6_rule_saddr(struct net *net, struct fib_rule *rule, int flags, ...@@ -157,10 +153,10 @@ static int fib6_rule_saddr(struct net *net, struct fib_rule *rule, int flags,
static int fib6_rule_action_alt(struct fib_rule *rule, struct flowi *flp, static int fib6_rule_action_alt(struct fib_rule *rule, struct flowi *flp,
int flags, struct fib_lookup_arg *arg) int flags, struct fib_lookup_arg *arg)
{ {
struct fib6_result *res = arg->result;
struct flowi6 *flp6 = &flp->u.ip6; struct flowi6 *flp6 = &flp->u.ip6;
struct net *net = rule->fr_net; struct net *net = rule->fr_net;
struct fib6_table *table; struct fib6_table *table;
struct fib6_info *f6i;
int err = -EAGAIN, *oif; int err = -EAGAIN, *oif;
u32 tb_id; u32 tb_id;
...@@ -182,14 +178,10 @@ static int fib6_rule_action_alt(struct fib_rule *rule, struct flowi *flp, ...@@ -182,14 +178,10 @@ static int fib6_rule_action_alt(struct fib_rule *rule, struct flowi *flp,
return -EAGAIN; return -EAGAIN;
oif = (int *)arg->lookup_data; oif = (int *)arg->lookup_data;
f6i = fib6_table_lookup(net, table, *oif, flp6, flags); err = fib6_table_lookup(net, table, *oif, flp6, res, flags);
if (f6i != net->ipv6.fib6_null_entry) { if (!err && res->f6i != net->ipv6.fib6_null_entry)
err = fib6_rule_saddr(net, rule, flags, flp6, err = fib6_rule_saddr(net, rule, flags, flp6,
fib6_info_nh_dev(f6i)); res->nh->fib_nh_dev);
if (likely(!err))
arg->result = f6i;
}
return err; return err;
} }
......
...@@ -354,10 +354,11 @@ struct dst_entry *fib6_rule_lookup(struct net *net, struct flowi6 *fl6, ...@@ -354,10 +354,11 @@ struct dst_entry *fib6_rule_lookup(struct net *net, struct flowi6 *fl6,
} }
/* called with rcu lock held; no reference taken on fib6_info */ /* called with rcu lock held; no reference taken on fib6_info */
struct fib6_info *fib6_lookup(struct net *net, int oif, struct flowi6 *fl6, int fib6_lookup(struct net *net, int oif, struct flowi6 *fl6,
int flags) struct fib6_result *res, int flags)
{ {
return fib6_table_lookup(net, net->ipv6.fib6_main_tbl, oif, fl6, flags); return fib6_table_lookup(net, net->ipv6.fib6_main_tbl, oif, fl6,
res, flags);
} }
static void __net_init fib6_tables_init(struct net *net) static void __net_init fib6_tables_init(struct net *net)
......
...@@ -110,7 +110,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, ...@@ -110,7 +110,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
struct in6_addr *dest, struct in6_addr *src, struct in6_addr *dest, struct in6_addr *src,
int iif, int type, u32 portid, u32 seq, int iif, int type, u32 portid, u32 seq,
unsigned int flags); unsigned int flags);
static struct rt6_info *rt6_find_cached_rt(struct fib6_info *rt, static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,
struct in6_addr *daddr, struct in6_addr *daddr,
struct in6_addr *saddr); struct in6_addr *saddr);
...@@ -428,13 +428,15 @@ static bool rt6_check_expired(const struct rt6_info *rt) ...@@ -428,13 +428,15 @@ static bool rt6_check_expired(const struct rt6_info *rt)
return false; return false;
} }
struct fib6_info *fib6_multipath_select(const struct net *net, void fib6_select_path(const struct net *net, struct fib6_result *res,
struct fib6_info *match, struct flowi6 *fl6, int oif, bool have_oif_match,
struct flowi6 *fl6, int oif, const struct sk_buff *skb, int strict)
const struct sk_buff *skb,
int strict)
{ {
struct fib6_info *sibling, *next_sibling; struct fib6_info *sibling, *next_sibling;
struct fib6_info *match = res->f6i;
if (!match->fib6_nsiblings || have_oif_match)
goto out;
/* We might have already computed the hash for ICMPv6 errors. In such /* We might have already computed the hash for ICMPv6 errors. In such
* case it will always be non-zero. Otherwise now is the time to do it. * case it will always be non-zero. Otherwise now is the time to do it.
...@@ -443,7 +445,7 @@ struct fib6_info *fib6_multipath_select(const struct net *net, ...@@ -443,7 +445,7 @@ struct fib6_info *fib6_multipath_select(const struct net *net,
fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL); fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL);
if (fl6->mp_hash <= atomic_read(&match->fib6_nh.fib_nh_upper_bound)) if (fl6->mp_hash <= atomic_read(&match->fib6_nh.fib_nh_upper_bound))
return match; goto out;
list_for_each_entry_safe(sibling, next_sibling, &match->fib6_siblings, list_for_each_entry_safe(sibling, next_sibling, &match->fib6_siblings,
fib6_siblings) { fib6_siblings) {
...@@ -459,7 +461,9 @@ struct fib6_info *fib6_multipath_select(const struct net *net, ...@@ -459,7 +461,9 @@ struct fib6_info *fib6_multipath_select(const struct net *net,
break; break;
} }
return match; out:
res->f6i = match;
res->nh = &match->fib6_nh;
} }
/* /*
...@@ -487,29 +491,42 @@ static bool __rt6_device_match(struct net *net, const struct fib6_nh *nh, ...@@ -487,29 +491,42 @@ static bool __rt6_device_match(struct net *net, const struct fib6_nh *nh,
return false; return false;
} }
static inline struct fib6_info *rt6_device_match(struct net *net, static void rt6_device_match(struct net *net, struct fib6_result *res,
struct fib6_info *rt, const struct in6_addr *saddr, int oif, int flags)
const struct in6_addr *saddr,
int oif,
int flags)
{ {
const struct fib6_nh *nh; struct fib6_info *f6i = res->f6i;
struct fib6_info *sprt; struct fib6_info *spf6i;
struct fib6_nh *nh;
if (!oif && ipv6_addr_any(saddr) && if (!oif && ipv6_addr_any(saddr)) {
!(rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD)) nh = &f6i->fib6_nh;
return rt; if (!(nh->fib_nh_flags & RTNH_F_DEAD))
goto out;
}
for (sprt = rt; sprt; sprt = rcu_dereference(sprt->fib6_next)) { for (spf6i = f6i; spf6i; spf6i = rcu_dereference(spf6i->fib6_next)) {
nh = &sprt->fib6_nh; nh = &spf6i->fib6_nh;
if (__rt6_device_match(net, nh, saddr, oif, flags)) if (__rt6_device_match(net, nh, saddr, oif, flags)) {
return sprt; res->f6i = spf6i;
goto out;
}
} }
if (oif && flags & RT6_LOOKUP_F_IFACE) if (oif && flags & RT6_LOOKUP_F_IFACE) {
return net->ipv6.fib6_null_entry; res->f6i = net->ipv6.fib6_null_entry;
nh = &res->f6i->fib6_nh;
goto out;
}
return rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD ? net->ipv6.fib6_null_entry : rt; nh = &f6i->fib6_nh;
if (nh->fib_nh_flags & RTNH_F_DEAD) {
res->f6i = net->ipv6.fib6_null_entry;
nh = &res->f6i->fib6_nh;
}
out:
res->nh = nh;
res->fib6_type = res->f6i->fib6_type;
res->fib6_flags = res->f6i->fib6_flags;
} }
#ifdef CONFIG_IPV6_ROUTER_PREF #ifdef CONFIG_IPV6_ROUTER_PREF
...@@ -680,66 +697,70 @@ static bool find_match(struct fib6_nh *nh, u32 fib6_flags, ...@@ -680,66 +697,70 @@ static bool find_match(struct fib6_nh *nh, u32 fib6_flags,
return rc; return rc;
} }
static void __find_rr_leaf(struct fib6_info *rt_start, static void __find_rr_leaf(struct fib6_info *f6i_start,
struct fib6_info *nomatch, u32 metric, struct fib6_info *nomatch, u32 metric,
struct fib6_info **match, struct fib6_info **cont, struct fib6_result *res, struct fib6_info **cont,
int oif, int strict, bool *do_rr, int *mpri) int oif, int strict, bool *do_rr, int *mpri)
{ {
struct fib6_info *rt; struct fib6_info *f6i;
for (rt = rt_start; for (f6i = f6i_start;
rt && rt != nomatch; f6i && f6i != nomatch;
rt = rcu_dereference(rt->fib6_next)) { f6i = rcu_dereference(f6i->fib6_next)) {
struct fib6_nh *nh; struct fib6_nh *nh;
if (cont && rt->fib6_metric != metric) { if (cont && f6i->fib6_metric != metric) {
*cont = rt; *cont = f6i;
return; return;
} }
if (fib6_check_expired(rt)) if (fib6_check_expired(f6i))
continue; continue;
nh = &rt->fib6_nh; nh = &f6i->fib6_nh;
if (find_match(nh, rt->fib6_flags, oif, strict, mpri, do_rr)) if (find_match(nh, f6i->fib6_flags, oif, strict, mpri, do_rr)) {
*match = rt; res->f6i = f6i;
res->nh = nh;
res->fib6_flags = f6i->fib6_flags;
res->fib6_type = f6i->fib6_type;
}
} }
} }
static struct fib6_info *find_rr_leaf(struct fib6_node *fn, static void find_rr_leaf(struct fib6_node *fn, struct fib6_info *leaf,
struct fib6_info *leaf, struct fib6_info *rr_head, int oif, int strict,
struct fib6_info *rr_head, bool *do_rr, struct fib6_result *res)
u32 metric, int oif, int strict,
bool *do_rr)
{ {
struct fib6_info *match = NULL, *cont = NULL; u32 metric = rr_head->fib6_metric;
struct fib6_info *cont = NULL;
int mpri = -1; int mpri = -1;
__find_rr_leaf(rr_head, NULL, metric, &match, &cont, __find_rr_leaf(rr_head, NULL, metric, res, &cont,
oif, strict, do_rr, &mpri); oif, strict, do_rr, &mpri);
__find_rr_leaf(leaf, rr_head, metric, &match, &cont, __find_rr_leaf(leaf, rr_head, metric, res, &cont,
oif, strict, do_rr, &mpri); oif, strict, do_rr, &mpri);
if (match || !cont) if (res->f6i || !cont)
return match; return;
__find_rr_leaf(cont, NULL, metric, &match, NULL, __find_rr_leaf(cont, NULL, metric, res, NULL,
oif, strict, do_rr, &mpri); oif, strict, do_rr, &mpri);
return match;
} }
static struct fib6_info *rt6_select(struct net *net, struct fib6_node *fn, static void rt6_select(struct net *net, struct fib6_node *fn, int oif,
int oif, int strict) struct fib6_result *res, int strict)
{ {
struct fib6_info *leaf = rcu_dereference(fn->leaf); struct fib6_info *leaf = rcu_dereference(fn->leaf);
struct fib6_info *match, *rt0; struct fib6_info *rt0;
bool do_rr = false; bool do_rr = false;
int key_plen; int key_plen;
/* make sure this function or its helpers sets f6i */
res->f6i = NULL;
if (!leaf || leaf == net->ipv6.fib6_null_entry) if (!leaf || leaf == net->ipv6.fib6_null_entry)
return net->ipv6.fib6_null_entry; goto out;
rt0 = rcu_dereference(fn->rr_ptr); rt0 = rcu_dereference(fn->rr_ptr);
if (!rt0) if (!rt0)
...@@ -756,11 +777,9 @@ static struct fib6_info *rt6_select(struct net *net, struct fib6_node *fn, ...@@ -756,11 +777,9 @@ static struct fib6_info *rt6_select(struct net *net, struct fib6_node *fn,
key_plen = rt0->fib6_src.plen; key_plen = rt0->fib6_src.plen;
#endif #endif
if (fn->fn_bit != key_plen) if (fn->fn_bit != key_plen)
return net->ipv6.fib6_null_entry; goto out;
match = find_rr_leaf(fn, leaf, rt0, rt0->fib6_metric, oif, strict,
&do_rr);
find_rr_leaf(fn, leaf, rt0, oif, strict, &do_rr, res);
if (do_rr) { if (do_rr) {
struct fib6_info *next = rcu_dereference(rt0->fib6_next); struct fib6_info *next = rcu_dereference(rt0->fib6_next);
...@@ -777,12 +796,19 @@ static struct fib6_info *rt6_select(struct net *net, struct fib6_node *fn, ...@@ -777,12 +796,19 @@ static struct fib6_info *rt6_select(struct net *net, struct fib6_node *fn,
} }
} }
return match ? match : net->ipv6.fib6_null_entry; out:
if (!res->f6i) {
res->f6i = net->ipv6.fib6_null_entry;
res->nh = &res->f6i->fib6_nh;
res->fib6_flags = res->f6i->fib6_flags;
res->fib6_type = res->f6i->fib6_type;
}
} }
static bool rt6_is_gw_or_nonexthop(const struct fib6_info *rt) static bool rt6_is_gw_or_nonexthop(const struct fib6_result *res)
{ {
return (rt->fib6_flags & RTF_NONEXTHOP) || rt->fib6_nh.fib_nh_gw_family; return (res->f6i->fib6_flags & RTF_NONEXTHOP) ||
res->nh->fib_nh_gw_family;
} }
#ifdef CONFIG_IPV6_ROUTE_INFO #ifdef CONFIG_IPV6_ROUTE_INFO
...@@ -866,17 +892,17 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, ...@@ -866,17 +892,17 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
*/ */
/* called with rcu_lock held */ /* called with rcu_lock held */
static struct net_device *ip6_rt_get_dev_rcu(struct fib6_info *rt) static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res)
{ {
struct net_device *dev = rt->fib6_nh.fib_nh_dev; struct net_device *dev = res->nh->fib_nh_dev;
if (rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST)) { if (res->fib6_flags & (RTF_LOCAL | RTF_ANYCAST)) {
/* for copies of local routes, dst->dev needs to be the /* for copies of local routes, dst->dev needs to be the
* device if it is a master device, the master device if * device if it is a master device, the master device if
* device is enslaved, and the loopback as the default * device is enslaved, and the loopback as the default
*/ */
if (netif_is_l3_slave(dev) && if (netif_is_l3_slave(dev) &&
!rt6_need_strict(&rt->fib6_dst.addr)) !rt6_need_strict(&res->f6i->fib6_dst.addr))
dev = l3mdev_master_dev_rcu(dev); dev = l3mdev_master_dev_rcu(dev);
else if (!netif_is_l3_master(dev)) else if (!netif_is_l3_master(dev))
dev = dev_net(dev)->loopback_dev; dev = dev_net(dev)->loopback_dev;
...@@ -922,11 +948,11 @@ static unsigned short fib6_info_dst_flags(struct fib6_info *rt) ...@@ -922,11 +948,11 @@ static unsigned short fib6_info_dst_flags(struct fib6_info *rt)
return flags; return flags;
} }
static void ip6_rt_init_dst_reject(struct rt6_info *rt, struct fib6_info *ort) static void ip6_rt_init_dst_reject(struct rt6_info *rt, u8 fib6_type)
{ {
rt->dst.error = ip6_rt_type_to_error(ort->fib6_type); rt->dst.error = ip6_rt_type_to_error(fib6_type);
switch (ort->fib6_type) { switch (fib6_type) {
case RTN_BLACKHOLE: case RTN_BLACKHOLE:
rt->dst.output = dst_discard_out; rt->dst.output = dst_discard_out;
rt->dst.input = dst_discard; rt->dst.input = dst_discard;
...@@ -944,26 +970,28 @@ static void ip6_rt_init_dst_reject(struct rt6_info *rt, struct fib6_info *ort) ...@@ -944,26 +970,28 @@ static void ip6_rt_init_dst_reject(struct rt6_info *rt, struct fib6_info *ort)
} }
} }
static void ip6_rt_init_dst(struct rt6_info *rt, struct fib6_info *ort) static void ip6_rt_init_dst(struct rt6_info *rt, const struct fib6_result *res)
{ {
if (ort->fib6_flags & RTF_REJECT) { struct fib6_info *f6i = res->f6i;
ip6_rt_init_dst_reject(rt, ort);
if (res->fib6_flags & RTF_REJECT) {
ip6_rt_init_dst_reject(rt, res->fib6_type);
return; return;
} }
rt->dst.error = 0; rt->dst.error = 0;
rt->dst.output = ip6_output; rt->dst.output = ip6_output;
if (ort->fib6_type == RTN_LOCAL || ort->fib6_type == RTN_ANYCAST) { if (res->fib6_type == RTN_LOCAL || res->fib6_type == RTN_ANYCAST) {
rt->dst.input = ip6_input; rt->dst.input = ip6_input;
} else if (ipv6_addr_type(&ort->fib6_dst.addr) & IPV6_ADDR_MULTICAST) { } else if (ipv6_addr_type(&f6i->fib6_dst.addr) & IPV6_ADDR_MULTICAST) {
rt->dst.input = ip6_mc_input; rt->dst.input = ip6_mc_input;
} else { } else {
rt->dst.input = ip6_forward; rt->dst.input = ip6_forward;
} }
if (ort->fib6_nh.fib_nh_lws) { if (res->nh->fib_nh_lws) {
rt->dst.lwtstate = lwtstate_get(ort->fib6_nh.fib_nh_lws); rt->dst.lwtstate = lwtstate_get(res->nh->fib_nh_lws);
lwtunnel_set_redirect(&rt->dst); lwtunnel_set_redirect(&rt->dst);
} }
...@@ -978,23 +1006,25 @@ static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from) ...@@ -978,23 +1006,25 @@ static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
ip_dst_init_metrics(&rt->dst, from->fib6_metrics); ip_dst_init_metrics(&rt->dst, from->fib6_metrics);
} }
/* Caller must already hold reference to @ort */ /* Caller must already hold reference to f6i in result */
static void ip6_rt_copy_init(struct rt6_info *rt, struct fib6_info *ort) static void ip6_rt_copy_init(struct rt6_info *rt, const struct fib6_result *res)
{ {
struct net_device *dev = fib6_info_nh_dev(ort); const struct fib6_nh *nh = res->nh;
const struct net_device *dev = nh->fib_nh_dev;
struct fib6_info *f6i = res->f6i;
ip6_rt_init_dst(rt, ort); ip6_rt_init_dst(rt, res);
rt->rt6i_dst = ort->fib6_dst; rt->rt6i_dst = f6i->fib6_dst;
rt->rt6i_idev = dev ? in6_dev_get(dev) : NULL; rt->rt6i_idev = dev ? in6_dev_get(dev) : NULL;
rt->rt6i_flags = ort->fib6_flags; rt->rt6i_flags = res->fib6_flags;
if (ort->fib6_nh.fib_nh_gw_family) { if (nh->fib_nh_gw_family) {
rt->rt6i_gateway = ort->fib6_nh.fib_nh_gw6; rt->rt6i_gateway = nh->fib_nh_gw6;
rt->rt6i_flags |= RTF_GATEWAY; rt->rt6i_flags |= RTF_GATEWAY;
} }
rt6_set_from(rt, ort); rt6_set_from(rt, f6i);
#ifdef CONFIG_IPV6_SUBTREES #ifdef CONFIG_IPV6_SUBTREES
rt->rt6i_src = ort->fib6_src; rt->rt6i_src = f6i->fib6_src;
#endif #endif
} }
...@@ -1033,22 +1063,24 @@ static bool ip6_hold_safe(struct net *net, struct rt6_info **prt) ...@@ -1033,22 +1063,24 @@ static bool ip6_hold_safe(struct net *net, struct rt6_info **prt)
} }
/* called with rcu_lock held */ /* called with rcu_lock held */
static struct rt6_info *ip6_create_rt_rcu(struct fib6_info *rt) static struct rt6_info *ip6_create_rt_rcu(const struct fib6_result *res)
{ {
unsigned short flags = fib6_info_dst_flags(rt); struct net_device *dev = res->nh->fib_nh_dev;
struct net_device *dev = rt->fib6_nh.fib_nh_dev; struct fib6_info *f6i = res->f6i;
unsigned short flags;
struct rt6_info *nrt; struct rt6_info *nrt;
if (!fib6_info_hold_safe(rt)) if (!fib6_info_hold_safe(f6i))
goto fallback; goto fallback;
flags = fib6_info_dst_flags(f6i);
nrt = ip6_dst_alloc(dev_net(dev), dev, flags); nrt = ip6_dst_alloc(dev_net(dev), dev, flags);
if (!nrt) { if (!nrt) {
fib6_info_release(rt); fib6_info_release(f6i);
goto fallback; goto fallback;
} }
ip6_rt_copy_init(nrt, rt); ip6_rt_copy_init(nrt, res);
return nrt; return nrt;
fallback: fallback:
...@@ -1063,7 +1095,7 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net, ...@@ -1063,7 +1095,7 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net,
const struct sk_buff *skb, const struct sk_buff *skb,
int flags) int flags)
{ {
struct fib6_info *f6i; struct fib6_result res = {};
struct fib6_node *fn; struct fib6_node *fn;
struct rt6_info *rt; struct rt6_info *rt;
...@@ -1073,14 +1105,14 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net, ...@@ -1073,14 +1105,14 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net,
rcu_read_lock(); rcu_read_lock();
fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr); fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
restart: restart:
f6i = rcu_dereference(fn->leaf); res.f6i = rcu_dereference(fn->leaf);
if (!f6i) if (!res.f6i)
f6i = net->ipv6.fib6_null_entry; res.f6i = net->ipv6.fib6_null_entry;
else else
f6i = rt6_device_match(net, f6i, &fl6->saddr, rt6_device_match(net, &res, &fl6->saddr, fl6->flowi6_oif,
fl6->flowi6_oif, flags); flags);
if (f6i == net->ipv6.fib6_null_entry) { if (res.f6i == net->ipv6.fib6_null_entry) {
fn = fib6_backtrack(fn, &fl6->saddr); fn = fib6_backtrack(fn, &fl6->saddr);
if (fn) if (fn)
goto restart; goto restart;
...@@ -1090,20 +1122,20 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net, ...@@ -1090,20 +1122,20 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net,
goto out; goto out;
} }
if (f6i->fib6_nsiblings && fl6->flowi6_oif == 0) fib6_select_path(net, &res, fl6, fl6->flowi6_oif,
f6i = fib6_multipath_select(net, f6i, fl6, fl6->flowi6_oif, skb, fl6->flowi6_oif != 0, skb, flags);
flags);
/* Search through exception table */ /* Search through exception table */
rt = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr); rt = rt6_find_cached_rt(&res, &fl6->daddr, &fl6->saddr);
if (rt) { if (rt) {
if (ip6_hold_safe(net, &rt)) if (ip6_hold_safe(net, &rt))
dst_use_noref(&rt->dst, jiffies); dst_use_noref(&rt->dst, jiffies);
} else { } else {
rt = ip6_create_rt_rcu(f6i); rt = ip6_create_rt_rcu(&res);
} }
out: out:
trace_fib6_table_lookup(net, f6i, table, fl6); trace_fib6_table_lookup(net, &res, table, fl6);
rcu_read_unlock(); rcu_read_unlock();
...@@ -1170,10 +1202,11 @@ int ip6_ins_rt(struct net *net, struct fib6_info *rt) ...@@ -1170,10 +1202,11 @@ int ip6_ins_rt(struct net *net, struct fib6_info *rt)
return __ip6_ins_rt(rt, &info, NULL); return __ip6_ins_rt(rt, &info, NULL);
} }
static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort, static struct rt6_info *ip6_rt_cache_alloc(const struct fib6_result *res,
const struct in6_addr *daddr, const struct in6_addr *daddr,
const struct in6_addr *saddr) const struct in6_addr *saddr)
{ {
struct fib6_info *f6i = res->f6i;
struct net_device *dev; struct net_device *dev;
struct rt6_info *rt; struct rt6_info *rt;
...@@ -1181,25 +1214,25 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort, ...@@ -1181,25 +1214,25 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort,
* Clone the route. * Clone the route.
*/ */
if (!fib6_info_hold_safe(ort)) if (!fib6_info_hold_safe(f6i))
return NULL; return NULL;
dev = ip6_rt_get_dev_rcu(ort); dev = ip6_rt_get_dev_rcu(res);
rt = ip6_dst_alloc(dev_net(dev), dev, 0); rt = ip6_dst_alloc(dev_net(dev), dev, 0);
if (!rt) { if (!rt) {
fib6_info_release(ort); fib6_info_release(f6i);
return NULL; return NULL;
} }
ip6_rt_copy_init(rt, ort); ip6_rt_copy_init(rt, res);
rt->rt6i_flags |= RTF_CACHE; rt->rt6i_flags |= RTF_CACHE;
rt->dst.flags |= DST_HOST; rt->dst.flags |= DST_HOST;
rt->rt6i_dst.addr = *daddr; rt->rt6i_dst.addr = *daddr;
rt->rt6i_dst.plen = 128; rt->rt6i_dst.plen = 128;
if (!rt6_is_gw_or_nonexthop(ort)) { if (!rt6_is_gw_or_nonexthop(res)) {
if (ort->fib6_dst.plen != 128 && if (f6i->fib6_dst.plen != 128 &&
ipv6_addr_equal(&ort->fib6_dst.addr, daddr)) ipv6_addr_equal(&f6i->fib6_dst.addr, daddr))
rt->rt6i_flags |= RTF_ANYCAST; rt->rt6i_flags |= RTF_ANYCAST;
#ifdef CONFIG_IPV6_SUBTREES #ifdef CONFIG_IPV6_SUBTREES
if (rt->rt6i_src.plen && saddr) { if (rt->rt6i_src.plen && saddr) {
...@@ -1212,34 +1245,35 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort, ...@@ -1212,34 +1245,35 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort,
return rt; return rt;
} }
static struct rt6_info *ip6_rt_pcpu_alloc(struct fib6_info *rt) static struct rt6_info *ip6_rt_pcpu_alloc(const struct fib6_result *res)
{ {
unsigned short flags = fib6_info_dst_flags(rt); struct fib6_info *f6i = res->f6i;
unsigned short flags = fib6_info_dst_flags(f6i);
struct net_device *dev; struct net_device *dev;
struct rt6_info *pcpu_rt; struct rt6_info *pcpu_rt;
if (!fib6_info_hold_safe(rt)) if (!fib6_info_hold_safe(f6i))
return NULL; return NULL;
rcu_read_lock(); rcu_read_lock();
dev = ip6_rt_get_dev_rcu(rt); dev = ip6_rt_get_dev_rcu(res);
pcpu_rt = ip6_dst_alloc(dev_net(dev), dev, flags); pcpu_rt = ip6_dst_alloc(dev_net(dev), dev, flags);
rcu_read_unlock(); rcu_read_unlock();
if (!pcpu_rt) { if (!pcpu_rt) {
fib6_info_release(rt); fib6_info_release(f6i);
return NULL; return NULL;
} }
ip6_rt_copy_init(pcpu_rt, rt); ip6_rt_copy_init(pcpu_rt, res);
pcpu_rt->rt6i_flags |= RTF_PCPU; pcpu_rt->rt6i_flags |= RTF_PCPU;
return pcpu_rt; return pcpu_rt;
} }
/* It should be called with rcu_read_lock() acquired */ /* It should be called with rcu_read_lock() acquired */
static struct rt6_info *rt6_get_pcpu_route(struct fib6_info *rt) static struct rt6_info *rt6_get_pcpu_route(const struct fib6_result *res)
{ {
struct rt6_info *pcpu_rt, **p; struct rt6_info *pcpu_rt, **p;
p = this_cpu_ptr(rt->rt6i_pcpu); p = this_cpu_ptr(res->f6i->rt6i_pcpu);
pcpu_rt = *p; pcpu_rt = *p;
if (pcpu_rt) if (pcpu_rt)
...@@ -1249,18 +1283,18 @@ static struct rt6_info *rt6_get_pcpu_route(struct fib6_info *rt) ...@@ -1249,18 +1283,18 @@ static struct rt6_info *rt6_get_pcpu_route(struct fib6_info *rt)
} }
static struct rt6_info *rt6_make_pcpu_route(struct net *net, static struct rt6_info *rt6_make_pcpu_route(struct net *net,
struct fib6_info *rt) const struct fib6_result *res)
{ {
struct rt6_info *pcpu_rt, *prev, **p; struct rt6_info *pcpu_rt, *prev, **p;
pcpu_rt = ip6_rt_pcpu_alloc(rt); pcpu_rt = ip6_rt_pcpu_alloc(res);
if (!pcpu_rt) { if (!pcpu_rt) {
dst_hold(&net->ipv6.ip6_null_entry->dst); dst_hold(&net->ipv6.ip6_null_entry->dst);
return net->ipv6.ip6_null_entry; return net->ipv6.ip6_null_entry;
} }
dst_hold(&pcpu_rt->dst); dst_hold(&pcpu_rt->dst);
p = this_cpu_ptr(rt->rt6i_pcpu); p = this_cpu_ptr(res->f6i->rt6i_pcpu);
prev = cmpxchg(p, NULL, pcpu_rt); prev = cmpxchg(p, NULL, pcpu_rt);
BUG_ON(prev); BUG_ON(prev);
...@@ -1403,14 +1437,15 @@ __rt6_find_exception_rcu(struct rt6_exception_bucket **bucket, ...@@ -1403,14 +1437,15 @@ __rt6_find_exception_rcu(struct rt6_exception_bucket **bucket,
return NULL; return NULL;
} }
static unsigned int fib6_mtu(const struct fib6_info *rt) static unsigned int fib6_mtu(const struct fib6_result *res)
{ {
const struct fib6_nh *nh = res->nh;
unsigned int mtu; unsigned int mtu;
if (rt->fib6_pmtu) { if (res->f6i->fib6_pmtu) {
mtu = rt->fib6_pmtu; mtu = res->f6i->fib6_pmtu;
} else { } else {
struct net_device *dev = fib6_info_nh_dev(rt); struct net_device *dev = nh->fib_nh_dev;
struct inet6_dev *idev; struct inet6_dev *idev;
rcu_read_lock(); rcu_read_lock();
...@@ -1421,26 +1456,27 @@ static unsigned int fib6_mtu(const struct fib6_info *rt) ...@@ -1421,26 +1456,27 @@ static unsigned int fib6_mtu(const struct fib6_info *rt)
mtu = min_t(unsigned int, mtu, IP6_MAX_MTU); mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);
return mtu - lwtunnel_headroom(rt->fib6_nh.fib_nh_lws, mtu); return mtu - lwtunnel_headroom(nh->fib_nh_lws, mtu);
} }
static int rt6_insert_exception(struct rt6_info *nrt, static int rt6_insert_exception(struct rt6_info *nrt,
struct fib6_info *ort) const struct fib6_result *res)
{ {
struct net *net = dev_net(nrt->dst.dev); struct net *net = dev_net(nrt->dst.dev);
struct rt6_exception_bucket *bucket; struct rt6_exception_bucket *bucket;
struct in6_addr *src_key = NULL; struct in6_addr *src_key = NULL;
struct rt6_exception *rt6_ex; struct rt6_exception *rt6_ex;
struct fib6_info *f6i = res->f6i;
int err = 0; int err = 0;
spin_lock_bh(&rt6_exception_lock); spin_lock_bh(&rt6_exception_lock);
if (ort->exception_bucket_flushed) { if (f6i->exception_bucket_flushed) {
err = -EINVAL; err = -EINVAL;
goto out; goto out;
} }
bucket = rcu_dereference_protected(ort->rt6i_exception_bucket, bucket = rcu_dereference_protected(f6i->rt6i_exception_bucket,
lockdep_is_held(&rt6_exception_lock)); lockdep_is_held(&rt6_exception_lock));
if (!bucket) { if (!bucket) {
bucket = kcalloc(FIB6_EXCEPTION_BUCKET_SIZE, sizeof(*bucket), bucket = kcalloc(FIB6_EXCEPTION_BUCKET_SIZE, sizeof(*bucket),
...@@ -1449,24 +1485,24 @@ static int rt6_insert_exception(struct rt6_info *nrt, ...@@ -1449,24 +1485,24 @@ static int rt6_insert_exception(struct rt6_info *nrt,
err = -ENOMEM; err = -ENOMEM;
goto out; goto out;
} }
rcu_assign_pointer(ort->rt6i_exception_bucket, bucket); rcu_assign_pointer(f6i->rt6i_exception_bucket, bucket);
} }
#ifdef CONFIG_IPV6_SUBTREES #ifdef CONFIG_IPV6_SUBTREES
/* rt6i_src.plen != 0 indicates ort is in subtree /* fib6_src.plen != 0 indicates f6i is in subtree
* and exception table is indexed by a hash of * and exception table is indexed by a hash of
* both rt6i_dst and rt6i_src. * both fib6_dst and fib6_src.
* Otherwise, the exception table is indexed by * Otherwise, the exception table is indexed by
* a hash of only rt6i_dst. * a hash of only fib6_dst.
*/ */
if (ort->fib6_src.plen) if (f6i->fib6_src.plen)
src_key = &nrt->rt6i_src.addr; src_key = &nrt->rt6i_src.addr;
#endif #endif
/* rt6_mtu_change() might lower mtu on ort. /* rt6_mtu_change() might lower mtu on f6i.
* Only insert this exception route if its mtu * Only insert this exception route if its mtu
* is less than ort's mtu value. * is less than f6i's mtu value.
*/ */
if (dst_metric_raw(&nrt->dst, RTAX_MTU) >= fib6_mtu(ort)) { if (dst_metric_raw(&nrt->dst, RTAX_MTU) >= fib6_mtu(res)) {
err = -EINVAL; err = -EINVAL;
goto out; goto out;
} }
...@@ -1495,9 +1531,9 @@ static int rt6_insert_exception(struct rt6_info *nrt, ...@@ -1495,9 +1531,9 @@ static int rt6_insert_exception(struct rt6_info *nrt,
/* Update fn->fn_sernum to invalidate all cached dst */ /* Update fn->fn_sernum to invalidate all cached dst */
if (!err) { if (!err) {
spin_lock_bh(&ort->fib6_table->tb6_lock); spin_lock_bh(&f6i->fib6_table->tb6_lock);
fib6_update_sernum(net, ort); fib6_update_sernum(net, f6i);
spin_unlock_bh(&ort->fib6_table->tb6_lock); spin_unlock_bh(&f6i->fib6_table->tb6_lock);
fib6_force_start_gc(net); fib6_force_start_gc(net);
} }
...@@ -1534,33 +1570,33 @@ void rt6_flush_exceptions(struct fib6_info *rt) ...@@ -1534,33 +1570,33 @@ void rt6_flush_exceptions(struct fib6_info *rt)
/* Find cached rt in the hash table inside passed in rt /* Find cached rt in the hash table inside passed in rt
* Caller has to hold rcu_read_lock() * Caller has to hold rcu_read_lock()
*/ */
static struct rt6_info *rt6_find_cached_rt(struct fib6_info *rt, static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,
struct in6_addr *daddr, struct in6_addr *daddr,
struct in6_addr *saddr) struct in6_addr *saddr)
{ {
struct rt6_exception_bucket *bucket; struct rt6_exception_bucket *bucket;
struct in6_addr *src_key = NULL; struct in6_addr *src_key = NULL;
struct rt6_exception *rt6_ex; struct rt6_exception *rt6_ex;
struct rt6_info *res = NULL; struct rt6_info *ret = NULL;
bucket = rcu_dereference(rt->rt6i_exception_bucket); bucket = rcu_dereference(res->f6i->rt6i_exception_bucket);
#ifdef CONFIG_IPV6_SUBTREES #ifdef CONFIG_IPV6_SUBTREES
/* rt6i_src.plen != 0 indicates rt is in subtree /* fib6i_src.plen != 0 indicates f6i is in subtree
* and exception table is indexed by a hash of * and exception table is indexed by a hash of
* both rt6i_dst and rt6i_src. * both fib6_dst and fib6_src.
* Otherwise, the exception table is indexed by * Otherwise, the exception table is indexed by
* a hash of only rt6i_dst. * a hash of only fib6_dst.
*/ */
if (rt->fib6_src.plen) if (res->f6i->fib6_src.plen)
src_key = saddr; src_key = saddr;
#endif #endif
rt6_ex = __rt6_find_exception_rcu(&bucket, daddr, src_key); rt6_ex = __rt6_find_exception_rcu(&bucket, daddr, src_key);
if (rt6_ex && !rt6_check_expired(rt6_ex->rt6i)) if (rt6_ex && !rt6_check_expired(rt6_ex->rt6i))
res = rt6_ex->rt6i; ret = rt6_ex->rt6i;
return res; return ret;
} }
/* Remove the passed in cached rt from the hash table that contains it */ /* Remove the passed in cached rt from the hash table that contains it */
...@@ -1808,11 +1844,10 @@ void rt6_age_exceptions(struct fib6_info *rt, ...@@ -1808,11 +1844,10 @@ void rt6_age_exceptions(struct fib6_info *rt,
} }
/* must be called with rcu lock held */ /* must be called with rcu lock held */
struct fib6_info *fib6_table_lookup(struct net *net, struct fib6_table *table, int fib6_table_lookup(struct net *net, struct fib6_table *table, int oif,
int oif, struct flowi6 *fl6, int strict) struct flowi6 *fl6, struct fib6_result *res, int strict)
{ {
struct fib6_node *fn, *saved_fn; struct fib6_node *fn, *saved_fn;
struct fib6_info *f6i;
fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr); fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
saved_fn = fn; saved_fn = fn;
...@@ -1821,8 +1856,8 @@ struct fib6_info *fib6_table_lookup(struct net *net, struct fib6_table *table, ...@@ -1821,8 +1856,8 @@ struct fib6_info *fib6_table_lookup(struct net *net, struct fib6_table *table,
oif = 0; oif = 0;
redo_rt6_select: redo_rt6_select:
f6i = rt6_select(net, fn, oif, strict); rt6_select(net, fn, oif, res, strict);
if (f6i == net->ipv6.fib6_null_entry) { if (res->f6i == net->ipv6.fib6_null_entry) {
fn = fib6_backtrack(fn, &fl6->saddr); fn = fib6_backtrack(fn, &fl6->saddr);
if (fn) if (fn)
goto redo_rt6_select; goto redo_rt6_select;
...@@ -1834,16 +1869,16 @@ struct fib6_info *fib6_table_lookup(struct net *net, struct fib6_table *table, ...@@ -1834,16 +1869,16 @@ struct fib6_info *fib6_table_lookup(struct net *net, struct fib6_table *table,
} }
} }
trace_fib6_table_lookup(net, f6i, table, fl6); trace_fib6_table_lookup(net, res, table, fl6);
return f6i; return 0;
} }
struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
int oif, struct flowi6 *fl6, int oif, struct flowi6 *fl6,
const struct sk_buff *skb, int flags) const struct sk_buff *skb, int flags)
{ {
struct fib6_info *f6i; struct fib6_result res = {};
struct rt6_info *rt; struct rt6_info *rt;
int strict = 0; int strict = 0;
...@@ -1854,19 +1889,18 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1854,19 +1889,18 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
rcu_read_lock(); rcu_read_lock();
f6i = fib6_table_lookup(net, table, oif, fl6, strict); fib6_table_lookup(net, table, oif, fl6, &res, strict);
if (f6i == net->ipv6.fib6_null_entry) { if (res.f6i == net->ipv6.fib6_null_entry) {
rt = net->ipv6.ip6_null_entry; rt = net->ipv6.ip6_null_entry;
rcu_read_unlock(); rcu_read_unlock();
dst_hold(&rt->dst); dst_hold(&rt->dst);
return rt; return rt;
} }
if (f6i->fib6_nsiblings) fib6_select_path(net, &res, fl6, oif, false, skb, strict);
f6i = fib6_multipath_select(net, f6i, fl6, oif, skb, strict);
/*Search through exception table */ /*Search through exception table */
rt = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr); rt = rt6_find_cached_rt(&res, &fl6->daddr, &fl6->saddr);
if (rt) { if (rt) {
if (ip6_hold_safe(net, &rt)) if (ip6_hold_safe(net, &rt))
dst_use_noref(&rt->dst, jiffies); dst_use_noref(&rt->dst, jiffies);
...@@ -1874,7 +1908,7 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1874,7 +1908,7 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
rcu_read_unlock(); rcu_read_unlock();
return rt; return rt;
} else if (unlikely((fl6->flowi6_flags & FLOWI_FLAG_KNOWN_NH) && } else if (unlikely((fl6->flowi6_flags & FLOWI_FLAG_KNOWN_NH) &&
!f6i->fib6_nh.fib_nh_gw_family)) { !res.nh->fib_nh_gw_family)) {
/* Create a RTF_CACHE clone which will not be /* Create a RTF_CACHE clone which will not be
* owned by the fib6 tree. It is for the special case where * owned by the fib6 tree. It is for the special case where
* the daddr in the skb during the neighbor look-up is different * the daddr in the skb during the neighbor look-up is different
...@@ -1882,7 +1916,7 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1882,7 +1916,7 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
*/ */
struct rt6_info *uncached_rt; struct rt6_info *uncached_rt;
uncached_rt = ip6_rt_cache_alloc(f6i, &fl6->daddr, NULL); uncached_rt = ip6_rt_cache_alloc(&res, &fl6->daddr, NULL);
rcu_read_unlock(); rcu_read_unlock();
...@@ -1904,10 +1938,10 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1904,10 +1938,10 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
struct rt6_info *pcpu_rt; struct rt6_info *pcpu_rt;
local_bh_disable(); local_bh_disable();
pcpu_rt = rt6_get_pcpu_route(f6i); pcpu_rt = rt6_get_pcpu_route(&res);
if (!pcpu_rt) if (!pcpu_rt)
pcpu_rt = rt6_make_pcpu_route(net, f6i); pcpu_rt = rt6_make_pcpu_route(net, &res);
local_bh_enable(); local_bh_enable();
rcu_read_unlock(); rcu_read_unlock();
...@@ -2326,19 +2360,23 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk, ...@@ -2326,19 +2360,23 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
if (rt6->rt6i_flags & RTF_CACHE) if (rt6->rt6i_flags & RTF_CACHE)
rt6_update_exception_stamp_rt(rt6); rt6_update_exception_stamp_rt(rt6);
} else if (daddr) { } else if (daddr) {
struct fib6_info *from; struct fib6_result res = {};
struct rt6_info *nrt6; struct rt6_info *nrt6;
rcu_read_lock(); rcu_read_lock();
from = rcu_dereference(rt6->from); res.f6i = rcu_dereference(rt6->from);
if (!from) { if (!res.f6i) {
rcu_read_unlock(); rcu_read_unlock();
return; return;
} }
nrt6 = ip6_rt_cache_alloc(from, daddr, saddr); res.nh = &res.f6i->fib6_nh;
res.fib6_flags = res.f6i->fib6_flags;
res.fib6_type = res.f6i->fib6_type;
nrt6 = ip6_rt_cache_alloc(&res, daddr, saddr);
if (nrt6) { if (nrt6) {
rt6_do_update_pmtu(nrt6, mtu); rt6_do_update_pmtu(nrt6, mtu);
if (rt6_insert_exception(nrt6, from)) if (rt6_insert_exception(nrt6, &res))
dst_release_immediate(&nrt6->dst); dst_release_immediate(&nrt6->dst);
} }
rcu_read_unlock(); rcu_read_unlock();
...@@ -2411,12 +2449,13 @@ void ip6_sk_dst_store_flow(struct sock *sk, struct dst_entry *dst, ...@@ -2411,12 +2449,13 @@ void ip6_sk_dst_store_flow(struct sock *sk, struct dst_entry *dst,
NULL); NULL);
} }
static bool ip6_redirect_nh_match(struct fib6_info *f6i, static bool ip6_redirect_nh_match(const struct fib6_result *res,
struct fib6_nh *nh,
struct flowi6 *fl6, struct flowi6 *fl6,
const struct in6_addr *gw, const struct in6_addr *gw,
struct rt6_info **ret) struct rt6_info **ret)
{ {
const struct fib6_nh *nh = res->nh;
if (nh->fib_nh_flags & RTNH_F_DEAD || !nh->fib_nh_gw_family || if (nh->fib_nh_flags & RTNH_F_DEAD || !nh->fib_nh_gw_family ||
fl6->flowi6_oif != nh->fib_nh_dev->ifindex) fl6->flowi6_oif != nh->fib_nh_dev->ifindex)
return false; return false;
...@@ -2429,7 +2468,7 @@ static bool ip6_redirect_nh_match(struct fib6_info *f6i, ...@@ -2429,7 +2468,7 @@ static bool ip6_redirect_nh_match(struct fib6_info *f6i,
if (!ipv6_addr_equal(gw, &nh->fib_nh_gw6)) { if (!ipv6_addr_equal(gw, &nh->fib_nh_gw6)) {
struct rt6_info *rt_cache; struct rt6_info *rt_cache;
rt_cache = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr); rt_cache = rt6_find_cached_rt(res, &fl6->daddr, &fl6->saddr);
if (rt_cache && if (rt_cache &&
ipv6_addr_equal(gw, &rt_cache->rt6i_gateway)) { ipv6_addr_equal(gw, &rt_cache->rt6i_gateway)) {
*ret = rt_cache; *ret = rt_cache;
...@@ -2454,6 +2493,7 @@ static struct rt6_info *__ip6_route_redirect(struct net *net, ...@@ -2454,6 +2493,7 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
{ {
struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6; struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6;
struct rt6_info *ret = NULL; struct rt6_info *ret = NULL;
struct fib6_result res = {};
struct fib6_info *rt; struct fib6_info *rt;
struct fib6_node *fn; struct fib6_node *fn;
...@@ -2471,12 +2511,14 @@ static struct rt6_info *__ip6_route_redirect(struct net *net, ...@@ -2471,12 +2511,14 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr); fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
restart: restart:
for_each_fib6_node_rt_rcu(fn) { for_each_fib6_node_rt_rcu(fn) {
res.f6i = rt;
res.nh = &rt->fib6_nh;
if (fib6_check_expired(rt)) if (fib6_check_expired(rt))
continue; continue;
if (rt->fib6_flags & RTF_REJECT) if (rt->fib6_flags & RTF_REJECT)
break; break;
if (ip6_redirect_nh_match(rt, &rt->fib6_nh, fl6, if (ip6_redirect_nh_match(&res, fl6, &rdfl->gateway, &ret))
&rdfl->gateway, &ret))
goto out; goto out;
} }
...@@ -2493,15 +2535,20 @@ static struct rt6_info *__ip6_route_redirect(struct net *net, ...@@ -2493,15 +2535,20 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
goto restart; goto restart;
} }
res.f6i = rt;
res.nh = &rt->fib6_nh;
out: out:
if (ret) if (ret) {
ip6_hold_safe(net, &ret); ip6_hold_safe(net, &ret);
else } else {
ret = ip6_create_rt_rcu(rt); res.fib6_flags = res.f6i->fib6_flags;
res.fib6_type = res.f6i->fib6_type;
ret = ip6_create_rt_rcu(&res);
}
rcu_read_unlock(); rcu_read_unlock();
trace_fib6_table_lookup(net, rt, table, fl6); trace_fib6_table_lookup(net, &res, table, fl6);
return ret; return ret;
}; };
...@@ -2619,12 +2666,15 @@ static unsigned int ip6_mtu(const struct dst_entry *dst) ...@@ -2619,12 +2666,15 @@ static unsigned int ip6_mtu(const struct dst_entry *dst)
* based on ip6_dst_mtu_forward and exception logic of * based on ip6_dst_mtu_forward and exception logic of
* rt6_find_cached_rt; called with rcu_read_lock * rt6_find_cached_rt; called with rcu_read_lock
*/ */
u32 ip6_mtu_from_fib6(struct fib6_info *f6i, struct in6_addr *daddr, u32 ip6_mtu_from_fib6(const struct fib6_result *res,
struct in6_addr *saddr) const struct in6_addr *daddr,
const struct in6_addr *saddr)
{ {
struct rt6_exception_bucket *bucket; struct rt6_exception_bucket *bucket;
const struct fib6_nh *nh = res->nh;
struct fib6_info *f6i = res->f6i;
const struct in6_addr *src_key;
struct rt6_exception *rt6_ex; struct rt6_exception *rt6_ex;
struct in6_addr *src_key;
struct inet6_dev *idev; struct inet6_dev *idev;
u32 mtu = 0; u32 mtu = 0;
...@@ -2646,7 +2696,7 @@ u32 ip6_mtu_from_fib6(struct fib6_info *f6i, struct in6_addr *daddr, ...@@ -2646,7 +2696,7 @@ u32 ip6_mtu_from_fib6(struct fib6_info *f6i, struct in6_addr *daddr,
mtu = dst_metric_raw(&rt6_ex->rt6i->dst, RTAX_MTU); mtu = dst_metric_raw(&rt6_ex->rt6i->dst, RTAX_MTU);
if (likely(!mtu)) { if (likely(!mtu)) {
struct net_device *dev = fib6_info_nh_dev(f6i); struct net_device *dev = nh->fib_nh_dev;
mtu = IPV6_MIN_MTU; mtu = IPV6_MIN_MTU;
idev = __in6_dev_get(dev); idev = __in6_dev_get(dev);
...@@ -2656,7 +2706,7 @@ u32 ip6_mtu_from_fib6(struct fib6_info *f6i, struct in6_addr *daddr, ...@@ -2656,7 +2706,7 @@ u32 ip6_mtu_from_fib6(struct fib6_info *f6i, struct in6_addr *daddr,
mtu = min_t(unsigned int, mtu, IP6_MAX_MTU); mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);
out: out:
return mtu - lwtunnel_headroom(fib6_info_nh_lwt(f6i), mtu); return mtu - lwtunnel_headroom(nh->fib_nh_lws, mtu);
} }
struct dst_entry *icmp6_dst_alloc(struct net_device *dev, struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
...@@ -3308,9 +3358,13 @@ static int ip6_route_del(struct fib6_config *cfg, ...@@ -3308,9 +3358,13 @@ static int ip6_route_del(struct fib6_config *cfg,
struct fib6_nh *nh; struct fib6_nh *nh;
if (cfg->fc_flags & RTF_CACHE) { if (cfg->fc_flags & RTF_CACHE) {
struct fib6_result res = {
.f6i = rt,
};
int rc; int rc;
rt_cache = rt6_find_cached_rt(rt, &cfg->fc_dst, rt_cache = rt6_find_cached_rt(&res,
&cfg->fc_dst,
&cfg->fc_src); &cfg->fc_src);
if (rt_cache) { if (rt_cache) {
rc = ip6_del_cached_rt(rt_cache, cfg); rc = ip6_del_cached_rt(rt_cache, cfg);
...@@ -3354,10 +3408,10 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -3354,10 +3408,10 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
{ {
struct netevent_redirect netevent; struct netevent_redirect netevent;
struct rt6_info *rt, *nrt = NULL; struct rt6_info *rt, *nrt = NULL;
struct fib6_result res = {};
struct ndisc_options ndopts; struct ndisc_options ndopts;
struct inet6_dev *in6_dev; struct inet6_dev *in6_dev;
struct neighbour *neigh; struct neighbour *neigh;
struct fib6_info *from;
struct rd_msg *msg; struct rd_msg *msg;
int optlen, on_link; int optlen, on_link;
u8 *lladdr; u8 *lladdr;
...@@ -3440,14 +3494,17 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -3440,14 +3494,17 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
NDISC_REDIRECT, &ndopts); NDISC_REDIRECT, &ndopts);
rcu_read_lock(); rcu_read_lock();
from = rcu_dereference(rt->from); res.f6i = rcu_dereference(rt->from);
/* This fib6_info_hold() is safe here because we hold reference to rt /* This fib6_info_hold() is safe here because we hold reference to rt
* and rt already holds reference to fib6_info. * and rt already holds reference to fib6_info.
*/ */
fib6_info_hold(from); fib6_info_hold(res.f6i);
rcu_read_unlock(); rcu_read_unlock();
nrt = ip6_rt_cache_alloc(from, &msg->dest, NULL); res.nh = &res.f6i->fib6_nh;
res.fib6_flags = res.f6i->fib6_flags;
res.fib6_type = res.f6i->fib6_type;
nrt = ip6_rt_cache_alloc(&res, &msg->dest, NULL);
if (!nrt) if (!nrt)
goto out; goto out;
...@@ -3461,7 +3518,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -3461,7 +3518,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
* a cached route because rt6_insert_exception() will * a cached route because rt6_insert_exception() will
* takes care of it * takes care of it
*/ */
if (rt6_insert_exception(nrt, from)) { if (rt6_insert_exception(nrt, &res)) {
dst_release_immediate(&nrt->dst); dst_release_immediate(&nrt->dst);
goto out; goto out;
} }
...@@ -3473,7 +3530,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -3473,7 +3530,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
call_netevent_notifiers(NETEVENT_REDIRECT, &netevent); call_netevent_notifiers(NETEVENT_REDIRECT, &netevent);
out: out:
fib6_info_release(from); fib6_info_release(res.f6i);
neigh_release(neigh); neigh_release(neigh);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment