+/*
+ * Lookup an AF_INET scoped or non-scoped route depending on the ifscope
+ * value passed in by the caller (IFSCOPE_NONE implies non-scoped).
+ */
+static struct radix_node *
+node_lookup(struct sockaddr *dst, struct sockaddr *netmask,
+ unsigned int ifscope)
+{
+ struct radix_node_head *rnh = rt_tables[AF_INET];
+ struct radix_node *rn;
+ struct sockaddr_in sin, mask;
+ struct matchleaf_arg ma = { ifscope };
+ rn_matchf_t *f = rn_match_ifscope;
+ void *w = &ma;
+
+ if (dst->sa_family != AF_INET)
+ return (NULL);
+
+ /*
+ * Embed ifscope into the search key; for a non-scoped
+ * search this will clear out any embedded scope value.
+ */
+ dst = sin_copy(SIN(dst), &sin, ifscope);
+
+ /* Embed (or clear) ifscope into netmask */
+ if (netmask != NULL)
+ netmask = mask_copy(netmask, &mask, ifscope);
+
+ if (ifscope == IFSCOPE_NONE)
+ f = w = NULL;
+
+ rn = rnh->rnh_lookup_args(dst, netmask, rnh, f, w);
+ if (rn != NULL && (rn->rn_flags & RNF_ROOT))
+ rn = NULL;
+
+ return (rn);
+}
+
+/*
+ * Lookup the AF_INET non-scoped default route.
+ */
+static struct radix_node *
+node_lookup_default(void)
+{
+ struct radix_node_head *rnh = rt_tables[AF_INET];
+ return (rnh->rnh_lookup(&sin_def, NULL, rnh));
+}
+
+/*
+ * Common routine to lookup/match a route. It invokes the lookup/matchaddr
+ * callback which could be address family-specific. The main difference
+ * between the two (at least for AF_INET/AF_INET6) is that a lookup does
+ * not alter the expiring state of a route, whereas a match would unexpire
+ * or revalidate the route.
+ *
+ * The optional scope or interface index property of a route allows for a
+ * per-interface route instance. This permits multiple route entries having
+ * the same destination (but not necessarily the same gateway) to exist in
+ * the routing table; each of these entries is specific to the corresponding
+ * interface. This is made possible by embedding the scope value into the
+ * radix key, thus making each route entry unique. These scoped entries
+ * exist along with the regular, non-scoped entries in the same radix tree
+ * for a given address family (currently AF_INET only); the scope logically
+ * partitions it into multiple per-interface sub-trees.
+ *
+ * When a scoped route lookup is performed, the routing table is searched for
+ * the best match that would result in a route using the same interface as the
+ * one associated with the scope (the exception to this are routes that point
+ * to the loopback interface). The search rule follows the longest matching
+ * prefix with the additional interface constraint.
+ */
+struct rtentry *
+rt_lookup(boolean_t lookup_only, struct sockaddr *dst, struct sockaddr *netmask,
+ struct radix_node_head *rnh, unsigned int ifscope)
+{
+ struct radix_node *rn0, *rn;
+ boolean_t dontcare = (ifscope == IFSCOPE_NONE);
+
+ lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_OWNED);
+
+ if (!lookup_only)
+ netmask = NULL;
+
+ /*
+ * Non-scoped route lookup.
+ */
+ if (!ip_doscopedroute || dst->sa_family != AF_INET) {
+ if (lookup_only)
+ rn = rnh->rnh_lookup(dst, netmask, rnh);
+ else
+ rn = rnh->rnh_matchaddr(dst, rnh);
+
+ /*
+ * Don't return a root node; also, rnh_matchaddr callback
+ * would have done the necessary work to clear RTPRF_OURS
+ * for certain protocol families.
+ */
+ if (rn != NULL && (rn->rn_flags & RNF_ROOT))
+ rn = NULL;
+ if (rn != NULL) {
+ RT_LOCK_SPIN(RT(rn));
+ if (!(RT(rn)->rt_flags & RTF_CONDEMNED)) {
+ RT_ADDREF_LOCKED(RT(rn));
+ RT_UNLOCK(RT(rn));
+ } else {
+ RT_UNLOCK(RT(rn));
+ rn = NULL;
+ }
+ }
+ return (RT(rn));
+ }
+
+ /*
+ * Scoped route lookup:
+ *
+ * We first perform a non-scoped lookup for the original result.
+ * Afterwards, depending on whether or not the caller has specified
+ * a scope, we perform a more specific scoped search and fallback
+ * to this original result upon failure.
+ */
+ rn0 = rn = node_lookup(dst, netmask, IFSCOPE_NONE);
+
+ /*
+ * If the caller did not specify a scope, use the primary scope
+ * derived from the system's non-scoped default route. If, for
+ * any reason, there is no primary interface, return what we have.
+ */
+ if (dontcare && (ifscope = get_primary_ifscope()) == IFSCOPE_NONE)
+ goto done;
+
+ /*
+ * Keep the original result if either of the following is true:
+ *
+ * 1) The interface portion of the route has the same interface
+ * index as the scope value and it is marked with RTF_IFSCOPE.
+ * 2) The route uses the loopback interface, in which case the
+ * destination (host/net) is local/loopback.
+ *
+ * Otherwise, do a more specified search using the scope;
+ * we're holding rnh_lock now, so rt_ifp should not change.
+ */
+ if (rn != NULL) {
+ struct rtentry *rt = RT(rn);
+ if (rt->rt_ifp != lo_ifp) {
+ if (rt->rt_ifp->if_index != ifscope) {
+ /*
+ * Wrong interface; keep the original result
+ * only if the caller did not specify a scope,
+ * and do a more specific scoped search using
+ * the scope of the found route. Otherwise,
+ * start again from scratch.
+ */
+ rn = NULL;
+ if (dontcare)
+ ifscope = rt->rt_ifp->if_index;
+ else
+ rn0 = NULL;
+ } else if (!(rt->rt_flags & RTF_IFSCOPE)) {
+ /*
+ * Right interface, except that this route
+ * isn't marked with RTF_IFSCOPE. Do a more
+ * specific scoped search. Keep the original
+ * result and return it it in case the scoped
+ * search fails.
+ */
+ rn = NULL;
+ }
+ }
+ }
+
+ /*
+ * Scoped search. Find the most specific entry having the same
+ * interface scope as the one requested. The following will result
+ * in searching for the longest prefix scoped match.
+ */
+ if (rn == NULL)
+ rn = node_lookup(dst, netmask, ifscope);
+
+ /*
+ * Use the original result if either of the following is true:
+ *
+ * 1) The scoped search did not yield any result.
+ * 2) The result from the scoped search is a scoped default route,
+ * and the original (non-scoped) result is not a default route,
+ * i.e. the original result is a more specific host/net route.
+ * 3) The scoped search yielded a net route but the original
+ * result is a host route, i.e. the original result is treated
+ * as a more specific route.
+ */
+ if (rn == NULL || (rn0 != NULL &&
+ ((INET_DEFAULT(rt_key(RT(rn))) && !INET_DEFAULT(rt_key(RT(rn0)))) ||
+ (!RT_HOST(rn) && RT_HOST(rn0)))))
+ rn = rn0;
+
+ /*
+ * If we still don't have a route, use the non-scoped default
+ * route as long as the interface portion satistifes the scope.
+ */
+ if (rn == NULL && (rn = node_lookup_default()) != NULL &&
+ RT(rn)->rt_ifp->if_index != ifscope)
+ rn = NULL;
+
+done:
+ if (rn != NULL) {
+ /*
+ * Manually clear RTPRF_OURS using in_validate() and
+ * bump up the reference count after, and not before;
+ * we only get here for AF_INET. node_lookup() has
+ * done the check against RNF_ROOT, so we can be sure
+ * that we're not returning a root node here.
+ */
+ RT_LOCK_SPIN(RT(rn));
+ if (!(RT(rn)->rt_flags & RTF_CONDEMNED)) {
+ if (!lookup_only)
+ (void) in_validate(rn);
+ RT_ADDREF_LOCKED(RT(rn));
+ RT_UNLOCK(RT(rn));
+ } else {
+ RT_UNLOCK(RT(rn));
+ rn = NULL;
+ }
+ }
+
+ return (RT(rn));
+}
+