__private_extern__ errno_t arp_route_to_gateway_route(const struct sockaddr *net_dest, route_t hint0, route_t *out_route) { struct timeval timenow; route_t rt = hint0, hint = hint0; errno_t error = 0; *out_route = NULL; /* * Next hop determination. Because we may involve the gateway route * in addition to the original route, locking is rather complicated. * The general concept is that regardless of whether the route points * to the original route or to the gateway route, this routine takes * an extra reference on such a route. This extra reference will be * released at the end. * * Care must be taken to ensure that the "hint0" route never gets freed * via rtfree(), since the caller may have stored it inside a struct * route with a reference held for that placeholder. */ if (rt != NULL) { unsigned int ifindex; RT_LOCK_SPIN(rt); ifindex = rt->rt_ifp->if_index; RT_ADDREF_LOCKED(rt); if (!(rt->rt_flags & RTF_UP)) { RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); /* route is down, find a new one */ hint = rt = rtalloc1_scoped((struct sockaddr *) (size_t)net_dest, 1, 0, ifindex); if (hint != NULL) { RT_LOCK_SPIN(rt); ifindex = rt->rt_ifp->if_index; } else { senderr(EHOSTUNREACH); } } /* * We have a reference to "rt" by now; it will either * be released or freed at the end of this routine. */ RT_LOCK_ASSERT_HELD(rt); if (rt->rt_flags & RTF_GATEWAY) { struct rtentry *gwrt = rt->rt_gwroute; struct sockaddr_in gw; /* If there's no gateway rt, look it up */ if (gwrt == NULL) { gw = *((struct sockaddr_in *)rt->rt_gateway); RT_UNLOCK(rt); goto lookup; } /* Become a regular mutex */ RT_CONVERT_LOCK(rt); /* * Take gwrt's lock while holding route's lock; * this is okay since gwrt never points back * to "rt", so no lock ordering issues. */ RT_LOCK_SPIN(gwrt); if (!(gwrt->rt_flags & RTF_UP)) { struct rtentry *ogwrt; rt->rt_gwroute = NULL; RT_UNLOCK(gwrt); gw = *((struct sockaddr_in *)rt->rt_gateway); RT_UNLOCK(rt); rtfree(gwrt); lookup: gwrt = rtalloc1_scoped( (struct sockaddr *)&gw, 1, 0, ifindex); RT_LOCK(rt); /* * Bail out if the route is down, no route * to gateway, circular route, or if the * gateway portion of "rt" has changed. */ if (!(rt->rt_flags & RTF_UP) || gwrt == NULL || gwrt == rt || !equal(SA(&gw), rt->rt_gateway)) { if (gwrt == rt) { RT_REMREF_LOCKED(gwrt); gwrt = NULL; } RT_UNLOCK(rt); if (gwrt != NULL) rtfree(gwrt); senderr(EHOSTUNREACH); } /* Remove any existing gwrt */ ogwrt = rt->rt_gwroute; if ((rt->rt_gwroute = gwrt) != NULL) RT_ADDREF(gwrt); /* Clean up "rt" now while we can */ if (rt == hint0) { RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); } else { RT_UNLOCK(rt); rtfree(rt); } rt = gwrt; /* Now free the replaced gwrt */ if (ogwrt != NULL) rtfree(ogwrt); /* If still no route to gateway, bail out */ if (rt == NULL) senderr(EHOSTUNREACH); } else { RT_ADDREF_LOCKED(gwrt); RT_UNLOCK(gwrt); /* Clean up "rt" now while we can */ if (rt == hint0) { RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); } else { RT_UNLOCK(rt); rtfree(rt); } rt = gwrt; } /* rt == gwrt; if it is now down, give up */ RT_LOCK_SPIN(rt); if (!(rt->rt_flags & RTF_UP)) { RT_UNLOCK(rt); senderr(EHOSTUNREACH); } } if (rt->rt_flags & RTF_REJECT) { getmicrotime(&timenow); if (rt->rt_rmx.rmx_expire == 0 || timenow.tv_sec < rt->rt_rmx.rmx_expire) { RT_UNLOCK(rt); senderr(rt == hint ? EHOSTDOWN : EHOSTUNREACH); } } /* Become a regular mutex */ RT_CONVERT_LOCK(rt); /* Caller is responsible for cleaning up "rt" */ *out_route = rt; } return (0); bad: /* Clean up route (either it is "rt" or "gwrt") */ if (rt != NULL) { RT_LOCK_SPIN(rt); if (rt == hint0) { RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); } else { RT_UNLOCK(rt); rtfree(rt); } } return (error); }
/* * Given a source IPv6 address (and route, if available), determine the best * interface to send the packet from. Checking for (and updating) the * ROF_SRCIF_SELECTED flag in the pcb-supplied route placeholder is done * without any locks, based on the assumption that in the event this is * called from ip6_output(), the output operation is single-threaded per-pcb, * i.e. for any given pcb there can only be one thread performing output at * the IPv6 layer. * * This routine is analogous to in_selectsrcif() for IPv4. * * clone - meaningful only for bsdi and freebsd */ static int selectroute(struct sockaddr_in6 *srcsock, struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts, struct ip6_moptions *mopts, struct route_in6 *ro, struct ifnet **retifp, struct rtentry **retrt, int clone, int norouteok, unsigned int ifscope, unsigned int nocell) { int error = 0; struct ifnet *ifp = NULL; struct route_in6 *route = NULL; struct sockaddr_in6 *sin6_next; struct in6_pktinfo *pi = NULL; struct in6_addr *dst = &dstsock->sin6_addr; struct ifaddr *ifa = NULL; char s_src[MAX_IPv6_STR_LEN], s_dst[MAX_IPv6_STR_LEN]; boolean_t select_srcif; #if 0 char ip6buf[INET6_ADDRSTRLEN]; if (dstsock->sin6_addr.s6_addr32[0] == 0 && dstsock->sin6_addr.s6_addr32[1] == 0 && !IN6_IS_ADDR_LOOPBACK(&dstsock->sin6_addr)) { printf("in6_selectroute: strange destination %s\n", ip6_sprintf(ip6buf, &dstsock->sin6_addr)); } else { printf("in6_selectroute: destination = %s%%%d\n", ip6_sprintf(ip6buf, &dstsock->sin6_addr), dstsock->sin6_scope_id); /* for debug */ } #endif if (retifp != NULL) *retifp = NULL; if (retrt != NULL) *retrt = NULL; if (ip6_select_srcif_debug) { struct in6_addr src; src = (srcsock != NULL) ? srcsock->sin6_addr : in6addr_any; (void) inet_ntop(AF_INET6, &src, s_src, sizeof (s_src)); (void) inet_ntop(AF_INET6, dst, s_dst, sizeof (s_dst)); } /* * If the destination address is UNSPECIFIED addr, bail out. */ if (IN6_IS_ADDR_UNSPECIFIED(dst)) { error = EHOSTUNREACH; goto done; } /* * Perform source interface selection only if Scoped Routing * is enabled and a source address that isn't unspecified. */ select_srcif = (ip6_doscopedroute && srcsock != NULL && !IN6_IS_ADDR_UNSPECIFIED(&srcsock->sin6_addr)); /* * If Scoped Routing is disabled, ignore the given ifscope. * Otherwise even if source selection won't be performed, * we still obey IPV6_BOUND_IF. */ if (!ip6_doscopedroute && ifscope != IFSCOPE_NONE) ifscope = IFSCOPE_NONE; /* If the caller specified the outgoing interface explicitly, use it */ if (opts != NULL && (pi = opts->ip6po_pktinfo) != NULL && pi->ipi6_ifindex != 0) { /* * If IPV6_PKTINFO takes precedence over IPV6_BOUND_IF. */ ifscope = pi->ipi6_ifindex; ifnet_head_lock_shared(); /* ifp may be NULL if detached or out of range */ ifp = (ifscope <= if_index) ? ifindex2ifnet[ifscope] : NULL; ifnet_head_done(); if (norouteok || retrt == NULL || IN6_IS_ADDR_MULTICAST(dst)) { /* * We do not have to check or get the route for * multicast. If the caller didn't ask/care for * the route and we have no interface to use, * it's an error. */ if (ifp == NULL) error = EHOSTUNREACH; goto done; } else { goto getsrcif; } } /* * If the destination address is a multicast address and the outgoing * interface for the address is specified by the caller, use it. */ if (IN6_IS_ADDR_MULTICAST(dst) && mopts != NULL) { IM6O_LOCK(mopts); if ((ifp = mopts->im6o_multicast_ifp) != NULL) { IM6O_UNLOCK(mopts); goto done; /* we do not need a route for multicast. */ } IM6O_UNLOCK(mopts); } getsrcif: /* * If the outgoing interface was not set via IPV6_BOUND_IF or * IPV6_PKTINFO, use the scope ID in the destination address. */ if (ip6_doscopedroute && ifscope == IFSCOPE_NONE) ifscope = dstsock->sin6_scope_id; /* * Perform source interface selection; the source IPv6 address * must belong to one of the addresses of the interface used * by the route. For performance reasons, do this only if * there is no route, or if the routing table has changed, * or if we haven't done source interface selection on this * route (for this PCB instance) before. */ if (!select_srcif || (ro != NULL && ro->ro_rt != NULL && (ro->ro_rt->rt_flags & RTF_UP) && ro->ro_rt->generation_id == route_generation && (ro->ro_flags & ROF_SRCIF_SELECTED))) { if (ro != NULL && ro->ro_rt != NULL) { ifa = ro->ro_rt->rt_ifa; IFA_ADDREF(ifa); } goto getroute; } /* * Given the source IPv6 address, find a suitable source interface * to use for transmission; if a scope ID has been specified, * optimize the search by looking at the addresses only for that * interface. This is still suboptimal, however, as we need to * traverse the per-interface list. */ if (ifscope != IFSCOPE_NONE || (ro != NULL && ro->ro_rt != NULL)) { unsigned int scope = ifscope; struct ifnet *rt_ifp; rt_ifp = (ro->ro_rt != NULL) ? ro->ro_rt->rt_ifp : NULL; /* * If no scope is specified and the route is stale (pointing * to a defunct interface) use the current primary interface; * this happens when switching between interfaces configured * with the same IPv6 address. Otherwise pick up the scope * information from the route; the ULP may have looked up a * correct route and we just need to verify it here and mark * it with the ROF_SRCIF_SELECTED flag below. */ if (scope == IFSCOPE_NONE) { scope = rt_ifp->if_index; if (scope != get_primary_ifscope(AF_INET6) && ro->ro_rt->generation_id != route_generation) scope = get_primary_ifscope(AF_INET6); } ifa = (struct ifaddr *) ifa_foraddr6_scoped(&srcsock->sin6_addr, scope); if (ip6_select_srcif_debug && ifa != NULL) { if (ro->ro_rt != NULL) { printf("%s->%s ifscope %d->%d ifa_if %s " "ro_if %s\n", s_src, s_dst, ifscope, scope, if_name(ifa->ifa_ifp), if_name(rt_ifp)); } else { printf("%s->%s ifscope %d->%d ifa_if %s\n", s_src, s_dst, ifscope, scope, if_name(ifa->ifa_ifp)); } } } /* * Slow path; search for an interface having the corresponding source * IPv6 address if the scope was not specified by the caller, and: * * 1) There currently isn't any route, or, * 2) The interface used by the route does not own that source * IPv6 address; in this case, the route will get blown away * and we'll do a more specific scoped search using the newly * found interface. */ if (ifa == NULL && ifscope == IFSCOPE_NONE) { ifa = (struct ifaddr *)ifa_foraddr6(&srcsock->sin6_addr); if (ip6_select_srcif_debug && ifa != NULL) { printf("%s->%s ifscope %d ifa_if %s\n", s_src, s_dst, ifscope, if_name(ifa->ifa_ifp)); } } getroute: if (ifa != NULL) ifscope = ifa->ifa_ifp->if_index; /* * If the next hop address for the packet is specified by the caller, * use it as the gateway. */ if (opts != NULL && opts->ip6po_nexthop != NULL) { struct route_in6 *ron; sin6_next = satosin6(opts->ip6po_nexthop); /* at this moment, we only support AF_INET6 next hops */ if (sin6_next->sin6_family != AF_INET6) { error = EAFNOSUPPORT; /* or should we proceed? */ goto done; } /* * If the next hop is an IPv6 address, then the node identified * by that address must be a neighbor of the sending host. */ ron = &opts->ip6po_nextroute; if (ron->ro_rt != NULL) RT_LOCK(ron->ro_rt); if ((ron->ro_rt != NULL && ((ron->ro_rt->rt_flags & (RTF_UP | RTF_LLINFO)) != (RTF_UP | RTF_LLINFO) || ron->ro_rt->generation_id != route_generation || (select_srcif && (ifa == NULL || ifa->ifa_ifp != ron->ro_rt->rt_ifp)))) || !IN6_ARE_ADDR_EQUAL(&satosin6(&ron->ro_dst)->sin6_addr, &sin6_next->sin6_addr)) { if (ron->ro_rt != NULL) { RT_UNLOCK(ron->ro_rt); rtfree(ron->ro_rt); ron->ro_rt = NULL; } *satosin6(&ron->ro_dst) = *sin6_next; } if (ron->ro_rt == NULL) { rtalloc_scoped((struct route *)ron, ifscope); if (ron->ro_rt != NULL) RT_LOCK(ron->ro_rt); if (ron->ro_rt == NULL || !(ron->ro_rt->rt_flags & RTF_LLINFO) || !IN6_ARE_ADDR_EQUAL(&satosin6(rt_key(ron->ro_rt))-> sin6_addr, &sin6_next->sin6_addr)) { if (ron->ro_rt != NULL) { RT_UNLOCK(ron->ro_rt); rtfree(ron->ro_rt); ron->ro_rt = NULL; } error = EHOSTUNREACH; goto done; } } route = ron; ifp = ron->ro_rt->rt_ifp; /* * When cloning is required, try to allocate a route to the * destination so that the caller can store path MTU * information. */ if (!clone) { if (select_srcif) { /* Keep the route locked */ goto validateroute; } RT_UNLOCK(ron->ro_rt); goto done; } RT_UNLOCK(ron->ro_rt); } /* * Use a cached route if it exists and is valid, else try to allocate * a new one. Note that we should check the address family of the * cached destination, in case of sharing the cache with IPv4. */ if (ro == NULL) goto done; if (ro->ro_rt != NULL) RT_LOCK(ro->ro_rt); if (ro->ro_rt != NULL && (!(ro->ro_rt->rt_flags & RTF_UP) || satosin6(&ro->ro_dst)->sin6_family != AF_INET6 || ro->ro_rt->generation_id != route_generation || !IN6_ARE_ADDR_EQUAL(&satosin6(&ro->ro_dst)->sin6_addr, dst) || (select_srcif && (ifa == NULL || ifa->ifa_ifp != ro->ro_rt->rt_ifp)))) { RT_UNLOCK(ro->ro_rt); rtfree(ro->ro_rt); ro->ro_rt = NULL; } if (ro->ro_rt == NULL) { struct sockaddr_in6 *sa6; if (ro->ro_rt != NULL) RT_UNLOCK(ro->ro_rt); /* No route yet, so try to acquire one */ bzero(&ro->ro_dst, sizeof(struct sockaddr_in6)); sa6 = (struct sockaddr_in6 *)&ro->ro_dst; sa6->sin6_family = AF_INET6; sa6->sin6_len = sizeof(struct sockaddr_in6); sa6->sin6_addr = *dst; if (IN6_IS_ADDR_MULTICAST(dst)) { ro->ro_rt = rtalloc1_scoped( &((struct route *)ro)->ro_dst, 0, 0, ifscope); } else { rtalloc_scoped((struct route *)ro, ifscope); } if (ro->ro_rt != NULL) RT_LOCK(ro->ro_rt); } /* * Do not care about the result if we have the nexthop * explicitly specified (in case we're asked to clone.) */ if (opts != NULL && opts->ip6po_nexthop != NULL) { if (ro->ro_rt != NULL) RT_UNLOCK(ro->ro_rt); goto done; } if (ro->ro_rt != NULL) { RT_LOCK_ASSERT_HELD(ro->ro_rt); ifp = ro->ro_rt->rt_ifp; } else { error = EHOSTUNREACH; } route = ro; validateroute: if (select_srcif) { boolean_t has_route = (route != NULL && route->ro_rt != NULL); if (has_route) RT_LOCK_ASSERT_HELD(route->ro_rt); /* * If there is a non-loopback route with the wrong interface, * or if there is no interface configured with such an address, * blow it away. Except for local/loopback, we look for one * with a matching interface scope/index. */ if (has_route && (ifa == NULL || (ifa->ifa_ifp != ifp && ifp != lo_ifp) || !(route->ro_rt->rt_flags & RTF_UP))) { if (ip6_select_srcif_debug) { if (ifa != NULL) { printf("%s->%s ifscope %d ro_if %s " "!= ifa_if %s (cached route " "cleared)\n", s_src, s_dst, ifscope, if_name(ifp), if_name(ifa->ifa_ifp)); } else { printf("%s->%s ifscope %d ro_if %s " "(no ifa_if found)\n", s_src, s_dst, ifscope, if_name(ifp)); } } RT_UNLOCK(route->ro_rt); rtfree(route->ro_rt); route->ro_rt = NULL; route->ro_flags &= ~ROF_SRCIF_SELECTED; error = EHOSTUNREACH; /* Undo the settings done above */ route = NULL; ifp = NULL; } else if (has_route) { route->ro_flags |= ROF_SRCIF_SELECTED; route->ro_rt->generation_id = route_generation; RT_UNLOCK(route->ro_rt); } } else { if (ro->ro_rt != NULL) RT_UNLOCK(ro->ro_rt); if (ifp != NULL && opts != NULL && opts->ip6po_pktinfo != NULL && opts->ip6po_pktinfo->ipi6_ifindex != 0) { /* * Check if the outgoing interface conflicts with the * interface specified by ipi6_ifindex (if specified). * Note that loopback interface is always okay. * (this may happen when we are sending a packet to * one of our own addresses.) */ if (!(ifp->if_flags & IFF_LOOPBACK) && ifp->if_index != opts->ip6po_pktinfo->ipi6_ifindex) { error = EHOSTUNREACH; goto done; } } } done: if (nocell && error == 0) { if ((ifp != NULL && ifp->if_type == IFT_CELLULAR) || (route != NULL && route->ro_rt != NULL && route->ro_rt->rt_ifp->if_type == IFT_CELLULAR)) { if (route != NULL && route->ro_rt != NULL) { rtfree(route->ro_rt); route->ro_rt = NULL; route->ro_flags &= ~ROF_SRCIF_SELECTED; route = NULL; } ifp = NULL; error = EHOSTUNREACH; } } if (ifp == NULL && (route == NULL || route->ro_rt == NULL)) { /* * This can happen if the caller did not pass a cached route * nor any other hints. We treat this case an error. */ error = EHOSTUNREACH; } if (error == EHOSTUNREACH) ip6stat.ip6s_noroute++; if (error == 0) { if (retifp != NULL) { if (ifp != NULL) ifnet_reference(ifp); /* for caller */ *retifp = ifp; } if (retrt != NULL && route != NULL) *retrt = route->ro_rt; /* ro_rt may be NULL */ } else if (select_srcif && ip6_select_srcif_debug) { printf("%s->%s ifscope %d ifa_if %s ro_if %s (error=%d)\n", s_src, s_dst, ifscope, (ifa != NULL) ? if_name(ifa->ifa_ifp) : "NONE", (ifp != NULL) ? if_name(ifp) : "NONE", error); } if (ifa != NULL) IFA_REMREF(ifa); return (error); }
/* * arp_lookup_route will lookup the route for a given address. * * The address must be for a host on a local network on this interface. * If the returned route is non-NULL, the route is locked and the caller * is responsible for unlocking it and releasing its reference. */ static errno_t arp_lookup_route(const struct in_addr *addr, int create, int proxy, route_t *route, unsigned int ifscope) { struct sockaddr_inarp sin = {sizeof(sin), AF_INET, 0, {0}, {0}, 0, 0}; const char *why = NULL; errno_t error = 0; route_t rt; *route = NULL; sin.sin_addr.s_addr = addr->s_addr; sin.sin_other = proxy ? SIN_PROXY : 0; rt = rtalloc1_scoped((struct sockaddr*)&sin, create, 0, ifscope); if (rt == NULL) return (ENETUNREACH); RT_LOCK(rt); if (rt->rt_flags & RTF_GATEWAY) { why = "host is not on local network"; error = ENETUNREACH; } else if (!(rt->rt_flags & RTF_LLINFO)) { why = "could not allocate llinfo"; error = ENOMEM; } else if (rt->rt_gateway->sa_family != AF_LINK) { why = "gateway route is not ours"; error = EPROTONOSUPPORT; } if (error != 0) { if (create && log_arp_warnings) { char tmp[MAX_IPv4_STR_LEN]; log(LOG_DEBUG, "arplookup link#%d %s failed: %s\n", ifscope, inet_ntop(AF_INET, addr, tmp, sizeof (tmp)), why); } /* * If there are no references to this route, and it is * a cloned route, and not static, and ARP had created * the route, then purge it from the routing table as * it is probably bogus. */ if (rt->rt_refcnt == 1 && (rt->rt_flags & (RTF_WASCLONED | RTF_STATIC)) == RTF_WASCLONED) { /* * Prevent another thread from modiying rt_key, * rt_gateway via rt_setgate() after rt_lock is * dropped by marking the route as defunct. */ rt->rt_flags |= RTF_CONDEMNED; RT_UNLOCK(rt); rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway, rt_mask(rt), rt->rt_flags, 0); rtfree(rt); } else { RT_REMREF_LOCKED(rt); RT_UNLOCK(rt); } return (error); } /* * Caller releases reference and does RT_UNLOCK(rt). */ *route = rt; return (0); }