]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/net/netsrc.c
xnu-4903.241.1.tar.gz
[apple/xnu.git] / bsd / net / netsrc.c
index 2c1037c262f556a20c1cc16ca58174c9e7a2d681..17f00fead1d630cd11190eb7cf55f4423c50ce76 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Apple Inc. All rights reserved.
+ * Copyright (c) 2011-2017 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
  */
 
+// Include netinet/in.h first. net/netsrc.h depends on netinet/in.h but
+// netinet/in.h doesn't work with -Wpadded, -Wpacked.
+#include <netinet/in.h>
+
+#pragma clang diagnostic push
+#pragma clang diagnostic error "-Wpadded"
+#pragma clang diagnostic error "-Wpacked"
+// This header defines structures shared with user space, so we need to ensure there is
+// no compiler inserted padding in case the user space process isn't using the same
+// architecture as the kernel (example: i386 process with x86_64 kernel).
+#include <net/netsrc.h>
+#pragma clang diagnostic pop
+
 #include <sys/param.h>
 #include <sys/types.h>
 #include <sys/kpi_mbuf.h>
 #include <netinet/ip6.h>
 #include <netinet6/ip6_var.h>
 
-#include <net/netsrc.h>
-
-static errno_t netsrc_ctlsend(kern_ctl_ref, uint32_t, void *, mbuf_t, int);
-static errno_t netsrc_ctlconnect(kern_ctl_ref, struct sockaddr_ctl *, void **);
-static errno_t netsrc_ipv4(kern_ctl_ref, uint32_t, struct netsrc_req *); 
-static errno_t netsrc_ipv6(kern_ctl_ref, uint32_t, struct netsrc_req *);
-
-static kern_ctl_ref    netsrc_ctlref = NULL;
-
-__private_extern__ void
-netsrc_init(void)
-{
-       errno_t error;
-       struct kern_ctl_reg netsrc_ctl = {
-               .ctl_connect = netsrc_ctlconnect,
-               .ctl_send    = netsrc_ctlsend,
-       };
-
-       strlcpy(netsrc_ctl.ctl_name, NETSRC_CTLNAME, sizeof(NETSRC_CTLNAME));
-
-       if ((error = ctl_register(&netsrc_ctl, &netsrc_ctlref)))
-               printf("%s: ctl_register failed %d\n", __func__, error);
-}
+#include <net/ntstat.h>
 
 static errno_t
 netsrc_ctlconnect(kern_ctl_ref kctl, struct sockaddr_ctl *sac, void **uinfo)
@@ -84,6 +75,198 @@ netsrc_ctlconnect(kern_ctl_ref kctl, struct sockaddr_ctl *sac, void **uinfo)
        return (0);
 }
 
+static errno_t
+netsrc_reply(kern_ctl_ref kctl, uint32_t unit, uint16_t version,
+                        struct netsrc_rep *reply)
+{
+       switch (version) {
+               case NETSRC_CURVERS:
+                       return ctl_enqueuedata(kctl, unit, reply,
+                                                                  sizeof(*reply), CTL_DATA_EOR);
+               case NETSRC_VERSION1: {
+                       if ((reply->nrp_flags & NETSRC_FLAG_ROUTEABLE) == 0) {
+                               return EHOSTUNREACH;
+                       }
+#define NETSRC_FLAG_V1_MASK (NETSRC_IP6_FLAG_TENTATIVE | \
+                                                        NETSRC_IP6_FLAG_TEMPORARY | \
+                                                        NETSRC_IP6_FLAG_DEPRECATED | \
+                                                        NETSRC_IP6_FLAG_OPTIMISTIC | \
+                                                        NETSRC_IP6_FLAG_SECURED)
+                       struct netsrc_repv1 v1 = {
+                               .nrp_src = reply->nrp_src,
+                               .nrp_flags = (reply->nrp_flags & NETSRC_FLAG_V1_MASK),
+                               .nrp_label = reply->nrp_label,
+                               .nrp_precedence = reply->nrp_precedence,
+                               .nrp_dstlabel = reply->nrp_dstlabel,
+                               .nrp_dstprecedence = reply->nrp_dstprecedence
+                       };
+                       return ctl_enqueuedata(kctl, unit, &v1, sizeof(v1), CTL_DATA_EOR);
+               }
+       }
+       return EINVAL;
+}
+
+static void
+netsrc_common(struct rtentry *rt, struct netsrc_rep *reply)
+{
+       if (!rt) {
+               return;
+       }
+
+       // Gather statistics information
+       struct nstat_counts     *rt_stats = rt->rt_stats;
+       if (rt_stats) {
+               reply->nrp_min_rtt = rt_stats->nstat_min_rtt;
+               reply->nrp_connection_attempts = rt_stats->nstat_connectattempts;
+               reply->nrp_connection_successes = rt_stats->nstat_connectsuccesses;
+       }
+
+       // If this route didn't have any stats, check its parent
+       if (reply->nrp_min_rtt == 0) {
+               // Is this lock necessary?
+               RT_LOCK(rt);
+               if (rt->rt_parent) {
+                       rt_stats = rt->rt_parent->rt_stats;
+                       if (rt_stats) {
+                               reply->nrp_min_rtt = rt_stats->nstat_min_rtt;
+                               reply->nrp_connection_attempts = rt_stats->nstat_connectattempts;
+                               reply->nrp_connection_successes = rt_stats->nstat_connectsuccesses;
+                       }
+               }
+               RT_UNLOCK(rt);
+       }
+       reply->nrp_ifindex = rt->rt_ifp ? rt->rt_ifp->if_index : 0;
+
+       if (rt->rt_ifp->if_eflags & IFEF_AWDL) {
+               reply->nrp_flags |= NETSRC_FLAG_AWDL;
+       }
+       if (rt->rt_flags & RTF_LOCAL) {
+               reply->nrp_flags |= NETSRC_FLAG_DIRECT;
+       } else if (!(rt->rt_flags & RTF_GATEWAY) &&
+                          (rt->rt_ifa && rt->rt_ifa->ifa_ifp &&
+                          !(rt->rt_ifa->ifa_ifp->if_flags & IFF_POINTOPOINT))) {
+               reply->nrp_flags |= NETSRC_FLAG_DIRECT;
+       }
+}
+
+static struct in6_addrpolicy *
+lookup_policy(struct sockaddr* sa)
+{
+       // alignment fun - if sa_family is AF_INET or AF_INET6, this is one of those
+       // addresses and it should be aligned, so this should be safe.
+       union sockaddr_in_4_6 *addr = (union sockaddr_in_4_6 *)(void*)sa;
+       if (addr->sa.sa_family == AF_INET6) {
+               return in6_addrsel_lookup_policy(&addr->sin6);
+       } else if (sa->sa_family == AF_INET) {
+               struct sockaddr_in6 mapped = {
+                       .sin6_family = AF_INET6,
+                       .sin6_len = sizeof(mapped),
+                       .sin6_addr = IN6ADDR_V4MAPPED_INIT,
+               };
+               mapped.sin6_addr.s6_addr32[3] = addr->sin.sin_addr.s_addr;
+               return in6_addrsel_lookup_policy(&mapped);
+       }
+       return NULL;
+}
+
+static void
+netsrc_policy_common(struct netsrc_req *request, struct netsrc_rep *reply)
+{
+       // Destination policy
+       struct in6_addrpolicy *policy = lookup_policy(&request->nrq_dst.sa);
+       if (policy != NULL && policy->label != -1) {
+               reply->nrp_dstlabel = policy->label;
+               reply->nrp_dstprecedence = policy->preced;
+       }
+
+       // Source policy
+       policy = lookup_policy(&reply->nrp_src.sa);
+       if (policy != NULL && policy->label != -1) {
+               reply->nrp_label = policy->label;
+               reply->nrp_precedence = policy->preced;
+       }
+}
+
+static errno_t
+netsrc_ipv6(kern_ctl_ref kctl, uint32_t unit, struct netsrc_req *request)
+{
+       struct route_in6 ro = {
+               .ro_dst = request->nrq_sin6,
+       };
+
+       int error = 0;
+       struct in6_addr storage, *in6 = in6_selectsrc(&request->nrq_sin6, NULL,
+                                                                                                 NULL, &ro, NULL, &storage,
+                                                                                                 request->nrq_ifscope, &error);
+       struct netsrc_rep reply = {
+               .nrp_sin6.sin6_family = AF_INET6,
+               .nrp_sin6.sin6_len = sizeof(reply.nrp_sin6),
+               .nrp_sin6.sin6_addr = in6 ? *in6 : (struct in6_addr){},
+       };
+       netsrc_common(ro.ro_rt, &reply);
+       if (ro.ro_srcia == NULL && in6 != NULL) {
+               ro.ro_srcia = (struct ifaddr *)ifa_foraddr6_scoped(in6, reply.nrp_ifindex);
+       }
+       if (ro.ro_srcia) {
+               struct in6_ifaddr *ia = (struct in6_ifaddr *)ro.ro_srcia;
+#define IA_TO_NRP_FLAG(flag)   \
+               if (ia->ia6_flags & IN6_IFF_##flag) {                   \
+                       reply.nrp_flags |= NETSRC_FLAG_IP6_##flag;      \
+               }
+               IA_TO_NRP_FLAG(TENTATIVE);
+               IA_TO_NRP_FLAG(TEMPORARY);
+               IA_TO_NRP_FLAG(DEPRECATED);
+               IA_TO_NRP_FLAG(OPTIMISTIC);
+               IA_TO_NRP_FLAG(SECURED);
+               IA_TO_NRP_FLAG(DYNAMIC);
+               IA_TO_NRP_FLAG(AUTOCONF);
+#undef IA_TO_NRP_FLAG
+               reply.nrp_flags |= NETSRC_FLAG_ROUTEABLE;
+       }
+       ROUTE_RELEASE(&ro);
+       netsrc_policy_common(request, &reply);
+       return netsrc_reply(kctl, unit, request->nrq_ver, &reply);
+}
+
+static errno_t
+netsrc_ipv4(kern_ctl_ref kctl, uint32_t unit, struct netsrc_req *request)
+{
+       // Unfortunately, IPv4 doesn't have a function like in6_selectsrc
+       // Look up the route
+       lck_mtx_lock(rnh_lock);
+       struct rtentry *rt = rt_lookup(TRUE, &request->nrq_dst.sa,
+                                                                  NULL, rt_tables[AF_INET],
+                                                                  request->nrq_ifscope);
+       lck_mtx_unlock(rnh_lock);
+
+       // Look up the ifa
+       struct netsrc_rep reply = {};
+       if (rt) {
+               struct in_ifaddr *ia = NULL;
+               lck_rw_lock_shared(in_ifaddr_rwlock);
+               TAILQ_FOREACH(ia, &in_ifaddrhead, ia_link) {
+                       IFA_LOCK_SPIN(&ia->ia_ifa);
+                       if (ia->ia_ifp == rt->rt_ifp) {
+                               IFA_ADDREF_LOCKED(&ia->ia_ifa);
+                               break;
+                       }
+                       IFA_UNLOCK(&ia->ia_ifa);
+               }
+               lck_rw_done(in_ifaddr_rwlock);
+
+               if (ia) {
+                       reply.nrp_sin = *IA_SIN(ia);
+                       IFA_REMREF_LOCKED(&ia->ia_ifa);
+                       IFA_UNLOCK(&ia->ia_ifa);
+                       reply.nrp_flags |= NETSRC_FLAG_ROUTEABLE;
+               }
+               netsrc_common(rt, &reply);
+               rtfree(rt);
+       }
+       netsrc_policy_common(request, &reply);
+       return netsrc_reply(kctl, unit, request->nrq_ver, &reply);
+}
+
 static errno_t
 netsrc_ctlsend(kern_ctl_ref kctl, uint32_t unit, void *uinfo, mbuf_t m,
     int flags)
@@ -102,17 +285,26 @@ netsrc_ctlsend(kern_ctl_ref kctl, uint32_t unit, void *uinfo, mbuf_t m,
                mbuf_copydata(m, 0, sizeof(storage), &storage);
                nrq = &storage;
        }
-       /* We only have one version right now. */
-       if (nrq->nrq_ver != NETSRC_VERSION1) {
+       if (nrq->nrq_ver > NETSRC_CURVERS) {
                error = EINVAL;
                goto out;
        }
        switch (nrq->nrq_sin.sin_family) {
        case AF_INET:
-               error = netsrc_ipv4(kctl, unit, nrq);
+               if (nrq->nrq_sin.sin_len < sizeof (nrq->nrq_sin) ||
+                       nrq->nrq_sin.sin_addr.s_addr == INADDR_ANY) {
+                       error = EINVAL;
+               } else {
+                       error = netsrc_ipv4(kctl, unit, nrq);
+               }
                break;
        case AF_INET6:
-               error = netsrc_ipv6(kctl, unit, nrq);
+               if (nrq->nrq_sin6.sin6_len < sizeof(nrq->nrq_sin6) ||
+                       IN6_IS_ADDR_UNSPECIFIED(&nrq->nrq_sin6.sin6_addr)) {
+                       error = EINVAL;
+               } else {
+                       error = netsrc_ipv6(kctl, unit, nrq);
+               }
                break;
        default:
                printf("%s: invalid family\n", __func__);
@@ -125,129 +317,19 @@ out:
 
 }
 
-static errno_t
-netsrc_ipv4(kern_ctl_ref kctl, uint32_t unit, struct netsrc_req *nrq)
+__private_extern__ void
+netsrc_init(void)
 {
-       errno_t error = EHOSTUNREACH;
-       struct sockaddr_in *dstsin;
-       struct rtentry *rt;
-       struct in_ifaddr *ia;
-       struct netsrc_rep nrp;
-       struct sockaddr_in6 v4entry = {
-               .sin6_family = AF_INET6,
-               .sin6_len = sizeof(struct sockaddr_in6),
-               .sin6_addr = IN6ADDR_V4MAPPED_INIT,
+       struct kern_ctl_reg netsrc_ctl = {
+               .ctl_connect = netsrc_ctlconnect,
+               .ctl_send    = netsrc_ctlsend,
        };
-       struct in6_addrpolicy *policy;
-
-       dstsin = &nrq->nrq_sin;
-
-       if (dstsin->sin_len < sizeof (*dstsin) ||
-           dstsin->sin_addr.s_addr == INADDR_ANY)
-               return (EINVAL);
-
-       lck_mtx_lock(rnh_lock);
-       rt = rt_lookup(TRUE, (struct sockaddr *)dstsin, NULL,
-           rt_tables[AF_INET], nrq->nrq_ifscope);
-       lck_mtx_unlock(rnh_lock);
-       if (!rt)
-               return (EHOSTUNREACH);
-       lck_rw_lock_shared(in_ifaddr_rwlock);
-       TAILQ_FOREACH(ia, &in_ifaddrhead, ia_link) {
-               IFA_LOCK_SPIN(&ia->ia_ifa);
-               if (ia->ia_ifp == rt->rt_ifp) {
-                       memset(&nrp, 0, sizeof(nrp));
-                       memcpy(&nrp.nrp_sin, IA_SIN(ia), sizeof(nrp.nrp_sin));
-                       IFA_UNLOCK(&ia->ia_ifa);
-                       v4entry.sin6_addr.s6_addr32[3] =
-                           nrp.nrp_sin.sin_addr.s_addr;
-                       policy = in6_addrsel_lookup_policy(&v4entry);
-                       if (policy->label != -1) {
-                               nrp.nrp_label = policy->label;
-                               nrp.nrp_precedence = policy->preced;
-                               /* XXX might not be true */
-                               nrp.nrp_dstlabel = policy->label;
-                               nrp.nrp_dstprecedence = policy->preced;
-                       }
-                       error = ctl_enqueuedata(kctl, unit, &nrp,
-                           sizeof(nrp), CTL_DATA_EOR);
-                       break;
-               }
-               IFA_UNLOCK(&ia->ia_ifa);
-       }
-       lck_rw_done(in_ifaddr_rwlock);
-       if (rt)
-               rtfree(rt);
 
-       return (error);
-}
+       strlcpy(netsrc_ctl.ctl_name, NETSRC_CTLNAME, sizeof(netsrc_ctl.ctl_name));
 
-static errno_t
-netsrc_ipv6(kern_ctl_ref kctl, uint32_t unit, struct netsrc_req *nrq)
-{
-       struct sockaddr_in6 *dstsin6;
-       struct in6_addr *in6, storage;
-       struct in6_ifaddr *ia;
-       struct route_in6 ro;
-       int error = EHOSTUNREACH;
-       struct netsrc_rep nrp;
-
-       dstsin6 = &nrq->nrq_sin6;
-
-       if (dstsin6->sin6_len < sizeof (*dstsin6) ||
-           IN6_IS_ADDR_UNSPECIFIED(&dstsin6->sin6_addr))
-               return (EINVAL);
-
-       memset(&ro, 0, sizeof(ro));
-       lck_mtx_lock(rnh_lock);
-       ro.ro_rt = rt_lookup(TRUE, (struct sockaddr *)dstsin6, NULL,
-           rt_tables[AF_INET6], nrq->nrq_ifscope);
-       lck_mtx_unlock(rnh_lock);
-       if (!ro.ro_rt)
-               return (EHOSTUNREACH);
-       in6 = in6_selectsrc(dstsin6, NULL, NULL, &ro, NULL, &storage,
-           nrq->nrq_ifscope, &error);
-       if (ro.ro_rt)
-               rtfree(ro.ro_rt);
-       if (!in6 || error)
-               return (error);
-       memset(&nrp, 0, sizeof(nrp));
-       nrp.nrp_sin6.sin6_family = AF_INET6;
-       nrp.nrp_sin6.sin6_len    = sizeof(nrp.nrp_sin6);
-       memcpy(&nrp.nrp_sin6.sin6_addr, in6, sizeof(nrp.nrp_sin6.sin6_addr));
-       lck_rw_lock_shared(&in6_ifaddr_rwlock);
-       for (ia = in6_ifaddrs; ia; ia = ia->ia_next) {
-               if (memcmp(&ia->ia_addr.sin6_addr, in6, sizeof(*in6)) == 0) {
-                       struct sockaddr_in6 sin6;
-                       struct in6_addrpolicy *policy;
-
-                       if (ia->ia6_flags & IN6_IFF_TEMPORARY)
-                               nrp.nrp_flags |= NETSRC_IP6_FLAG_TEMPORARY;
-                       if (ia->ia6_flags & IN6_IFF_TENTATIVE)
-                               nrp.nrp_flags |= NETSRC_IP6_FLAG_TENTATIVE;
-                       if (ia->ia6_flags & IN6_IFF_DEPRECATED)
-                               nrp.nrp_flags |= NETSRC_IP6_FLAG_DEPRECATED;
-                       sin6.sin6_family = AF_INET6;
-                       sin6.sin6_len    = sizeof(sin6);
-                       memcpy(&sin6.sin6_addr, in6, sizeof(*in6));
-                       policy = in6_addrsel_lookup_policy(&sin6);
-                       if (policy->label != -1) {
-                               nrp.nrp_label = policy->label;
-                               nrp.nrp_precedence = policy->preced;
-                       }
-                       memcpy(&sin6.sin6_addr, &dstsin6->sin6_addr,
-                           sizeof(dstsin6->sin6_addr));
-                       policy = in6_addrsel_lookup_policy(&sin6);
-                       if (policy->label != -1) {
-                               nrp.nrp_dstlabel = policy->label;
-                               nrp.nrp_dstprecedence = policy->preced;
-                       }
-                       break;
-               }
+       static kern_ctl_ref     netsrc_ctlref = NULL;
+       errno_t error = ctl_register(&netsrc_ctl, &netsrc_ctlref);
+       if (error != 0) {
+               printf("%s: ctl_register failed %d\n", __func__, error);
        }
-       lck_rw_done(&in6_ifaddr_rwlock);
-       error = ctl_enqueuedata(kctl, unit, &nrp, sizeof(nrp),
-           CTL_DATA_EOR);
-
-       return (error);
 }