+static ip6_check_if_result_t
+ip6_input_check_interface(struct mbuf *m, struct ip6_hdr *ip6, struct ifnet *inifp, struct route_in6 *rin6, struct ifnet **deliverifp)
+{
+ struct in6_ifaddr *ia6 = NULL;
+ struct in6_addr tmp_dst = ip6->ip6_dst; /* copy to avoid unaligned access */
+ struct in6_ifaddr *best_ia6 = NULL;
+ ip6_check_if_result_t result = IP6_CHECK_IF_NONE;
+
+ *deliverifp = NULL;
+
+ /*
+ * Check for exact addresses in the hash bucket.
+ */
+ lck_rw_lock_shared(&in6_ifaddr_rwlock);
+ TAILQ_FOREACH(ia6, IN6ADDR_HASH(&tmp_dst), ia6_hash) {
+ /*
+ * TODO: should we accept loopbacl
+ */
+ if (IN6_ARE_ADDR_EQUAL(&ia6->ia_addr.sin6_addr, &tmp_dst)) {
+ if ((ia6->ia6_flags & (IN6_IFF_NOTREADY | IN6_IFF_CLAT46))) {
+ continue;
+ }
+ best_ia6 = ia6;
+ if (ia6->ia_ifp == inifp) {
+ /*
+ * TODO: should we also accept locally originated packets
+ * or from loopback ???
+ */
+ break;
+ }
+ /*
+ * Continue the loop in case there's a exact match with another
+ * interface
+ */
+ }
+ }
+ if (best_ia6 != NULL) {
+ if (best_ia6->ia_ifp != inifp && ip6_forwarding == 0 &&
+ ((ip6_checkinterface == IP6_CHECKINTERFACE_HYBRID_ES &&
+ (best_ia6->ia_ifp->if_family == IFNET_FAMILY_IPSEC ||
+ best_ia6->ia_ifp->if_family == IFNET_FAMILY_UTUN)) ||
+ ip6_checkinterface == IP6_CHECKINTERFACE_STRONG_ES)) {
+ /*
+ * Drop when interface address check is strict and forwarding
+ * is disabled
+ */
+ result = IP6_CHECK_IF_DROP;
+ } else {
+ result = IP6_CHECK_IF_OURS;
+ *deliverifp = best_ia6->ia_ifp;
+ ip6_setdstifaddr_info(m, 0, best_ia6);
+ }
+ }
+ lck_rw_done(&in6_ifaddr_rwlock);
+
+ if (result == IP6_CHECK_IF_NONE) {
+ /*
+ * Slow path: route lookup.
+ */
+ struct sockaddr_in6 *dst6;
+
+ dst6 = SIN6(&rin6->ro_dst);
+ dst6->sin6_len = sizeof(struct sockaddr_in6);
+ dst6->sin6_family = AF_INET6;
+ dst6->sin6_addr = ip6->ip6_dst;
+
+ rtalloc_scoped_ign((struct route *)rin6,
+ RTF_PRCLONING, IFSCOPE_NONE);
+ if (rin6->ro_rt != NULL) {
+ RT_LOCK_SPIN(rin6->ro_rt);
+ }
+
+#define rt6_key(r) (SIN6((r)->rt_nodes->rn_key))
+
+ /*
+ * Accept the packet if the forwarding interface to the destination
+ * according to the routing table is the loopback interface,
+ * unless the associated route has a gateway.
+ * Note that this approach causes to accept a packet if there is a
+ * route to the loopback interface for the destination of the packet.
+ * But we think it's even useful in some situations, e.g. when using
+ * a special daemon which wants to intercept the packet.
+ *
+ * XXX: some OSes automatically make a cloned route for the destination
+ * of an outgoing packet. If the outgoing interface of the packet
+ * is a loopback one, the kernel would consider the packet to be
+ * accepted, even if we have no such address assinged on the interface.
+ * We check the cloned flag of the route entry to reject such cases,
+ * assuming that route entries for our own addresses are not made by
+ * cloning (it should be true because in6_addloop explicitly installs
+ * the host route). However, we might have to do an explicit check
+ * while it would be less efficient. Or, should we rather install a
+ * reject route for such a case?
+ */
+ if (rin6->ro_rt != NULL &&
+ (rin6->ro_rt->rt_flags & (RTF_HOST | RTF_GATEWAY)) == RTF_HOST &&
+#if RTF_WASCLONED
+ !(rin6->ro_rt->rt_flags & RTF_WASCLONED) &&
+#endif
+ rin6->ro_rt->rt_ifp->if_type == IFT_LOOP) {
+ ia6 = (struct in6_ifaddr *)rin6->ro_rt->rt_ifa;
+ /*
+ * Packets to a tentative, duplicated, or somehow invalid
+ * address must not be accepted.
+ *
+ * For performance, test without acquiring the address lock;
+ * a lot of things in the address are set once and never
+ * changed (e.g. ia_ifp.)
+ */
+ if (!(ia6->ia6_flags & IN6_IFF_NOTREADY)) {
+ /* this address is ready */
+ result = IP6_CHECK_IF_OURS;
+ *deliverifp = ia6->ia_ifp; /* correct? */
+ /*
+ * record dst address information into mbuf.
+ */
+ (void) ip6_setdstifaddr_info(m, 0, ia6);
+ }
+ }
+
+ if (rin6->ro_rt != NULL) {
+ RT_UNLOCK(rin6->ro_rt);
+ }
+ }
+
+ if (result == IP6_CHECK_IF_NONE) {
+ if (ip6_forwarding == 0) {
+ result = IP6_CHECK_IF_DROP;
+ } else {
+ result = IP6_CHECK_IF_FORWARD;
+ ip6_setdstifaddr_info(m, inifp->if_index, NULL);
+ }
+ }
+
+ if (result == IP6_CHECK_IF_OURS && *deliverifp != inifp) {
+ ASSERT(*deliverifp != NULL);
+ ip6stat.ip6s_rcv_if_weak_match++;
+
+ /* Logging is too noisy when forwarding is enabled */
+ if (ip6_checkinterface_debug != IP6_CHECKINTERFACE_WEAK_ES && ip6_forwarding != 0) {
+ char src_str[MAX_IPv6_STR_LEN];
+ char dst_str[MAX_IPv6_STR_LEN];
+
+ inet_ntop(AF_INET6, &ip6->ip6_src, src_str, sizeof(src_str));
+ inet_ntop(AF_INET6, &ip6->ip6_dst, dst_str, sizeof(dst_str));
+ os_log_info(OS_LOG_DEFAULT,
+ "%s: weak ES interface match to %s for packet from %s to %s proto %u received via %s",
+ __func__, (*deliverifp)->if_xname, src_str, dst_str, ip6->ip6_nxt, inifp->if_xname);
+ }
+ } else if (result == IP6_CHECK_IF_DROP) {
+ ip6stat.ip6s_rcv_if_no_match++;
+ if (ip6_checkinterface_debug > 0) {
+ char src_str[MAX_IPv6_STR_LEN];
+ char dst_str[MAX_IPv6_STR_LEN];
+
+ inet_ntop(AF_INET6, &ip6->ip6_src, src_str, sizeof(src_str));
+ inet_ntop(AF_INET6, &ip6->ip6_dst, dst_str, sizeof(dst_str));
+ os_log(OS_LOG_DEFAULT,
+ "%s: no interface match for packet from %s to %s proto %u received via %s",
+ __func__, src_str, dst_str, ip6->ip6_nxt, inifp->if_xname);
+ }
+ }
+
+ return result;
+}