#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)
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;
}
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;
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);
ROUTE_RELEASE(&ro);
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;
if (ia->ia6_flags & IN6_IFF_OPTIMISTIC)
nrp.nrp_flags |= NETSRC_IP6_FLAG_OPTIMISTIC;
if (ia->ia6_flags & IN6_IFF_SECURED)
nrp.nrp_flags |= NETSRC_IP6_FLAG_SECURED;
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);
}