From 17a036563d991f6f1cd398551f0806be3e2a0f11 Mon Sep 17 00:00:00 2001 From: Eric van Gyzen Date: Mon, 14 Sep 2015 19:17:25 +0000 Subject: [PATCH] Fix the handling of IPv6 On-Link Redirects. On receipt of a redirect message, install an interface route for the redirected destination. On removal of the corresponding Neighbor Cache entry, remove the interface route. This requires changes in rtredirect_fib() to cope with an AF_LINK address for the gateway and with the absence of RTF_GATEWAY. This fixes the "Redirected On-Link" test cases in the Tahi IPv6 Ready Logo Phase 2 test suite. Unrelated to the above, fix a recursion on the radix node head lock triggered by the Tahi Redirected to Alternate Router test cases. When I first wrote this patch in October 2012, all Section 2 (Neighbor Discovery) test cases passed on 10-CURRENT, 9-STABLE, and 8-STABLE. cem@ recently rebased the 10.x patch onto head and reported that it passes Tahi. (Thanks!) These other test cases also passed in 2012: * the RTF_MODIFIED case, with IPv4 and IPv6 (using a RTF_HOST|RTF_GATEWAY route for the destination) * the redirected-to-self case, with IPv4 and IPv6 * a valid IPv4 redirect All testing in 2012 was done with WITNESS and INVARIANTS. Tested by: EMC / Isilon Storage Division via Conrad Meyer (cem) in 2015, Mark Kelley in 2012, TC Telkamp in 2012 PR: 152791 Reviewed by: melifaro (current rev), bz (earlier rev) Approved by: kib (mentor) MFC after: 1 month Relnotes: yes Sponsored by: Dell Inc. Differential Revision: https://reviews.freebsd.org/D3602 --- sys/net/if_llatbl.h | 1 + sys/net/route.c | 24 +++++++++++++++------- sys/netinet6/icmp6.c | 34 ++++++++++++++++++++----------- sys/netinet6/nd6.c | 45 ++++++++++++++++++++++++++++++++++++++++-- sys/netinet6/nd6_rtr.c | 2 +- 5 files changed, 85 insertions(+), 21 deletions(-) diff --git a/sys/net/if_llatbl.h b/sys/net/if_llatbl.h index 8e8adff1b817..98d28f2a83a8 100644 --- a/sys/net/if_llatbl.h +++ b/sys/net/if_llatbl.h @@ -183,6 +183,7 @@ MALLOC_DECLARE(M_LLTABLE); #define LLE_STATIC 0x0002 /* entry is static */ #define LLE_IFADDR 0x0004 /* entry is interface addr */ #define LLE_VALID 0x0008 /* ll_addr is valid */ +#define LLE_REDIRECT 0x0010 /* installed by redirect; has host rtentry */ #define LLE_PUB 0x0020 /* publish entry ??? */ #define LLE_LINKED 0x0040 /* linked to lookup structure */ /* LLE request flags */ diff --git a/sys/net/route.c b/sys/net/route.c index 1517dca1e22c..4035b03db623 100644 --- a/sys/net/route.c +++ b/sys/net/route.c @@ -584,13 +584,20 @@ rtredirect_fib(struct sockaddr *dst, * we have a routing loop, perhaps as a result of an interface * going down recently. */ - if (!(flags & RTF_DONE) && rt && - (!sa_equal(src, rt->rt_gateway) || rt->rt_ifa != ifa)) - error = EINVAL; - else if (ifa_ifwithaddr_check(gateway)) + if (!(flags & RTF_DONE) && rt) { + if (!sa_equal(src, rt->rt_gateway)) { + error = EINVAL; + goto done; + } + if (rt->rt_ifa != ifa && ifa->ifa_addr->sa_family != AF_LINK) { + error = EINVAL; + goto done; + } + } + if ((flags & RTF_GATEWAY) && ifa_ifwithaddr_check(gateway)) { error = EHOSTUNREACH; - if (error) goto done; + } /* * Create a new entry if we just got back a wildcard entry * or the lookup failed. This is necessary for hosts @@ -613,7 +620,7 @@ rtredirect_fib(struct sockaddr *dst, rt0 = rt; rt = NULL; - flags |= RTF_GATEWAY | RTF_DYNAMIC; + flags |= RTF_DYNAMIC; bzero((caddr_t)&info, sizeof(info)); info.rti_info[RTAX_DST] = dst; info.rti_info[RTAX_GATEWAY] = gateway; @@ -640,6 +647,8 @@ rtredirect_fib(struct sockaddr *dst, * Smash the current notion of the gateway to * this destination. Should check about netmask!!! */ + if ((flags & RTF_GATEWAY) == 0) + rt->rt_flags &= ~RTF_GATEWAY; rt->rt_flags |= RTF_MODIFIED; flags |= RTF_MODIFIED; stat = &V_rtstat.rts_newgateway; @@ -653,7 +662,8 @@ rtredirect_fib(struct sockaddr *dst, gwrt = rtalloc1(gateway, 1, RTF_RNH_LOCKED); RADIX_NODE_HEAD_UNLOCK(rnh); EVENTHANDLER_INVOKE(route_redirect_event, rt, gwrt, dst); - RTFREE_LOCKED(gwrt); + if (gwrt) + RTFREE_LOCKED(gwrt); } } else error = EHOSTUNREACH; diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c index 29dd3edf841e..c918e69627fb 100644 --- a/sys/netinet6/icmp6.c +++ b/sys/netinet6/icmp6.c @@ -2434,27 +2434,39 @@ icmp6_redirect_input(struct mbuf *m, int off) nd6_cache_lladdr(ifp, &redtgt6, lladdr, lladdrlen, ND_REDIRECT, is_onlink ? ND_REDIRECT_ONLINK : ND_REDIRECT_ROUTER); - if (!is_onlink) { /* better router case. perform rtredirect. */ - /* perform rtredirect */ + /* + * Install a gateway route in the better-router case or an interface + * route in the on-link-destination case. + */ + { struct sockaddr_in6 sdst; struct sockaddr_in6 sgw; struct sockaddr_in6 ssrc; + struct sockaddr *gw; + int rt_flags; u_int fibnum; bzero(&sdst, sizeof(sdst)); - bzero(&sgw, sizeof(sgw)); bzero(&ssrc, sizeof(ssrc)); - sdst.sin6_family = sgw.sin6_family = ssrc.sin6_family = AF_INET6; - sdst.sin6_len = sgw.sin6_len = ssrc.sin6_len = - sizeof(struct sockaddr_in6); - bcopy(&redtgt6, &sgw.sin6_addr, sizeof(struct in6_addr)); + sdst.sin6_family = ssrc.sin6_family = AF_INET6; + sdst.sin6_len = ssrc.sin6_len = sizeof(struct sockaddr_in6); bcopy(&reddst6, &sdst.sin6_addr, sizeof(struct in6_addr)); bcopy(&src6, &ssrc.sin6_addr, sizeof(struct in6_addr)); + rt_flags = RTF_HOST; + if (is_router) { + bzero(&sgw, sizeof(sgw)); + sgw.sin6_family = AF_INET6; + sgw.sin6_len = sizeof(struct sockaddr_in6); + bcopy(&redtgt6, &sgw.sin6_addr, + sizeof(struct in6_addr)); + gw = (struct sockaddr *)&sgw; + rt_flags |= RTF_GATEWAY; + } else + gw = ifp->if_addr->ifa_addr; for (fibnum = 0; fibnum < rt_numfibs; fibnum++) - in6_rtredirect((struct sockaddr *)&sdst, - (struct sockaddr *)&sgw, (struct sockaddr *)NULL, - RTF_GATEWAY | RTF_HOST, (struct sockaddr *)&ssrc, - fibnum); + in6_rtredirect((struct sockaddr *)&sdst, gw, + (struct sockaddr *)NULL, rt_flags, + (struct sockaddr *)&ssrc, fibnum); } /* finally update cached route in each socket via pfctlinput */ { diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c index 1284ffc9173c..cb6f7bbf07dc 100644 --- a/sys/netinet6/nd6.c +++ b/sys/netinet6/nd6.c @@ -132,6 +132,7 @@ static void nd6_setmtu0(struct ifnet *, struct nd_ifinfo *); static void nd6_slowtimo(void *); static int regen_tmpaddr(struct in6_ifaddr *); static struct llentry *nd6_free(struct llentry *, int); +static void nd6_free_redirect(const struct llentry *); static void nd6_llinfo_timer(void *); static void clear_llinfo_pqueue(struct llentry *); static void nd6_rtrequest(int, struct rtentry *, struct rt_addrinfo *); @@ -1223,6 +1224,13 @@ nd6_free(struct llentry *ln, int gc) defrouter_select(); } + /* + * If this entry was added by an on-link redirect, remove the + * corresponding host route. + */ + if (ln->la_flags & LLE_REDIRECT) + nd6_free_redirect(ln); + if (ln->ln_router || dr) LLE_WLOCK(ln); } @@ -1255,6 +1263,36 @@ nd6_free(struct llentry *ln, int gc) return (next); } +/* + * Remove the rtentry for the given llentry, + * both of which were installed by a redirect. + */ +static void +nd6_free_redirect(const struct llentry *ln) +{ + int fibnum; + struct rtentry *rt; + struct radix_node_head *rnh; + struct sockaddr_in6 sin6; + + lltable_fill_sa_entry(ln, (struct sockaddr *)&sin6); + for (fibnum = 0; fibnum < rt_numfibs; fibnum++) { + rnh = rt_tables_get_rnh(fibnum, AF_INET6); + if (rnh == NULL) + continue; + + RADIX_NODE_HEAD_LOCK(rnh); + rt = in6_rtalloc1((struct sockaddr *)&sin6, 0, + RTF_RNH_LOCKED, fibnum); + if (rt) { + if (rt->rt_flags == (RTF_UP | RTF_HOST | RTF_DYNAMIC)) + rt_expunge(rnh, rt); + RTFREE_LOCKED(rt); + } + RADIX_NODE_HEAD_UNLOCK(rnh); + } +} + /* * Upper-layer reachability hint for Neighbor Unreachability Detection. * @@ -1746,8 +1784,11 @@ nd6_cache_lladdr(struct ifnet *ifp, struct in6_addr *from, char *lladdr, */ if (code == ND_REDIRECT_ROUTER) ln->ln_router = 1; - else if (is_newentry) /* (6-7) */ - ln->ln_router = 0; + else { + if (is_newentry) /* (6-7) */ + ln->ln_router = 0; + ln->la_flags |= LLE_REDIRECT; + } break; case ND_ROUTER_SOLICIT: /* diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c index aa0ff477375c..cbcca46624cf 100644 --- a/sys/netinet6/nd6_rtr.c +++ b/sys/netinet6/nd6_rtr.c @@ -2105,7 +2105,7 @@ rt6_deleteroute(struct rtentry *rt, void *arg) return (0); return (in6_rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway, - rt_mask(rt), rt->rt_flags, NULL, rt->rt_fibnum)); + rt_mask(rt), rt->rt_flags | RTF_RNH_LOCKED, NULL, rt->rt_fibnum)); #undef SIN6 }