diff options
author | Hannes Frederic Sowa <hannes@stressinduktion.org> | 2013-07-11 06:43:42 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-07-11 14:51:10 -0400 |
commit | afc154e978de1eb11c555bc8bcec1552f75ebc43 (patch) | |
tree | 9398696864b0d770b0c108eda89dc4cf1bbf4482 /net/ipv6 | |
parent | 1b4fc0e249d61916b8a525b0e7b3c028232457c9 (diff) |
ipv6: fix route selection if kernel is not compiled with CONFIG_IPV6_ROUTER_PREF
This is a follow-up patch to 3630d40067a21d4dfbadc6002bb469ce26ac5d52
("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>
Diffstat (limited to 'net/ipv6')
-rw-r--r-- | net/ipv6/route.c | 63 |
1 files changed, 40 insertions, 23 deletions
diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 5b127e09c224..a8c891aa2464 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c | |||
@@ -65,6 +65,12 @@ | |||
65 | #include <linux/sysctl.h> | 65 | #include <linux/sysctl.h> |
66 | #endif | 66 | #endif |
67 | 67 | ||
68 | enum rt6_nud_state { | ||
69 | RT6_NUD_FAIL_HARD = -2, | ||
70 | RT6_NUD_FAIL_SOFT = -1, | ||
71 | RT6_NUD_SUCCEED = 1 | ||
72 | }; | ||
73 | |||
68 | static struct rt6_info *ip6_rt_copy(struct rt6_info *ort, | 74 | static struct rt6_info *ip6_rt_copy(struct rt6_info *ort, |
69 | const struct in6_addr *dest); | 75 | const struct in6_addr *dest); |
70 | static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie); | 76 | 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 | return 0; | 537 | return 0; |
532 | } | 538 | } |
533 | 539 | ||
534 | static inline bool rt6_check_neigh(struct rt6_info *rt) | 540 | static inline enum rt6_nud_state rt6_check_neigh(struct rt6_info *rt) |
535 | { | 541 | { |
536 | struct neighbour *neigh; | 542 | struct neighbour *neigh; |
537 | bool ret = false; | 543 | enum rt6_nud_state ret = RT6_NUD_FAIL_HARD; |
538 | 544 | ||
539 | if (rt->rt6i_flags & RTF_NONEXTHOP || | 545 | if (rt->rt6i_flags & RTF_NONEXTHOP || |
540 | !(rt->rt6i_flags & RTF_GATEWAY)) | 546 | !(rt->rt6i_flags & RTF_GATEWAY)) |
541 | return true; | 547 | return RT6_NUD_SUCCEED; |
542 | 548 | ||
543 | rcu_read_lock_bh(); | 549 | rcu_read_lock_bh(); |
544 | neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway); | 550 | neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway); |
545 | if (neigh) { | 551 | if (neigh) { |
546 | read_lock(&neigh->lock); | 552 | read_lock(&neigh->lock); |
547 | if (neigh->nud_state & NUD_VALID) | 553 | if (neigh->nud_state & NUD_VALID) |
548 | ret = true; | 554 | ret = RT6_NUD_SUCCEED; |
549 | #ifdef CONFIG_IPV6_ROUTER_PREF | 555 | #ifdef CONFIG_IPV6_ROUTER_PREF |
550 | else if (!(neigh->nud_state & NUD_FAILED)) | 556 | else if (!(neigh->nud_state & NUD_FAILED)) |
551 | ret = true; | 557 | ret = RT6_NUD_SUCCEED; |
552 | #endif | 558 | #endif |
553 | read_unlock(&neigh->lock); | 559 | read_unlock(&neigh->lock); |
554 | } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) { | 560 | } else { |
555 | ret = true; | 561 | ret = IS_ENABLED(CONFIG_IPV6_ROUTER_PREF) ? |
562 | RT6_NUD_SUCCEED : RT6_NUD_FAIL_SOFT; | ||
556 | } | 563 | } |
557 | rcu_read_unlock_bh(); | 564 | rcu_read_unlock_bh(); |
558 | 565 | ||
@@ -566,43 +573,52 @@ static int rt6_score_route(struct rt6_info *rt, int oif, | |||
566 | 573 | ||
567 | m = rt6_check_dev(rt, oif); | 574 | m = rt6_check_dev(rt, oif); |
568 | if (!m && (strict & RT6_LOOKUP_F_IFACE)) | 575 | if (!m && (strict & RT6_LOOKUP_F_IFACE)) |
569 | return -1; | 576 | return RT6_NUD_FAIL_HARD; |
570 | #ifdef CONFIG_IPV6_ROUTER_PREF | 577 | #ifdef CONFIG_IPV6_ROUTER_PREF |
571 | m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2; | 578 | m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2; |
572 | #endif | 579 | #endif |
573 | if (!rt6_check_neigh(rt) && (strict & RT6_LOOKUP_F_REACHABLE)) | 580 | if (strict & RT6_LOOKUP_F_REACHABLE) { |
574 | return -1; | 581 | int n = rt6_check_neigh(rt); |
582 | if (n < 0) | ||
583 | return n; | ||
584 | } | ||
575 | return m; | 585 | return m; |
576 | } | 586 | } |
577 | 587 | ||
578 | static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict, | 588 | static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict, |
579 | int *mpri, struct rt6_info *match) | 589 | int *mpri, struct rt6_info *match, |
590 | bool *do_rr) | ||
580 | { | 591 | { |
581 | int m; | 592 | int m; |
593 | bool match_do_rr = false; | ||
582 | 594 | ||
583 | if (rt6_check_expired(rt)) | 595 | if (rt6_check_expired(rt)) |
584 | goto out; | 596 | goto out; |
585 | 597 | ||
586 | m = rt6_score_route(rt, oif, strict); | 598 | m = rt6_score_route(rt, oif, strict); |
587 | if (m < 0) | 599 | if (m == RT6_NUD_FAIL_SOFT && !IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) { |
600 | match_do_rr = true; | ||
601 | m = 0; /* lowest valid score */ | ||
602 | } else if (m < 0) { | ||
588 | goto out; | 603 | goto out; |
604 | } | ||
605 | |||
606 | if (strict & RT6_LOOKUP_F_REACHABLE) | ||
607 | rt6_probe(rt); | ||
589 | 608 | ||
590 | if (m > *mpri) { | 609 | if (m > *mpri) { |
591 | if (strict & RT6_LOOKUP_F_REACHABLE) | 610 | *do_rr = match_do_rr; |
592 | rt6_probe(match); | ||
593 | *mpri = m; | 611 | *mpri = m; |
594 | match = rt; | 612 | match = rt; |
595 | } else if (strict & RT6_LOOKUP_F_REACHABLE) { | ||
596 | rt6_probe(rt); | ||
597 | } | 613 | } |
598 | |||
599 | out: | 614 | out: |
600 | return match; | 615 | return match; |
601 | } | 616 | } |
602 | 617 | ||
603 | static struct rt6_info *find_rr_leaf(struct fib6_node *fn, | 618 | static struct rt6_info *find_rr_leaf(struct fib6_node *fn, |
604 | struct rt6_info *rr_head, | 619 | struct rt6_info *rr_head, |
605 | u32 metric, int oif, int strict) | 620 | u32 metric, int oif, int strict, |
621 | bool *do_rr) | ||
606 | { | 622 | { |
607 | struct rt6_info *rt, *match; | 623 | struct rt6_info *rt, *match; |
608 | int mpri = -1; | 624 | int mpri = -1; |
@@ -610,10 +626,10 @@ static struct rt6_info *find_rr_leaf(struct fib6_node *fn, | |||
610 | match = NULL; | 626 | match = NULL; |
611 | for (rt = rr_head; rt && rt->rt6i_metric == metric; | 627 | for (rt = rr_head; rt && rt->rt6i_metric == metric; |
612 | rt = rt->dst.rt6_next) | 628 | rt = rt->dst.rt6_next) |
613 | match = find_match(rt, oif, strict, &mpri, match); | 629 | match = find_match(rt, oif, strict, &mpri, match, do_rr); |
614 | for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric; | 630 | for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric; |
615 | rt = rt->dst.rt6_next) | 631 | rt = rt->dst.rt6_next) |
616 | match = find_match(rt, oif, strict, &mpri, match); | 632 | match = find_match(rt, oif, strict, &mpri, match, do_rr); |
617 | 633 | ||
618 | return match; | 634 | return match; |
619 | } | 635 | } |
@@ -622,15 +638,16 @@ static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict) | |||
622 | { | 638 | { |
623 | struct rt6_info *match, *rt0; | 639 | struct rt6_info *match, *rt0; |
624 | struct net *net; | 640 | struct net *net; |
641 | bool do_rr = false; | ||
625 | 642 | ||
626 | rt0 = fn->rr_ptr; | 643 | rt0 = fn->rr_ptr; |
627 | if (!rt0) | 644 | if (!rt0) |
628 | fn->rr_ptr = rt0 = fn->leaf; | 645 | fn->rr_ptr = rt0 = fn->leaf; |
629 | 646 | ||
630 | match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict); | 647 | match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict, |
648 | &do_rr); | ||
631 | 649 | ||
632 | if (!match && | 650 | if (do_rr) { |
633 | (strict & RT6_LOOKUP_F_REACHABLE)) { | ||
634 | struct rt6_info *next = rt0->dst.rt6_next; | 651 | struct rt6_info *next = rt0->dst.rt6_next; |
635 | 652 | ||
636 | /* no entries matched; do round-robin */ | 653 | /* no entries matched; do round-robin */ |