]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/net/netsrc.c
xnu-1699.22.73.tar.gz
[apple/xnu.git] / bsd / net / netsrc.c
diff --git a/bsd/net/netsrc.c b/bsd/net/netsrc.c
new file mode 100644 (file)
index 0000000..2c1037c
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2011 Apple Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/kpi_mbuf.h>
+#include <sys/socket.h>
+#include <sys/kern_control.h>
+#include <sys/mcache.h>
+#include <sys/socketvar.h>
+
+#include <kern/debug.h>
+
+#include <libkern/libkern.h>
+
+#include <net/if.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/in_var.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);
+}
+
+static errno_t
+netsrc_ctlconnect(kern_ctl_ref kctl, struct sockaddr_ctl *sac, void **uinfo)
+{
+#pragma unused(kctl, sac, uinfo)
+
+       /*
+        * We don't need to do anything here. This callback is only necessary
+        * for ctl_register() to succeed.
+        */
+       return (0);
+}
+
+static errno_t
+netsrc_ctlsend(kern_ctl_ref kctl, uint32_t unit, void *uinfo, mbuf_t m,
+    int flags)
+{
+#pragma unused(uinfo, flags)
+       errno_t error;
+       struct netsrc_req *nrq, storage;
+
+       if (mbuf_pkthdr_len(m) < sizeof(*nrq)) {
+               error = EINVAL;
+               goto out;
+       }
+       if (mbuf_len(m) >= sizeof(*nrq))
+               nrq = mbuf_data(m);
+       else {
+               mbuf_copydata(m, 0, sizeof(storage), &storage);
+               nrq = &storage;
+       }
+       /* We only have one version right now. */
+       if (nrq->nrq_ver != NETSRC_VERSION1) {
+               error = EINVAL;
+               goto out;
+       }
+       switch (nrq->nrq_sin.sin_family) {
+       case AF_INET:
+               error = netsrc_ipv4(kctl, unit, nrq);
+               break;
+       case AF_INET6:
+               error = netsrc_ipv6(kctl, unit, nrq);
+               break;
+       default:
+               printf("%s: invalid family\n", __func__);
+               error = EINVAL;
+       }
+out:
+       mbuf_freem(m);
+
+       return (error);
+
+}
+
+static errno_t
+netsrc_ipv4(kern_ctl_ref kctl, uint32_t unit, struct netsrc_req *nrq)
+{
+       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 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);
+}
+
+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;
+               }
+       }
+       lck_rw_done(&in6_ifaddr_rwlock);
+       error = ctl_enqueuedata(kctl, unit, &nrp, sizeof(nrp),
+           CTL_DATA_EOR);
+
+       return (error);
+}