-/*
- * arp_route_to_gateway_route will find the gateway route for a given route.
- *
- * If the route is down, look the route up again.
- * If the route goes through a gateway, get the route to the gateway.
- * If the gateway route is down, look it up again.
- * If the route is set to reject, verify it hasn't expired.
- *
- * If the returned route is non-NULL, the caller is responsible for
- * releasing the reference and unlocking the route.
- */
-#define senderr(e) { error = (e); goto bad; }
-__private_extern__ errno_t
-arp_route_to_gateway_route(const struct sockaddr *net_dest, route_t hint0,
- route_t *out_route)
-{
- uint64_t 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) {
- VERIFY(rt->rt_expire == 0 || rt->rt_rmx.rmx_expire != 0);
- VERIFY(rt->rt_expire != 0 || rt->rt_rmx.rmx_expire == 0);
- timenow = net_uptime();
- if (rt->rt_expire == 0 ||
- timenow < rt->rt_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);
-}
-#undef senderr
-