diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c index a8ee648..07091b7 100644 --- a/sys/netinet6/nd6.c +++ b/sys/netinet6/nd6.c @@ -2157,90 +2157,125 @@ nd6_slowtimo(void *ignored_arg) mutex_exit(softnet_lock); } +/* + * Next hop determination. This routine was derived from ether_output. + */ +static int +nd6_determine_nexthop(struct ifnet *ifp, const struct sockaddr_in6 *dst, + struct rtentry *rt00, struct rtentry **ret_rt, bool *sendpkt) +{ + struct rtentry *rt, *rt0; + struct rtentry *gwrt; + struct sockaddr_in6 *gw6; + +#define RTFREE_IF_NEEDED(_rt) \ + if ((_rt) != NULL && (_rt) != rt00) \ + rtfree((_rt)); + + KASSERT(rt00 != NULL); + + rt = rt0 = rt00; + + if ((rt->rt_flags & RTF_UP) == 0) { + rt0 = rt = rtalloc1(sin6tocsa(dst), 1); + if (rt == NULL) + goto hostunreach; + if (rt->rt_ifp != ifp) + goto hostunreach; + } + + if ((rt->rt_flags & RTF_GATEWAY) == 0) + goto out; + + gw6 = (struct sockaddr_in6 *)rt->rt_gateway; + + /* + * We skip link-layer address resolution and NUD + * if the gateway is not a neighbor from ND point + * of view, regardless of the value of nd_ifinfo.flags. + * The second condition is a bit tricky; we skip + * if the gateway is our own address, which is + * sometimes used to install a route to a p2p link. + */ + if (!nd6_is_addr_neighbor(gw6, ifp) || + in6ifa_ifpwithaddr(ifp, &gw6->sin6_addr)) { + /* + * We allow this kind of tricky route only + * when the outgoing interface is p2p. + * XXX: we may need a more generic rule here. + */ + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) + goto hostunreach; + + *sendpkt = true; + goto out; + } + + gwrt = rt_get_gwroute(rt); + if (gwrt == NULL) + goto lookup; + + RTFREE_IF_NEEDED(rt); + + rt = gwrt; + if ((rt->rt_flags & RTF_UP) == 0) { + RTFREE_IF_NEEDED(rt); + rt = rt0; + lookup: + gwrt = rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1); + RTFREE_IF_NEEDED(rt); + rt = gwrt; + if (rt == NULL) + goto hostunreach; + /* the "G" test below also prevents rt == rt0 */ + if ((rt->rt_flags & RTF_GATEWAY) || + (rt->rt_ifp != ifp)) { + rt0->rt_gwroute = NULL; + goto hostunreach; + } + } + +out: + *ret_rt = rt; + return 0; + +hostunreach: + RTFREE_IF_NEEDED(rt); + + return EHOSTUNREACH; +#undef RTFREE_IF_NEEDED +} + #define senderr(e) { error = (e); goto bad;} int nd6_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0, - const struct sockaddr_in6 *dst, struct rtentry *rt00) + const struct sockaddr_in6 *dst, struct rtentry *rt0) { struct mbuf *m = m0; - struct rtentry *rt, *rt0; - struct sockaddr_in6 *gw6 = NULL; + struct rtentry *rt = rt0; struct llinfo_nd6 *ln = NULL; int error = 0; #define RTFREE_IF_NEEDED(_rt) \ - if ((_rt) != NULL && (_rt) != rt00) \ + if ((_rt) != NULL && (_rt) != rt0) \ rtfree((_rt)); - rt = rt0 = rt00; - if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr)) goto sendpkt; if (nd6_need_cache(ifp) == 0) goto sendpkt; - /* - * next hop determination. This routine is derived from ether_output. - */ if (rt) { - if ((rt->rt_flags & RTF_UP) == 0) { - if ((rt0 = rt = rtalloc1(sin6tocsa(dst), 1)) != NULL) { - if (rt->rt_ifp != ifp) - senderr(EHOSTUNREACH); - } else - senderr(EHOSTUNREACH); - } - - if (rt->rt_flags & RTF_GATEWAY) { - struct rtentry *gwrt; - gw6 = (struct sockaddr_in6 *)rt->rt_gateway; - - /* - * We skip link-layer address resolution and NUD - * if the gateway is not a neighbor from ND point - * of view, regardless of the value of nd_ifinfo.flags. - * The second condition is a bit tricky; we skip - * if the gateway is our own address, which is - * sometimes used to install a route to a p2p link. - */ - if (!nd6_is_addr_neighbor(gw6, ifp) || - in6ifa_ifpwithaddr(ifp, &gw6->sin6_addr)) { - /* - * We allow this kind of tricky route only - * when the outgoing interface is p2p. - * XXX: we may need a more generic rule here. - */ - if ((ifp->if_flags & IFF_POINTOPOINT) == 0) - senderr(EHOSTUNREACH); - - goto sendpkt; - } - - gwrt = rt_get_gwroute(rt); - if (gwrt == NULL) - goto lookup; - - RTFREE_IF_NEEDED(rt); - rt = gwrt; - if ((rt->rt_flags & RTF_UP) == 0) { - rtfree(rt); - rt = rt0; - lookup: - gwrt = rt->rt_gwroute = - rtalloc1(rt->rt_gateway, 1); - rtfree(rt); - rt = gwrt; - if (rt == NULL) - senderr(EHOSTUNREACH); - /* the "G" test below also prevents rt == rt0 */ - if ((rt->rt_flags & RTF_GATEWAY) || - (rt->rt_ifp != ifp)) { - rt0->rt_gwroute = NULL; - senderr(EHOSTUNREACH); - } - } - } + struct rtentry *nexthop = NULL; + bool sendpkt = false; + + error = nd6_determine_nexthop(ifp, dst, rt, &nexthop, &sendpkt); + if (error != 0) + senderr(error); + rt = nexthop; + if (sendpkt) + goto sendpkt; } /*