+static inline void rtref_audit(struct rtentry_dbg *);
+static inline void rtunref_audit(struct rtentry_dbg *);
+static struct rtentry *rtalloc1_common_locked(struct sockaddr *, int, uint32_t,
+ unsigned int);
+static int rtrequest_common_locked(int, struct sockaddr *,
+ struct sockaddr *, struct sockaddr *, int, struct rtentry **,
+ unsigned int);
+static struct rtentry *rtalloc1_locked(struct sockaddr *, int, uint32_t);
+static void rtalloc_ign_common_locked(struct route *, uint32_t, unsigned int);
+static inline void sin6_set_ifscope(struct sockaddr *, unsigned int);
+static inline void sin6_set_embedded_ifscope(struct sockaddr *, unsigned int);
+static inline unsigned int sin6_get_embedded_ifscope(struct sockaddr *);
+static struct sockaddr *sa_copy(struct sockaddr *, struct sockaddr_storage *,
+ unsigned int *);
+static struct sockaddr *ma_copy(int, struct sockaddr *,
+ struct sockaddr_storage *, unsigned int);
+static struct sockaddr *sa_trim(struct sockaddr *, int);
+static struct radix_node *node_lookup(struct sockaddr *, struct sockaddr *,
+ unsigned int);
+static struct radix_node *node_lookup_default(int);
+static int rn_match_ifscope(struct radix_node *, void *);
+static struct ifaddr *ifa_ifwithroute_common_locked(int,
+ const struct sockaddr *, const struct sockaddr *, unsigned int);
+static struct rtentry *rte_alloc(void);
+static void rte_free(struct rtentry *);
+static void rtfree_common(struct rtentry *, boolean_t);
+static void rte_if_ref(struct ifnet *, int);
+
+uint32_t route_generation = 0;
+
+/*
+ * sockaddr_in with scope ID field; this is used internally to keep
+ * track of scoped route entries in the routing table. The fact that
+ * such a value is embedded in the structure is an artifact of the
+ * current implementation which could change in future.
+ */
+struct sockaddr_inifscope {
+ __uint8_t sin_len;
+ sa_family_t sin_family;
+ in_port_t sin_port;
+ struct in_addr sin_addr;
+ /*
+ * To avoid possible conflict with an overlaid sockaddr_inarp
+ * having sin_other set to SIN_PROXY, we use the first 4-bytes
+ * of sin_zero since sin_srcaddr is one of the unused fields
+ * in sockaddr_inarp.
+ */
+ union {
+ char sin_zero[8];
+ struct {
+ __uint32_t ifscope;
+ } _in_index;
+ } un;
+#define sin_scope_id un._in_index.ifscope
+};
+
+#define SA(sa) ((struct sockaddr *)(size_t)(sa))
+#define SIN(sa) ((struct sockaddr_in *)(size_t)(sa))
+#define SIN6(sa) ((struct sockaddr_in6 *)(size_t)(sa))
+#define SINIFSCOPE(sa) ((struct sockaddr_inifscope *)(size_t)(sa))
+#define SIN6IFSCOPE(sa) SIN6(sa)
+
+#define ASSERT_SINIFSCOPE(sa) { \
+ if ((sa)->sa_family != AF_INET || \
+ (sa)->sa_len < sizeof (struct sockaddr_in)) \
+ panic("%s: bad sockaddr_in %p\n", __func__, sa); \
+}
+
+#define ASSERT_SIN6IFSCOPE(sa) { \
+ if ((sa)->sa_family != AF_INET6 || \
+ (sa)->sa_len < sizeof (struct sockaddr_in6)) \
+ panic("%s: bad sockaddr_in %p\n", __func__, sa); \
+}
+
+/*
+ * Argument to leaf-matching routine; at present it is scoped routing
+ * specific but can be expanded in future to include other search filters.
+ */
+struct matchleaf_arg {
+ unsigned int ifscope; /* interface scope */
+};
+
+/*
+ * For looking up the non-scoped default route (sockaddr instead
+ * of sockaddr_in for convenience).
+ */
+static struct sockaddr sin_def = {
+ sizeof (struct sockaddr_in), AF_INET, { 0, }
+};
+
+static struct sockaddr_in6 sin6_def = {
+ sizeof (struct sockaddr_in6), AF_INET6, 0, 0, IN6ADDR_ANY_INIT, 0
+};
+
+/*
+ * Interface index (scope) of the primary interface; determined at
+ * the time when the default, non-scoped route gets added, changed
+ * or deleted. Protected by rnh_lock.
+ */
+static unsigned int primary_ifscope = IFSCOPE_NONE;
+static unsigned int primary6_ifscope = IFSCOPE_NONE;
+
+#define INET_DEFAULT(sa) \
+ ((sa)->sa_family == AF_INET && SIN(sa)->sin_addr.s_addr == 0)
+
+#define INET6_DEFAULT(sa) \
+ ((sa)->sa_family == AF_INET6 && \
+ IN6_IS_ADDR_UNSPECIFIED(&SIN6(sa)->sin6_addr))
+
+#define SA_DEFAULT(sa) (INET_DEFAULT(sa) || INET6_DEFAULT(sa))
+#define RT(r) ((struct rtentry *)r)
+#define RN(r) ((struct radix_node *)r)
+#define RT_HOST(r) (RT(r)->rt_flags & RTF_HOST)
+
+SYSCTL_DECL(_net_idle_route);
+
+static int rt_if_idle_expire_timeout = RT_IF_IDLE_EXPIRE_TIMEOUT;
+SYSCTL_INT(_net_idle_route, OID_AUTO, expire_timeout, CTLFLAG_RW,
+ &rt_if_idle_expire_timeout, 0, "Default expiration time on routes for "
+ "interface idle reference counting");
+
+/*
+ * Given a route, determine whether or not it is the non-scoped default
+ * route; dst typically comes from rt_key(rt) but may be coming from
+ * a separate place when rt is in the process of being created.
+ */
+boolean_t
+rt_primary_default(struct rtentry *rt, struct sockaddr *dst)
+{
+ return (SA_DEFAULT(dst) && !(rt->rt_flags & RTF_IFSCOPE));
+}
+
+/*
+ * Set the ifscope of the primary interface; caller holds rnh_lock.
+ */
+void
+set_primary_ifscope(int af, unsigned int ifscope)
+{
+ if (af == AF_INET)
+ primary_ifscope = ifscope;
+ else
+ primary6_ifscope = ifscope;
+}
+
+/*
+ * Return the ifscope of the primary interface; caller holds rnh_lock.
+ */
+unsigned int
+get_primary_ifscope(int af)
+{
+ return (af == AF_INET ? primary_ifscope : primary6_ifscope);
+}
+
+/*
+ * Set the scope ID of a given a sockaddr_in.
+ */
+void
+sin_set_ifscope(struct sockaddr *sa, unsigned int ifscope)
+{
+ /* Caller must pass in sockaddr_in */
+ ASSERT_SINIFSCOPE(sa);
+
+ SINIFSCOPE(sa)->sin_scope_id = ifscope;
+}
+
+/*
+ * Set the scope ID of given a sockaddr_in6.
+ */
+static inline void
+sin6_set_ifscope(struct sockaddr *sa, unsigned int ifscope)
+{
+ /* Caller must pass in sockaddr_in6 */
+ ASSERT_SIN6IFSCOPE(sa);
+
+ SIN6IFSCOPE(sa)->sin6_scope_id = ifscope;
+}
+
+/*
+ * Given a sockaddr_in, return the scope ID to the caller.
+ */
+unsigned int
+sin_get_ifscope(struct sockaddr *sa)
+{
+ /* Caller must pass in sockaddr_in */
+ ASSERT_SINIFSCOPE(sa);
+
+ return (SINIFSCOPE(sa)->sin_scope_id);
+}
+
+/*
+ * Given a sockaddr_in6, return the scope ID to the caller.
+ */
+unsigned int
+sin6_get_ifscope(struct sockaddr *sa)
+{
+ /* Caller must pass in sockaddr_in6 */
+ ASSERT_SIN6IFSCOPE(sa);
+
+ return (SIN6IFSCOPE(sa)->sin6_scope_id);
+}
+
+static inline void
+sin6_set_embedded_ifscope(struct sockaddr *sa, unsigned int ifscope)
+{
+ /* Caller must pass in sockaddr_in6 */
+ ASSERT_SIN6IFSCOPE(sa);
+ VERIFY(IN6_IS_SCOPE_EMBED(&(SIN6(sa)->sin6_addr)));
+
+ SIN6(sa)->sin6_addr.s6_addr16[1] = htons(ifscope);
+}
+
+static inline unsigned int
+sin6_get_embedded_ifscope(struct sockaddr *sa)
+{
+ /* Caller must pass in sockaddr_in6 */
+ ASSERT_SIN6IFSCOPE(sa);
+
+ return (ntohs(SIN6(sa)->sin6_addr.s6_addr16[1]));
+}
+
+/*
+ * Copy a sockaddr_{in,in6} src to a dst storage and set scope ID into dst.
+ *
+ * To clear the scope ID, pass is a NULL pifscope. To set the scope ID, pass
+ * in a non-NULL pifscope with non-zero ifscope. Otherwise if pifscope is
+ * non-NULL and ifscope is IFSCOPE_NONE, the existing scope ID is left intact.
+ * In any case, the effective scope ID value is returned to the caller via
+ * pifscope, if it is non-NULL.
+ */
+static struct sockaddr *
+sa_copy(struct sockaddr *src, struct sockaddr_storage *dst,
+ unsigned int *pifscope)
+{
+ int af = src->sa_family;
+ unsigned int ifscope = (pifscope != NULL) ? *pifscope : IFSCOPE_NONE;
+
+ VERIFY(af == AF_INET || af == AF_INET6);
+
+ bzero(dst, sizeof (*dst));
+
+ if (af == AF_INET) {
+ bcopy(src, dst, sizeof (struct sockaddr_in));
+ if (pifscope == NULL || ifscope != IFSCOPE_NONE)
+ sin_set_ifscope(SA(dst), ifscope);
+ } else {
+ bcopy(src, dst, sizeof (struct sockaddr_in6));
+ if (pifscope != NULL &&
+ IN6_IS_SCOPE_EMBED(&SIN6(dst)->sin6_addr)) {
+ unsigned int eifscope;
+ /*
+ * If the address contains the embedded scope ID,
+ * use that as the value for sin6_scope_id as long
+ * the caller doesn't insist on clearing it (by
+ * passing NULL) or setting it.
+ */
+ eifscope = sin6_get_embedded_ifscope(SA(dst));
+ if (eifscope != IFSCOPE_NONE && ifscope == IFSCOPE_NONE)
+ ifscope = eifscope;
+ sin6_set_ifscope(SA(dst), ifscope);
+ /*
+ * If sin6_scope_id is set but the address doesn't
+ * contain the equivalent embedded value, set it.
+ */
+ if (ifscope != IFSCOPE_NONE && eifscope != ifscope)
+ sin6_set_embedded_ifscope(SA(dst), ifscope);
+ } else if (pifscope == NULL || ifscope != IFSCOPE_NONE) {
+ sin6_set_ifscope(SA(dst), ifscope);
+ }
+ }
+
+ if (pifscope != NULL) {
+ *pifscope = (af == AF_INET) ? sin_get_ifscope(SA(dst)) :
+ sin6_get_ifscope(SA(dst));
+ }
+
+ return (SA(dst));
+}