ipv6: fix route selection if kernel is not compiled with CONFIG_IPV6_ROUTER_PREF
This is a follow-up patch to 3630d40067
("ipv6: rt6_check_neigh should successfully verify neigh if no NUD
information are available").
Since the removal of rt->n in rt6_info we can end up with a dst ==
NULL in rt6_check_neigh. In case the kernel is not compiled with
CONFIG_IPV6_ROUTER_PREF we should also select a route with unkown
NUD state but we must not avoid doing round robin selection on routes
with the same target. So introduce and pass down a boolean ``do_rr'' to
indicate when we should update rt->rr_ptr. As soon as no route is valid
we do backtracking and do a lookup on a higher level in the fib trie.
v2:
a) Improved rt6_check_neigh logic (no need to create neighbour there)
and documented return values.
v3:
a) Introduce enum rt6_nud_state to get rid of the magic numbers
(thanks to David Miller).
b) Update and shorten commit message a bit to actualy reflect
the source.
Reported-by: Pierre Emeriaud <petrus.lt@gmail.com>
Cc: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
committed by
David S. Miller
parent
1b4fc0e249
commit
afc154e978
@@ -65,6 +65,12 @@
|
|||||||
#include <linux/sysctl.h>
|
#include <linux/sysctl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
enum rt6_nud_state {
|
||||||
|
RT6_NUD_FAIL_HARD = -2,
|
||||||
|
RT6_NUD_FAIL_SOFT = -1,
|
||||||
|
RT6_NUD_SUCCEED = 1
|
||||||
|
};
|
||||||
|
|
||||||
static struct rt6_info *ip6_rt_copy(struct rt6_info *ort,
|
static struct rt6_info *ip6_rt_copy(struct rt6_info *ort,
|
||||||
const struct in6_addr *dest);
|
const struct in6_addr *dest);
|
||||||
static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie);
|
static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie);
|
||||||
@@ -531,28 +537,29 @@ static inline int rt6_check_dev(struct rt6_info *rt, int oif)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool rt6_check_neigh(struct rt6_info *rt)
|
static inline enum rt6_nud_state rt6_check_neigh(struct rt6_info *rt)
|
||||||
{
|
{
|
||||||
struct neighbour *neigh;
|
struct neighbour *neigh;
|
||||||
bool ret = false;
|
enum rt6_nud_state ret = RT6_NUD_FAIL_HARD;
|
||||||
|
|
||||||
if (rt->rt6i_flags & RTF_NONEXTHOP ||
|
if (rt->rt6i_flags & RTF_NONEXTHOP ||
|
||||||
!(rt->rt6i_flags & RTF_GATEWAY))
|
!(rt->rt6i_flags & RTF_GATEWAY))
|
||||||
return true;
|
return RT6_NUD_SUCCEED;
|
||||||
|
|
||||||
rcu_read_lock_bh();
|
rcu_read_lock_bh();
|
||||||
neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway);
|
neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway);
|
||||||
if (neigh) {
|
if (neigh) {
|
||||||
read_lock(&neigh->lock);
|
read_lock(&neigh->lock);
|
||||||
if (neigh->nud_state & NUD_VALID)
|
if (neigh->nud_state & NUD_VALID)
|
||||||
ret = true;
|
ret = RT6_NUD_SUCCEED;
|
||||||
#ifdef CONFIG_IPV6_ROUTER_PREF
|
#ifdef CONFIG_IPV6_ROUTER_PREF
|
||||||
else if (!(neigh->nud_state & NUD_FAILED))
|
else if (!(neigh->nud_state & NUD_FAILED))
|
||||||
ret = true;
|
ret = RT6_NUD_SUCCEED;
|
||||||
#endif
|
#endif
|
||||||
read_unlock(&neigh->lock);
|
read_unlock(&neigh->lock);
|
||||||
} else if (IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) {
|
} else {
|
||||||
ret = true;
|
ret = IS_ENABLED(CONFIG_IPV6_ROUTER_PREF) ?
|
||||||
|
RT6_NUD_SUCCEED : RT6_NUD_FAIL_SOFT;
|
||||||
}
|
}
|
||||||
rcu_read_unlock_bh();
|
rcu_read_unlock_bh();
|
||||||
|
|
||||||
@@ -566,43 +573,52 @@ static int rt6_score_route(struct rt6_info *rt, int oif,
|
|||||||
|
|
||||||
m = rt6_check_dev(rt, oif);
|
m = rt6_check_dev(rt, oif);
|
||||||
if (!m && (strict & RT6_LOOKUP_F_IFACE))
|
if (!m && (strict & RT6_LOOKUP_F_IFACE))
|
||||||
return -1;
|
return RT6_NUD_FAIL_HARD;
|
||||||
#ifdef CONFIG_IPV6_ROUTER_PREF
|
#ifdef CONFIG_IPV6_ROUTER_PREF
|
||||||
m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2;
|
m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2;
|
||||||
#endif
|
#endif
|
||||||
if (!rt6_check_neigh(rt) && (strict & RT6_LOOKUP_F_REACHABLE))
|
if (strict & RT6_LOOKUP_F_REACHABLE) {
|
||||||
return -1;
|
int n = rt6_check_neigh(rt);
|
||||||
|
if (n < 0)
|
||||||
|
return n;
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict,
|
static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict,
|
||||||
int *mpri, struct rt6_info *match)
|
int *mpri, struct rt6_info *match,
|
||||||
|
bool *do_rr)
|
||||||
{
|
{
|
||||||
int m;
|
int m;
|
||||||
|
bool match_do_rr = false;
|
||||||
|
|
||||||
if (rt6_check_expired(rt))
|
if (rt6_check_expired(rt))
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
m = rt6_score_route(rt, oif, strict);
|
m = rt6_score_route(rt, oif, strict);
|
||||||
if (m < 0)
|
if (m == RT6_NUD_FAIL_SOFT && !IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) {
|
||||||
|
match_do_rr = true;
|
||||||
|
m = 0; /* lowest valid score */
|
||||||
|
} else if (m < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
if (m > *mpri) {
|
|
||||||
if (strict & RT6_LOOKUP_F_REACHABLE)
|
|
||||||
rt6_probe(match);
|
|
||||||
*mpri = m;
|
|
||||||
match = rt;
|
|
||||||
} else if (strict & RT6_LOOKUP_F_REACHABLE) {
|
|
||||||
rt6_probe(rt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strict & RT6_LOOKUP_F_REACHABLE)
|
||||||
|
rt6_probe(rt);
|
||||||
|
|
||||||
|
if (m > *mpri) {
|
||||||
|
*do_rr = match_do_rr;
|
||||||
|
*mpri = m;
|
||||||
|
match = rt;
|
||||||
|
}
|
||||||
out:
|
out:
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct rt6_info *find_rr_leaf(struct fib6_node *fn,
|
static struct rt6_info *find_rr_leaf(struct fib6_node *fn,
|
||||||
struct rt6_info *rr_head,
|
struct rt6_info *rr_head,
|
||||||
u32 metric, int oif, int strict)
|
u32 metric, int oif, int strict,
|
||||||
|
bool *do_rr)
|
||||||
{
|
{
|
||||||
struct rt6_info *rt, *match;
|
struct rt6_info *rt, *match;
|
||||||
int mpri = -1;
|
int mpri = -1;
|
||||||
@@ -610,10 +626,10 @@ static struct rt6_info *find_rr_leaf(struct fib6_node *fn,
|
|||||||
match = NULL;
|
match = NULL;
|
||||||
for (rt = rr_head; rt && rt->rt6i_metric == metric;
|
for (rt = rr_head; rt && rt->rt6i_metric == metric;
|
||||||
rt = rt->dst.rt6_next)
|
rt = rt->dst.rt6_next)
|
||||||
match = find_match(rt, oif, strict, &mpri, match);
|
match = find_match(rt, oif, strict, &mpri, match, do_rr);
|
||||||
for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric;
|
for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric;
|
||||||
rt = rt->dst.rt6_next)
|
rt = rt->dst.rt6_next)
|
||||||
match = find_match(rt, oif, strict, &mpri, match);
|
match = find_match(rt, oif, strict, &mpri, match, do_rr);
|
||||||
|
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
@@ -622,15 +638,16 @@ static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict)
|
|||||||
{
|
{
|
||||||
struct rt6_info *match, *rt0;
|
struct rt6_info *match, *rt0;
|
||||||
struct net *net;
|
struct net *net;
|
||||||
|
bool do_rr = false;
|
||||||
|
|
||||||
rt0 = fn->rr_ptr;
|
rt0 = fn->rr_ptr;
|
||||||
if (!rt0)
|
if (!rt0)
|
||||||
fn->rr_ptr = rt0 = fn->leaf;
|
fn->rr_ptr = rt0 = fn->leaf;
|
||||||
|
|
||||||
match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict);
|
match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict,
|
||||||
|
&do_rr);
|
||||||
|
|
||||||
if (!match &&
|
if (do_rr) {
|
||||||
(strict & RT6_LOOKUP_F_REACHABLE)) {
|
|
||||||
struct rt6_info *next = rt0->dst.rt6_next;
|
struct rt6_info *next = rt0->dst.rt6_next;
|
||||||
|
|
||||||
/* no entries matched; do round-robin */
|
/* no entries matched; do round-robin */
|
||||||
|
Reference in New Issue
Block a user