/*
 * Copyright (c) 2010-2021 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 <string.h>
#include <stdint.h>
#include <stdbool.h>

#include <mach/mach_types.h>

#include <kern/kalloc.h>
#include <kern/locks.h>
#include <kern/debug.h>

#include <sys/kernel.h>
#include <sys/param.h>
#include <sys/sockio.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/cdefs.h>
#include <sys/kern_control.h>
#include <sys/mbuf.h>
#include <sys/sysctl.h>

#include <net/if_types.h>
#include <net/if.h>
#include <net/kpi_interface.h>
#include <net/bpf.h>
#include <net/remote_vif.h>

#include <libkern/libkern.h>
#include <libkern/OSAtomic.h>

#include <os/log.h>

#define RVI_IF_NAME            "rvi"

#define RVI_DIR_IN              IFF_LINK0
#define RVI_DIR_OUT             IFF_LINK1
#define RVI_DIR_INOUT           (RVI_DIR_IN | RVI_DIR_OUT)

#define RVI_IF_FAMILY           IFNET_FAMILY_LOOPBACK
#define RVI_IF_TYPE             IFT_OTHER
#define RVI_IF_FLAGS            (IFF_UP | IFF_DEBUG | RVI_DIR_INOUT)

struct rvi_client_t {
	LIST_ENTRY(rvi_client_t)        _cle;
	ifnet_t                         _ifp;
	uint32_t                        _unit;
	uint32_t                        _vif;
	uint32_t                        _raw_count;
	uint32_t                        _pktap_count;
};

static LIST_HEAD(, rvi_client_t)        _s_rvi_clients;

static LCK_GRP_DECLARE(rvi_grp, "remote virtual interface lock");
static LCK_RW_DECLARE(rvi_mtx, &rvi_grp);

static kern_ctl_ref     rvi_kernctl = NULL;

kern_return_t           rvi_start(kmod_info_t *, void *);
kern_return_t           rvi_stop(kmod_info_t *, void *);

static void             rvi_insert_client(struct rvi_client_t *);
static errno_t          rvi_create_if(struct rvi_client_t *);
static errno_t          rvi_destroy_if(struct rvi_client_t *);

static inline void      rvi_lock_shared(lck_rw_t *);
static inline void      rvi_lock_exclusive(lck_rw_t *);
static inline void      rvi_lock_done_shared(lck_rw_t *);
static inline void      rvi_lock_done_exclusive(lck_rw_t *);

static errno_t          rvi_output(ifnet_t, mbuf_t);
static errno_t          rvi_demux(ifnet_t, mbuf_t, char *, protocol_family_t *);
static errno_t          rvi_ioctl(ifnet_t, unsigned long, void *);
static errno_t          rvi_add_proto(ifnet_t, protocol_family_t, const struct ifnet_demux_desc *, uint32_t);
static errno_t          rvi_del_proto(ifnet_t, protocol_family_t);
static errno_t          rvi_set_bpf_tap(ifnet_t, uint32_t, bpf_tap_mode);
static void             rvi_detach(ifnet_t);

static errno_t          rvi_bpf_tap(ifnet_t, mbuf_t, int, struct rvi_client_t *, struct pktap_header *);

static errno_t          rvi_register_control(void);
static errno_t          rvi_ctl_connect(kern_ctl_ref, struct sockaddr_ctl *, void **);
static errno_t          rvi_ctl_send(kern_ctl_ref, uint32_t, void *, mbuf_t, int);
static errno_t          rvi_ctl_disconnect(kern_ctl_ref, uint32_t, void *);
static errno_t          rvi_ctl_getopt(kern_ctl_ref, uint32_t, void *, int, void *__sized_by(*len), size_t *len);

int
rvi_init()
{
	int error = 0;

	if ((error = rvi_register_control()) != 0) {
		os_log(OS_LOG_DEFAULT, "rvi_start failed: rvi_register_control failure");
		return error;
	}

	return 0;
}

static inline void
rvi_lock_shared(lck_rw_t *mtx)
{
	lck_rw_lock_shared(mtx);
}

static inline void
rvi_lock_exclusive(lck_rw_t *mtx)
{
	lck_rw_lock_exclusive(mtx);
}

static inline void
rvi_lock_done_shared(lck_rw_t *mtx)
{
	lck_rw_unlock_shared(mtx);
}

static inline void
rvi_lock_done_exclusive(lck_rw_t *mtx)
{
	lck_rw_unlock_exclusive(mtx);
}

static errno_t
rvi_create_if(struct rvi_client_t *client)
{
	errno_t err = 0;
	struct ifnet_init_params rvi_ifinit;

	memset(&rvi_ifinit, 0x0, sizeof(rvi_ifinit));
	rvi_ifinit.name = RVI_IF_NAME;
	rvi_ifinit.unit = client->_vif;
	rvi_ifinit.type = RVI_IF_TYPE;
	rvi_ifinit.family = RVI_IF_FAMILY;
	rvi_ifinit.output = rvi_output;
	rvi_ifinit.demux = rvi_demux;
	rvi_ifinit.add_proto = rvi_add_proto;
	rvi_ifinit.del_proto = rvi_del_proto;
	rvi_ifinit.ioctl = rvi_ioctl;
	rvi_ifinit.detach = rvi_detach;
	rvi_ifinit.softc = client;

	err = ifnet_allocate(&rvi_ifinit, &client->_ifp);
	if (err != 0) {
		os_log(OS_LOG_DEFAULT, "%s: ifnet_allocate for %s%d failed - %d",
		    __func__, RVI_IF_NAME, client->_vif, err);
		goto done;
	}

	ifnet_set_flags(client->_ifp, RVI_IF_FLAGS, RVI_IF_FLAGS);

	err = ifnet_attach(client->_ifp, NULL);
	if (err != 0) {
		os_log(OS_LOG_DEFAULT, "%s: ifnet_attach for %s%d failed - %d",
		    __func__, RVI_IF_NAME, client->_vif, err);
		ifnet_release(client->_ifp);
		goto done;
	}

	bpf_attach(client->_ifp, DLT_PKTAP, sizeof(struct pktap_header), NULL,
	    rvi_set_bpf_tap);
	bpf_attach(client->_ifp, DLT_RAW, 0, NULL, rvi_set_bpf_tap);
done:
	return err;
}

static errno_t
rvi_destroy_if(struct rvi_client_t *client)
{
	errno_t err = 0;

	if (client == NULL) {
		goto done;
	}

	err = ifnet_detach(client->_ifp);
	if (err != 0) {
		os_log(OS_LOG_DEFAULT, "%s: ifnet_detach for %s%d failed - %d",
		    __func__, RVI_IF_NAME, client->_vif, err);
	}
done:
	return err;
}

static void
rvi_detach(ifnet_t ifp)
{
	struct rvi_client_t *__single client;

	rvi_lock_exclusive(&rvi_mtx);

	client = ifnet_softc(ifp);
	LIST_REMOVE(client, _cle);

	ifnet_release(ifp);

	rvi_lock_done_exclusive(&rvi_mtx);

	kfree_type(struct rvi_client_t, client);
}

static void
rvi_insert_client(struct rvi_client_t *client)
{
	struct rvi_client_t *__single itr = NULL;
	uint32_t ph = 0;

	rvi_lock_exclusive(&rvi_mtx);

	if (LIST_EMPTY(&_s_rvi_clients)) {
		LIST_INSERT_HEAD(&_s_rvi_clients, client, _cle);
	} else {
		LIST_FOREACH(itr, &_s_rvi_clients, _cle) {
			if (ph != itr->_vif) {
				LIST_INSERT_BEFORE(itr, client, _cle);
				break;
			}

			ph++;

			if (LIST_NEXT(itr, _cle) == NULL) {
				LIST_INSERT_AFTER(itr, client, _cle);
				break;
			}
		}
	}

	rvi_lock_done_exclusive(&rvi_mtx);

	client->_vif = ph;
}

static void
rvi_remove_client(uint32_t unit)
{
	struct rvi_client_t *__single client = NULL;

	rvi_lock_shared(&rvi_mtx);

	LIST_FOREACH(client, &_s_rvi_clients, _cle) {
		if (client->_unit == unit) {
			break;
		}
	}

	rvi_lock_done_shared(&rvi_mtx);

	if (client == NULL) {
		panic("rvi_ctl_disconnect: received a disconnect notification without a cache entry");
	}

	(void)rvi_destroy_if(client);
}


static errno_t
rvi_register_control(void)
{
	errno_t err = 0;
	struct kern_ctl_reg kern_ctl;

	bzero(&kern_ctl, sizeof(kern_ctl));
	strlcpy(kern_ctl.ctl_name, RVI_CONTROL_NAME, sizeof(kern_ctl.ctl_name));
	kern_ctl.ctl_name[sizeof(kern_ctl.ctl_name) - 1] = 0;
	kern_ctl.ctl_flags = CTL_FLAG_PRIVILEGED;
	kern_ctl.ctl_sendsize = RVI_BUFFERSZ;
	kern_ctl.ctl_recvsize = RVI_BUFFERSZ;
	kern_ctl.ctl_connect = rvi_ctl_connect;
	kern_ctl.ctl_disconnect = rvi_ctl_disconnect;
	kern_ctl.ctl_send = rvi_ctl_send;
	kern_ctl.ctl_setopt = NULL;
	kern_ctl.ctl_getopt = rvi_ctl_getopt;

	err = ctl_register(&kern_ctl, &rvi_kernctl);

	return err;
}

static errno_t
rvi_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac, void **unitinfo)
{
#pragma unused(kctlref)
	errno_t err = 0;
	struct rvi_client_t *__single client = NULL;

	client = kalloc_type(struct rvi_client_t, Z_WAITOK | Z_ZERO | Z_NOFAIL);

	client->_unit = sac->sc_unit;
	rvi_insert_client(client);

	err = rvi_create_if(client);
	if (err != 0) {
		os_log(OS_LOG_DEFAULT, "%s: failure to create virtual interface %d",
		    __func__, err);
	}
	*unitinfo = client;

	return err;
}

static errno_t
rvi_ctl_disconnect(kern_ctl_ref kctlref, uint32_t unit, void *unitinfo)
{
#pragma unused(kctlref)
#pragma unused(unitinfo)
	errno_t err = 0;

	rvi_remove_client(unit);

	return err;
}

static errno_t
rvi_ctl_getopt(kern_ctl_ref kctlref, uint32_t unit, void *unitinfo,
    int opt, void *__sized_by(*len) data, size_t *len)
{
#pragma unused(kctlref)
#pragma unused(unit)
	errno_t err = 0;
	int n;
	struct rvi_client_t *__single client = (struct rvi_client_t *)unitinfo;

	rvi_lock_shared(&rvi_mtx);

	switch (opt) {
	case RVI_COMMAND_GET_INTERFACE:
		if (data == NULL || len == NULL) {
			err = EINVAL;
			break;
		}
		n = scnprintf(data, *len, "%s%u", ifnet_name(client->_ifp),
		    ifnet_unit(client->_ifp));

		*len = n + 1;
		break;

	case RVI_COMMAND_VERSION:
		if (data == NULL || len == NULL || *len < sizeof(int)) {
			err = EINVAL;
			break;
		}
		*(int *)data = RVI_VERSION_CURRENT;
		*len = sizeof(int);
		break;

	default:
		err = ENOPROTOOPT;
		break;
	}

	rvi_lock_done_shared(&rvi_mtx);

	return err;
}

static errno_t
rvi_ctl_send(kern_ctl_ref kctlref, uint32_t unit, void *unitinfo, mbuf_t m, int flags)
{
#pragma unused(kctlref)
#pragma unused(unit)
#pragma unused(flags)
	errno_t err = 0;
	struct rvi_client_t *__single client = (struct rvi_client_t *)unitinfo;
	struct pktap_header pktap_hdr;
	uint32_t hdr_length;

	err = mbuf_copydata(m, 0, sizeof(struct pktap_header), (void *)&pktap_hdr);
	if (err != 0) {
		os_log(OS_LOG_DEFAULT, "%s: mbuf_copydata failed %d", __func__, err);
		goto done;
	}
	hdr_length = pktap_hdr.pth_length;

	mbuf_adj(m, hdr_length);

	rvi_lock_shared(&rvi_mtx);

	err = rvi_bpf_tap(client->_ifp, m,
	    pktap_hdr.pth_flags & PTH_FLAG_DIR_OUT ? 1 : 0,
	    client, &pktap_hdr);

	rvi_lock_done_shared(&rvi_mtx);
done:
	mbuf_freem(m);
	return err;
}

static errno_t
rvi_output(ifnet_t ifp, mbuf_t m)
{
#pragma unused(ifp)

	mbuf_freem(m);
	return 0;
}

static errno_t
rvi_demux(ifnet_t ifp, mbuf_t m, char *header, protocol_family_t *ppf)
{
#pragma unused(ifp)
#pragma unused(m)
#pragma unused(header)
#pragma unused(ppf)

	return ENOTSUP;
}

static errno_t
rvi_add_proto( ifnet_t ifp, protocol_family_t pf,
    const struct ifnet_demux_desc *dmx, uint32_t cnt)
{
#pragma unused(ifp)
#pragma unused(pf)
#pragma unused(dmx)
#pragma unused(cnt)

	return EINVAL;
}

static errno_t
rvi_del_proto(ifnet_t ifp, protocol_family_t pf)
{
#pragma unused(ifp)
#pragma unused(pf)

	return EINVAL;
}

static errno_t
rvi_ioctl(ifnet_t ifp, unsigned long cmd, void *data)
{
#pragma unused(ifp)
#pragma unused(cmd)
#pragma unused(data)

	return ENOTSUP;
}

static errno_t
rvi_set_bpf_tap(ifnet_t ifp, uint32_t dlt, bpf_tap_mode mode)
{
	struct rvi_client_t *__single client;

	rvi_lock_shared(&rvi_mtx);

	client = ifnet_softc(ifp);
	if (client == NULL) {
		os_log(OS_LOG_DEFAULT, "%s: ifnet_softc is NULL for ifp %p", __func__, ifp);
		goto done;
	}
	switch (dlt) {
	case DLT_RAW:
		if (mode == 0) {
			if (client->_raw_count > 0) {
				client->_raw_count--;
			}
		} else {
			client->_raw_count++;
		}
		break;
	case DLT_PKTAP:
		if (mode == 0) {
			if (client->_pktap_count > 0) {
				client->_pktap_count--;
			}
		} else {
			client->_pktap_count++;
		}
		break;
	}
done:
	rvi_lock_done_shared(&rvi_mtx);

	return 0;
}

/*
 * Note: called with the rvi lock taken as shared
 */
static errno_t
rvi_bpf_tap(ifnet_t ifp, mbuf_t m, int outgoing, struct rvi_client_t *client,
    struct pktap_header *pktap_hdr)
{
#pragma unused(ifp)
	errno_t err = 0;
	void (*bpf_tap_fn)(ifnet_t, uint32_t, mbuf_t, void *, size_t ) =
	    outgoing ? bpf_tap_out : bpf_tap_in;

	if (client->_pktap_count > 0) {
		bpf_tap_fn(client->_ifp, DLT_PKTAP, m, pktap_hdr,
		    sizeof(struct pktap_header));
	}

	if (client->_raw_count > 0 &&
	    (pktap_hdr->pth_protocol_family == AF_INET ||
	    pktap_hdr->pth_protocol_family == AF_INET6)) {
		/*
		 * We can play just with the length of the first mbuf in the
		 * chain because bpf_tap_imp() disregard the packet length
		 * of the mbuf packet header.
		 */
		if (pktap_hdr->pth_frame_pre_length > mbuf_len(m)) {
			err = mbuf_pullup(&m, pktap_hdr->pth_frame_pre_length);
			if (err != 0) {
				os_log(OS_LOG_DEFAULT, "%s mbuf_pullup failed", __func__);
				return 0;
			}
		}

		if (mbuf_setdata(m, m_mtod_current(m) + pktap_hdr->pth_frame_pre_length,
		    m->m_len - pktap_hdr->pth_frame_pre_length) == 0) {
			bpf_tap_fn(client->_ifp, DLT_RAW, m, NULL, 0);
			mbuf_setdata(m, m_mtod_current(m) - pktap_hdr->pth_frame_pre_length,
			    m->m_len + pktap_hdr->pth_frame_pre_length);
		}
	}

	return err;
}
