This is xnu-11215.1.10. See this file in:
/*
 * Copyright (c) 2014-2021, 2023 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 <sys/systm.h>
#include <sys/types.h>
#include <sys/syslog.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/kern_control.h>
#include <sys/mbuf.h>
#include <sys/kpi_mbuf.h>
#include <sys/sysctl.h>
#include <sys/priv.h>
#include <sys/kern_event.h>
#include <sys/sysproto.h>
#include <net/network_agent.h>
#include <net/if_var.h>
#include <net/necp.h>
#include <os/log.h>

u_int32_t netagent_debug = LOG_NOTICE; // 0=None, 1=Basic

SYSCTL_NODE(_net, OID_AUTO, netagent, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "NetworkAgent");
SYSCTL_INT(_net_netagent, OID_AUTO, debug, CTLFLAG_LOCKED | CTLFLAG_RW, &netagent_debug, 0, "");

static int netagent_registered_count = 0;
SYSCTL_INT(_net_netagent, OID_AUTO, registered_count, CTLFLAG_RD | CTLFLAG_LOCKED,
    &netagent_registered_count, 0, "");

static int netagent_active_count = 0;
SYSCTL_INT(_net_netagent, OID_AUTO, active_count, CTLFLAG_RD | CTLFLAG_LOCKED,
    &netagent_active_count, 0, "");

#define NETAGENTLOG(level, format, ...) do {                                             \
    if (level <= netagent_debug) {                                                       \
	if (level == LOG_ERR) {                                                          \
	    os_log_error(OS_LOG_DEFAULT, "%s: " format "\n", __FUNCTION__, __VA_ARGS__); \
	} else {                                                                         \
	    os_log(OS_LOG_DEFAULT, "%s: " format "\n", __FUNCTION__, __VA_ARGS__);       \
	}                                                                                \
    }                                                                                    \
} while (0)

#define NETAGENTLOG0(level, msg) do {                                                    \
    if (level <= netagent_debug) {                                                       \
	        if (level == LOG_ERR) {                                                          \
	    os_log_error(OS_LOG_DEFAULT, "%s: %s\n", __FUNCTION__, msg);                 \
	} else {                                                                         \
	    os_log(OS_LOG_DEFAULT, "%s: %s\n", __FUNCTION__, msg);                       \
	}                                                                                \
    }                                                                                    \
} while (0)

#if __has_ptrcheck
static inline
__attribute__((always_inline)) __pure
uint8_t * __bidi_indexable
netagent_get_data(const struct netagent *agent)
{
	if (agent == NULL) {
		return NULL;
	}

	return __unsafe_forge_bidi_indexable(uint8_t *, agent->netagent_data, agent->netagent_data_size);
}
#else
#define netagent_get_data(agent) ((agent)->netagent_data)
#endif

#if __has_ptrcheck
static inline
__attribute__((always_inline)) __pure
uint8_t * __bidi_indexable
netagent_group_message_get_members(const struct netagent_client_group_message *msg, size_t members_length)
{
	if (msg == NULL) {
		return NULL;
	}

	return __unsafe_forge_bidi_indexable(uint8_t *, msg->group_members, members_length);
}
#else
#define netagent_group_message_get_members(msg, members_length) ((msg)->group_members)
#endif

#if __has_ptrcheck
static inline
__attribute__((always_inline)) __pure
uint8_t * __bidi_indexable
netagent_assign_message_get_necp_result(const struct netagent_assign_nexus_message *msg, size_t result_length)
{
	if (msg == NULL) {
		return NULL;
	}

	return __unsafe_forge_bidi_indexable(uint8_t *, msg->assign_necp_results, result_length);
}
#else
#define netagent_assign_message_get_necp_result(msg, result_length) ((msg)->assign_necp_results)
#endif

struct netagent_client {
	LIST_ENTRY(netagent_client) client_chain;
	uuid_t client_id;
	uuid_t client_proc_uuid;
	pid_t client_pid;
};

LIST_HEAD(netagent_client_list_s, netagent_client);

struct netagent_token {
	TAILQ_ENTRY(netagent_token) token_chain;
	u_int32_t token_length;
	u_int8_t *  __indexable token_bytes;
};

TAILQ_HEAD(netagent_token_list_s, netagent_token);

#define NETAGENT_MAX_CLIENT_ERROR_COUNT 32

struct netagent_wrapper {
	LIST_ENTRY(netagent_wrapper) list_chain;
	lck_rw_t agent_lock;
	u_int32_t control_unit;
	netagent_event_f event_handler;
	void *event_context;
	u_int32_t generation;
	u_int64_t use_count;
	u_int64_t need_tokens_event_deadline;
	u_int32_t token_count;
	u_int32_t token_low_water;
	int32_t last_client_error;
	u_int32_t client_error_count;
	u_int8_t __pad_bytes[3];
	struct netagent_token_list_s token_list;
	struct netagent_client_list_s pending_triggers_list;
	struct netagent *netagent;
};

struct netagent_session {
	u_int32_t control_unit; // A control unit of 0 indicates an agent owned by the kernel
	lck_mtx_t session_lock;
	struct netagent_wrapper *wrapper;
	netagent_event_f event_handler;
	void *event_context;
};

typedef enum {
	kNetagentErrorDomainPOSIX                       = 0,
	kNetagentErrorDomainUserDefined         = 1,
} netagent_error_domain_t;

static LIST_HEAD(_netagent_list, netagent_wrapper) shared_netagent_list =
    LIST_HEAD_INITIALIZER(master_netagent_list);

// Protected by netagent_list_lock
static u_int32_t g_next_generation = 1;

static kern_ctl_ref     netagent_kctlref;
static u_int32_t        netagent_family;
static LCK_GRP_DECLARE(netagent_mtx_grp, NETAGENT_CONTROL_NAME);
static LCK_RW_DECLARE(netagent_list_lock, &netagent_mtx_grp);

#define NETAGENT_LIST_LOCK_EXCLUSIVE() lck_rw_lock_exclusive(&netagent_list_lock)
#define NETAGENT_LIST_LOCK_SHARED() lck_rw_lock_shared(&netagent_list_lock)
#define NETAGENT_LIST_UNLOCK() lck_rw_done(&netagent_list_lock)
#define NETAGENT_LIST_ASSERT_LOCKED() LCK_RW_ASSERT(&netagent_list_lock, LCK_RW_ASSERT_HELD)

#define NETAGENT_SESSION_LOCK(session) lck_mtx_lock(&session->session_lock)
#define NETAGENT_SESSION_UNLOCK(session) lck_mtx_unlock(&session->session_lock)

#define NETAGENT_LOCK_EXCLUSIVE(wrapper) lck_rw_lock_exclusive(&wrapper->agent_lock)
#define NETAGENT_LOCK_SHARED(wrapper) lck_rw_lock_shared(&wrapper->agent_lock)
#define NETAGENT_LOCK_SHARED_TO_EXCLUSIVE(wrapper) lck_rw_lock_shared_to_exclusive(&wrapper->agent_lock)
#define NETAGENT_UNLOCK(wrapper) lck_rw_done(&wrapper->agent_lock)
#define NETAGENT_ASSERT_LOCKED(wrapper) LCK_RW_ASSERT(&wrapper->agent_lock, LCK_RW_ASSERT_HELD)

// Locking Notes

// Precedence, where 1 is the first lock that must be taken
// 1. NETAGENT_LIST_LOCK - protects shared_netagent_list
// 2. NETAGENT_SESSION_LOCK - protects the session->wrapper pointer
// 3. NETAGENT_LOCK -> protects values in a wrapper

static errno_t netagent_register_control(void);
static errno_t netagent_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac,
    void **unitinfo);
static errno_t netagent_ctl_disconnect(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo);
static errno_t netagent_ctl_send(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
    mbuf_t m, int flags);
static void netagent_ctl_rcvd(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int flags);
static errno_t netagent_ctl_getopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
    int opt, void * __sized_by(*len)data, size_t *len);
static errno_t netagent_ctl_setopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
    int opt, void * __sized_by(len)data, size_t len);

static int netagent_send_ctl_data(u_int32_t control_unit,
    u_int8_t *__sized_by(buffer_size)buffer, size_t buffer_size);

static struct netagent_session *netagent_create_session(u_int32_t control_unit);
static void netagent_delete_session(struct netagent_session *session);

// Register
static void netagent_handle_register_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset);
static errno_t netagent_handle_register_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length);

// Unregister
static void netagent_handle_unregister_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset);
static errno_t netagent_handle_unregister_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length);

// Update
static void netagent_handle_update_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset);
static errno_t netagent_handle_update_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length);

// Assign nexus
static void netagent_handle_assign_nexus_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset);
static errno_t netagent_handle_assign_nexus_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length);

// Assign group
static errno_t netagent_handle_assign_group_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length);

// Set/get assert count
static errno_t netagent_handle_use_count_setopt(struct netagent_session *session, u_int8_t * __sized_by(payload_length)payload, size_t payload_length);
static errno_t netagent_handle_use_count_getopt(struct netagent_session *session, u_int8_t * __sized_by(*buffer_length)buffer, size_t *buffer_length);

// Manage tokens
static errno_t netagent_handle_add_token_setopt(struct netagent_session *session, u_int8_t * __sized_by(token_length)token, size_t token_length);
static errno_t netagent_handle_flush_tokens_setopt(struct netagent_session *session, u_int8_t *payload, size_t payload_length);
static errno_t netagent_handle_token_count_getopt(struct netagent_session *session, u_int8_t * __sized_by(*buffer_length)buffer, size_t *buffer_length);
static errno_t netagent_handle_token_low_water_setopt(struct netagent_session *session, u_int8_t * __sized_by(buffer_length)buffer, size_t buffer_length);
static errno_t netagent_handle_token_low_water_getopt(struct netagent_session *session, u_int8_t * __sized_by(*buffer_length)buffer, size_t *buffer_length);

// Client error
static errno_t netagent_handle_reset_client_error_setopt(struct netagent_session *session, u_int8_t *payload, size_t payload_length);

// Requires list lock being held
static struct netagent_wrapper *netagent_find_agent_with_uuid_and_lock(uuid_t uuid, bool exclusively, bool ignore_lock);

errno_t
netagent_init(void)
{
	return netagent_register_control();
}

static errno_t
netagent_register_control(void)
{
	struct kern_ctl_reg     kern_ctl;
	errno_t                 result = 0;

	// Find a unique value for our interface family
	result = mbuf_tag_id_find(NETAGENT_CONTROL_NAME, &netagent_family);
	if (result != 0) {
		NETAGENTLOG(LOG_ERR, "mbuf_tag_id_find_internal failed: %d", result);
		return result;
	}

	bzero(&kern_ctl, sizeof(kern_ctl));
	strlcpy(kern_ctl.ctl_name, NETAGENT_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; // Require root
	kern_ctl.ctl_sendsize = 64 * 1024;
	kern_ctl.ctl_recvsize = 64 * 1024;
	kern_ctl.ctl_connect = netagent_ctl_connect;
	kern_ctl.ctl_disconnect = netagent_ctl_disconnect;
	kern_ctl.ctl_send = netagent_ctl_send;
	kern_ctl.ctl_rcvd = netagent_ctl_rcvd;
	kern_ctl.ctl_setopt = netagent_ctl_setopt;
	kern_ctl.ctl_getopt = netagent_ctl_getopt;

	result = ctl_register(&kern_ctl, &netagent_kctlref);
	if (result != 0) {
		NETAGENTLOG(LOG_ERR, "ctl_register failed: %d", result);
		return result;
	}

	return 0;
}

static errno_t
netagent_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac, void **unitinfo)
{
#pragma unused(kctlref)
	*unitinfo = netagent_create_session(sac->sc_unit);
	if (*unitinfo == NULL) {
		// Could not allocate session
		return ENOBUFS;
	}

	return 0;
}

static errno_t
netagent_ctl_disconnect(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo)
{
#pragma unused(kctlref, unit)
	struct netagent_session *session = (struct netagent_session *)unitinfo;
	if (session != NULL) {
		netagent_delete_session(session);
	}

	return 0;
}

// Kernel events
static void
netagent_post_event(uuid_t agent_uuid, u_int32_t event_code, bool update_necp, bool should_update_immediately)
{
	if (update_necp) {
		necp_update_all_clients_immediately_if_needed(should_update_immediately);
	}

	struct kev_msg ev_msg;
	memset(&ev_msg, 0, sizeof(ev_msg));

	struct kev_netagent_data event_data;

	ev_msg.vendor_code      = KEV_VENDOR_APPLE;
	ev_msg.kev_class        = KEV_NETWORK_CLASS;
	ev_msg.kev_subclass     = KEV_NETAGENT_SUBCLASS;
	ev_msg.event_code       = event_code;

	uuid_copy(event_data.netagent_uuid, agent_uuid);
	ev_msg.dv[0].data_ptr    = &event_data;
	ev_msg.dv[0].data_length = sizeof(event_data);

	kev_post_msg(&ev_msg);
}

// Message handling
static u_int8_t * __indexable
netagent_buffer_write_message_header(u_int8_t * __sized_by(sizeof(struct netagent_message_header) + payload_length)buffer, u_int8_t message_type, u_int8_t flags, u_int32_t message_id, u_int32_t error, size_t payload_length)
{
	memset(buffer, 0, sizeof(struct netagent_message_header));
	((struct netagent_message_header *)(void *)buffer)->message_type = message_type;
	((struct netagent_message_header *)(void *)buffer)->message_flags = flags;
	((struct netagent_message_header *)(void *)buffer)->message_id = message_id;
	((struct netagent_message_header *)(void *)buffer)->message_error = error;
	((struct netagent_message_header *)(void *)buffer)->message_payload_length = (u_int32_t)payload_length;
	return payload_length ? buffer + sizeof(struct netagent_message_header) : NULL;
}

static int
netagent_send_ctl_data(u_int32_t control_unit, u_int8_t *__sized_by(buffer_size) buffer, size_t buffer_size)
{
	if (netagent_kctlref == NULL || control_unit == 0 || buffer == NULL || buffer_size == 0) {
		return EINVAL;
	}

	return ctl_enqueuedata(netagent_kctlref, control_unit, buffer, buffer_size, CTL_DATA_EOR);
}

static int
netagent_send_trigger(struct netagent_wrapper *wrapper, struct proc *p, u_int32_t flags, u_int8_t trigger_type)
{
	int error = 0;
	struct netagent_trigger_message *trigger_message = NULL;
	u_int8_t *trigger = NULL;
	size_t trigger_size = sizeof(struct netagent_message_header) + sizeof(struct netagent_trigger_message);
	trigger = (u_int8_t *)kalloc_data(trigger_size, Z_WAITOK);
	if (trigger == NULL) {
		return ENOMEM;
	}

	(void)netagent_buffer_write_message_header(trigger, trigger_type, 0, 0, 0, sizeof(struct netagent_trigger_message));

	trigger_message = (struct netagent_trigger_message *)(void *)(trigger + sizeof(struct netagent_message_header));
	trigger_message->trigger_flags = flags;
	if (p != NULL) {
		trigger_message->trigger_pid = proc_pid(p);
		proc_getexecutableuuid(p, trigger_message->trigger_proc_uuid, sizeof(trigger_message->trigger_proc_uuid));
	} else {
		trigger_message->trigger_pid = 0;
		uuid_clear(trigger_message->trigger_proc_uuid);
	}

	if ((error = netagent_send_ctl_data(wrapper->control_unit, trigger, trigger_size))) {
		NETAGENTLOG(LOG_ERR, "Failed to send trigger message on control unit %d", wrapper->control_unit);
	}

	kfree_data(trigger, trigger_size);
	return error;
}

static int
netagent_send_client_message(struct netagent_wrapper *wrapper, uuid_t client_id, u_int8_t message_type)
{
	int error = 0;
	struct netagent_client_message *client_message = NULL;
	u_int8_t *message = NULL;
	size_t message_size = sizeof(struct netagent_message_header) + sizeof(struct netagent_client_message);

	message = (u_int8_t *)kalloc_data(message_size, Z_WAITOK);
	if (message == NULL) {
		return ENOMEM;
	}

	(void)netagent_buffer_write_message_header(message, message_type, 0, 0, 0, sizeof(struct netagent_client_message));

	client_message = (struct netagent_client_message *)(void *)(message + sizeof(struct netagent_message_header));
	uuid_copy(client_message->client_id, client_id);

	if ((error = netagent_send_ctl_data(wrapper->control_unit, message, message_size))) {
		NETAGENTLOG(LOG_ERR, "Failed to send client message %d on control unit %d", message_type, wrapper->control_unit);
	}

	kfree_data(message, message_size);
	return error;
}

static int
netagent_send_error_message(struct netagent_wrapper *wrapper, uuid_t client_id, u_int8_t message_type, int32_t error_code)
{
	int error = 0;
	struct netagent_client_error_message *client_message = NULL;
	u_int8_t *message = NULL;
	size_t message_size = sizeof(struct netagent_message_header) + sizeof(struct netagent_client_error_message);

	message = (u_int8_t *)kalloc_data(message_size, Z_WAITOK);
	if (message == NULL) {
		return ENOMEM;
	}

	(void)netagent_buffer_write_message_header(message, message_type, 0, 0, 0, sizeof(struct netagent_client_error_message));

	client_message = (struct netagent_client_error_message *)(void *)(message + sizeof(struct netagent_message_header));
	uuid_copy(client_message->client_id, client_id);
	client_message->error_code = error_code;

	if ((error = netagent_send_ctl_data(wrapper->control_unit, message, message_size))) {
		NETAGENTLOG(LOG_ERR, "Failed to send client message %d on control unit %d", message_type, wrapper->control_unit);
	}

	kfree_data(message, message_size);
	return error;
}

static int
netagent_send_group_message(struct netagent_wrapper *wrapper, uuid_t client_id, u_int8_t message_type, struct necp_client_group_members *group_members)
{
	int error = 0;
	struct netagent_client_group_message * __single client_message = NULL;
	u_int8_t *message = NULL;
	size_t message_size = sizeof(struct netagent_message_header) + sizeof(struct netagent_client_group_message) + group_members->group_members_length;

	message = (u_int8_t *)kalloc_data(message_size, Z_WAITOK);
	if (message == NULL) {
		return ENOMEM;
	}

	(void)netagent_buffer_write_message_header(message, message_type, 0, 0, 0, sizeof(struct netagent_client_group_message) + group_members->group_members_length);

	client_message = (struct netagent_client_group_message *)(void *)(message + sizeof(struct netagent_message_header));
	uuid_copy(client_message->client_id, client_id);
	memcpy(netagent_group_message_get_members(client_message, group_members->group_members_length), group_members->group_members, group_members->group_members_length);

	if ((error = netagent_send_ctl_data(wrapper->control_unit, message, message_size))) {
		NETAGENTLOG(LOG_ERR, "Failed to send client group message %d on control unit %d", message_type, wrapper->control_unit);
	}

	kfree_data(message, message_size);
	return error;
}

static int
netagent_send_tokens_needed(struct netagent_wrapper *wrapper)
{
	const u_int8_t message_type = NETAGENT_MESSAGE_TYPE_TOKENS_NEEDED;
	int error = 0;
	u_int8_t *message = NULL;
	size_t message_size = sizeof(struct netagent_message_header);

	message = (u_int8_t *)kalloc_data(message_size, Z_WAITOK);
	if (message == NULL) {
		return ENOMEM;
	}

	(void)netagent_buffer_write_message_header(message, message_type, 0, 0, 0, 0);

	if ((error = netagent_send_ctl_data(wrapper->control_unit, message, message_size))) {
		NETAGENTLOG(LOG_ERR, "Failed to send client tokens needed message on control unit %d", wrapper->control_unit);
	}

	kfree_data(message, message_size);
	return error;
}

static int
netagent_send_success_response(struct netagent_session *session, u_int8_t message_type, u_int32_t message_id)
{
	int error = 0;
	u_int8_t *response = NULL;
	size_t response_size = sizeof(struct netagent_message_header);

	response = (u_int8_t *)kalloc_data(response_size, Z_WAITOK);
	if (response == NULL) {
		return ENOMEM;
	}
	(void)netagent_buffer_write_message_header(response, message_type, NETAGENT_MESSAGE_FLAGS_RESPONSE, message_id, 0, 0);

	if ((error = netagent_send_ctl_data(session->control_unit, response, response_size))) {
		NETAGENTLOG0(LOG_ERR, "Failed to send response");
	}

	kfree_data(response, response_size);
	return error;
}

static errno_t
netagent_send_error_response(struct netagent_session *session, u_int8_t message_type,
    u_int32_t message_id, u_int32_t error_code)
{
	int error = 0;
	u_int8_t *response = NULL;
	size_t response_size = sizeof(struct netagent_message_header);

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Got a NULL session");
		return EINVAL;
	}

	response = (u_int8_t *)kalloc_data(response_size, Z_WAITOK);
	if (response == NULL) {
		return ENOMEM;
	}
	(void)netagent_buffer_write_message_header(response, message_type, NETAGENT_MESSAGE_FLAGS_RESPONSE,
	    message_id, error_code, 0);

	if ((error = netagent_send_ctl_data(session->control_unit, response, response_size))) {
		NETAGENTLOG0(LOG_ERR, "Failed to send response");
	}

	kfree_data(response, response_size);
	return error;
}

static errno_t
netagent_ctl_send(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, mbuf_t packet, int flags)
{
#pragma unused(kctlref, unit, flags)
	struct netagent_session *session = (struct netagent_session *)unitinfo;
	struct netagent_message_header header;
	int error = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Got a NULL session");
		error = EINVAL;
		goto done;
	}

	if (mbuf_pkthdr_len(packet) < sizeof(header)) {
		NETAGENTLOG(LOG_ERR, "Got a bad packet, length (%lu) < sizeof header (%lu)",
		    mbuf_pkthdr_len(packet), sizeof(header));
		error = EINVAL;
		goto done;
	}

	error = mbuf_copydata(packet, 0, sizeof(header), &header);
	if (error) {
		NETAGENTLOG(LOG_ERR, "mbuf_copydata failed for the header: %d", error);
		error = ENOBUFS;
		goto done;
	}

	switch (header.message_type) {
	case NETAGENT_MESSAGE_TYPE_REGISTER: {
		netagent_handle_register_message(session, header.message_id, header.message_payload_length,
		    packet, sizeof(header));
		break;
	}
	case NETAGENT_MESSAGE_TYPE_UNREGISTER: {
		netagent_handle_unregister_message(session, header.message_id, header.message_payload_length,
		    packet, sizeof(header));
		break;
	}
	case NETAGENT_MESSAGE_TYPE_UPDATE: {
		netagent_handle_update_message(session, header.message_id, header.message_payload_length,
		    packet, sizeof(header));
		break;
	}
	case NETAGENT_MESSAGE_TYPE_GET: {
		NETAGENTLOG0(LOG_ERR, "NETAGENT_MESSAGE_TYPE_GET no longer supported");
		break;
	}
	case NETAGENT_MESSAGE_TYPE_ASSERT: {
		NETAGENTLOG0(LOG_ERR, "NETAGENT_MESSAGE_TYPE_ASSERT no longer supported");
		break;
	}
	case NETAGENT_MESSAGE_TYPE_UNASSERT: {
		NETAGENTLOG0(LOG_ERR, "NETAGENT_MESSAGE_TYPE_UNASSERT no longer supported");
		break;
	}
	case NETAGENT_MESSAGE_TYPE_ASSIGN_NEXUS: {
		netagent_handle_assign_nexus_message(session, header.message_id, header.message_payload_length,
		    packet, sizeof(header));
		break;
	}
	default: {
		NETAGENTLOG(LOG_ERR, "Received unknown message type %d", header.message_type);
		netagent_send_error_response(session, header.message_type, header.message_id,
		    NETAGENT_MESSAGE_ERROR_UNKNOWN_TYPE);
		break;
	}
	}

done:
	mbuf_freem(packet);
	return error;
}

static void
netagent_ctl_rcvd(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int flags)
{
#pragma unused(kctlref, unit, unitinfo, flags)
	return;
}

static errno_t
netagent_ctl_getopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int opt,
    void * __sized_by(*len)data, size_t *len)
{
#pragma unused(kctlref, unit)
	struct netagent_session *session = (struct netagent_session *)unitinfo;
	errno_t error;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Received a NULL session");
		error = EINVAL;
		goto done;
	}

	switch (opt) {
	case NETAGENT_OPTION_TYPE_USE_COUNT: {
		NETAGENTLOG0(LOG_DEBUG, "Request to get use count");
		error = netagent_handle_use_count_getopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_TOKEN_COUNT: {
		NETAGENTLOG0(LOG_DEBUG, "Request to get token count");
		error = netagent_handle_token_count_getopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_TOKEN_LOW_WATER: {
		NETAGENTLOG0(LOG_DEBUG, "Request to get token low water mark");
		error = netagent_handle_token_low_water_getopt(session, data, len);
		break;
	}
	default:
		NETAGENTLOG0(LOG_ERR, "Received unknown option");
		error = ENOPROTOOPT;
		break;
	}

done:
	return error;
}

static errno_t
netagent_ctl_setopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo, int opt,
    void * __sized_by(len)data, size_t len)
{
#pragma unused(kctlref, unit)
	struct netagent_session *session = (struct netagent_session *)unitinfo;
	errno_t error;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Received a NULL session");
		error = EINVAL;
		goto done;
	}

	switch (opt) {
	case NETAGENT_OPTION_TYPE_REGISTER: {
		NETAGENTLOG0(LOG_DEBUG, "Request for registration");
		error = netagent_handle_register_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_UPDATE: {
		NETAGENTLOG0(LOG_DEBUG, "Request for update");
		error = netagent_handle_update_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_UNREGISTER: {
		NETAGENTLOG0(LOG_DEBUG, "Request for unregistration");
		error = netagent_handle_unregister_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_ASSIGN_NEXUS: {
		NETAGENTLOG0(LOG_DEBUG, "Request for assigning nexus");
		error = netagent_handle_assign_nexus_setopt(session, data, len);
		break;
	}
	case NETAGENT_MESSAGE_TYPE_ASSIGN_GROUP_MEMBERS: {
		NETAGENTLOG0(LOG_DEBUG, "Request for assigning group members");
		error = netagent_handle_assign_group_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_USE_COUNT: {
		NETAGENTLOG0(LOG_DEBUG, "Request to set use count");
		error = netagent_handle_use_count_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_ADD_TOKEN: {
		NETAGENTLOG0(LOG_DEBUG, "Request to add a token");
		error = netagent_handle_add_token_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_FLUSH_TOKENS: {
		NETAGENTLOG0(LOG_DEBUG, "Request to flush tokens");
		error = netagent_handle_flush_tokens_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_TOKEN_LOW_WATER: {
		NETAGENTLOG0(LOG_DEBUG, "Request to set token low water mark");
		error = netagent_handle_token_low_water_setopt(session, data, len);
		break;
	}
	case NETAGENT_OPTION_TYPE_RESET_CLIENT_ERROR: {
		NETAGENTLOG0(LOG_DEBUG, "Request to reset client error");
		error = netagent_handle_reset_client_error_setopt(session, data, len);
		break;
	}
	default:
		NETAGENTLOG0(LOG_ERR, "Received unknown option");
		error = ENOPROTOOPT;
		break;
	}

done:
	return error;
}

// Session Management
static struct netagent_session *
netagent_create_session(u_int32_t control_unit)
{
	struct netagent_session *new_session = NULL;

	new_session = kalloc_type(struct netagent_session,
	    Z_WAITOK | Z_ZERO | Z_NOFAIL);
	NETAGENTLOG(LOG_DEBUG, "Create agent session, control unit %d", control_unit);
	new_session->control_unit = control_unit;
	lck_mtx_init(&new_session->session_lock, &netagent_mtx_grp, LCK_ATTR_NULL);

	return new_session;
}

netagent_session_t
netagent_create(netagent_event_f event_handler, void *context)
{
	struct netagent_session *session = netagent_create_session(0);
	if (session == NULL) {
		return NULL;
	}

	session->event_handler = event_handler;
	session->event_context = context;
	return session;
}

static void
netagent_token_free(struct netagent_token *token)
{
	kfree_data(token->token_bytes, token->token_length);
	kfree_type(struct netagent_token, token);
}

static struct netagent_wrapper *
netagent_alloc_wrapper_memory(uint32_t data_size)
{
	struct netagent_wrapper *new_wrapper;

	new_wrapper = kalloc_type(struct netagent_wrapper,
	    Z_WAITOK | Z_ZERO | Z_NOFAIL);
	new_wrapper->netagent = kalloc_data(sizeof(struct netagent) + data_size,
	    Z_WAITOK | Z_NOFAIL);

	lck_rw_init(&new_wrapper->agent_lock, &netagent_mtx_grp, LCK_ATTR_NULL);

	return new_wrapper;
}

static void
netagent_free_wrapper_memory(struct netagent_wrapper *wrapper)
{
	// Before destroying the lock, take the lock exclusively and then
	// drop it again. This ensures that no other thread was holding
	// onto the lock at the time of destroying it.
	// This can happen in netagent_client_message_with_params due
	// to the fact that the wrapper lock needs to be held during the
	// event callout, while the list lock has been released. Taking
	// this lock here ensures that any such remaining thread completes
	// before this object is released. Since the wrapper object has
	// already been removed from any and all lists by this point,
	// there isn't any way for a new thread to start referencing it.
	NETAGENT_LOCK_EXCLUSIVE(wrapper);
	NETAGENT_UNLOCK(wrapper);
	lck_rw_destroy(&wrapper->agent_lock, &netagent_mtx_grp);

	kfree_data(wrapper->netagent, sizeof(struct netagent) +
	    wrapper->netagent->netagent_data_size);
	kfree_type(struct netagent_wrapper, wrapper);
}

static void
netagent_free_wrapper(struct netagent_wrapper *wrapper)
{
	// Free any leftover tokens
	struct netagent_token *search_token = NULL;
	struct netagent_token *temp_token = NULL;
	TAILQ_FOREACH_SAFE(search_token, &wrapper->token_list, token_chain, temp_token) {
		TAILQ_REMOVE(&wrapper->token_list, search_token, token_chain);
		netagent_token_free(search_token);
	}

	// Free any pending client triggers
	struct netagent_client * __single search_client = NULL;
	struct netagent_client *temp_client = NULL;
	LIST_FOREACH_SAFE(search_client, &wrapper->pending_triggers_list, client_chain, temp_client) {
		LIST_REMOVE(search_client, client_chain);
		kfree_type(struct netagent_client, search_client);
	}

	// Free wrapper itself
	netagent_free_wrapper_memory(wrapper);
}

static void
netagent_unregister_session_wrapper(struct netagent_session *session)
{
	bool unregistered = FALSE;
	uuid_t unregistered_uuid;
	struct netagent_wrapper *wrapper = NULL;
	NETAGENT_LIST_LOCK_EXCLUSIVE();
	if (session != NULL) {
		NETAGENT_SESSION_LOCK(session);
		wrapper = session->wrapper;
		if (wrapper != NULL) {
			if (netagent_registered_count > 0) {
				netagent_registered_count--;
			}
			if ((session->wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE) &&
			    netagent_active_count > 0) {
				netagent_active_count--;
			}

			LIST_REMOVE(wrapper, list_chain);

			unregistered = TRUE;
			uuid_copy(unregistered_uuid, session->wrapper->netagent->netagent_uuid);

			netagent_free_wrapper(session->wrapper);
			session->wrapper = NULL;
			NETAGENTLOG0(LOG_DEBUG, "Unregistered agent");
		}
		NETAGENT_SESSION_UNLOCK(session);
	}
	NETAGENT_LIST_UNLOCK();

	if (unregistered) {
		ifnet_clear_netagent(unregistered_uuid);
		netagent_post_event(unregistered_uuid, KEV_NETAGENT_UNREGISTERED, TRUE, false);
	}
}

static void
netagent_delete_session(struct netagent_session *session)
{
	if (session != NULL) {
		netagent_unregister_session_wrapper(session);
		lck_mtx_destroy(&session->session_lock, &netagent_mtx_grp);
		kfree_type(struct netagent_session, session);
	}
}

void
netagent_destroy(netagent_session_t session)
{
	return netagent_delete_session((struct netagent_session *)session);
}

static size_t
netagent_packet_get_netagent_data_size(mbuf_t packet, size_t offset, int *err)
{
	int error = 0;

	struct netagent netagent_peek;
	memset(&netagent_peek, 0, sizeof(netagent_peek));

	*err = 0;

	error = mbuf_copydata(packet, offset, sizeof(netagent_peek), &netagent_peek);
	if (error) {
		*err = ENOENT;
		return 0;
	}

	return netagent_peek.netagent_data_size;
}

static errno_t
netagent_handle_register_inner(struct netagent_session *session, struct netagent_wrapper *new_wrapper)
{
	NETAGENT_LIST_LOCK_EXCLUSIVE();

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper != NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENT_LIST_UNLOCK();
		return EINVAL;
	}

	new_wrapper->control_unit = session->control_unit;
	new_wrapper->event_handler = session->event_handler;
	new_wrapper->event_context = session->event_context;
	new_wrapper->generation = g_next_generation++;

	session->wrapper = new_wrapper;
	LIST_INSERT_HEAD(&shared_netagent_list, new_wrapper, list_chain);
	TAILQ_INIT(&new_wrapper->token_list);
	LIST_INIT(&new_wrapper->pending_triggers_list);

	new_wrapper->netagent->netagent_flags |= NETAGENT_FLAG_REGISTERED;
	netagent_registered_count++;
	if (new_wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE) {
		netagent_active_count++;
	}

	NETAGENT_SESSION_UNLOCK(session);
	NETAGENT_LIST_UNLOCK();
	return 0;
}

errno_t
netagent_register(netagent_session_t _session, struct netagent *agent)
{
	struct netagent_wrapper *new_wrapper = NULL;
	uuid_t registered_uuid;

	struct netagent_session *session = (struct netagent_session *)_session;
	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Cannot register agent on NULL session");
		return EINVAL;
	}

	if (agent == NULL) {
		NETAGENTLOG0(LOG_ERR, "Cannot register NULL agent");
		return EINVAL;
	}

	size_t data_size = agent->netagent_data_size;
	if (data_size > NETAGENT_MAX_DATA_SIZE) {
		NETAGENTLOG(LOG_ERR, "Register message size could not be read, data_size %zu",
		    data_size);
		return EINVAL;
	}

	new_wrapper = netagent_alloc_wrapper_memory(data_size);

	__nochk_memcpy(new_wrapper->netagent, agent, sizeof(struct netagent));
	__nochk_memcpy(netagent_get_data(new_wrapper->netagent), netagent_get_data(agent), data_size);

	uuid_copy(registered_uuid, new_wrapper->netagent->netagent_uuid);

	errno_t error = netagent_handle_register_inner(session, new_wrapper);
	if (error != 0) {
		netagent_free_wrapper_memory(new_wrapper);
		return error;
	}

	NETAGENTLOG0(LOG_DEBUG, "Registered new agent");
	netagent_post_event(registered_uuid, KEV_NETAGENT_REGISTERED, TRUE, false);

	return 0;
}

static errno_t
netagent_handle_register_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length)
{
	struct netagent_wrapper *new_wrapper = NULL;
	errno_t response_error = 0;
	struct netagent *register_netagent = (struct netagent *)(void *)payload;
	uuid_t registered_uuid;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = EINVAL;
		goto done;
	}

	if (payload == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	if (payload_length < sizeof(struct netagent)) {
		NETAGENTLOG(LOG_ERR, "Register message size too small for agent: (%zu < %zu)",
		    payload_length, sizeof(struct netagent));
		response_error = EINVAL;
		goto done;
	}

	size_t data_size = register_netagent->netagent_data_size;
	if (data_size > NETAGENT_MAX_DATA_SIZE) {
		NETAGENTLOG(LOG_ERR, "Register message size could not be read, data_size %zu", data_size);
		response_error = EINVAL;
		goto done;
	}

	if (payload_length != (sizeof(struct netagent) + data_size)) {
		NETAGENTLOG(LOG_ERR, "Mismatch between data size and payload length (%lu != %zu)", (sizeof(struct netagent) + data_size), payload_length);
		response_error = EINVAL;
		goto done;
	}

	new_wrapper = netagent_alloc_wrapper_memory(data_size);

	__nochk_memcpy(new_wrapper->netagent, register_netagent, sizeof(struct netagent));
	__nochk_memcpy(netagent_get_data(new_wrapper->netagent), netagent_get_data(register_netagent), data_size);

	uuid_copy(registered_uuid, new_wrapper->netagent->netagent_uuid);

	response_error = netagent_handle_register_inner(session, new_wrapper);
	if (response_error != 0) {
		netagent_free_wrapper_memory(new_wrapper);
		goto done;
	}

	NETAGENTLOG0(LOG_DEBUG, "Registered new agent");
	netagent_post_event(registered_uuid, KEV_NETAGENT_REGISTERED, TRUE, false);

done:
	return response_error;
}

static void
netagent_handle_register_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset)
{
	errno_t error;
	struct netagent_wrapper *new_wrapper = NULL;
	u_int32_t response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
	uuid_t registered_uuid;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	if (payload_length < sizeof(struct netagent)) {
		NETAGENTLOG(LOG_ERR, "Register message size too small for agent: (%zu < %zu)",
		    payload_length, sizeof(struct netagent));
		response_error = NETAGENT_MESSAGE_ERROR_INVALID_DATA;
		goto fail;
	}

	size_t data_size = netagent_packet_get_netagent_data_size(packet, offset, &error);
	if (error || data_size > NETAGENT_MAX_DATA_SIZE) {
		NETAGENTLOG(LOG_ERR, "Register message size could not be read, error %d data_size %zu",
		    error, data_size);
		response_error = NETAGENT_MESSAGE_ERROR_INVALID_DATA;
		goto fail;
	}

	new_wrapper = netagent_alloc_wrapper_memory(data_size);

	error = mbuf_copydata(packet, offset, sizeof(struct netagent) + data_size,
	    new_wrapper->netagent);
	if (error) {
		NETAGENTLOG(LOG_ERR, "Failed to read data into agent structure: %d", error);
		netagent_free_wrapper_memory(new_wrapper);
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	uuid_copy(registered_uuid, new_wrapper->netagent->netagent_uuid);

	error = netagent_handle_register_inner(session, new_wrapper);
	if (error) {
		NETAGENTLOG(LOG_ERR, "Failed to register agent: %d", error);
		netagent_free_wrapper_memory(new_wrapper);
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	NETAGENTLOG0(LOG_DEBUG, "Registered new agent");
	netagent_send_success_response(session, NETAGENT_MESSAGE_TYPE_REGISTER, message_id);
	netagent_post_event(registered_uuid, KEV_NETAGENT_REGISTERED, TRUE, false);
	return;
fail:
	netagent_send_error_response(session, NETAGENT_MESSAGE_TYPE_REGISTER, message_id, response_error);
}

errno_t
netagent_unregister(netagent_session_t _session)
{
	struct netagent_session *session = (struct netagent_session *)_session;
	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Cannot unregister NULL session");
		return EINVAL;
	}

	netagent_unregister_session_wrapper(session);
	return 0;
}

static errno_t
netagent_handle_unregister_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length)
{
#pragma unused(payload, payload_length)
	errno_t response_error = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = EINVAL;
		goto done;
	}

	netagent_unregister_session_wrapper(session);

done:
	return response_error;
}

static void
netagent_handle_unregister_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset)
{
#pragma unused(payload_length, packet, offset)
	u_int32_t response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	netagent_unregister_session_wrapper(session);

	netagent_send_success_response(session, NETAGENT_MESSAGE_TYPE_UNREGISTER, message_id);
	return;
fail:
	netagent_send_error_response(session, NETAGENT_MESSAGE_TYPE_UNREGISTER, message_id, response_error);
}

static void
netagent_send_cellular_failed_event(struct netagent_wrapper *wrapper,
    pid_t pid, uuid_t proc_uuid)
{
	if (strlcmp(wrapper->netagent->netagent_domain, "Cellular", NETAGENT_DOMAINSIZE) != 0) {
		return;
	}

	struct kev_netpolicy_ifdenied ev_ifdenied;

	bzero(&ev_ifdenied, sizeof(ev_ifdenied));

	ev_ifdenied.ev_data.epid = (u_int64_t)pid;
	uuid_copy(ev_ifdenied.ev_data.euuid, proc_uuid);
	ev_ifdenied.ev_if_functional_type = IFRTYPE_FUNCTIONAL_CELLULAR;

	netpolicy_post_msg(KEV_NETPOLICY_IFFAILED, &ev_ifdenied.ev_data, sizeof(ev_ifdenied));
}

static errno_t
netagent_handle_update_inner(struct netagent_session *session, struct netagent_wrapper *new_wrapper,
    size_t data_size, u_int8_t *agent_changed, netagent_error_domain_t error_domain)
{
	errno_t response_error = 0;

	if (agent_changed == NULL) {
		NETAGENTLOG0(LOG_ERR, "Invalid argument: agent_changed");
		return EINVAL;
	}

	NETAGENT_LIST_LOCK_EXCLUSIVE();

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENT_LIST_UNLOCK();
		response_error = ENOENT;
		return response_error;
	}

	NETAGENT_LOCK_EXCLUSIVE(session->wrapper);

	if (uuid_compare(session->wrapper->netagent->netagent_uuid, new_wrapper->netagent->netagent_uuid) != 0 ||
	    memcmp(&session->wrapper->netagent->netagent_domain, &new_wrapper->netagent->netagent_domain,
	    sizeof(new_wrapper->netagent->netagent_domain)) != 0 ||
	    memcmp(&session->wrapper->netagent->netagent_type, &new_wrapper->netagent->netagent_type,
	    sizeof(new_wrapper->netagent->netagent_type)) != 0) {
		NETAGENT_UNLOCK(session->wrapper);
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENT_LIST_UNLOCK();
		NETAGENTLOG0(LOG_ERR, "Basic agent parameters do not match, cannot update");
		if (error_domain == kNetagentErrorDomainPOSIX) {
			response_error = EINVAL;
		} else if (error_domain == kNetagentErrorDomainUserDefined) {
			response_error = NETAGENT_MESSAGE_ERROR_CANNOT_UPDATE;
		}
		return response_error;
	}

	new_wrapper->netagent->netagent_flags |= NETAGENT_FLAG_REGISTERED;
	if (session->wrapper->netagent->netagent_data_size == new_wrapper->netagent->netagent_data_size &&
	    memcmp(session->wrapper->netagent, new_wrapper->netagent, sizeof(struct netagent)) == 0 &&
	    memcmp(netagent_get_data(session->wrapper->netagent), netagent_get_data(new_wrapper->netagent), data_size) == 0) {
		// Agent is exactly identical, don't increment the generation count

		// Make a copy of the list of pending clients, and clear the current list
		struct netagent_client_list_s pending_triggers_list_copy;
		LIST_INIT(&pending_triggers_list_copy);
		struct netagent_client * __single search_client = NULL;
		struct netagent_client *temp_client = NULL;
		LIST_FOREACH_SAFE(search_client, &session->wrapper->pending_triggers_list, client_chain, temp_client) {
			LIST_REMOVE(search_client, client_chain);
			LIST_INSERT_HEAD(&pending_triggers_list_copy, search_client, client_chain);
		}
		NETAGENT_UNLOCK(session->wrapper);
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENT_LIST_UNLOCK();

		// Update pending client triggers without holding a lock
		search_client = NULL;
		temp_client = NULL;
		LIST_FOREACH_SAFE(search_client, &pending_triggers_list_copy, client_chain, temp_client) {
			necp_force_update_client(search_client->client_id, session->wrapper->netagent->netagent_uuid, session->wrapper->generation);
			netagent_send_cellular_failed_event(new_wrapper, search_client->client_pid, search_client->client_proc_uuid);
			LIST_REMOVE(search_client, client_chain);
			kfree_type(struct netagent_client, search_client);
		}
		NETAGENTLOG0(LOG_DEBUG, "Updated agent (no changes)");
		*agent_changed = FALSE;
		return response_error;
	}

	new_wrapper->generation = g_next_generation++;
	new_wrapper->use_count = session->wrapper->use_count;

	TAILQ_INIT(&new_wrapper->token_list);
	TAILQ_CONCAT(&new_wrapper->token_list, &session->wrapper->token_list, token_chain);
	new_wrapper->token_count = session->wrapper->token_count;
	new_wrapper->token_low_water = session->wrapper->token_low_water;
	new_wrapper->last_client_error = session->wrapper->last_client_error;
	new_wrapper->client_error_count = session->wrapper->client_error_count;

	if ((new_wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE) &&
	    !(session->wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE)) {
		netagent_active_count++;
	} else if (!(new_wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE) &&
	    (session->wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE) &&
	    netagent_active_count > 0) {
		netagent_active_count--;
	}

	LIST_REMOVE(session->wrapper, list_chain);
	NETAGENT_UNLOCK(session->wrapper);
	netagent_free_wrapper(session->wrapper);
	session->wrapper = new_wrapper;
	new_wrapper->control_unit = session->control_unit;
	new_wrapper->event_handler = session->event_handler;
	new_wrapper->event_context = session->event_context;
	LIST_INSERT_HEAD(&shared_netagent_list, new_wrapper, list_chain);
	LIST_INIT(&new_wrapper->pending_triggers_list);

	NETAGENT_SESSION_UNLOCK(session);
	NETAGENT_LIST_UNLOCK();

	NETAGENTLOG0(LOG_DEBUG, "Updated agent");
	*agent_changed = TRUE;

	return response_error;
}

errno_t
netagent_update(netagent_session_t _session, struct netagent *agent)
{
	u_int8_t agent_changed;
	struct netagent_wrapper *new_wrapper = NULL;
	bool should_update_immediately;
	uuid_t updated_uuid;

	struct netagent_session *session = (struct netagent_session *)_session;
	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Cannot update agent on NULL session");
		return EINVAL;
	}

	if (agent == NULL) {
		NETAGENTLOG0(LOG_ERR, "Cannot register NULL agent");
		return EINVAL;
	}

	size_t data_size = agent->netagent_data_size;
	if (data_size > NETAGENT_MAX_DATA_SIZE) {
		NETAGENTLOG(LOG_ERR, "Update message size (%zu > %u) too large", data_size, NETAGENT_MAX_DATA_SIZE);
		return EINVAL;
	}

	new_wrapper = netagent_alloc_wrapper_memory(data_size);

	__nochk_memcpy(new_wrapper->netagent, agent, sizeof(struct netagent));
	__nochk_memcpy(netagent_get_data(new_wrapper->netagent), netagent_get_data(agent), data_size);

	uuid_copy(updated_uuid, new_wrapper->netagent->netagent_uuid);
	should_update_immediately = (NETAGENT_FLAG_UPDATE_IMMEDIATELY == (new_wrapper->netagent->netagent_flags & NETAGENT_FLAG_UPDATE_IMMEDIATELY));

	errno_t error = netagent_handle_update_inner(session, new_wrapper, data_size, &agent_changed, kNetagentErrorDomainPOSIX);
	if (error == 0) {
		netagent_post_event(updated_uuid, KEV_NETAGENT_UPDATED, agent_changed, should_update_immediately);
		if (agent_changed == FALSE) {
			// The session wrapper does not need the "new_wrapper" as nothing changed
			netagent_free_wrapper_memory(new_wrapper);
		}
	} else {
		netagent_free_wrapper_memory(new_wrapper);
		return error;
	}

	return 0;
}

static errno_t
netagent_handle_update_setopt(struct netagent_session *session, u_int8_t *payload, size_t payload_length)
{
	struct netagent_wrapper *new_wrapper = NULL;
	errno_t response_error = 0;
	struct netagent *update_netagent = (struct netagent *)(void *)payload;
	u_int8_t agent_changed;
	bool should_update_immediately;
	uuid_t updated_uuid;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = EINVAL;
		goto done;
	}

	if (payload == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	if (payload_length < sizeof(struct netagent)) {
		NETAGENTLOG(LOG_ERR, "Update message size too small for agent: (%zu < %zu)",
		    payload_length, sizeof(struct netagent));
		response_error = EINVAL;
		goto done;
	}

	size_t data_size = update_netagent->netagent_data_size;
	if (data_size > NETAGENT_MAX_DATA_SIZE) {
		NETAGENTLOG(LOG_ERR, "Update message size (%zu > %u) too large", data_size, NETAGENT_MAX_DATA_SIZE);
		response_error = EINVAL;
		goto done;
	}

	if (payload_length != (sizeof(struct netagent) + data_size)) {
		NETAGENTLOG(LOG_ERR, "Mismatch between data size and payload length (%lu != %zu)", (sizeof(struct netagent) + data_size), payload_length);
		response_error = EINVAL;
		goto done;
	}

	new_wrapper = netagent_alloc_wrapper_memory(data_size);

	__nochk_memcpy(new_wrapper->netagent, update_netagent, sizeof(struct netagent));
	__nochk_memcpy(netagent_get_data(new_wrapper->netagent), netagent_get_data(update_netagent), data_size);

	uuid_copy(updated_uuid, new_wrapper->netagent->netagent_uuid);
	should_update_immediately = (NETAGENT_FLAG_UPDATE_IMMEDIATELY == (new_wrapper->netagent->netagent_flags & NETAGENT_FLAG_UPDATE_IMMEDIATELY));

	response_error = netagent_handle_update_inner(session, new_wrapper, data_size, &agent_changed, kNetagentErrorDomainPOSIX);
	if (response_error == 0) {
		netagent_post_event(updated_uuid, KEV_NETAGENT_UPDATED, agent_changed, should_update_immediately);
		if (agent_changed == FALSE) {
			// The session wrapper does not need the "new_wrapper" as nothing changed
			netagent_free_wrapper_memory(new_wrapper);
		}
	} else {
		netagent_free_wrapper_memory(new_wrapper);
	}

done:
	return response_error;
}

static void
netagent_handle_update_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset)
{
	int error;
	struct netagent_wrapper *new_wrapper = NULL;
	u_int32_t response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
	u_int8_t agent_changed;
	uuid_t updated_uuid;
	bool should_update_immediately;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	if (payload_length < sizeof(struct netagent)) {
		NETAGENTLOG(LOG_ERR, "Update message size too small for agent: (%zu < %zu)",
		    payload_length, sizeof(struct netagent));
		response_error = NETAGENT_MESSAGE_ERROR_INVALID_DATA;
		goto fail;
	}

	size_t data_size = netagent_packet_get_netagent_data_size(packet, offset, &error);
	if (error || data_size > NETAGENT_MAX_DATA_SIZE) {
		NETAGENTLOG(LOG_ERR, "Update message size could not be read, error %d data_size %zu",
		    error, data_size);
		response_error = NETAGENT_MESSAGE_ERROR_INVALID_DATA;
		goto fail;
	}

	new_wrapper = netagent_alloc_wrapper_memory(data_size);

	error = mbuf_copydata(packet, offset, sizeof(struct netagent) + data_size, new_wrapper->netagent);
	if (error) {
		NETAGENTLOG(LOG_ERR, "Failed to read data into agent structure: %d", error);
		netagent_free_wrapper_memory(new_wrapper);
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	uuid_copy(updated_uuid, new_wrapper->netagent->netagent_uuid);
	should_update_immediately = (NETAGENT_FLAG_UPDATE_IMMEDIATELY == (new_wrapper->netagent->netagent_flags & NETAGENT_FLAG_UPDATE_IMMEDIATELY));

	response_error = (u_int32_t)netagent_handle_update_inner(session, new_wrapper, data_size, &agent_changed, kNetagentErrorDomainUserDefined);
	if (response_error != 0) {
		if (response_error == ENOENT) {
			response_error = NETAGENT_MESSAGE_ERROR_NOT_REGISTERED;
		}
		netagent_free_wrapper_memory(new_wrapper);
		goto fail;
	}

	netagent_send_success_response(session, NETAGENT_MESSAGE_TYPE_UPDATE, message_id);

	netagent_post_event(updated_uuid, KEV_NETAGENT_UPDATED, agent_changed, should_update_immediately);

	if (agent_changed == FALSE) {
		// The session wrapper does not need the "new_wrapper" as nothing changed
		netagent_free_wrapper_memory(new_wrapper);
	}

	return;
fail:
	netagent_send_error_response(session, NETAGENT_MESSAGE_TYPE_UPDATE, message_id, response_error);
}

errno_t
netagent_assign_nexus(netagent_session_t _session, uuid_t necp_client_uuid,
    void * __sized_by(assigned_results_length)assign_message, size_t assigned_results_length)
{
	struct netagent_session *session = (struct netagent_session *)_session;
	uuid_t netagent_uuid;
	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Cannot assign nexus from NULL session");
		return EINVAL;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent");
		return ENOENT;
	}
	NETAGENT_LOCK_SHARED(session->wrapper);
	uuid_copy(netagent_uuid, session->wrapper->netagent->netagent_uuid);
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	// Note that if the error is 0, NECP has taken over our malloc'ed buffer
	int error = necp_assign_client_result(netagent_uuid, necp_client_uuid, assign_message, assigned_results_length);
	if (error) {
		// necp_assign_client_result returns POSIX errors; don't error for ENOENT
		NETAGENTLOG((error == ENOENT ? LOG_DEBUG : LOG_ERR), "Client assignment failed: %d", error);
		return error;
	}

	NETAGENTLOG0(LOG_DEBUG, "Agent assigned nexus properties to client");
	return 0;
}

errno_t
netagent_update_flow_protoctl_event(netagent_session_t _session,
    uuid_t client_id, uint32_t protoctl_event_code,
    uint32_t protoctl_event_val, uint32_t protoctl_event_tcp_seq_number)
{
	struct netagent_session *session = (struct netagent_session *)_session;
	uuid_t netagent_uuid;
	int error = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Cannot assign nexus from NULL session");
		return EINVAL;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent");
		return ENOENT;
	}
	NETAGENT_LOCK_SHARED(session->wrapper);
	uuid_copy(netagent_uuid, session->wrapper->netagent->netagent_uuid);
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	error = necp_update_flow_protoctl_event(netagent_uuid,
	    client_id, protoctl_event_code, protoctl_event_val, protoctl_event_tcp_seq_number);

	return error;
}

static errno_t
netagent_handle_assign_nexus_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length)
{
	errno_t response_error = 0;
	struct netagent_assign_nexus_message * __single assign_nexus_netagent = (struct netagent_assign_nexus_message *)(void *)payload;
	uuid_t client_id;
	uuid_t netagent_uuid;
	u_int8_t *assigned_results = NULL;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (payload == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent to get");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_SHARED(session->wrapper);
	uuid_copy(netagent_uuid, session->wrapper->netagent->netagent_uuid);
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	if (payload_length < sizeof(uuid_t)) {
		NETAGENTLOG0(LOG_ERR, "Assign message is too short");
		response_error = EINVAL;
		goto done;
	}

	memcpy(client_id, assign_nexus_netagent->assign_client_id, sizeof(client_id));
	size_t assigned_results_length = (payload_length - sizeof(client_id));

	if (assigned_results_length > 0) {
		assigned_results = kalloc_data(assigned_results_length, Z_WAITOK);
		if (assigned_results == NULL) {
			NETAGENTLOG(LOG_ERR, "Failed to allocate assign message (%lu bytes)", assigned_results_length);
			response_error = ENOMEM;
			goto done;
		}
		memcpy(assigned_results,
		    netagent_assign_message_get_necp_result(assign_nexus_netagent, assigned_results_length),
		    assigned_results_length);
	}

	// Note that if the error is 0, NECP has taken over our malloc'ed buffer
	response_error = necp_assign_client_result(netagent_uuid, client_id, assigned_results, assigned_results_length);
	if (response_error) {
		// necp_assign_client_result returns POSIX errors
		kfree_data(assigned_results, assigned_results_length);
		NETAGENTLOG(LOG_ERR, "Client assignment failed: %d", response_error);
		goto done;
	}

	NETAGENTLOG0(LOG_DEBUG, "Agent assigned nexus properties to client");
done:
	return response_error;
}


static void
netagent_handle_assign_nexus_message(struct netagent_session *session, u_int32_t message_id,
    size_t payload_length, mbuf_t packet, size_t offset)
{
	int error = 0;
	u_int32_t response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
	uuid_t client_id;
	uuid_t netagent_uuid;
	u_int8_t * assigned_results = NULL;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent to get");
		response_error = NETAGENT_MESSAGE_ERROR_NOT_REGISTERED;
		goto fail;
	}
	NETAGENT_LOCK_SHARED(session->wrapper);
	uuid_copy(netagent_uuid, session->wrapper->netagent->netagent_uuid);
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	if (payload_length < sizeof(uuid_t)) {
		NETAGENTLOG0(LOG_ERR, "Assign message is too short");
		response_error = NETAGENT_MESSAGE_ERROR_INVALID_DATA;
		goto fail;
	}

	error = mbuf_copydata(packet, offset, sizeof(client_id), &client_id);
	if (error) {
		NETAGENTLOG(LOG_ERR, "Failed to read uuid for assign message: %d", error);
		response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
		goto fail;
	}

	size_t assigned_results_length = (payload_length - sizeof(client_id));
	if (assigned_results_length > 0) {
		assigned_results = kalloc_data( assigned_results_length, Z_WAITOK);
		if (assigned_results == NULL) {
			NETAGENTLOG(LOG_ERR, "Failed to allocate assign message (%lu bytes)", assigned_results_length);
			response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
			goto fail;
		}

		error = mbuf_copydata(packet, offset + sizeof(client_id), assigned_results_length, assigned_results);
		if (error) {
			kfree_data(assigned_results, assigned_results_length);
			NETAGENTLOG(LOG_ERR, "Failed to read assign message: %d", error);
			response_error = NETAGENT_MESSAGE_ERROR_INTERNAL;
			goto fail;
		}
	}

	// Note that if the error is 0, NECP has taken over our malloc'ed buffer
	error = necp_assign_client_result(netagent_uuid, client_id, assigned_results, assigned_results_length);
	if (error) {
		kfree_data(assigned_results, assigned_results_length);
		NETAGENTLOG(LOG_ERR, "Client assignment failed: %d", error);
		response_error = NETAGENT_MESSAGE_ERROR_CANNOT_ASSIGN;
		goto fail;
	}

	NETAGENTLOG0(LOG_DEBUG, "Agent assigned nexus properties to client");
	netagent_send_success_response(session, NETAGENT_MESSAGE_TYPE_ASSIGN_NEXUS, message_id);
	return;
fail:
	netagent_send_error_response(session, NETAGENT_MESSAGE_TYPE_ASSIGN_NEXUS, message_id, response_error);
}

static errno_t
netagent_handle_assign_group_setopt(struct netagent_session *session, u_int8_t *payload,
    size_t payload_length)
{
	errno_t response_error = 0;
	struct netagent_assign_nexus_message *assign_message = (struct netagent_assign_nexus_message *)(void *)payload;
	uuid_t client_id;
	uuid_t netagent_uuid;
	u_int8_t *assigned_group_members = NULL;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (payload == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent to get");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_SHARED(session->wrapper);
	uuid_copy(netagent_uuid, session->wrapper->netagent->netagent_uuid);
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	if (payload_length < sizeof(uuid_t)) {
		NETAGENTLOG0(LOG_ERR, "Group assign message is too short");
		response_error = EINVAL;
		goto done;
	}

	memcpy(client_id, assign_message->assign_client_id, sizeof(client_id));
	size_t assigned_group_members_length = (payload_length - sizeof(client_id));

	if (assigned_group_members_length > 0) {
		assigned_group_members = (u_int8_t *)kalloc_data(assigned_group_members_length, Z_WAITOK);
		if (assigned_group_members == NULL) {
			NETAGENTLOG(LOG_ERR, "Failed to allocate group assign message (%lu bytes)", assigned_group_members_length);
			response_error = ENOMEM;
			goto done;
		}
		memcpy(assigned_group_members, netagent_assign_message_get_necp_result(assign_message, assigned_group_members_length), assigned_group_members_length);
	}

	// Note that if the error is 0, NECP has taken over our malloc'ed buffer
	response_error = necp_assign_client_group_members(netagent_uuid, client_id, assigned_group_members, assigned_group_members_length);
	if (response_error != 0) {
		// necp_assign_client_group_members returns POSIX errors
		if (assigned_group_members != NULL) {
			kfree_data(assigned_group_members, assigned_group_members_length);
		}
		NETAGENTLOG(LOG_ERR, "Client group assignment failed: %d", response_error);
		goto done;
	}

	NETAGENTLOG0(LOG_DEBUG, "Agent assigned group members to client");
done:
	return response_error;
}


errno_t
netagent_handle_use_count_setopt(struct netagent_session *session, u_int8_t * __sized_by(payload_length)payload, size_t payload_length)
{
	errno_t response_error = 0;
	uint64_t use_count = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (payload == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	if (payload_length != sizeof(use_count)) {
		NETAGENTLOG(LOG_ERR, "Payload length is invalid (%lu)", payload_length);
		response_error = EINVAL;
		goto done;
	}

	memcpy(&use_count, payload, sizeof(use_count));

	NETAGENT_SESSION_LOCK(session);

	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_EXCLUSIVE(session->wrapper);
	session->wrapper->use_count = use_count;
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

done:
	return response_error;
}

errno_t
netagent_handle_use_count_getopt(struct netagent_session *session, u_int8_t * __sized_by(*buffer_length)buffer, size_t *buffer_length)
{
	errno_t response_error = 0;
	uint64_t use_count = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (buffer == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	if (*buffer_length != sizeof(use_count)) {
		NETAGENTLOG(LOG_ERR, "Buffer length is invalid (%lu)", *buffer_length);
		response_error = EINVAL;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);

	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_SHARED(session->wrapper);
	use_count = session->wrapper->use_count;
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	memcpy(buffer, &use_count, sizeof(use_count));
	*buffer_length = sizeof(use_count);

done:
	return response_error;
}

static errno_t
netagent_handle_add_token_setopt(struct netagent_session *session, u_int8_t * __sized_by(token_length)token, size_t token_length)
{
	errno_t response_error = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (token == NULL) {
		NETAGENTLOG0(LOG_ERR, "No token received");
		response_error = EINVAL;
		goto done;
	}

	if (token_length > NETAGENT_MAX_DATA_SIZE) {
		NETAGENTLOG(LOG_ERR, "Token length is invalid (%lu)", token_length);
		response_error = EINVAL;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_EXCLUSIVE(session->wrapper);
	if (session->wrapper->token_count >= NETAGENT_MAX_TOKEN_COUNT) {
		NETAGENT_UNLOCK(session->wrapper);
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session cannot add more tokens");
		response_error = EINVAL;
		goto done;
	}

	struct netagent_token *token_struct = NULL;

	token_struct = kalloc_type(struct netagent_token, Z_WAITOK | Z_ZERO | Z_NOFAIL);
	token_struct->token_bytes = kalloc_data(token_length, Z_WAITOK | Z_NOFAIL);
	token_struct->token_length = (u_int32_t)token_length;
	memcpy(token_struct->token_bytes, token, token_length);

	TAILQ_INSERT_TAIL(&session->wrapper->token_list, token_struct, token_chain);

	session->wrapper->token_count++;

	// Reset deadline time, now that there are more than 0 tokens
	session->wrapper->need_tokens_event_deadline = 0;

	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);
done:
	return response_error;
}

static errno_t
netagent_handle_flush_tokens_setopt(struct netagent_session *session, __unused u_int8_t *buffer, __unused size_t buffer_length)
{
	errno_t response_error = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_EXCLUSIVE(session->wrapper);
	struct netagent_token *search_token = NULL;
	struct netagent_token *temp_token = NULL;
	TAILQ_FOREACH_SAFE(search_token, &session->wrapper->token_list, token_chain, temp_token) {
		TAILQ_REMOVE(&session->wrapper->token_list, search_token, token_chain);
		netagent_token_free(search_token);
	}
	session->wrapper->token_count = 0;
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);
done:
	return response_error;
}

static errno_t
netagent_handle_token_count_getopt(struct netagent_session *session, u_int8_t * __sized_by(*buffer_length)buffer, size_t *buffer_length)
{
	errno_t response_error = 0;
	uint32_t token_count = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (buffer == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	if (*buffer_length != sizeof(token_count)) {
		NETAGENTLOG(LOG_ERR, "Buffer length is invalid (%lu)", *buffer_length);
		response_error = EINVAL;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_SHARED(session->wrapper);
	token_count = session->wrapper->token_count;
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	memcpy(buffer, &token_count, sizeof(token_count));
	*buffer_length = sizeof(token_count);

done:
	return response_error;
}

static errno_t
netagent_handle_token_low_water_setopt(struct netagent_session *session, u_int8_t * __sized_by(buffer_length)buffer, size_t buffer_length)
{
	errno_t response_error = 0;
	uint32_t token_low_water = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (buffer == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	if (buffer_length != sizeof(token_low_water)) {
		NETAGENTLOG(LOG_ERR, "Buffer length is invalid (%lu)", buffer_length);
		response_error = EINVAL;
		goto done;
	}

	memcpy(&token_low_water, buffer, sizeof(token_low_water));

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_EXCLUSIVE(session->wrapper);
	session->wrapper->token_low_water = token_low_water;
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

done:
	return response_error;
}

static errno_t
netagent_handle_token_low_water_getopt(struct netagent_session *session, u_int8_t * __sized_by(*buffer_length)buffer, size_t *buffer_length)
{
	errno_t response_error = 0;
	uint32_t token_low_water = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	if (buffer == NULL) {
		NETAGENTLOG0(LOG_ERR, "No payload received");
		response_error = EINVAL;
		goto done;
	}

	if (*buffer_length != sizeof(token_low_water)) {
		NETAGENTLOG(LOG_ERR, "Buffer length is invalid (%lu)", *buffer_length);
		response_error = EINVAL;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_SHARED(session->wrapper);
	token_low_water = session->wrapper->token_low_water;
	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);

	memcpy(buffer, &token_low_water, sizeof(token_low_water));
	*buffer_length = sizeof(token_low_water);

done:
	return response_error;
}

static errno_t
netagent_handle_reset_client_error_setopt(struct netagent_session *session, __unused u_int8_t *payload, __unused size_t payload_length)
{
	errno_t response_error = 0;

	if (session == NULL) {
		NETAGENTLOG0(LOG_ERR, "Failed to find session");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_SESSION_LOCK(session);
	if (session->wrapper == NULL) {
		NETAGENT_SESSION_UNLOCK(session);
		NETAGENTLOG0(LOG_ERR, "Session has no agent registered");
		response_error = ENOENT;
		goto done;
	}

	NETAGENT_LOCK_EXCLUSIVE(session->wrapper);
	struct netagent_token *search_token = NULL;
	struct netagent_token *temp_token = NULL;
	TAILQ_FOREACH_SAFE(search_token, &session->wrapper->token_list, token_chain, temp_token) {
		TAILQ_REMOVE(&session->wrapper->token_list, search_token, token_chain);
		netagent_token_free(search_token);
	}
	session->wrapper->last_client_error = 0;
	session->wrapper->client_error_count = 0;

	NETAGENT_UNLOCK(session->wrapper);
	NETAGENT_SESSION_UNLOCK(session);
done:
	return response_error;
}

static struct netagent_wrapper *
netagent_find_agent_with_uuid_and_lock(uuid_t uuid, bool exclusively, bool ignore_lock)
{
	NETAGENT_LIST_ASSERT_LOCKED();

	struct netagent_wrapper *search_netagent = NULL;

	LIST_FOREACH(search_netagent, &shared_netagent_list, list_chain) {
		if (uuid_compare(search_netagent->netagent->netagent_uuid, uuid) == 0) {
			if (!ignore_lock) {
				if (exclusively) {
					NETAGENT_LOCK_EXCLUSIVE(search_netagent);
				} else {
					NETAGENT_LOCK_SHARED(search_netagent);
				}
			}
			return search_netagent;
		}
	}

	return NULL;
}

void
netagent_post_updated_interfaces(uuid_t uuid)
{
	if (!uuid_is_null(uuid)) {
		netagent_post_event(uuid, KEV_NETAGENT_UPDATED_INTERFACES, true, false);
	} else {
		NETAGENTLOG0(LOG_DEBUG, "Interface event with no associated agent");
	}
}

static u_int32_t
netagent_dump_get_data_size_locked()
{
	NETAGENT_LIST_ASSERT_LOCKED();

	struct netagent_wrapper *search_netagent = NULL;
	u_int32_t total_netagent_data_size = 0;
	// Traverse the shared list to know how much data the client needs to allocate to get the list of agent UUIDs
	LIST_FOREACH(search_netagent, &shared_netagent_list, list_chain) {
		total_netagent_data_size += sizeof(search_netagent->netagent->netagent_uuid);
	}
	return total_netagent_data_size;
}

static void
netagent_dump_copy_data_locked(u_int8_t * __sized_by(buffer_length)buffer, u_int32_t buffer_length)
{
	NETAGENT_LIST_ASSERT_LOCKED();

	size_t response_size = 0;
	u_int8_t * __indexable cursor = NULL;
	struct netagent_wrapper * __single search_netagent = NULL;

	response_size = buffer_length; // We already know that buffer_length is the same as total_netagent_data_size.
	cursor = buffer;
	LIST_FOREACH(search_netagent, &shared_netagent_list, list_chain) {
		memcpy(cursor, search_netagent->netagent->netagent_uuid, sizeof(search_netagent->netagent->netagent_uuid));
		cursor += sizeof(search_netagent->netagent->netagent_uuid);
	}
}

int
netagent_ioctl(u_long cmd, caddr_t __sized_by(IOCPARM_LEN(cmd)) data)
{
	int error = 0;

	switch (cmd) {
	case SIOCGIFAGENTLIST32:
	case SIOCGIFAGENTLIST64: {
		/* Check entitlement if the client requests agent dump */
		errno_t cred_result = priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NECP_POLICIES, 0);
		if (cred_result != 0) {
			NETAGENTLOG0(LOG_ERR, "Client does not hold the necessary entitlement to get netagent information");
			return EINVAL;
		}
		break;
	}
	default:
		break;
	}

	NETAGENT_LIST_LOCK_SHARED();
	switch (cmd) {
	case SIOCGIFAGENTDATA32: {
		struct netagent_req32 *ifsir32 = (struct netagent_req32 *)(void *)data;
		struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(ifsir32->netagent_uuid, false, false);
		if (wrapper == NULL) {
			error = ENOENT;
			break;
		}
		uuid_copy(ifsir32->netagent_uuid, wrapper->netagent->netagent_uuid);
		memcpy(ifsir32->netagent_domain, wrapper->netagent->netagent_domain, sizeof(ifsir32->netagent_domain));
		memcpy(ifsir32->netagent_type, wrapper->netagent->netagent_type, sizeof(ifsir32->netagent_type));
		memcpy(ifsir32->netagent_desc, wrapper->netagent->netagent_desc, sizeof(ifsir32->netagent_desc));
		ifsir32->netagent_flags = wrapper->netagent->netagent_flags;
		if (ifsir32->netagent_data_size == 0) {
			// First pass, client wants data size
			ifsir32->netagent_data_size = wrapper->netagent->netagent_data_size;
		} else if (ifsir32->netagent_data != USER_ADDR_NULL &&
		    ifsir32->netagent_data_size == wrapper->netagent->netagent_data_size) {
			// Second pass, client wants data buffer filled out
			error = copyout(netagent_get_data(wrapper->netagent), ifsir32->netagent_data, wrapper->netagent->netagent_data_size);
		} else {
			error = EINVAL;
		}
		NETAGENT_UNLOCK(wrapper);
		break;
	}
	case SIOCGIFAGENTDATA64: {
		struct netagent_req64 *ifsir64 = (struct netagent_req64 *)(void *)data;
		struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(ifsir64->netagent_uuid, false, false);
		if (wrapper == NULL) {
			error = ENOENT;
			break;
		}
		uuid_copy(ifsir64->netagent_uuid, wrapper->netagent->netagent_uuid);
		memcpy(ifsir64->netagent_domain, wrapper->netagent->netagent_domain, sizeof(ifsir64->netagent_domain));
		memcpy(ifsir64->netagent_type, wrapper->netagent->netagent_type, sizeof(ifsir64->netagent_type));
		memcpy(ifsir64->netagent_desc, wrapper->netagent->netagent_desc, sizeof(ifsir64->netagent_desc));
		ifsir64->netagent_flags = wrapper->netagent->netagent_flags;
		if (ifsir64->netagent_data_size == 0) {
			// First pass, client wants data size
			ifsir64->netagent_data_size = wrapper->netagent->netagent_data_size;
		} else if (ifsir64->netagent_data != USER_ADDR_NULL &&
		    ifsir64->netagent_data_size == wrapper->netagent->netagent_data_size) {
			// Second pass, client wants data buffer filled out
			error = copyout(netagent_get_data(wrapper->netagent), ifsir64->netagent_data, wrapper->netagent->netagent_data_size);
		} else {
			error = EINVAL;
		}
		NETAGENT_UNLOCK(wrapper);
		break;
	}
	case SIOCGIFAGENTLIST32: {
		struct netagentlist_req32 *ifsir32 = (struct netagentlist_req32 *)(void *)data;
		if (ifsir32->data_size == 0) {
			// First pass, client wants data size
			ifsir32->data_size = netagent_dump_get_data_size_locked();
		} else if (ifsir32->data != USER_ADDR_NULL &&
		    ifsir32->data_size > 0 &&
		    ifsir32->data_size == netagent_dump_get_data_size_locked()) {
			// Second pass, client wants data buffer filled out
			u_int8_t *response = NULL;
			response = (u_int8_t *)kalloc_data(ifsir32->data_size, Z_NOWAIT | Z_ZERO);
			if (response == NULL) {
				error = ENOMEM;
				break;
			}

			netagent_dump_copy_data_locked(response, ifsir32->data_size);
			error = copyout(response, ifsir32->data, ifsir32->data_size);
			kfree_data(response, ifsir32->data_size);
		} else {
			error = EINVAL;
		}
		break;
	}
	case SIOCGIFAGENTLIST64: {
		struct netagentlist_req64 *ifsir64 = (struct netagentlist_req64 *)(void *)data;
		if (ifsir64->data_size == 0) {
			// First pass, client wants data size
			ifsir64->data_size = netagent_dump_get_data_size_locked();
		} else if (ifsir64->data != USER_ADDR_NULL &&
		    ifsir64->data_size > 0 &&
		    ifsir64->data_size == netagent_dump_get_data_size_locked()) {
			// Second pass, client wants data buffer filled out
			u_int8_t *response = NULL;
			response = (u_int8_t *)kalloc_data(ifsir64->data_size, Z_NOWAIT | Z_ZERO);
			if (response == NULL) {
				error = ENOMEM;
				break;
			}

			netagent_dump_copy_data_locked(response, ifsir64->data_size);
			error = copyout(response, ifsir64->data, ifsir64->data_size);
			kfree_data(response, ifsir64->data_size);
		} else {
			error = EINVAL;
		}
		break;
	}
	default: {
		error = EINVAL;
		break;
	}
	}
	NETAGENT_LIST_UNLOCK();
	return error;
}

u_int32_t
netagent_get_flags(uuid_t uuid)
{
	u_int32_t flags = 0;
	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(uuid, false, false);
	if (wrapper != NULL) {
		flags = wrapper->netagent->netagent_flags;
		NETAGENT_UNLOCK(wrapper);
	} else {
		NETAGENTLOG0(LOG_DEBUG, "Flags requested for invalid netagent");
	}
	NETAGENT_LIST_UNLOCK();

	return flags;
}

errno_t
netagent_set_flags(uuid_t uuid, u_int32_t flags)
{
	errno_t error = 0;
	bool updated = false;

	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(uuid, true, false);
	if (wrapper != NULL) {
		// Don't allow the clients to clear
		// NETAGENT_FLAG_REGISTERED.
		uint32_t registered =
		    wrapper->netagent->netagent_flags & NETAGENT_FLAG_REGISTERED;
		flags |= registered;
		if (wrapper->netagent->netagent_flags != flags) {
			wrapper->netagent->netagent_flags = flags;
			wrapper->generation = g_next_generation++;
			updated = true;
		}
		NETAGENT_UNLOCK(wrapper);
	} else {
		NETAGENTLOG0(LOG_DEBUG,
		    "Attempt to set flags for invalid netagent");
		error = ENOENT;
	}
	NETAGENT_LIST_UNLOCK();
	if (updated) {
		netagent_post_event(uuid, KEV_NETAGENT_UPDATED, true, false);
	}

	return error;
}

u_int32_t
netagent_get_generation(uuid_t uuid)
{
	u_int32_t generation = 0;
	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(uuid, false, false);
	if (wrapper != NULL) {
		generation = wrapper->generation;
		NETAGENT_UNLOCK(wrapper);
	} else {
		NETAGENTLOG0(LOG_DEBUG, "Generation requested for invalid netagent");
	}
	NETAGENT_LIST_UNLOCK();

	return generation;
}

bool
netagent_get_agent_domain_and_type(uuid_t uuid, char * __sized_by(NETAGENT_DOMAINSIZE)domain, char * __sized_by(NETAGENT_TYPESIZE)type)
{
	bool found = FALSE;
	if (domain == NULL || type == NULL) {
		NETAGENTLOG(LOG_ERR, "Invalid arguments for netagent_get_agent_domain_and_type %p %p", domain, type);
		return FALSE;
	}

	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(uuid, false, false);
	if (wrapper != NULL) {
		found = TRUE;
		memcpy(domain, wrapper->netagent->netagent_domain, NETAGENT_DOMAINSIZE);
		memcpy(type, wrapper->netagent->netagent_type, NETAGENT_TYPESIZE);
		NETAGENT_UNLOCK(wrapper);
	} else {
		NETAGENTLOG0(LOG_ERR, "Type requested for invalid netagent");
	}
	NETAGENT_LIST_UNLOCK();

	return found;
}

int
netagent_kernel_trigger(uuid_t uuid)
{
	int error = 0;

	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(uuid, false, false);
	if (wrapper == NULL) {
		NETAGENTLOG0(LOG_ERR, "Requested netagent for kernel trigger could not be found");
		error = ENOENT;
		goto done;
	}

	if ((wrapper->netagent->netagent_flags & NETAGENT_FLAG_KERNEL_ACTIVATED) == 0) {
		NETAGENTLOG0(LOG_ERR, "Requested netagent for kernel trigger is not kernel activated");
		// Agent does not accept kernel triggers
		error = EINVAL;
		goto done;
	}

	if ((wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE)) {
		// Agent already active
		NETAGENTLOG0(LOG_INFO, "Requested netagent for kernel trigger is already active");
		error = 0;
		goto done;
	}

	error = netagent_send_trigger(wrapper, current_proc(), NETAGENT_TRIGGER_FLAG_KERNEL, NETAGENT_MESSAGE_TYPE_TRIGGER);
	NETAGENTLOG((error ? LOG_ERR : LOG_INFO), "Triggered netagent from kernel (error %d)", error);
done:
	if (wrapper != NULL) {
		NETAGENT_UNLOCK(wrapper);
	}
	NETAGENT_LIST_UNLOCK();
	return error;
}

int
netagent_client_message_with_params(uuid_t agent_uuid,
    uuid_t necp_client_uuid,
    pid_t pid,
    void *handle,
    u_int8_t message_type,
    struct necp_client_agent_parameters *parameters,
    void * __sized_by(*assigned_results_length) *assigned_results,
    size_t *assigned_results_length)
{
	int error = 0;

	if (message_type != NETAGENT_MESSAGE_TYPE_CLIENT_TRIGGER &&
	    message_type != NETAGENT_MESSAGE_TYPE_CLIENT_ASSERT &&
	    message_type != NETAGENT_MESSAGE_TYPE_CLIENT_UNASSERT &&
	    message_type != NETAGENT_MESSAGE_TYPE_CLIENT_ERROR &&
	    message_type != NETAGENT_MESSAGE_TYPE_REQUEST_NEXUS &&
	    message_type != NETAGENT_MESSAGE_TYPE_CLOSE_NEXUS &&
	    message_type != NETAGENT_MESSAGE_TYPE_ABORT_NEXUS &&
	    message_type != NETAGENT_MESSAGE_TYPE_ADD_GROUP_MEMBERS &&
	    message_type != NETAGENT_MESSAGE_TYPE_REMOVE_GROUP_MEMBERS) {
		NETAGENTLOG(LOG_ERR, "Client netagent message type (%d) is invalid", message_type);
		return EINVAL;
	}

	NETAGENT_LIST_LOCK_SHARED();
	bool should_unlock_list = true;
	bool should_unlock_wrapper = true;
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(agent_uuid, false, false);
	if (wrapper == NULL) {
		NETAGENTLOG0(LOG_DEBUG, "Requested netagent for nexus instance could not be found");
		error = ENOENT;
		goto done;
	}

	if (message_type == NETAGENT_MESSAGE_TYPE_CLIENT_TRIGGER) {
		if ((wrapper->netagent->netagent_flags & NETAGENT_FLAG_USER_ACTIVATED) == 0) {
			// Agent does not accept user triggers
			// Don't log, since this is a common case used to trigger events that cellular data is blocked, etc.
			error = ENOTSUP;


			pid_t report_pid = 0;
			uuid_t report_proc_uuid = {};
			if (parameters != NULL) {
				report_pid = parameters->u.nexus_request.epid;
				uuid_copy(report_proc_uuid, parameters->u.nexus_request.euuid);
			} else {
				struct proc *p = current_proc();
				if (p != NULL) {
					report_pid = proc_pid(p);
					proc_getexecutableuuid(p, report_proc_uuid, sizeof(report_proc_uuid));
				}
			}
			netagent_send_cellular_failed_event(wrapper, report_pid, report_proc_uuid);
			goto done;
		}
	} else if (message_type == NETAGENT_MESSAGE_TYPE_REQUEST_NEXUS ||
	    message_type == NETAGENT_MESSAGE_TYPE_CLOSE_NEXUS ||
	    message_type == NETAGENT_MESSAGE_TYPE_ABORT_NEXUS) {
		bool is_nexus_agent = ((wrapper->netagent->netagent_flags &
		    (NETAGENT_FLAG_NEXUS_PROVIDER |
		    NETAGENT_FLAG_NEXUS_LISTENER |
		    NETAGENT_FLAG_CUSTOM_IP_NEXUS |
		    NETAGENT_FLAG_CUSTOM_ETHER_NEXUS |
		    NETAGENT_FLAG_INTERPOSE_NEXUS)) != 0);
		if (!is_nexus_agent) {
			NETAGENTLOG0(LOG_ERR, "Requested netagent for nexus instance is not a nexus provider");
			// Agent is not a nexus provider
			error = EINVAL;
			goto done;
		}

		if ((wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE) == 0) {
			// Agent not active
			NETAGENTLOG0(LOG_INFO, "Requested netagent for nexus instance is not active");
			error = EINVAL;
			goto done;
		}
	} else if (message_type == NETAGENT_MESSAGE_TYPE_ADD_GROUP_MEMBERS ||
	    message_type == NETAGENT_MESSAGE_TYPE_REMOVE_GROUP_MEMBERS) {
		bool is_group_agent = ((wrapper->netagent->netagent_flags & (NETAGENT_FLAG_SUPPORTS_GROUPS)) != 0);
		if (!is_group_agent) {
			NETAGENTLOG0(LOG_ERR, "Requested netagent for group operation is not a group provider");
			error = EINVAL;
			goto done;
		}

		if ((wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE) == 0) {
			// Agent not active
			NETAGENTLOG0(LOG_INFO, "Requested netagent for group operation is not active");
			error = EINVAL;
			goto done;
		}
	}

	if (wrapper->control_unit == 0) {
		if (wrapper->event_handler == NULL) {
			// No event handler registered for kernel agent
			error = EINVAL;
		} else {
			// We hold the wrapper lock during the event handler callout, so it is expected
			// that the event handler will not lead to any registrations or unregistrations
			// of network agents.
			// We release the list lock before calling the event handler to allow other threads
			// to access the list while the event is processing.
			NETAGENT_LIST_UNLOCK();
			should_unlock_list = false;
			error = wrapper->event_handler(message_type, necp_client_uuid, pid, handle,
			    wrapper->event_context, parameters,
			    assigned_results, assigned_results_length);
			if (error != 0) {
				VERIFY(assigned_results == NULL || *assigned_results == NULL);
				VERIFY(assigned_results_length == NULL || *assigned_results_length == 0);
			}
		}
	} else {
		// ABORT_NEXUS is kernel-private, so translate it for userspace nexus
		if (message_type == NETAGENT_MESSAGE_TYPE_ABORT_NEXUS) {
			message_type = NETAGENT_MESSAGE_TYPE_CLOSE_NEXUS;
		}

		if (message_type == NETAGENT_MESSAGE_TYPE_CLIENT_ERROR) {
			const int32_t client_error = parameters->u.error.error;
			const bool force_report = parameters->u.error.force_report;
			if (wrapper->last_client_error != client_error || // Always notify for an error change
			    force_report || // Always notify if force reporting was requested
			    (client_error == 0 && wrapper->client_error_count == 0) || // Only notify once for no-error
			    (client_error != 0 && wrapper->client_error_count < NETAGENT_MAX_CLIENT_ERROR_COUNT)) {
				if (NETAGENT_LOCK_SHARED_TO_EXCLUSIVE(wrapper)) {
					if (wrapper->last_client_error != client_error) {
						wrapper->last_client_error = client_error;
						wrapper->client_error_count = 1;
					} else {
						wrapper->client_error_count++;
					}
					error = netagent_send_error_message(wrapper, necp_client_uuid, message_type, client_error);
				} else {
					// If NETAGENT_LOCK_SHARED_TO_EXCLUSIVE fails, it unlocks automatically
					should_unlock_wrapper = false;
				}
			}
		} else if (message_type == NETAGENT_MESSAGE_TYPE_ADD_GROUP_MEMBERS ||
		    message_type == NETAGENT_MESSAGE_TYPE_REMOVE_GROUP_MEMBERS) {
			error = netagent_send_group_message(wrapper, necp_client_uuid, message_type, &parameters->u.group_members);
		} else {
			error = netagent_send_client_message(wrapper, necp_client_uuid, message_type);
		}
		if (error == 0 && message_type == NETAGENT_MESSAGE_TYPE_CLIENT_TRIGGER) {
			if (NETAGENT_LOCK_SHARED_TO_EXCLUSIVE(wrapper)) {
				// Grab the lock exclusively to add a pending client to the list
				struct netagent_client *new_pending_client = NULL;
				new_pending_client = kalloc_type(struct netagent_client, Z_WAITOK);
				if (new_pending_client == NULL) {
					NETAGENTLOG0(LOG_ERR, "Failed to allocate client for trigger");
				} else {
					uuid_copy(new_pending_client->client_id, necp_client_uuid);
					if (parameters != NULL) {
						new_pending_client->client_pid = parameters->u.nexus_request.epid;
						uuid_copy(new_pending_client->client_proc_uuid, parameters->u.nexus_request.euuid);
					} else {
						struct proc *p = current_proc();
						if (p != NULL) {
							new_pending_client->client_pid = proc_pid(p);
							proc_getexecutableuuid(p, new_pending_client->client_proc_uuid, sizeof(new_pending_client->client_proc_uuid));
						}
					}
					LIST_INSERT_HEAD(&wrapper->pending_triggers_list, new_pending_client, client_chain);
				}
			} else {
				// If NETAGENT_LOCK_SHARED_TO_EXCLUSIVE fails, it unlocks automatically
				should_unlock_wrapper = false;
			}
		}
	}
	NETAGENTLOG(((error && error != ENOENT) ? LOG_ERR : LOG_INFO), "Send message %d for client (error %d)", message_type, error);
	if (message_type == NETAGENT_MESSAGE_TYPE_CLIENT_TRIGGER) {
		uuid_string_t uuid_str;
		uuid_unparse(agent_uuid, uuid_str);
		NETAGENTLOG(LOG_NOTICE, "Triggered network agent %s, error = %d", uuid_str, error);
	}
done:
	if (should_unlock_wrapper && wrapper != NULL) {
		NETAGENT_UNLOCK(wrapper);
	}
	if (should_unlock_list) {
		NETAGENT_LIST_UNLOCK();
	}
	return error;
}

int
netagent_client_message(uuid_t agent_uuid, uuid_t necp_client_uuid, pid_t pid, void *handle, u_int8_t message_type)
{
	size_t dummy_length = 0;
	void *dummy_results __sized_by(dummy_length) = NULL;

	return netagent_client_message_with_params(agent_uuid, necp_client_uuid, pid, handle, message_type, NULL, &dummy_results, &dummy_length);
}

int
netagent_use(uuid_t agent_uuid, uint64_t *out_use_count)
{
	int error = 0;

	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(agent_uuid, true, false);
	if (wrapper == NULL) {
		NETAGENTLOG0(LOG_ERR, "netagent_assert: Requested netagent UUID is not registered");
		error = ENOENT;
		goto done;
	}

	uint64_t current_count = wrapper->use_count;
	wrapper->use_count++;

	if (out_use_count != NULL) {
		*out_use_count = current_count;
	}

done:
	if (wrapper != NULL) {
		NETAGENT_UNLOCK(wrapper);
	}
	NETAGENT_LIST_UNLOCK();
	return error;
}

int
netagent_copyout(uuid_t agent_uuid, user_addr_t user_addr, u_int32_t user_size)
{
	int error = 0;

	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(agent_uuid, false, false);
	if (wrapper == NULL) {
		NETAGENTLOG0(LOG_DEBUG, "Requested netagent for nexus instance could not be found");
		error = ENOENT;
		goto done;
	}

	u_int32_t total_size = (sizeof(struct netagent) + wrapper->netagent->netagent_data_size);
	if (user_size < total_size) {
		NETAGENTLOG(LOG_ERR, "Provided user buffer is too small (%u < %u)", user_size, total_size);
		error = EINVAL;
		goto done;
	}

	u_int8_t *ptr = __unsafe_forge_bidi_indexable(u_int8_t *, wrapper->netagent, total_size);
	error = copyout(ptr, user_addr, total_size);

	NETAGENTLOG((error ? LOG_ERR : LOG_DEBUG), "Copied agent content (error %d)", error);
done:
	if (wrapper != NULL) {
		NETAGENT_UNLOCK(wrapper);
	}
	NETAGENT_LIST_UNLOCK();
	return error;
}

#define NETAGENT_TOKEN_EVENT_INTERVAL_NSEC (NSEC_PER_SEC * 10) // Only fire repeated events up to once every 10 seconds

int
netagent_acquire_token(uuid_t agent_uuid, user_addr_t user_addr, u_int32_t user_size, int *retval)
{
	int error = 0;

	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(agent_uuid, true, false);
	if (wrapper == NULL) {
		NETAGENTLOG0(LOG_DEBUG, "Network agent for request UUID could not be found");
		error = ENOENT;
		goto done;
	}

	struct netagent_token *token = TAILQ_FIRST(&wrapper->token_list);
	if (token == NULL) {
		NETAGENTLOG0(LOG_DEBUG, "Network agent does not have any tokens");
		if (wrapper->token_low_water != 0) {
			// Only fire an event if one hasn't occurred in the last 10 seconds
			if (mach_absolute_time() >= wrapper->need_tokens_event_deadline) {
				int event_error = netagent_send_tokens_needed(wrapper);
				if (event_error == 0) {
					// Reset the deadline
					uint64_t deadline = 0;
					nanoseconds_to_absolutetime(NETAGENT_TOKEN_EVENT_INTERVAL_NSEC, &deadline);
					clock_absolutetime_interval_to_deadline(deadline, &deadline);
					wrapper->need_tokens_event_deadline = deadline;
				}
			}
		}
		error = ENODATA;
		goto done;
	}

	if (user_size < token->token_length) {
		NETAGENTLOG(LOG_ERR, "Provided user buffer is too small (%u < %u)", user_size, token->token_length);
		error = EMSGSIZE;
		goto done;
	}

	error = copyout(token->token_bytes, user_addr, token->token_length);
	if (error == 0) {
		*retval = (int)token->token_length;
	}

	NETAGENTLOG((error ? LOG_ERR : LOG_DEBUG), "Copied token content (error %d)", error);

	TAILQ_REMOVE(&wrapper->token_list, token, token_chain);
	netagent_token_free(token);
	if (wrapper->token_count > 0) {
		wrapper->token_count--;
	}
	if (wrapper->token_count < wrapper->token_low_water) {
		(void)netagent_send_tokens_needed(wrapper);
	}
done:
	if (wrapper != NULL) {
		NETAGENT_UNLOCK(wrapper);
	}
	NETAGENT_LIST_UNLOCK();
	return error;
}

int
netagent_trigger(struct proc *p, struct netagent_trigger_args *uap, int32_t *retval)
{
#pragma unused(p, retval)
	uuid_t agent_uuid = {};
	int error = 0;

	if (uap == NULL) {
		NETAGENTLOG0(LOG_ERR, "uap == NULL");
		return EINVAL;
	}

	if (uap->agent_uuid) {
		if (uap->agent_uuidlen != sizeof(uuid_t)) {
			NETAGENTLOG(LOG_ERR, "Incorrect length (got %zu, expected %lu)",
			    (size_t)uap->agent_uuidlen, sizeof(uuid_t));
			return ERANGE;
		}

		error = copyin(uap->agent_uuid, agent_uuid, sizeof(uuid_t));
		if (error) {
			NETAGENTLOG(LOG_ERR, "copyin error (%d)", error);
			return error;
		}
	}

	if (uuid_is_null(agent_uuid)) {
		NETAGENTLOG0(LOG_ERR, "Requested netagent UUID is empty");
		return EINVAL;
	}

	NETAGENT_LIST_LOCK_SHARED();
	struct netagent_wrapper *wrapper = netagent_find_agent_with_uuid_and_lock(agent_uuid, false, false);
	if (wrapper == NULL) {
		NETAGENTLOG0(LOG_ERR, "Requested netagent UUID is not registered");
		error = ENOENT;
		goto done;
	}

	if ((wrapper->netagent->netagent_flags & NETAGENT_FLAG_USER_ACTIVATED) == 0) {
		// Agent does not accept triggers
		NETAGENTLOG0(LOG_ERR, "Requested netagent UUID is not eligible for triggering");
		error = ENOTSUP;
		goto done;
	}

	if ((wrapper->netagent->netagent_flags & NETAGENT_FLAG_ACTIVE)) {
		// Agent already active
		NETAGENTLOG0(LOG_INFO, "Requested netagent UUID is already active");
		error = 0;
		goto done;
	}

	error = netagent_send_trigger(wrapper, p, NETAGENT_TRIGGER_FLAG_USER, NETAGENT_MESSAGE_TYPE_TRIGGER);
	NETAGENTLOG((error ? LOG_ERR : LOG_INFO), "Triggered netagent (error %d)", error);
done:
	if (wrapper != NULL) {
		NETAGENT_UNLOCK(wrapper);
	}
	NETAGENT_LIST_UNLOCK();
	return error;
}