This is xnu-11215.1.10. See this file in:
/*
 * Copyright (c) 2024 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/queue.h>
#include <sys/socketvar.h>
#include <net/bpf.h>
#include <net/droptap.h>
#include <net/if_var_private.h>
#include <net/kpi_interface.h>
#include <net/pktap.h>


struct droptap_softc {
	LIST_ENTRY(droptap_softc)         dtap_link;
	uint32_t                          dtap_unit;
	uint32_t                          dtap_dlt_pktap_count;
	struct ifnet                      *dtap_ifp;
};

static int droptap_inited = 0;

SYSCTL_DECL(_net_link);
SYSCTL_NODE(_net_link, OID_AUTO, droptap,
    CTLFLAG_RW  | CTLFLAG_LOCKED, 0, "droptap virtual interface");

uint32_t droptap_total_tap_count = 0;
SYSCTL_UINT(_net_link_droptap, OID_AUTO, total_tap_count,
    CTLFLAG_RD | CTLFLAG_LOCKED, &droptap_total_tap_count, 0, "");

static LCK_GRP_DECLARE(droptap_lck_grp, "droptap");
static LCK_ATTR_DECLARE(droptap_lck_attr, 0, 0);
static LCK_RW_DECLARE_ATTR(droptap_lck_rw, &droptap_lck_grp, &droptap_lck_attr);


static LIST_HEAD(droptap_list, droptap_softc) droptap_list =
    LIST_HEAD_INITIALIZER(droptap_list);

int droptap_clone_create(struct if_clone *, u_int32_t, void *);
int droptap_clone_destroy(struct ifnet *);

#define DROPTAP_MAXUNIT IF_MAXUNIT

static struct if_clone droptap_cloner =
    IF_CLONE_INITIALIZER(DROPTAP_IFNAME,
    droptap_clone_create,
    droptap_clone_destroy,
    0,
    DROPTAP_MAXUNIT);

errno_t droptap_if_output(ifnet_t, mbuf_t);
errno_t droptap_add_proto(ifnet_t, protocol_family_t,
    const struct ifnet_demux_desc *, u_int32_t);
errno_t droptap_del_proto(ifnet_t, protocol_family_t);
errno_t droptap_tap_callback(ifnet_t, u_int32_t, bpf_tap_mode);
void droptap_detach(ifnet_t);
static void droptap_bpf_tap_packet(kern_packet_t, uint32_t,
    struct droptap_header *, uint32_t, struct ifnet *, pid_t,
    const char *, pid_t, const char *, uint8_t, uint32_t);
static void droptap_bpf_tap_mbuf(struct mbuf *, uint16_t,
    struct droptap_header *, struct ifnet *);


void
droptap_init(void)
{
	int error = 0;

	VERIFY(droptap_inited == 0);
	droptap_inited = 1;

	LIST_INIT(&droptap_list);

	error = if_clone_attach(&droptap_cloner);
	if (error != 0) {
		panic("%s: if_clone_attach() failed, error %d",
		    __func__, error);
	}
}

int
droptap_clone_create(struct if_clone *ifc, u_int32_t unit, __unused void *params)
{
	int error = 0;
	struct droptap_softc *droptap = NULL;
	struct ifnet_init_eparams if_init;

	droptap = kalloc_type(struct droptap_softc, Z_WAITOK_ZERO_NOFAIL);
	droptap->dtap_unit = unit;

	bzero(&if_init, sizeof(if_init));
	if_init.ver = IFNET_INIT_CURRENT_VERSION;
	if_init.len = sizeof(if_init);
	if_init.flags = IFNET_INIT_LEGACY;
	if_init.name = __unsafe_null_terminated_from_indexable(ifc->ifc_name);
	if_init.unit = unit;
	if_init.type = IFT_OTHER;
	if_init.family = IFNET_FAMILY_LOOPBACK;
	if_init.output = droptap_if_output;
	if_init.add_proto = droptap_add_proto;
	if_init.del_proto = droptap_del_proto;
	if_init.softc = droptap;
	if_init.detach = droptap_detach;

	error = ifnet_allocate_extended(&if_init, &droptap->dtap_ifp);
	if (error != 0) {
		printf("%s: ifnet_allocate_extended failed, error: %d\n",
		    __func__, error);
		goto done;
	}

	ifnet_set_flags(droptap->dtap_ifp, IFF_UP, IFF_UP);

	error = ifnet_attach(droptap->dtap_ifp, NULL);
	if (error != 0) {
		printf("%s: ifnet_attach failed, error: %d\n", __func__, error);
		ifnet_release(droptap->dtap_ifp);
		goto done;
	}

	/* We use DLT_PKTAP for droptap as well. */
	bpf_attach(droptap->dtap_ifp, DLT_PKTAP, sizeof(struct droptap_header),
	    NULL, droptap_tap_callback);

	ifnet_reference(droptap->dtap_ifp);
	lck_rw_lock_exclusive(&droptap_lck_rw);
	LIST_INSERT_HEAD(&droptap_list, droptap, dtap_link);
	lck_rw_done(&droptap_lck_rw);
done:
	if (error != 0 && droptap != NULL) {
		kfree_type(struct droptap_softc, droptap);
	}
	return error;
}

int
droptap_clone_destroy(struct ifnet *ifp)
{
	int error = 0;

	(void) ifnet_detach(ifp);
	return error;
}

errno_t
droptap_tap_callback(ifnet_t ifp, u_int32_t dlt, bpf_tap_mode direction)
{
	struct droptap_softc *__single droptap;

	droptap = ifp->if_softc;
	switch (dlt) {
	case DLT_PKTAP:
		if (direction == BPF_MODE_DISABLED) {
			if (droptap->dtap_dlt_pktap_count > 0) {
				droptap->dtap_dlt_pktap_count--;
				OSAddAtomic(-1, &droptap_total_tap_count);
			}
		} else {
			droptap->dtap_dlt_pktap_count++;
			OSAddAtomic(1, &droptap_total_tap_count);
		}
		break;
	}
	return 0;
}

errno_t
droptap_if_output(ifnet_t __unused ifp, mbuf_t __unused m)
{
	return ENOTSUP;
}

errno_t
droptap_add_proto(__unused ifnet_t ifp, __unused protocol_family_t pf,
    __unused const struct ifnet_demux_desc *dmx, __unused u_int32_t cnt)
{
	return 0;
}

errno_t
droptap_del_proto(__unused ifnet_t ifp, __unused protocol_family_t pf)
{
	return 0;
}

void
droptap_detach(ifnet_t ifp)
{
	struct droptap_softc *__single droptap;

	lck_rw_lock_exclusive(&droptap_lck_rw);

	droptap = ifp->if_softc;
	ifp->if_softc = NULL;
	LIST_REMOVE(droptap, dtap_link);

	lck_rw_done(&droptap_lck_rw);

	/* Drop reference as it's no more on the global list */
	ifnet_release(ifp);

	kfree_type(struct droptap_softc, droptap);
	/* This is for the reference taken by ifnet_attach() */
	(void) ifnet_release(ifp);
}

void
droptap_input_packet(kern_packet_t pkt, drop_reason_t reason,
    const char *funcname, uint16_t linenum, uint16_t flags, struct ifnet *ifp,
    pid_t pid, const char *pname, pid_t epid, const char *epname,
    uint8_t ipproto, uint32_t flowid)
{
	struct droptap_header     dtaphdr;
	uint32_t                  dlt;

	if (flags & DROPTAP_FLAG_L2_MISSING) {
		dlt = DLT_RAW;
	} else {
		dlt = DLT_EN10MB;
	}
	bzero(&dtaphdr, sizeof(struct droptap_header));
	dtaphdr.dth_dropreason = reason;
	if (funcname != NULL) {
		dtaphdr.dth_dropline = linenum;
		snprintf(dtaphdr.dth_dropfunc, sizeof(dtaphdr.dth_dropfunc), "%s", funcname);
		dtaphdr.dth_dropfunc_size = (uint8_t)strbuflen(dtaphdr.dth_dropfunc);
	}

	droptap_bpf_tap_packet(pkt, DROPTAP_FLAG_DIR_IN | PTH_FLAG_NEXUS_CHAN,
	    &dtaphdr, dlt, ifp, pid, pname, epid, epname, ipproto, flowid);
}

void
droptap_output_packet(kern_packet_t pkt, drop_reason_t reason,
    const char *funcname, uint16_t linenum, uint16_t flags, struct ifnet *ifp,
    pid_t pid, const char *pname, pid_t epid, const char *epname,
    uint8_t ipproto, uint32_t flowid)
{
	struct droptap_header     dtaphdr;
	uint32_t                  dlt;

	if (flags & DROPTAP_FLAG_L2_MISSING) {
		dlt = DLT_RAW;
	} else {
		dlt = DLT_EN10MB;
	}
	bzero(&dtaphdr, sizeof(struct droptap_header));
	dtaphdr.dth_dropreason = reason;
	if (funcname != NULL) {
		dtaphdr.dth_dropline = linenum;
		snprintf(dtaphdr.dth_dropfunc, sizeof(dtaphdr.dth_dropfunc), "%s", funcname);
		dtaphdr.dth_dropfunc_size = (uint8_t)strbuflen(dtaphdr.dth_dropfunc);
	}

	droptap_bpf_tap_packet(pkt, DROPTAP_FLAG_DIR_OUT | PTH_FLAG_NEXUS_CHAN,
	    &dtaphdr, dlt, ifp, pid, pname, epid, epname, ipproto, flowid);
}

static void
droptap_bpf_tap_packet(kern_packet_t pkt, uint32_t flags,
    struct droptap_header *dtaphdr, uint32_t dlt, struct ifnet *ifp, pid_t pid,
    const char *pname, pid_t epid, const char *epname, uint8_t ipproto,
    uint32_t flowid)
{
	struct droptap_softc *droptap;
	struct pktap_header *hdr;
	size_t hdr_size;
	void (*tap_packet_func)(ifnet_t, u_int32_t, kern_packet_t, void *, size_t) =
	    flags & DROPTAP_FLAG_DIR_OUT ? bpf_tap_packet_out : bpf_tap_packet_in;

	hdr_size = DROPTAP_HDR_SIZE(dtaphdr);

	hdr = (struct pktap_header *)dtaphdr;
	hdr->pth_length = sizeof(struct pktap_header);
	hdr->pth_type_next = PTH_TYPE_DROP;
	hdr->pth_dlt = dlt;
	hdr->pth_pid = pid;
	if (pid != epid) {
		hdr->pth_epid = epid;
	} else {
		hdr->pth_epid = -1;
	}
	if (pname != NULL) {
		strlcpy(hdr->pth_comm, pname, sizeof(hdr->pth_comm));
	}
	if (epname != NULL) {
		strlcpy(hdr->pth_ecomm, epname, sizeof(hdr->pth_ecomm));
	}
	if (ifp) {
		strlcpy(hdr->pth_ifname, ifp->if_xname, sizeof(hdr->pth_ifname));
		hdr->pth_iftype = ifp->if_type;
		hdr->pth_ifunit = ifp->if_unit;
	}
	hdr->pth_flags |= flags;
	hdr->pth_ipproto = ipproto;
	hdr->pth_flowid = flowid;

	hdr->pth_flags |= flags & DROPTAP_FLAG_DIR_OUT ? PTH_FLAG_DIR_OUT : PTH_FLAG_DIR_IN;
	if ((flags & PTH_FLAG_SOCKET) != 0 && ipproto != 0 && flowid != 0) {
		hdr->pth_flags |= PTH_FLAG_DELAY_PKTAP;
	}
	if (kern_packet_get_wake_flag(pkt)) {
		hdr->pth_flags |= PTH_FLAG_WAKE_PKT;
	}
	hdr->pth_trace_tag = kern_packet_get_trace_tag(pkt);
	hdr->pth_svc = so_svc2tc((mbuf_svc_class_t)
	    kern_packet_get_service_class(pkt));

	lck_rw_lock_shared(&droptap_lck_rw);
	LIST_FOREACH(droptap, &droptap_list, dtap_link) {
		if (droptap->dtap_dlt_pktap_count > 0) {
			tap_packet_func(droptap->dtap_ifp, DLT_PKTAP,
			    pkt, hdr, hdr_size);
		}
	}
	lck_rw_done(&droptap_lck_rw);
}

void
droptap_input_mbuf(struct mbuf *m, drop_reason_t reason, const char *funcname,
    uint16_t linenum, uint16_t flags, struct ifnet *ifp, char *frame_header)
{
	struct droptap_header    dtaphdr;
	char *hdr;
	char *start;

	bzero(&dtaphdr, sizeof(struct droptap_header));
	dtaphdr.dth_dropreason = reason;
	if (funcname != NULL) {
		dtaphdr.dth_dropline = linenum;
		snprintf(dtaphdr.dth_dropfunc, sizeof(dtaphdr.dth_dropfunc), "%s", funcname);
		dtaphdr.dth_dropfunc_size = (uint8_t)strbuflen(dtaphdr.dth_dropfunc);
	}

	hdr = (char *)mbuf_data(m);
	start = (char *)mbuf_datastart(m);
	if (frame_header != NULL && frame_header >= start && frame_header <= hdr) {
		size_t o_len = m->m_len;
		u_int32_t pre = (u_int32_t)(hdr - frame_header);

		if (mbuf_setdata(m, frame_header, o_len + pre) == 0) {
			droptap_bpf_tap_mbuf(m, DROPTAP_FLAG_DIR_IN | flags, &dtaphdr, ifp);
			mbuf_setdata(m, hdr, o_len);
		}
	} else {
		droptap_bpf_tap_mbuf(m, DROPTAP_FLAG_DIR_IN | flags, &dtaphdr, ifp);
	}
}

void
droptap_output_mbuf(struct mbuf *m, drop_reason_t reason, const char *funcname,
    uint16_t linenum, uint16_t flags, struct ifnet *ifp)
{
	struct droptap_header    dtaphdr;

	bzero(&dtaphdr, sizeof(struct droptap_header));
	dtaphdr.dth_dropreason = reason;
	if (funcname != NULL) {
		dtaphdr.dth_dropline = linenum;
		snprintf(dtaphdr.dth_dropfunc, sizeof(dtaphdr.dth_dropfunc), "%s", funcname);
		dtaphdr.dth_dropfunc_size = (uint8_t)strbuflen(dtaphdr.dth_dropfunc);
	}

	droptap_bpf_tap_mbuf(m, DROPTAP_FLAG_DIR_OUT | flags, &dtaphdr, ifp);
}

static void
droptap_bpf_tap_mbuf(struct mbuf *m, uint16_t flags,
    struct droptap_header *dtaphdr, struct ifnet *ifp)
{
	struct droptap_softc *droptap;
	struct pktap_header *hdr;
	size_t hdr_size;
	void (*bpf_tap_func)(ifnet_t, u_int32_t, mbuf_t, void *, size_t ) =
	    flags & DROPTAP_FLAG_DIR_OUT ? bpf_tap_out : bpf_tap_in;

	hdr_size = DROPTAP_HDR_SIZE(dtaphdr);

	hdr = (struct pktap_header *)dtaphdr;
	hdr->pth_length = sizeof(struct pktap_header);
	hdr->pth_type_next = PTH_TYPE_DROP;

	/* Use DLT_RAW if L2 frame header is NULL */
	if (flags & DROPTAP_FLAG_L2_MISSING) {
		hdr->pth_dlt = DLT_RAW;
	} else {
		hdr->pth_dlt = DLT_EN10MB;
	}

	hdr->pth_flags |= flags & DROPTAP_FLAG_DIR_OUT ? PTH_FLAG_DIR_OUT : PTH_FLAG_DIR_IN;
	if (ifp) {
		strlcpy(hdr->pth_ifname, ifp->if_xname, sizeof(hdr->pth_ifname));
		hdr->pth_iftype = ifp->if_type;
		hdr->pth_ifunit = ifp->if_unit;
	}

	if (m->m_pkthdr.pkt_flags & PKTF_KEEPALIVE) {
		hdr->pth_flags |= PTH_FLAG_KEEP_ALIVE;
	}
	if (m->m_pkthdr.pkt_flags & PKTF_TCP_REXMT) {
		hdr->pth_flags |= PTH_FLAG_REXMIT;
	}
	if (m->m_pkthdr.pkt_flags & PKTF_WAKE_PKT) {
		hdr->pth_flags |= PTH_FLAG_WAKE_PKT;
	}

	hdr->pth_svc = so_svc2tc(m->m_pkthdr.pkt_svc);

	lck_rw_lock_exclusive(&droptap_lck_rw);
	LIST_FOREACH(droptap, &droptap_list, dtap_link) {
		if (droptap->dtap_dlt_pktap_count > 0) {
			bpf_tap_func(droptap->dtap_ifp, DLT_PKTAP,
			    m, hdr, hdr_size);
		}
	}
	lck_rw_done(&droptap_lck_rw);
}