diff options
author | Hannes Frederic Sowa <hannes@stressinduktion.org> | 2013-07-11 06:43:42 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-07-28 19:30:00 -0400 |
commit | b4f1489ed58cb6337c663d6eabd8be86c84bf9f1 (patch) | |
tree | 9ade3659787639a207f61cf7bc87639dbff6441b /net | |
parent | 61b6f1280da836ca67e6e4cfaefd65e989f6bfbc (diff) |
ipv6: fix route selection if kernel is not compiled with CONFIG_IPV6_ROUTER_PREF
[ Upstream commit afc154e978de1eb11c555bc8bcec1552f75ebc43 ]
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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'net')
-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 262d6d8c7e89..bacce6c08644 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); |
@@ -527,28 +533,29 @@ static inline int rt6_check_dev(struct rt6_info *rt, int oif) | |||
527 | return 0; | 533 | return 0; |
528 | } | 534 | } |
529 | 535 | ||
530 | static inline bool rt6_check_neigh(struct rt6_info *rt) | 536 | static inline enum rt6_nud_state rt6_check_neigh(struct rt6_info *rt) |
531 | { | 537 | { |
532 | struct neighbour *neigh; | 538 | struct neighbour *neigh; |
533 | bool ret = false; | 539 | enum rt6_nud_state ret = RT6_NUD_FAIL_HARD; |
534 | 540 | ||
535 | if (rt->rt6i_flags & RTF_NONEXTHOP || | 541 | if (rt->rt6i_flags & RTF_NONEXTHOP || |
536 | !(rt->rt6i_flags & RTF_GATEWAY)) | 542 | !(rt->rt6i_flags & RTF_GATEWAY)) |
537 | return true; | 543 | return RT6_NUD_SUCCEED; |
538 | 544 | ||
539 | rcu_read_lock_bh(); | 545 | rcu_read_lock_bh(); |
540 | neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway); | 546 | neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway); |
541 | if (neigh) { | 547 | if (neigh) { |
542 | read_lock(&neigh->lock); | 548 | read_lock(&neigh->lock); |
543 | if (neigh->nud_state & NUD_VALID) | 549 | if (neigh->nud_state & NUD_VALID) |
544 | ret = true; | 550 | ret = RT6_NUD_SUCCEED; |
545 | #ifdef CONFIG_IPV6_ROUTER_PREF | 551 | #ifdef CONFIG_IPV6_ROUTER_PREF |
546 | else if (!(neigh->nud_state & NUD_FAILED)) | 552 | else if (!(neigh->nud_state & NUD_FAILED)) |
547 | ret = true; | 553 | ret = RT6_NUD_SUCCEED; |
548 | #endif | 554 | #endif |
549 | read_unlock(&neigh->lock); | 555 | read_unlock(&neigh->lock); |
550 | } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) { | 556 | } else { |
551 | ret = true; | 557 | ret = IS_ENABLED(CONFIG_IPV6_ROUTER_PREF) ? |
558 | RT6_NUD_SUCCEED : RT6_NUD_FAIL_SOFT; | ||
552 | } | 559 | } |
553 | rcu_read_unlock_bh(); | 560 | rcu_read_unlock_bh(); |
554 | 561 | ||
@@ -562,43 +569,52 @@ static int rt6_score_route(struct rt6_info *rt, int oif, | |||
562 | 569 | ||
563 | m = rt6_check_dev(rt, oif); | 570 | m = rt6_check_dev(rt, oif); |
564 | if (!m && (strict & RT6_LOOKUP_F_IFACE)) | 571 | if (!m && (strict & RT6_LOOKUP_F_IFACE)) |
565 | return -1; | 572 | return RT6_NUD_FAIL_HARD; |
566 | #ifdef CONFIG_IPV6_ROUTER_PREF | 573 | #ifdef CONFIG_IPV6_ROUTER_PREF |
567 | m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2; | 574 | m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2; |
568 | #endif | 575 | #endif |
569 | if (!rt6_check_neigh(rt) && (strict & RT6_LOOKUP_F_REACHABLE)) | 576 | if (strict & RT6_LOOKUP_F_REACHABLE) { |
570 | return -1; | 577 | int n = rt6_check_neigh(rt); |
578 | if (n < 0) | ||
579 | return n; | ||
580 | } | ||
571 | return m; | 581 | return m; |
572 | } | 582 | } |
573 | 583 | ||
574 | static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict, | 584 | static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict, |
575 | int *mpri, struct rt6_info *match) | 585 | int *mpri, struct rt6_info *match, |
586 | bool *do_rr) | ||
576 | { | 587 | { |
577 | int m; | 588 | int m; |
589 | bool match_do_rr = false; | ||
578 | 590 | ||
579 | if (rt6_check_expired(rt)) | 591 | if (rt6_check_expired(rt)) |
580 | goto out; | 592 | goto out; |
581 | 593 | ||
582 | m = rt6_score_route(rt, oif, strict); | 594 | m = rt6_score_route(rt, oif, strict); |
583 | if (m < 0) | 595 | if (m == RT6_NUD_FAIL_SOFT && !IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) { |
596 | match_do_rr = true; | ||
597 | m = 0; /* lowest valid score */ | ||
598 | } else if (m < 0) { | ||
584 | goto out; | 599 | goto out; |
600 | } | ||
601 | |||
602 | if (strict & RT6_LOOKUP_F_REACHABLE) | ||
603 | rt6_probe(rt); | ||
585 | 604 | ||
586 | if (m > *mpri) { | 605 | if (m > *mpri) { |
587 | if (strict & RT6_LOOKUP_F_REACHABLE) | 606 | *do_rr = match_do_rr; |
588 | rt6_probe(match); | ||
589 | *mpri = m; | 607 | *mpri = m; |
590 | match = rt; | 608 | match = rt; |
591 | } else if (strict & RT6_LOOKUP_F_REACHABLE) { | ||
592 | rt6_probe(rt); | ||
593 | } | 609 | } |
594 | |||
595 | out: | 610 | out: |
596 | return match; | 611 | return match; |
597 | } | 612 | } |
598 | 613 | ||
599 | static struct rt6_info *find_rr_leaf(struct fib6_node *fn, | 614 | static struct rt6_info *find_rr_leaf(struct fib6_node *fn, |
600 | struct rt6_info *rr_head, | 615 | struct rt6_info *rr_head, |
601 | u32 metric, int oif, int strict) | 616 | u32 metric, int oif, int strict, |
617 | bool *do_rr) | ||
602 | { | 618 | { |
603 | struct rt6_info *rt, *match; | 619 | struct rt6_info *rt, *match; |
604 | int mpri = -1; | 620 | int mpri = -1; |
@@ -606,10 +622,10 @@ static struct rt6_info *find_rr_leaf(struct fib6_node *fn, | |||
606 | match = NULL; | 622 | match = NULL; |
607 | for (rt = rr_head; rt && rt->rt6i_metric == metric; | 623 | for (rt = rr_head; rt && rt->rt6i_metric == metric; |
608 | rt = rt->dst.rt6_next) | 624 | rt = rt->dst.rt6_next) |
609 | match = find_match(rt, oif, strict, &mpri, match); | 625 | match = find_match(rt, oif, strict, &mpri, match, do_rr); |
610 | for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric; | 626 | for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric; |
611 | rt = rt->dst.rt6_next) | 627 | rt = rt->dst.rt6_next) |
612 | match = find_match(rt, oif, strict, &mpri, match); | 628 | match = find_match(rt, oif, strict, &mpri, match, do_rr); |
613 | 629 | ||
614 | return match; | 630 | return match; |
615 | } | 631 | } |
@@ -618,15 +634,16 @@ static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict) | |||
618 | { | 634 | { |
619 | struct rt6_info *match, *rt0; | 635 | struct rt6_info *match, *rt0; |
620 | struct net *net; | 636 | struct net *net; |
637 | bool do_rr = false; | ||
621 | 638 | ||
622 | rt0 = fn->rr_ptr; | 639 | rt0 = fn->rr_ptr; |
623 | if (!rt0) | 640 | if (!rt0) |
624 | fn->rr_ptr = rt0 = fn->leaf; | 641 | fn->rr_ptr = rt0 = fn->leaf; |
625 | 642 | ||
626 | match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict); | 643 | match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict, |
644 | &do_rr); | ||
627 | 645 | ||
628 | if (!match && | 646 | if (do_rr) { |
629 | (strict & RT6_LOOKUP_F_REACHABLE)) { | ||
630 | struct rt6_info *next = rt0->dst.rt6_next; | 647 | struct rt6_info *next = rt0->dst.rt6_next; |
631 | 648 | ||
632 | /* no entries matched; do round-robin */ | 649 | /* no entries matched; do round-robin */ |