netsrc.c   [plain text]


/*
 * 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);
}