Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Hannes Frederic Sowa and committed by
David S. Miller
afc154e9 1b4fc0e2

+43 -26
+43 -26
net/ipv6/route.c
··· 65 65 #include <linux/sysctl.h> 66 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 74 static struct rt6_info *ip6_rt_copy(struct rt6_info *ort, 69 75 const struct in6_addr *dest); 70 76 static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie); ··· 537 531 return 0; 538 532 } 539 533 540 - static inline bool rt6_check_neigh(struct rt6_info *rt) 534 + static inline enum rt6_nud_state rt6_check_neigh(struct rt6_info *rt) 541 535 { 542 536 struct neighbour *neigh; 543 - bool ret = false; 537 + enum rt6_nud_state ret = RT6_NUD_FAIL_HARD; 544 538 545 539 if (rt->rt6i_flags & RTF_NONEXTHOP || 546 540 !(rt->rt6i_flags & RTF_GATEWAY)) 547 - return true; 541 + return RT6_NUD_SUCCEED; 548 542 549 543 rcu_read_lock_bh(); 550 544 neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway); 551 545 if (neigh) { 552 546 read_lock(&neigh->lock); 553 547 if (neigh->nud_state & NUD_VALID) 554 - ret = true; 548 + ret = RT6_NUD_SUCCEED; 555 549 #ifdef CONFIG_IPV6_ROUTER_PREF 556 550 else if (!(neigh->nud_state & NUD_FAILED)) 557 - ret = true; 551 + ret = RT6_NUD_SUCCEED; 558 552 #endif 559 553 read_unlock(&neigh->lock); 560 - } else if (IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) { 561 - ret = true; 554 + } else { 555 + ret = IS_ENABLED(CONFIG_IPV6_ROUTER_PREF) ? 556 + RT6_NUD_SUCCEED : RT6_NUD_FAIL_SOFT; 562 557 } 563 558 rcu_read_unlock_bh(); 564 559 ··· 573 566 574 567 m = rt6_check_dev(rt, oif); 575 568 if (!m && (strict & RT6_LOOKUP_F_IFACE)) 576 - return -1; 569 + return RT6_NUD_FAIL_HARD; 577 570 #ifdef CONFIG_IPV6_ROUTER_PREF 578 571 m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2; 579 572 #endif 580 - if (!rt6_check_neigh(rt) && (strict & RT6_LOOKUP_F_REACHABLE)) 581 - return -1; 573 + if (strict & RT6_LOOKUP_F_REACHABLE) { 574 + int n = rt6_check_neigh(rt); 575 + if (n < 0) 576 + return n; 577 + } 582 578 return m; 583 579 } 584 580 585 581 static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict, 586 - int *mpri, struct rt6_info *match) 582 + int *mpri, struct rt6_info *match, 583 + bool *do_rr) 587 584 { 588 585 int m; 586 + bool match_do_rr = false; 589 587 590 588 if (rt6_check_expired(rt)) 591 589 goto out; 592 590 593 591 m = rt6_score_route(rt, oif, strict); 594 - if (m < 0) 592 + if (m == RT6_NUD_FAIL_SOFT && !IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) { 593 + match_do_rr = true; 594 + m = 0; /* lowest valid score */ 595 + } else if (m < 0) { 595 596 goto out; 596 - 597 - if (m > *mpri) { 598 - if (strict & RT6_LOOKUP_F_REACHABLE) 599 - rt6_probe(match); 600 - *mpri = m; 601 - match = rt; 602 - } else if (strict & RT6_LOOKUP_F_REACHABLE) { 603 - rt6_probe(rt); 604 597 } 605 598 599 + if (strict & RT6_LOOKUP_F_REACHABLE) 600 + rt6_probe(rt); 601 + 602 + if (m > *mpri) { 603 + *do_rr = match_do_rr; 604 + *mpri = m; 605 + match = rt; 606 + } 606 607 out: 607 608 return match; 608 609 } 609 610 610 611 static struct rt6_info *find_rr_leaf(struct fib6_node *fn, 611 612 struct rt6_info *rr_head, 612 - u32 metric, int oif, int strict) 613 + u32 metric, int oif, int strict, 614 + bool *do_rr) 613 615 { 614 616 struct rt6_info *rt, *match; 615 617 int mpri = -1; ··· 626 610 match = NULL; 627 611 for (rt = rr_head; rt && rt->rt6i_metric == metric; 628 612 rt = rt->dst.rt6_next) 629 - match = find_match(rt, oif, strict, &mpri, match); 613 + match = find_match(rt, oif, strict, &mpri, match, do_rr); 630 614 for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric; 631 615 rt = rt->dst.rt6_next) 632 - match = find_match(rt, oif, strict, &mpri, match); 616 + match = find_match(rt, oif, strict, &mpri, match, do_rr); 633 617 634 618 return match; 635 619 } ··· 638 622 { 639 623 struct rt6_info *match, *rt0; 640 624 struct net *net; 625 + bool do_rr = false; 641 626 642 627 rt0 = fn->rr_ptr; 643 628 if (!rt0) 644 629 fn->rr_ptr = rt0 = fn->leaf; 645 630 646 - match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict); 631 + match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict, 632 + &do_rr); 647 633 648 - if (!match && 649 - (strict & RT6_LOOKUP_F_REACHABLE)) { 634 + if (do_rr) { 650 635 struct rt6_info *next = rt0->dst.rt6_next; 651 636 652 637 /* no entries matched; do round-robin */