aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorHannes Frederic Sowa <hannes@stressinduktion.org>2013-07-11 06:43:42 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-07-28 19:30:00 -0400
commitb4f1489ed58cb6337c663d6eabd8be86c84bf9f1 (patch)
tree9ade3659787639a207f61cf7bc87639dbff6441b /net
parent61b6f1280da836ca67e6e4cfaefd65e989f6bfbc (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.c63
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
68enum rt6_nud_state {
69 RT6_NUD_FAIL_HARD = -2,
70 RT6_NUD_FAIL_SOFT = -1,
71 RT6_NUD_SUCCEED = 1
72};
73
68static struct rt6_info *ip6_rt_copy(struct rt6_info *ort, 74static struct rt6_info *ip6_rt_copy(struct rt6_info *ort,
69 const struct in6_addr *dest); 75 const struct in6_addr *dest);
70static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie); 76static 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
530static inline bool rt6_check_neigh(struct rt6_info *rt) 536static 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
574static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict, 584static 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
595out: 610out:
596 return match; 611 return match;
597} 612}
598 613
599static struct rt6_info *find_rr_leaf(struct fib6_node *fn, 614static 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 */