Commit afc154e9 authored by Hannes Frederic Sowa's avatar Hannes Frederic Sowa Committed by David S. Miller

ipv6: fix route selection if kernel is not compiled with CONFIG_IPV6_ROUTER_PREF

This is a follow-up patch to 3630d400
("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: default avatarPierre Emeriaud <petrus.lt@gmail.com>
Cc: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 1b4fc0e2
...@@ -65,6 +65,12 @@ ...@@ -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) ...@@ -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, ...@@ -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 (strict & RT6_LOOKUP_F_REACHABLE)
rt6_probe(rt);
if (m > *mpri) { if (m > *mpri) {
if (strict & RT6_LOOKUP_F_REACHABLE) *do_rr = match_do_rr;
rt6_probe(match);
*mpri = m; *mpri = m;
match = rt; match = rt;
} else if (strict & RT6_LOOKUP_F_REACHABLE) {
rt6_probe(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, ...@@ -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) ...@@ -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 */
......
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