X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/6d2010ae8f7a6078e10b361c6962983bab233e0f..b226f5e54a60dc81db17b1260381d7dbfea3cdf1:/bsd/net/netsrc.c diff --git a/bsd/net/netsrc.c b/bsd/net/netsrc.c index 2c1037c26..17f00fead 100644 --- a/bsd/net/netsrc.c +++ b/bsd/net/netsrc.c @@ -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@ * @@ -26,6 +26,19 @@ * @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 + +#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 +#pragma clang diagnostic pop + #include #include #include @@ -48,29 +61,7 @@ #include #include -#include - -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 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); }