]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/net/necp_client.c
xnu-3789.1.32.tar.gz
[apple/xnu.git] / bsd / net / necp_client.c
diff --git a/bsd/net/necp_client.c b/bsd/net/necp_client.c
new file mode 100644 (file)
index 0000000..999fab3
--- /dev/null
@@ -0,0 +1,2900 @@
+/*
+ * Copyright (c) 2015-2016 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/queue.h>
+#include <sys/malloc.h>
+#include <libkern/OSMalloc.h>
+#include <sys/kernel.h>
+#include <net/if.h>
+#include <sys/domain.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/in_pcb.h>
+#include <net/if_var.h>
+#include <netinet/tcp_cc.h>
+#include <net/ntstat.h>
+#include <sys/kauth.h>
+#include <sys/sysproto.h>
+#include <sys/priv.h>
+#include <net/network_agent.h>
+#include <net/necp.h>
+#include <sys/file_internal.h>
+#include <sys/poll.h>
+#include <kern/thread_call.h>
+
+/*
+ * NECP Client Architecture
+ * ------------------------------------------------
+ * See <net/necp.c> for a discussion on NECP database architecture.
+ *
+ * Each client of NECP provides a set of parameters for a connection or network state
+ * evaluation, on which NECP policy evaluation is run. This produces a policy result
+ * which can be accessed by the originating process, along with events for when policies
+ * results have changed.
+ *
+ * ------------------------------------------------
+ * NECP Client FD
+ * ------------------------------------------------
+ * A process opens an NECP file descriptor using necp_open(). This is a very simple
+ * file descriptor, upon which the process may do the following operations:
+ *   - necp_client_action(...), to add/remove/query clients
+ *   - kqueue, to watch for readable events
+ *   - close(), to close the client session and release all clients
+ *
+ * Client objects are allocated structures that hang off of the file descriptor. Each
+ * client contains:
+ *   - Client ID, a UUID that references the client across the system
+ *   - Parameters, a buffer of TLVs that describe the client's connection parameters,
+ *       such as the remote and local endpoints, interface requirements, etc.
+ *   - Result, a buffer of TLVs containing the current policy evaluation for the client.
+ *       This result will be updated whenever a network change occurs that impacts the
+ *       policy result for that client.
+ *
+ *                   +--------------+
+ *                   |   NECP fd    |
+ *                   +--------------+
+ *                          ||
+ *          ==================================
+ *          ||              ||              ||
+ *  +--------------+ +--------------+ +--------------+
+ *  |   Client ID  | |   Client ID  | |   Client ID  |
+ *  |     ----     | |     ----     | |     ----     |
+ *  |  Parameters  | |  Parameters  | |  Parameters  |
+ *  |     ----     | |     ----     | |     ----     |
+ *  |    Result    | |    Result    | |    Result    |
+ *  +--------------+ +--------------+ +--------------+
+ *
+ * ------------------------------------------------
+ * Client Actions
+ * ------------------------------------------------
+ *   - Add. Input parameters as a buffer of TLVs, and output a client ID. Allocates a
+ *       new client structure on the file descriptor.
+ *   - Remove. Input a client ID. Removes a client structure from the file descriptor.
+ *   - Copy Parameters. Input a client ID, and output parameter TLVs.
+ *   - Copy Result. Input a client ID, and output result TLVs. Alternatively, input empty
+ *       client ID and get next unread client result.
+ *   - Copy List. List all client IDs.
+ *
+ * ------------------------------------------------
+ * Client Policy Evaluation
+ * ------------------------------------------------
+ * Policies are evaluated for clients upon client creation, and upon update events,
+ * which are network/agent/policy changes coalesced by a timer.
+ *
+ * The policy evaluation goes through the following steps:
+ *   1. Parse client parameters.
+ *   2. Select a scoped interface if applicable. This involves using require/prohibit
+ *      parameters, along with the local address, to select the most appropriate interface
+ *      if not explicitly set by the client parameters.
+ *   3. Run NECP application-level policy evalution
+ *   4. Set policy result into client result buffer.
+ *
+ * ------------------------------------------------
+ * Client Observers
+ * ------------------------------------------------
+ * If necp_open() is called with the NECP_OPEN_FLAG_OBSERVER flag, and the process
+ * passes the necessary privilege check, the fd is allowed to use necp_client_action()
+ * to copy client state attached to the file descriptors of other processes, and to
+ * list all client IDs on the system.
+ */
+
+extern u_int32_t necp_debug;
+
+static int noop_read(struct fileproc *, struct uio *, int, vfs_context_t);
+static int noop_write(struct fileproc *, struct uio *, int, vfs_context_t);
+static int noop_ioctl(struct fileproc *, unsigned long, caddr_t,
+                                         vfs_context_t);
+static int necpop_select(struct fileproc *, int, void *, vfs_context_t);
+static int necpop_close(struct fileglob *, vfs_context_t);
+static int necpop_kqfilter(struct fileproc *, struct knote *, vfs_context_t);
+
+// Timer functions
+static int necp_timeout_microseconds = 1000 * 100; // 100ms
+static int necp_timeout_leeway_microseconds = 1000 * 500; // 500ms
+extern int tvtohz(struct timeval *);
+
+// Parsed parameters
+#define NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR                                0x0001
+#define NECP_PARSED_PARAMETERS_FIELD_REMOTE_ADDR                       0x0002
+#define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IF                       0x0004
+#define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF                     0x0008
+#define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE           0x0010
+#define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE         0x0020
+#define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT                    0x0040
+#define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT          0x0080
+#define NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT           0x0100
+#define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE       0x0200
+#define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE     0x0400
+#define NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE      0x0800
+
+#define NECP_MAX_PARSED_PARAMETERS 16
+struct necp_client_parsed_parameters {
+       u_int32_t valid_fields;
+       union necp_sockaddr_union local_addr;
+       union necp_sockaddr_union remote_addr;
+       u_int32_t required_interface_index;
+       char prohibited_interfaces[IFXNAMSIZ][NECP_MAX_PARSED_PARAMETERS];
+       u_int8_t required_interface_types[NECP_MAX_PARSED_PARAMETERS];
+       u_int8_t prohibited_interface_types[NECP_MAX_PARSED_PARAMETERS];
+       struct necp_client_parameter_netagent_type required_netagent_types[NECP_MAX_PARSED_PARAMETERS];
+       struct necp_client_parameter_netagent_type prohibited_netagent_types[NECP_MAX_PARSED_PARAMETERS];
+       struct necp_client_parameter_netagent_type preferred_netagent_types[NECP_MAX_PARSED_PARAMETERS];
+       uuid_t required_netagents[NECP_MAX_PARSED_PARAMETERS];
+       uuid_t prohibited_netagents[NECP_MAX_PARSED_PARAMETERS];
+       uuid_t preferred_netagents[NECP_MAX_PARSED_PARAMETERS];
+};
+
+static bool necp_find_matching_interface_index(struct necp_client_parsed_parameters *parsed_parameters, u_int *return_ifindex);
+
+static const struct fileops necp_fd_ops = {
+       .fo_type = DTYPE_NETPOLICY,
+       .fo_read = noop_read,
+       .fo_write = noop_write,
+       .fo_ioctl = noop_ioctl,
+       .fo_select = necpop_select,
+       .fo_close = necpop_close,
+       .fo_kqfilter = necpop_kqfilter,
+       .fo_drain = NULL,
+};
+
+struct necp_client_assertion {
+       LIST_ENTRY(necp_client_assertion) assertion_chain;
+       uuid_t asserted_netagent;
+};
+
+struct necp_client {
+       LIST_ENTRY(necp_client) chain;
+
+       uuid_t client_id;
+       bool result_read;
+       bool assigned_result_read;
+
+       size_t result_length;
+       u_int8_t result[NECP_MAX_CLIENT_RESULT_SIZE];
+
+       uuid_t nexus_agent;
+       size_t assigned_results_length;
+       u_int8_t *assigned_results;
+
+       LIST_HEAD(_necp_client_assertion_list, necp_client_assertion) assertion_list;
+
+       user_addr_t stats_uaddr;
+       user_size_t stats_ulen;
+       nstat_userland_context stats_handler_context;
+       necp_stats_hdr *stats_area;
+
+       size_t parameters_length;
+       u_int8_t parameters[0];
+};
+
+struct necp_fd_data {
+       LIST_ENTRY(necp_fd_data) chain;
+       LIST_HEAD(_clients, necp_client) clients;
+       int flags;
+       int proc_pid;
+       decl_lck_mtx_data(, fd_lock);
+       struct selinfo si;
+};
+
+static LIST_HEAD(_necp_fd_list, necp_fd_data) necp_fd_list;
+
+static lck_grp_attr_t  *necp_fd_grp_attr       = NULL;
+static lck_attr_t              *necp_fd_mtx_attr       = NULL;
+static lck_grp_t               *necp_fd_mtx_grp        = NULL;
+decl_lck_rw_data(static, necp_fd_lock);
+
+static thread_call_t necp_client_tcall;
+
+/// NECP file descriptor functions
+
+static int
+noop_read(struct fileproc *fp, struct uio *uio, int flags, vfs_context_t ctx)
+{
+#pragma unused(fp, uio, flags, ctx)
+       return (ENXIO);
+}
+
+static int
+noop_write(struct fileproc *fp, struct uio *uio, int flags,
+                  vfs_context_t ctx)
+{
+#pragma unused(fp, uio, flags, ctx)
+       return (ENXIO);
+}
+
+static int
+noop_ioctl(struct fileproc *fp, unsigned long com, caddr_t data,
+                  vfs_context_t ctx)
+{
+#pragma unused(fp, com, data, ctx)
+       return (ENOTTY);
+}
+
+static void
+necp_fd_notify(struct necp_fd_data *fd_data, bool locked)
+{
+       struct selinfo *si = &fd_data->si;
+
+       if (!locked) {
+               lck_mtx_lock(&fd_data->fd_lock);
+       }
+
+       selwakeup(si);
+
+       // use a non-zero hint to tell the notification from the
+       // call done in kqueue_scan() which uses 0
+       KNOTE(&si->si_note, 1); // notification
+
+       if (!locked) {
+               lck_mtx_unlock(&fd_data->fd_lock);
+       }
+}
+
+static int
+necp_fd_poll(struct necp_fd_data *fd_data, int events, void *wql, struct proc *p, int is_kevent)
+{
+#pragma unused(wql, p, is_kevent)
+       u_int revents = 0;
+       struct necp_client *client = NULL;
+       bool has_unread_clients = FALSE;
+
+       u_int want_rx = events & (POLLIN | POLLRDNORM);
+       if (want_rx) {
+
+               LIST_FOREACH(client, &fd_data->clients, chain) {
+                       if (!client->result_read || !client->assigned_result_read) {
+                               has_unread_clients = TRUE;
+                               break;
+                       }
+               }
+
+               if (has_unread_clients) {
+                       revents |= want_rx;
+               }
+       }
+
+       return (revents);
+}
+
+static int
+necpop_select(struct fileproc *fp, int which, void *wql, vfs_context_t ctx)
+{
+#pragma unused(fp, which, wql, ctx)
+       return (0);
+       struct necp_fd_data *fd_data = NULL;
+       int revents = 0;
+       int events = 0;
+       proc_t procp;
+
+       fd_data = (struct necp_fd_data *)fp->f_fglob->fg_data;
+       if (fd_data == NULL) {
+               return (0);
+       }
+
+       procp = vfs_context_proc(ctx);
+
+       switch (which) {
+               case FREAD: {
+                       events = POLLIN;
+                       break;
+               }
+
+               default: {
+                       return (1);
+               }
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       revents = necp_fd_poll(fd_data, events, wql, procp, 0);
+       lck_mtx_unlock(&fd_data->fd_lock);
+
+       return ((events & revents) ? 1 : 0);
+}
+
+static void
+necp_fd_knrdetach(struct knote *kn)
+{
+       struct necp_fd_data *fd_data = (struct necp_fd_data *)kn->kn_hook;
+       struct selinfo *si = &fd_data->si;
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       KNOTE_DETACH(&si->si_note, kn);
+       lck_mtx_unlock(&fd_data->fd_lock);
+}
+
+static int
+necp_fd_knread(struct knote *kn, long hint)
+{
+#pragma unused(kn, hint)
+       return 1; /* assume we are ready */
+}
+
+static int
+necp_fd_knrprocess(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev)
+{
+#pragma unused(data)
+       struct necp_fd_data *fd_data;
+       int revents;
+       int res;
+
+       fd_data = (struct necp_fd_data *)kn->kn_hook;
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       revents = necp_fd_poll(fd_data, POLLIN, NULL, current_proc(), 1);
+       res = ((revents & POLLIN) != 0);
+       if (res) {
+               *kev = kn->kn_kevent;
+       }
+       lck_mtx_unlock(&fd_data->fd_lock);
+       return (res);
+}
+
+static int 
+necp_fd_knrtouch(struct knote *kn, struct kevent_internal_s *kev)
+{
+#pragma unused(kev)
+       struct necp_fd_data *fd_data;
+       int revents;
+
+       fd_data = (struct necp_fd_data *)kn->kn_hook;
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       if ((kn->kn_status & KN_UDATA_SPECIFIC) == 0)
+               kn->kn_udata = kev->udata;
+       revents = necp_fd_poll(fd_data, POLLIN, NULL, current_proc(), 1);
+       lck_mtx_unlock(&fd_data->fd_lock);
+
+       return ((revents & POLLIN) != 0);
+}
+
+struct filterops necp_fd_rfiltops = {
+       .f_isfd = 1,
+       .f_detach = necp_fd_knrdetach,
+       .f_event = necp_fd_knread,
+       .f_touch = necp_fd_knrtouch,
+       .f_process = necp_fd_knrprocess,
+};
+
+static int
+necpop_kqfilter(struct fileproc *fp, struct knote *kn, vfs_context_t ctx)
+{
+#pragma unused(fp, ctx)
+       struct necp_fd_data *fd_data = NULL;
+       int revents;
+
+       if (kn->kn_filter != EVFILT_READ) {
+               NECPLOG(LOG_ERR, "bad filter request %d", kn->kn_filter);
+               kn->kn_flags = EV_ERROR;
+               kn->kn_data = EINVAL;
+               return (0);
+       }
+
+       fd_data = (struct necp_fd_data *)kn->kn_fp->f_fglob->fg_data;
+       if (fd_data == NULL) {
+               NECPLOG0(LOG_ERR, "No channel for kqfilter");
+               kn->kn_flags = EV_ERROR;
+               kn->kn_data = ENOENT;
+               return (0);
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       kn->kn_filtid = EVFILTID_NECP_FD;
+       kn->kn_hook = fd_data;
+       KNOTE_ATTACH(&fd_data->si.si_note, kn);
+
+       revents = necp_fd_poll(fd_data, POLLIN, NULL, current_proc(), 1);
+
+       lck_mtx_unlock(&fd_data->fd_lock);
+
+       return ((revents & POLLIN) != 0);
+}
+
+static void
+necp_destroy_client_stats(struct necp_client *client)
+{
+       if ((client->stats_area != NULL) &&
+               (client->stats_handler_context != NULL) &&
+               (client->stats_uaddr != 0)) {
+               // Close old stats if required.
+               int error = copyin(client->stats_uaddr, client->stats_area, client->stats_ulen);
+               if (error) {
+                       NECPLOG(LOG_ERR, "necp_destroy_client_stats copyin error on close (%d)", error);
+                       // Not much we can for an error on an obsolete address
+               }
+               ntstat_userland_stats_close(client->stats_handler_context);
+               FREE(client->stats_area, M_NECP);
+               client->stats_area = NULL;
+               client->stats_handler_context = NULL;
+               client->stats_uaddr = 0;
+               client->stats_ulen = 0;
+       }
+}
+
+static void
+necp_destroy_client(struct necp_client *client)
+{
+       // Remove from list
+       LIST_REMOVE(client, chain);
+
+       // Remove nexus assignment
+       if (client->assigned_results != NULL) {
+               if (!uuid_is_null(client->nexus_agent)) {
+                       int netagent_error = netagent_client_message(client->nexus_agent, client->client_id,
+                                                                                                                NETAGENT_MESSAGE_TYPE_CLOSE_NEXUS);
+                       if (netagent_error != 0) {
+                               NECPLOG(LOG_ERR, "necp_client_remove close nexus error (%d)", netagent_error);
+                       }
+               }
+               FREE(client->assigned_results, M_NETAGENT);
+       }
+
+       // Remove agent assertions
+       struct necp_client_assertion *search_assertion = NULL;
+       struct necp_client_assertion *temp_assertion = NULL;
+       LIST_FOREACH_SAFE(search_assertion, &client->assertion_list, assertion_chain, temp_assertion) {
+               int netagent_error = netagent_client_message(search_assertion->asserted_netagent, client->client_id, NETAGENT_MESSAGE_TYPE_CLIENT_UNASSERT);
+               if (netagent_error != 0) {
+                       NECPLOG(LOG_ERR, "necp_client_remove unassert agent error (%d)", netagent_error);
+               }
+               LIST_REMOVE(search_assertion, assertion_chain);
+               FREE(search_assertion, M_NECP);
+       }
+       necp_destroy_client_stats(client);
+
+       FREE(client, M_NECP);
+}
+
+static int
+necpop_close(struct fileglob *fg, vfs_context_t ctx)
+{
+#pragma unused(fg, ctx)
+       struct necp_fd_data *fd_data = NULL;
+       int error = 0;
+
+       fd_data = (struct necp_fd_data *)fg->fg_data;
+       fg->fg_data = NULL;
+
+       if (fd_data != NULL) {
+               lck_rw_lock_exclusive(&necp_fd_lock);
+
+               lck_mtx_lock(&fd_data->fd_lock);
+               struct necp_client *client = NULL;
+               struct necp_client *temp_client = NULL;
+               LIST_FOREACH_SAFE(client, &fd_data->clients, chain, temp_client) {
+                       necp_destroy_client(client);
+               }
+               lck_mtx_unlock(&fd_data->fd_lock);
+
+               selthreadclear(&fd_data->si);
+
+               lck_mtx_destroy(&fd_data->fd_lock, necp_fd_mtx_grp);
+
+               LIST_REMOVE(fd_data, chain);
+
+               lck_rw_done(&necp_fd_lock);
+
+               FREE(fd_data, M_NECP);
+               fd_data = NULL;
+       }
+
+       return (error);
+}
+
+/// NECP client utilities
+
+static int
+necp_find_fd_data(int fd, struct necp_fd_data **fd_data)
+{
+       proc_t p = current_proc();
+       struct fileproc *fp = NULL;
+       int error = 0;
+
+       proc_fdlock_spin(p);
+       if ((error = fp_lookup(p, fd, &fp, 1)) != 0) {
+               goto done;
+       }
+       if (fp->f_fglob->fg_ops->fo_type != DTYPE_NETPOLICY) {
+               fp_drop(p, fd, fp, 1);
+               error = ENODEV;
+               goto done;
+       }
+       *fd_data = (struct necp_fd_data *)fp->f_fglob->fg_data;
+
+done:
+       proc_fdunlock(p);
+       return (error);
+}
+
+static bool
+necp_netagent_applies_to_client(__unused struct necp_client *client, struct necp_client_parsed_parameters *parameters, uuid_t netagent_uuid)
+{
+       bool applies = FALSE;
+       u_int32_t flags = netagent_get_flags(netagent_uuid);
+       if (!(flags & NETAGENT_FLAG_REGISTERED)) {
+               // Unregistered agents never apply
+               return (applies);
+       }
+
+       if (flags & NETAGENT_FLAG_SPECIFIC_USE_ONLY) {
+               // Specific use agents only apply when required
+               bool required = FALSE;
+               if (parameters != NULL) {
+                       // Check required agent UUIDs
+                       for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                               if (uuid_is_null(parameters->required_netagents[i])) {
+                                       break;
+                               }
+                               if (uuid_compare(parameters->required_netagents[i], netagent_uuid) == 0) {
+                                       required = TRUE;
+                                       break;
+                               }
+                       }
+
+                       if (!required) {
+                               // Check required agent types
+                               bool fetched_type = FALSE;
+                               char netagent_domain[NETAGENT_DOMAINSIZE];
+                               char netagent_type[NETAGENT_TYPESIZE];
+                               memset(&netagent_domain, 0, NETAGENT_DOMAINSIZE);
+                               memset(&netagent_type, 0, NETAGENT_TYPESIZE);
+
+                               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                                       if (strlen(parameters->required_netagent_types[i].netagent_domain) == 0 ||
+                                               strlen(parameters->required_netagent_types[i].netagent_type) == 0) {
+                                               break;
+                                       }
+
+                                       if (!fetched_type) {
+                                               if (netagent_get_agent_domain_and_type(netagent_uuid, netagent_domain, netagent_type)) {
+                                                       fetched_type = TRUE;
+                                               } else {
+                                                       break;
+                                               }
+                                       }
+
+                                       if ((strlen(parameters->required_netagent_types[i].netagent_domain) == 0 ||
+                                                strncmp(netagent_domain, parameters->required_netagent_types[i].netagent_domain, NETAGENT_DOMAINSIZE) == 0) &&
+                                               (strlen(parameters->required_netagent_types[i].netagent_type) == 0 ||
+                                                strncmp(netagent_type, parameters->required_netagent_types[i].netagent_type, NETAGENT_TYPESIZE) == 0)) {
+                                               required = TRUE;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               applies = required;
+       } else {
+               applies = TRUE;
+       }
+
+       if (applies &&
+               (flags & NETAGENT_FLAG_NEXUS_PROVIDER) &&
+               uuid_is_null(client->nexus_agent)) {
+               uuid_copy(client->nexus_agent, netagent_uuid);
+       }
+
+       return (applies);
+}
+
+static int
+necp_client_parse_parameters(u_int8_t *parameters,
+                                                        u_int32_t parameters_size,
+                                                        struct necp_client_parsed_parameters *parsed_parameters)
+{
+       int error = 0;
+       size_t offset = 0;
+
+       u_int32_t num_prohibited_interfaces = 0;
+       u_int32_t num_required_interface_types = 0;
+       u_int32_t num_prohibited_interface_types = 0;
+       u_int32_t num_required_agents = 0;
+       u_int32_t num_prohibited_agents = 0;
+       u_int32_t num_preferred_agents = 0;
+       u_int32_t num_required_agent_types = 0;
+       u_int32_t num_prohibited_agent_types = 0;
+       u_int32_t num_preferred_agent_types = 0;
+
+       if (parsed_parameters == NULL) {
+               return (EINVAL);
+       }
+
+       memset(parsed_parameters, 0, sizeof(struct necp_client_parsed_parameters));
+
+       while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= parameters_size) {
+               u_int8_t type = necp_buffer_get_tlv_type(parameters, offset);
+               u_int32_t length = necp_buffer_get_tlv_length(parameters, offset);
+
+               if (length > 0 && (offset + sizeof(u_int8_t) + sizeof(u_int32_t) + length) <= parameters_size) {
+                       u_int8_t *value = necp_buffer_get_tlv_value(parameters, offset, NULL);
+                       if (value != NULL) {
+                               switch (type) {
+                                       case NECP_CLIENT_PARAMETER_BOUND_INTERFACE: {
+                                               if (length <= IFXNAMSIZ && length > 0) {
+                                                       ifnet_t bound_interface = NULL;
+                                                       char interface_name[IFXNAMSIZ];
+                                                       memcpy(interface_name, value, length);
+                                                       interface_name[length - 1] = 0; // Make sure the string is NULL terminated
+                                                       if (ifnet_find_by_name(interface_name, &bound_interface) == 0) {
+                                                               parsed_parameters->required_interface_index = bound_interface->if_index;
+                                                               parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IF;
+                                                               ifnet_release(bound_interface);
+                                                       }
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_LOCAL_ADDRESS: {
+                                               if (length >= sizeof(struct necp_policy_condition_addr)) {
+                                                       struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value;
+                                                       if ((address_struct->address.sa.sa_family == AF_INET ||
+                                                                address_struct->address.sa.sa_family == AF_INET6) &&
+                                                               address_struct->address.sa.sa_len <= length) {
+                                                               memcpy(&parsed_parameters->local_addr, &address_struct->address, sizeof(address_struct->address));
+                                                               parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR;
+                                                       }
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_LOCAL_ENDPOINT: {
+                                               if (length >= sizeof(struct necp_client_endpoint)) {
+                                                       struct necp_client_endpoint *endpoint = (struct necp_client_endpoint *)(void *)value;
+                                                       if ((endpoint->u.endpoint.endpoint_family == AF_INET ||
+                                                                endpoint->u.endpoint.endpoint_family == AF_INET6) &&
+                                                               endpoint->u.endpoint.endpoint_length <= length) {
+                                                               memcpy(&parsed_parameters->local_addr, &endpoint->u.sa, sizeof(union necp_sockaddr_union));
+                                                               parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR;
+                                                       }
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_REMOTE_ADDRESS: {
+                                               if (length >= sizeof(struct necp_policy_condition_addr)) {
+                                                       struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value;
+                                                       if ((address_struct->address.sa.sa_family == AF_INET ||
+                                                                address_struct->address.sa.sa_family == AF_INET6) &&
+                                                               address_struct->address.sa.sa_len <= length) {
+                                                               memcpy(&parsed_parameters->remote_addr, &address_struct->address, sizeof(address_struct->address));
+                                                               parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REMOTE_ADDR;
+                                                       }
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_REMOTE_ENDPOINT: {
+                                               if (length >= sizeof(struct necp_client_endpoint)) {
+                                                       struct necp_client_endpoint *endpoint = (struct necp_client_endpoint *)(void *)value;
+                                                       if ((endpoint->u.endpoint.endpoint_family == AF_INET ||
+                                                                endpoint->u.endpoint.endpoint_family == AF_INET6) &&
+                                                               endpoint->u.endpoint.endpoint_length <= length) {
+                                                               memcpy(&parsed_parameters->remote_addr, &endpoint->u.sa, sizeof(union necp_sockaddr_union));
+                                                               parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REMOTE_ADDR;
+                                                       }
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_PROHIBIT_INTERFACE: {
+                                               if (num_prohibited_interfaces >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length <= IFXNAMSIZ && length > 0) {
+                                                       memcpy(parsed_parameters->prohibited_interfaces[num_prohibited_interfaces], value, length);
+                                                       parsed_parameters->prohibited_interfaces[num_prohibited_interfaces][length - 1] = 0; // Make sure the string is NULL terminated
+                                                       num_prohibited_interfaces++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_REQUIRE_IF_TYPE: {
+                                               if (num_required_interface_types >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(u_int8_t)) {
+                                                       memcpy(&parsed_parameters->required_interface_types[num_required_interface_types], value, sizeof(u_int8_t));
+                                                       num_required_interface_types++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_PROHIBIT_IF_TYPE: {
+                                               if (num_prohibited_interface_types >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(u_int8_t)) {
+                                                       memcpy(&parsed_parameters->prohibited_interface_types[num_prohibited_interface_types], value, sizeof(u_int8_t));
+                                                       num_prohibited_interface_types++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_REQUIRE_AGENT: {
+                                               if (num_required_agents >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(uuid_t)) {
+                                                       memcpy(&parsed_parameters->required_netagents[num_required_agents], value, sizeof(uuid_t));
+                                                       num_required_agents++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_PROHIBIT_AGENT: {
+                                               if (num_prohibited_agents >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(uuid_t)) {
+                                                       memcpy(&parsed_parameters->prohibited_netagents[num_prohibited_agents], value, sizeof(uuid_t));
+                                                       num_prohibited_agents++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_PREFER_AGENT: {
+                                               if (num_preferred_agents >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(uuid_t)) {
+                                                       memcpy(&parsed_parameters->preferred_netagents[num_preferred_agents], value, sizeof(uuid_t));
+                                                       num_preferred_agents++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_REQUIRE_AGENT_TYPE: {
+                                               if (num_required_agent_types >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(struct necp_client_parameter_netagent_type)) {
+                                                       memcpy(&parsed_parameters->required_netagent_types[num_required_agent_types], value, sizeof(struct necp_client_parameter_netagent_type));
+                                                       num_required_agent_types++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_PROHIBIT_AGENT_TYPE: {
+                                               if (num_prohibited_agent_types >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(struct necp_client_parameter_netagent_type)) {
+                                                       memcpy(&parsed_parameters->prohibited_netagent_types[num_prohibited_agent_types], value, sizeof(struct necp_client_parameter_netagent_type));
+                                                       num_prohibited_agent_types++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE;
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_PREFER_AGENT_TYPE: {
+                                               if (num_preferred_agent_types >= NECP_MAX_PARSED_PARAMETERS) {
+                                                       break;
+                                               }
+                                               if (length >= sizeof(struct necp_client_parameter_netagent_type)) {
+                                                       memcpy(&parsed_parameters->preferred_netagent_types[num_preferred_agent_types], value, sizeof(struct necp_client_parameter_netagent_type));
+                                                       num_preferred_agent_types++;
+                                                       parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE;
+                                               }
+                                               break;
+                                       }
+                                       default: {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length;
+       }
+
+       return (error);
+}
+
+int
+necp_assign_client_result(uuid_t netagent_uuid, uuid_t client_id,
+                                                 u_int8_t *assigned_results, size_t assigned_results_length)
+{
+       int error = 0;
+       struct necp_fd_data *client_fd = NULL;
+       bool found_client = FALSE;
+       bool client_updated = FALSE;
+
+       lck_rw_lock_shared(&necp_fd_lock);
+
+       LIST_FOREACH(client_fd, &necp_fd_list, chain) {
+               struct necp_client *client = NULL;
+               lck_mtx_lock(&client_fd->fd_lock);
+               LIST_FOREACH(client, &client_fd->clients, chain) {
+                       if (uuid_compare(client->client_id, client_id) == 0) {
+                               // Found the right client!
+                               found_client = TRUE;
+
+                               if (uuid_compare(client->nexus_agent, netagent_uuid) == 0) {
+                                       // Verify that the client nexus agent matches
+                                       if (client->assigned_results != NULL) {
+                                               // Release prior result
+                                               FREE(client->assigned_results, M_NETAGENT);
+                                       }
+                                       client->assigned_results = assigned_results;
+                                       client->assigned_results_length = assigned_results_length;
+                                       client->assigned_result_read = FALSE;
+                                       client_updated = TRUE;
+                               }
+                       }
+               }
+               if (client_updated) {
+                       necp_fd_notify(client_fd, true);
+               }
+               lck_mtx_unlock(&client_fd->fd_lock);
+
+               if (found_client) {
+                       break;
+               }
+       }
+
+       lck_rw_done(&necp_fd_lock);
+
+       if (!found_client) {
+               error = ENOENT;
+       } else if (!client_updated) {
+               error = EINVAL;
+       }
+
+       return (error);
+}
+
+/// Client updating
+
+static bool
+necp_update_client_result(proc_t proc,
+                                                 struct necp_client *client)
+{
+       struct necp_client_result_netagent netagent;
+       struct necp_aggregate_result result;
+       struct necp_client_parsed_parameters parsed_parameters;
+       u_int32_t flags = 0;
+
+       uuid_clear(client->nexus_agent);
+
+       int error = necp_client_parse_parameters(client->parameters, (u_int32_t)client->parameters_length, &parsed_parameters);
+       if (error != 0) {
+               return (FALSE);
+       }
+
+       // Check parameters to find best interface
+       u_int matching_if_index = 0;
+       if (necp_find_matching_interface_index(&parsed_parameters, &matching_if_index)) {
+               if (matching_if_index != 0) {
+                       parsed_parameters.required_interface_index = matching_if_index;
+               }
+               // Interface found or not needed, match policy.
+               error = necp_application_find_policy_match_internal(proc, client->parameters, (u_int32_t)client->parameters_length, &result, &flags, matching_if_index);
+               if (error != 0) {
+                       return (FALSE);
+               }
+       } else {
+               // Interface not found. Clear out the whole result, make everything fail.
+               memset(&result, 0, sizeof(result));
+       }
+
+       // If the original request was scoped, and the policy result matches, make sure the result is scoped
+       if ((result.routing_result == NECP_KERNEL_POLICY_RESULT_NONE ||
+                result.routing_result == NECP_KERNEL_POLICY_RESULT_PASS) &&
+               result.routed_interface_index != IFSCOPE_NONE &&
+               parsed_parameters.required_interface_index == result.routed_interface_index) {
+               result.routing_result = NECP_KERNEL_POLICY_RESULT_SOCKET_SCOPED;
+               result.routing_result_parameter.scoped_interface_index = result.routed_interface_index;
+       }
+
+       bool updated = FALSE;
+       u_int8_t *cursor = client->result;
+       const u_int8_t *max = client->result + NECP_MAX_CLIENT_RESULT_SIZE;
+       cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_CLIENT_ID, sizeof(uuid_t), client->client_id, &updated);
+       cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_POLICY_RESULT, sizeof(result.routing_result), &result.routing_result, &updated);
+       if (result.routing_result_parameter.tunnel_interface_index != 0) {
+               cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_POLICY_RESULT_PARAMETER,
+                                                                                                       sizeof(result.routing_result_parameter), &result.routing_result_parameter, &updated);
+       }
+       if (result.filter_control_unit != 0) {
+               cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_FILTER_CONTROL_UNIT,
+                                                                                                       sizeof(result.filter_control_unit), &result.filter_control_unit, &updated);
+       }
+       if (result.routed_interface_index != 0) {
+               u_int routed_interface_index = result.routed_interface_index;
+               if (result.routing_result == NECP_KERNEL_POLICY_RESULT_IP_TUNNEL &&
+                       parsed_parameters.required_interface_index != IFSCOPE_NONE &&
+                       parsed_parameters.required_interface_index != result.routed_interface_index) {
+                       routed_interface_index = parsed_parameters.required_interface_index;
+               }
+
+               cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE_INDEX,
+                                                                                                       sizeof(routed_interface_index), &routed_interface_index, &updated);
+       }
+       if (flags != 0) {
+               cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_FLAGS,
+                                                                                                       sizeof(flags), &flags, &updated);
+       }
+       for (int i = 0; i < NECP_MAX_NETAGENTS; i++) {
+               if (uuid_is_null(result.netagents[i])) {
+                       break;
+               }
+               uuid_copy(netagent.netagent_uuid, result.netagents[i]);
+               netagent.generation = netagent_get_generation(netagent.netagent_uuid);
+               if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) {
+                       cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated);
+               }
+       }
+
+       ifnet_head_lock_shared();
+       ifnet_t direct_interface = NULL;
+       ifnet_t delegate_interface = NULL;
+       ifnet_t original_scoped_interface = NULL;
+
+       if (result.routed_interface_index != IFSCOPE_NONE && (int)result.routed_interface_index <= if_index) {
+               direct_interface = ifindex2ifnet[result.routed_interface_index];
+       } else if (parsed_parameters.required_interface_index != IFSCOPE_NONE &&
+                          (int)parsed_parameters.required_interface_index <= if_index) {
+               // If the request was scoped, but the route didn't match, still grab the agents
+               direct_interface = ifindex2ifnet[parsed_parameters.required_interface_index];
+       } else if (result.routed_interface_index == IFSCOPE_NONE &&
+                          result.routing_result == NECP_KERNEL_POLICY_RESULT_SOCKET_SCOPED &&
+                          result.routing_result_parameter.scoped_interface_index != IFSCOPE_NONE) {
+               direct_interface = ifindex2ifnet[result.routing_result_parameter.scoped_interface_index];
+       }
+       if (direct_interface != NULL) {
+               delegate_interface = direct_interface->if_delegated.ifp;
+       }
+       if (result.routing_result == NECP_KERNEL_POLICY_RESULT_IP_TUNNEL &&
+               parsed_parameters.required_interface_index != IFSCOPE_NONE &&
+               parsed_parameters.required_interface_index != result.routing_result_parameter.tunnel_interface_index &&
+               (int)parsed_parameters.required_interface_index <= if_index) {
+               original_scoped_interface = ifindex2ifnet[parsed_parameters.required_interface_index];
+       }
+       // Add interfaces
+       if (original_scoped_interface != NULL) {
+               struct necp_client_result_interface interface_struct;
+               interface_struct.index = original_scoped_interface->if_index;
+               interface_struct.generation = ifnet_get_generation(original_scoped_interface);
+               cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE, sizeof(interface_struct), &interface_struct, &updated);
+       }
+       if (direct_interface != NULL) {
+               struct necp_client_result_interface interface_struct;
+               interface_struct.index = direct_interface->if_index;
+               interface_struct.generation = ifnet_get_generation(direct_interface);
+               cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE, sizeof(interface_struct), &interface_struct, &updated);
+       }
+       if (delegate_interface != NULL) {
+               struct necp_client_result_interface interface_struct;
+               interface_struct.index = delegate_interface->if_index;
+               interface_struct.generation = ifnet_get_generation(delegate_interface);
+               cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE, sizeof(interface_struct), &interface_struct, &updated);
+       }
+       // Add agents
+       if (original_scoped_interface != NULL) {
+               ifnet_lock_shared(original_scoped_interface);
+               if (original_scoped_interface->if_agentids != NULL) {
+                       for (u_int32_t i = 0; i < original_scoped_interface->if_agentcount; i++) {
+                               if (uuid_is_null(original_scoped_interface->if_agentids[i])) {
+                                       continue;
+                               }
+                               uuid_copy(netagent.netagent_uuid, original_scoped_interface->if_agentids[i]);
+                               netagent.generation = netagent_get_generation(netagent.netagent_uuid);
+                               if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) {
+                                       cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated);
+                               }
+                       }
+               }
+               ifnet_lock_done(original_scoped_interface);
+       }
+       if (direct_interface != NULL) {
+               ifnet_lock_shared(direct_interface);
+               if (direct_interface->if_agentids != NULL) {
+                       for (u_int32_t i = 0; i < direct_interface->if_agentcount; i++) {
+                               if (uuid_is_null(direct_interface->if_agentids[i])) {
+                                       continue;
+                               }
+                               uuid_copy(netagent.netagent_uuid, direct_interface->if_agentids[i]);
+                               netagent.generation = netagent_get_generation(netagent.netagent_uuid);
+                               if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) {
+                                       cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated);
+                               }
+                       }
+               }
+               ifnet_lock_done(direct_interface);
+       }
+       if (delegate_interface != NULL) {
+               ifnet_lock_shared(delegate_interface);
+               if (delegate_interface->if_agentids != NULL) {
+                       for (u_int32_t i = 0; i < delegate_interface->if_agentcount; i++) {
+                               if (uuid_is_null(delegate_interface->if_agentids[i])) {
+                                       continue;
+                               }
+                               uuid_copy(netagent.netagent_uuid, delegate_interface->if_agentids[i]);
+                               netagent.generation = netagent_get_generation(netagent.netagent_uuid);
+                               if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) {
+                                       cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated);
+                               }
+                       }
+               }
+               ifnet_lock_done(delegate_interface);
+       }
+       ifnet_head_done();
+
+       size_t new_result_length = (cursor - client->result);
+       if (new_result_length != client->result_length) {
+               client->result_length = new_result_length;
+               updated = TRUE;
+       }
+       if (updated) {
+               client->result_read = FALSE;
+       }
+
+       return (updated);
+}
+
+static void
+necp_update_all_clients_callout(__unused thread_call_param_t dummy,
+                                                               __unused thread_call_param_t arg)
+{
+#pragma unused(arg)
+       struct necp_fd_data *client_fd = NULL;
+
+       lck_rw_lock_shared(&necp_fd_lock);
+
+       LIST_FOREACH(client_fd, &necp_fd_list, chain) {
+               bool updated_result = FALSE;
+               struct necp_client *client = NULL;
+               proc_t proc = proc_find(client_fd->proc_pid);
+               if (proc == NULL) {
+                       continue;
+               }
+
+               lck_mtx_lock(&client_fd->fd_lock);
+               LIST_FOREACH(client, &client_fd->clients, chain) {
+                       if (necp_update_client_result(proc, client)) {
+                               updated_result = TRUE;
+                       }
+               }
+               if (updated_result) {
+                       necp_fd_notify(client_fd, true);
+               }
+               lck_mtx_unlock(&client_fd->fd_lock);
+
+               proc_rele(proc);
+       }
+
+       lck_rw_done(&necp_fd_lock);
+}
+
+void
+necp_update_all_clients(void)
+{
+       if (necp_client_tcall == NULL) {
+               // Don't try to update clients if the module is not initialized
+               return;
+       }
+
+       uint64_t deadline = 0;
+       uint64_t leeway = 0;
+       clock_interval_to_deadline(necp_timeout_microseconds, NSEC_PER_USEC, &deadline);
+       clock_interval_to_absolutetime_interval(necp_timeout_leeway_microseconds, NSEC_PER_USEC, &leeway);
+
+       thread_call_enter_delayed_with_leeway(necp_client_tcall, NULL,
+                                                                                 deadline, leeway, THREAD_CALL_DELAY_LEEWAY);
+}
+
+static void
+necp_client_remove_agent_from_result(struct necp_client *client, uuid_t netagent_uuid)
+{
+       size_t offset = 0;
+
+       u_int8_t *result_buffer = client->result;
+       while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= client->result_length) {
+               u_int8_t type = necp_buffer_get_tlv_type(result_buffer, offset);
+               u_int32_t length = necp_buffer_get_tlv_length(result_buffer, offset);
+
+               size_t tlv_total_length = (sizeof(u_int8_t) + sizeof(u_int32_t) + length);
+               if (type == NECP_CLIENT_RESULT_NETAGENT &&
+                       length == sizeof(struct necp_client_result_netagent) &&
+                       (offset + tlv_total_length) <= client->result_length) {
+                       struct necp_client_result_netagent *value = ((struct necp_client_result_netagent *)(void *)
+                                                                                                                necp_buffer_get_tlv_value(result_buffer, offset, NULL));
+                       if (uuid_compare(value->netagent_uuid, netagent_uuid) == 0) {
+                               // Found a netagent to remove
+                               // Shift bytes down to remove the tlv, and adjust total length
+                               // Don't adjust the current offset
+                               memmove(result_buffer + offset,
+                                               result_buffer + offset + tlv_total_length,
+                                               client->result_length - (offset + tlv_total_length));
+                               client->result_length -= tlv_total_length;
+                               memset(result_buffer + client->result_length, 0, NECP_MAX_CLIENT_RESULT_SIZE - client->result_length);
+                               continue;
+                       }
+               }
+
+               offset += tlv_total_length;
+       }
+}
+
+void
+necp_force_update_client(uuid_t client_id, uuid_t remove_netagent_uuid)
+{
+       struct necp_fd_data *client_fd = NULL;
+
+       lck_rw_lock_shared(&necp_fd_lock);
+
+       LIST_FOREACH(client_fd, &necp_fd_list, chain) {
+               bool updated_result = FALSE;
+               struct necp_client *client = NULL;
+               lck_mtx_lock(&client_fd->fd_lock);
+               LIST_FOREACH(client, &client_fd->clients, chain) {
+                       if (uuid_compare(client->client_id, client_id) == 0) {
+                               if (!uuid_is_null(remove_netagent_uuid)) {
+                                       necp_client_remove_agent_from_result(client, remove_netagent_uuid);
+                               }
+                               client->assigned_result_read = FALSE;
+                               updated_result = TRUE;
+                               // Found the client, break
+                               break;
+                       }
+               }
+               if (updated_result) {
+                       necp_fd_notify(client_fd, true);
+               }
+               lck_mtx_unlock(&client_fd->fd_lock);
+               if (updated_result) {
+                       // Found the client, break
+                       break;
+               }
+       }
+
+       lck_rw_done(&necp_fd_lock);
+}
+
+/// Interface matching
+
+#define NECP_PARSED_PARAMETERS_INTERESTING_IFNET_FIELDS (NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR |                             \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF |                   \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE |                 \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE |               \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT |                  \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT |                \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT |                 \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE |             \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE |   \
+                                                                                                                NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE)
+
+#define NECP_PARSED_PARAMETERS_SCOPED_IFNET_FIELDS (NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR |                  \
+                                                                                                       NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE |          \
+                                                                                                       NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT |           \
+                                                                                                       NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT |          \
+                                                                                                       NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE |      \
+                                                                                                       NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE)
+
+#define NECP_PARSED_PARAMETERS_PREFERRED_IFNET_FIELDS (NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT | \
+                                                                                                          NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE)
+
+static bool
+necp_ifnet_matches_type(struct ifnet *ifp, u_int8_t interface_type, bool check_delegates)
+{
+       struct ifnet *check_ifp = ifp;
+       while (check_ifp) {
+               if (if_functional_type(check_ifp, TRUE) == interface_type) {
+                       return (TRUE);
+               }
+               if (!check_delegates) {
+                       break;
+               }
+               check_ifp = check_ifp->if_delegated.ifp;
+
+       }
+       return (FALSE);
+}
+
+static bool
+necp_ifnet_matches_name(struct ifnet *ifp, const char *interface_name, bool check_delegates)
+{
+       struct ifnet *check_ifp = ifp;
+       while (check_ifp) {
+               if (strncmp(check_ifp->if_xname, interface_name, IFXNAMSIZ) == 0) {
+                       return (TRUE);
+               }
+               if (!check_delegates) {
+                       break;
+               }
+               check_ifp = check_ifp->if_delegated.ifp;
+       }
+       return (FALSE);
+}
+
+static bool
+necp_ifnet_matches_agent(struct ifnet *ifp, uuid_t *agent_uuid, bool check_delegates)
+{
+       struct ifnet *check_ifp = ifp;
+
+       while (check_ifp != NULL) {
+               ifnet_lock_shared(check_ifp);
+               if (check_ifp->if_agentids != NULL) {
+                       for (u_int32_t index = 0; index < check_ifp->if_agentcount; index++) {
+                               if (uuid_compare(check_ifp->if_agentids[index], *agent_uuid) == 0) {
+                                       ifnet_lock_done(check_ifp);
+                                       return (TRUE);
+                               }
+                       }
+               }
+               ifnet_lock_done(check_ifp);
+
+               if (!check_delegates) {
+                       break;
+               }
+               check_ifp = check_ifp->if_delegated.ifp;
+       }
+       return (FALSE);
+}
+
+static bool
+necp_necp_ifnet_matches_agent_type(struct ifnet *ifp, const char *agent_domain, const char *agent_type, bool check_delegates)
+{
+       struct ifnet *check_ifp = ifp;
+
+       while (check_ifp != NULL) {
+               ifnet_lock_shared(check_ifp);
+               if (check_ifp->if_agentids != NULL) {
+                       for (u_int32_t index = 0; index < check_ifp->if_agentcount; index++) {
+                               if (uuid_is_null(check_ifp->if_agentids[index])) {
+                                       continue;
+                               }
+
+                               char if_agent_domain[NETAGENT_DOMAINSIZE] = { 0 };
+                               char if_agent_type[NETAGENT_TYPESIZE] = { 0 };
+
+                               if (netagent_get_agent_domain_and_type(check_ifp->if_agentids[index], if_agent_domain, if_agent_type)) {
+                                       if ((strlen(agent_domain) == 0 ||
+                                                strncmp(if_agent_domain, agent_domain, NETAGENT_DOMAINSIZE) == 0) &&
+                                               (strlen(agent_type) == 0 ||
+                                                strncmp(if_agent_type, agent_type, NETAGENT_TYPESIZE) == 0)) {
+                                                       ifnet_lock_done(check_ifp);
+                                                       return (TRUE);
+                                               }
+                               }
+                       }
+               }
+               ifnet_lock_done(check_ifp);
+
+               if (!check_delegates) {
+                       break;
+               }
+               check_ifp = check_ifp->if_delegated.ifp;
+       }
+       return (FALSE);
+}
+
+static bool
+necp_ifnet_matches_local_address(struct ifnet *ifp, struct sockaddr *sa)
+{
+       struct ifaddr *ifa = NULL;
+       bool matched_local_address = FALSE;
+
+       // Transform sa into the ifaddr form
+       // IPv6 Scope IDs are always embedded in the ifaddr list
+       struct sockaddr_storage address;
+       u_int ifscope = IFSCOPE_NONE;
+       (void)sa_copy(sa, &address, &ifscope);
+       SIN(&address)->sin_port = 0;
+       if (address.ss_family == AF_INET6) {
+               SIN6(&address)->sin6_scope_id = 0;
+       }
+
+       ifa = ifa_ifwithaddr_scoped_locked((struct sockaddr *)&address, ifp->if_index);
+       matched_local_address = (ifa != NULL);
+
+       if (ifa) {
+               ifaddr_release(ifa);
+       }
+
+       return (matched_local_address);
+}
+
+static bool
+necp_ifnet_matches_parameters(struct ifnet *ifp,
+                                                         struct necp_client_parsed_parameters *parsed_parameters,
+                                                         u_int32_t *preferred_count)
+{
+       if (preferred_count) {
+               *preferred_count = 0;
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR) {
+               if (!necp_ifnet_matches_local_address(ifp, &parsed_parameters->local_addr.sa)) {
+                       return (FALSE);
+               }
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE) {
+               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                       if (parsed_parameters->required_interface_types[i] == 0) {
+                               break;
+                       }
+
+                       if (!necp_ifnet_matches_type(ifp, parsed_parameters->required_interface_types[i], FALSE)) {
+                               return (FALSE);
+                       }
+               }
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE) {
+               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                       if (parsed_parameters->prohibited_interface_types[i] == 0) {
+                               break;
+                       }
+
+                       if (necp_ifnet_matches_type(ifp, parsed_parameters->prohibited_interface_types[i], TRUE)) {
+                               return (FALSE);
+                       }
+               }
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF) {
+               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                       if (strlen(parsed_parameters->prohibited_interfaces[i]) == 0) {
+                               break;
+                       }
+
+                       if (necp_ifnet_matches_name(ifp, parsed_parameters->prohibited_interfaces[i], TRUE)) {
+                               return (FALSE);
+                       }
+               }
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT) {
+               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                       if (uuid_is_null(parsed_parameters->required_netagents[i])) {
+                               break;
+                       }
+
+                       if (!necp_ifnet_matches_agent(ifp, &parsed_parameters->required_netagents[i], FALSE)) {
+                               return (FALSE);
+                       }
+               }
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT) {
+               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                       if (uuid_is_null(parsed_parameters->prohibited_netagents[i])) {
+                               break;
+                       }
+
+                       if (necp_ifnet_matches_agent(ifp, &parsed_parameters->prohibited_netagents[i], TRUE)) {
+                               return (FALSE);
+                       }
+               }
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE) {
+               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                       if (strlen(parsed_parameters->required_netagent_types[i].netagent_domain) == 0 &&
+                               strlen(parsed_parameters->required_netagent_types[i].netagent_type) == 0) {
+                               break;
+                       }
+
+                       if (!necp_necp_ifnet_matches_agent_type(ifp, parsed_parameters->required_netagent_types[i].netagent_domain, parsed_parameters->required_netagent_types[i].netagent_type, FALSE)) {
+                               return (FALSE);
+                       }
+               }
+       }
+
+       if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE) {
+               for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                       if (strlen(parsed_parameters->prohibited_netagent_types[i].netagent_domain) == 0 &&
+                               strlen(parsed_parameters->prohibited_netagent_types[i].netagent_type) == 0) {
+                               break;
+                       }
+
+                       if (necp_necp_ifnet_matches_agent_type(ifp, parsed_parameters->prohibited_netagent_types[i].netagent_domain, parsed_parameters->prohibited_netagent_types[i].netagent_type, TRUE)) {
+                               return (FALSE);
+                       }
+               }
+       }
+
+       // Checked preferred properties
+       if (preferred_count) {
+               if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT) {
+                       for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                               if (uuid_is_null(parsed_parameters->preferred_netagents[i])) {
+                                       break;
+                               }
+
+                               if (necp_ifnet_matches_agent(ifp, &parsed_parameters->preferred_netagents[i], TRUE)) {
+                                       (*preferred_count)++;
+                               }
+                       }
+               }
+
+               if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE) {
+                       for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) {
+                               if (strlen(parsed_parameters->preferred_netagent_types[i].netagent_domain) == 0 &&
+                                       strlen(parsed_parameters->preferred_netagent_types[i].netagent_type) == 0) {
+                                       break;
+                               }
+
+                               if (necp_necp_ifnet_matches_agent_type(ifp, parsed_parameters->preferred_netagent_types[i].netagent_domain, parsed_parameters->preferred_netagent_types[i].netagent_type, TRUE)) {
+                                       (*preferred_count)++;
+                               }
+                       }
+               }
+       }
+
+       return (TRUE);
+}
+
+static bool
+necp_find_matching_interface_index(struct necp_client_parsed_parameters *parsed_parameters, u_int *return_ifindex)
+{
+       struct ifnet *ifp = NULL;
+       u_int32_t best_preferred_count = 0;
+       bool has_preferred_fields = FALSE;
+       *return_ifindex = 0;
+
+       if (parsed_parameters->required_interface_index != 0) {
+               *return_ifindex = parsed_parameters->required_interface_index;
+               return (TRUE);
+       }
+
+       if (!(parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_INTERESTING_IFNET_FIELDS)) {
+               return (TRUE);
+       }
+
+       has_preferred_fields = (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_PREFERRED_IFNET_FIELDS);
+
+       // We have interesting parameters to parse and find a matching interface
+       ifnet_head_lock_shared();
+
+       if (!(parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_SCOPED_IFNET_FIELDS)) {
+               // We do have fields to match, but they are only prohibitory
+               // If the first interface in the list matches, we don't need to scope
+               ifp = TAILQ_FIRST(&ifnet_ordered_head);
+               if (ifp && necp_ifnet_matches_parameters(ifp, parsed_parameters, NULL)) {
+                       // Don't set return_ifindex, so the client doesn't need to scope
+                       ifnet_head_done();
+                       return (TRUE);
+               }
+       }
+
+       // First check the ordered interface list
+       TAILQ_FOREACH(ifp, &ifnet_ordered_head, if_ordered_link) {
+               u_int32_t preferred_count = 0;
+               if (necp_ifnet_matches_parameters(ifp, parsed_parameters, &preferred_count)) {
+                       if (preferred_count > best_preferred_count ||
+                               *return_ifindex == 0) {
+
+                               // Everything matched, and is most preferred. Return this interface.
+                               *return_ifindex = ifp->if_index;
+                               best_preferred_count = preferred_count;
+
+                               if (!has_preferred_fields) {
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       // Then check the remaining interfaces
+       if ((parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_SCOPED_IFNET_FIELDS) &&
+           !(parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE) &&
+               *return_ifindex == 0) {
+               TAILQ_FOREACH(ifp, &ifnet_head, if_link) {
+                       u_int32_t preferred_count = 0;
+                       if (ifp->if_ordered_link.tqe_next != NULL ||
+                               ifp->if_ordered_link.tqe_prev != NULL) {
+                               // This interface was in the ordered list, skip
+                               continue;
+                       }
+                       if (necp_ifnet_matches_parameters(ifp, parsed_parameters, &preferred_count)) {
+                               if (preferred_count > best_preferred_count ||
+                                       *return_ifindex == 0) {
+
+                                       // Everything matched, and is most preferred. Return this interface.
+                                       *return_ifindex = ifp->if_index;
+                                       best_preferred_count = preferred_count;
+
+                                       if (!has_preferred_fields) {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       ifnet_head_done();
+
+       if ((parsed_parameters->valid_fields == (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_PREFERRED_IFNET_FIELDS)) &&
+               best_preferred_count == 0) {
+               // If only has preferred fields, and nothing was found, clear the interface index and return TRUE
+               *return_ifindex = 0;
+               return (TRUE);
+       }
+
+       return (*return_ifindex != 0);
+}
+
+static void
+necp_find_netstat_data(struct necp_client *client, union necp_sockaddr_union *local, union necp_sockaddr_union *remote, u_int32_t *ifindex, uuid_t euuid, u_int32_t *traffic_class)
+{
+       size_t offset = 0;
+       u_int8_t *parameters;
+       u_int32_t parameters_size;
+
+       parameters = client->parameters;
+       parameters_size = (u_int32_t)client->parameters_length;
+
+       while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= parameters_size) {
+               u_int8_t type = necp_buffer_get_tlv_type(parameters, offset);
+               u_int32_t length = necp_buffer_get_tlv_length(parameters, offset);
+
+               if (length > 0 && (offset + sizeof(u_int8_t) + sizeof(u_int32_t) + length) <= parameters_size) {
+                       u_int8_t *value = necp_buffer_get_tlv_value(parameters, offset, NULL);
+                       if (value != NULL) {
+                               switch (type) {
+                                       case NECP_CLIENT_PARAMETER_REAL_APPLICATION: {
+                                               if (length >= sizeof(uuid_t)) {
+                                                       uuid_copy(euuid, value);
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_TRAFFIC_CLASS: {
+                                               if (length >= sizeof(u_int32_t)) {
+                                                       memcpy(traffic_class, value, sizeof(u_int32_t));
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_BOUND_INTERFACE: {
+                                               if (length <= IFXNAMSIZ && length > 0) {
+                                                       ifnet_t bound_interface = NULL;
+                                                       char interface_name[IFXNAMSIZ];
+                                                       memcpy(interface_name, value, length);
+                                                       interface_name[length - 1] = 0; // Make sure the string is NULL terminated
+                                                       if (ifnet_find_by_name(interface_name, &bound_interface) == 0) {
+                                                               *ifindex = bound_interface->if_index;
+                                                               ifnet_release(bound_interface);
+                                                       }
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_LOCAL_ADDRESS: {
+                                               if (length >= sizeof(struct necp_policy_condition_addr)) {
+                                                       struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value;
+                                                       memcpy(local, &address_struct->address, sizeof(address_struct->address));
+                                               }
+                                               break;
+                                       }
+                                       case NECP_CLIENT_PARAMETER_REMOTE_ADDRESS: {
+                                               if (length >= sizeof(struct necp_policy_condition_addr)) {
+                                                       struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value;
+                                                       memcpy(remote, &address_struct->address, sizeof(address_struct->address));
+                                               }
+                                               break;
+                                       }
+                                       default: {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length;
+       }
+}
+
+static void
+necp_fillout_current_process_details(u_int32_t *pid, u_int64_t *upid, unsigned char *uuid, char *pname, size_t len)
+{
+       *pid = proc_selfpid();
+       *upid = proc_uniqueid(current_proc());
+       proc_selfname(pname, (int) len);
+       proc_getexecutableuuid(current_proc(), uuid, sizeof(uuid_t));
+}
+
+// Called from NetworkStatistics when it wishes to collect latest information for a TCP flow.
+// It is a responsibility of NetworkStatistics to have previously zeroed any supplied memory.
+static bool
+necp_request_tcp_netstats(userland_stats_provider_context *ctx,
+                                                 nstat_counts *countsp,
+                                                 void *metadatap)
+{
+       if (ctx == NULL) {
+               return false;
+       }
+
+       struct necp_client *client = (struct necp_client *)ctx;
+       struct necp_tcp_stats *tcpstats = (struct necp_tcp_stats *)client->stats_area;
+       if (tcpstats == NULL) {
+               return false;
+       }
+
+       if (countsp) {
+               *countsp = *((struct nstat_counts *)&tcpstats->necp_tcp_counts);
+       }
+
+       if (metadatap) {
+               nstat_tcp_descriptor *desc = (nstat_tcp_descriptor *)metadatap;
+
+               // Metadata for the process
+               necp_fillout_current_process_details(&desc->pid, &desc->upid, desc->uuid, desc->pname, sizeof(desc->pname));
+
+               // Metadata that the necp client should have in TLV format.
+               necp_find_netstat_data(client, (union necp_sockaddr_union *)&desc->local, (union necp_sockaddr_union  *)&desc->remote, &desc->ifindex, desc->euuid, &desc->traffic_class);
+
+               // Basic metadata
+               desc->rcvbufsize = tcpstats->necp_tcp_basic.rcvbufsize;
+               desc->rcvbufused = tcpstats->necp_tcp_basic.rcvbufused;
+               desc->eupid = tcpstats->necp_tcp_basic.eupid;
+               desc->epid = tcpstats->necp_tcp_basic.epid;
+               memcpy(desc->vuuid, tcpstats->necp_tcp_basic.vuuid, sizeof(desc->vuuid));
+               desc->ifnet_properties = tcpstats->necp_tcp_basic.ifnet_properties;
+
+               // Additional TCP specific data
+               desc->sndbufsize = tcpstats->necp_tcp_extra.sndbufsize;
+               desc->sndbufused = tcpstats->necp_tcp_extra.sndbufused;
+               desc->txunacked = tcpstats->necp_tcp_extra.txunacked;
+               desc->txwindow = tcpstats->necp_tcp_extra.txwindow;
+               desc->txcwindow = tcpstats->necp_tcp_extra.txcwindow;
+               desc->traffic_mgt_flags = tcpstats->necp_tcp_extra.traffic_mgt_flags;
+
+               if (tcpstats->necp_tcp_extra.cc_alg_index < TCP_CC_ALGO_COUNT) {
+                       strlcpy(desc->cc_algo, tcp_cc_algo_list[tcpstats->necp_tcp_extra.cc_alg_index]->name, sizeof(desc->cc_algo));
+               } else {
+                       strlcpy(desc->cc_algo, "unknown", sizeof(desc->cc_algo));
+               }
+
+               desc->connstatus.write_probe_failed     = tcpstats->necp_tcp_extra.probestatus.write_probe_failed;
+               desc->connstatus.read_probe_failed      = tcpstats->necp_tcp_extra.probestatus.read_probe_failed;
+               desc->connstatus.conn_probe_failed      = tcpstats->necp_tcp_extra.probestatus.conn_probe_failed;
+       }
+       return true;
+}
+
+// Called from NetworkStatistics when it wishes to collect latest information for a UDP flow.
+static bool
+necp_request_udp_netstats(userland_stats_provider_context *ctx,
+                                                 nstat_counts *countsp,
+                                                 void *metadatap)
+{
+       if (ctx == NULL) {
+               return false;
+       }
+
+       struct necp_client *client = (struct necp_client *)ctx;
+       struct necp_udp_stats *udpstats = (struct necp_udp_stats *)client->stats_area;
+       if (udpstats == NULL) {
+               return false;
+       }
+
+       if (countsp) {
+               *countsp = *((struct nstat_counts *)&udpstats->necp_udp_counts);
+       }
+
+       if (metadatap) {
+               nstat_udp_descriptor *desc = (nstat_udp_descriptor *)metadatap;
+
+               // Metadata for the process
+               necp_fillout_current_process_details(&desc->pid, &desc->upid, desc->uuid, desc->pname, sizeof(desc->pname));
+
+               // Metadata that the necp client should have in TLV format.
+               necp_find_netstat_data(client, (union necp_sockaddr_union *)&desc->local, (union necp_sockaddr_union  *)&desc->remote, &desc->ifindex, desc->euuid, &desc->traffic_class);
+
+               // Basic metadata is all that is required for UDP
+               desc->rcvbufsize = udpstats->necp_udp_basic.rcvbufsize;
+               desc->rcvbufused = udpstats->necp_udp_basic.rcvbufused;
+               desc->eupid = udpstats->necp_udp_basic.eupid;
+               desc->epid = udpstats->necp_udp_basic.epid;
+               memcpy(desc->vuuid, udpstats->necp_udp_basic.vuuid, sizeof(desc->euuid));
+               desc->ifnet_properties = udpstats->necp_udp_basic.ifnet_properties;
+       }
+       return true;
+}
+
+static int
+necp_skywalk_priv_check_cred(proc_t p, kauth_cred_t cred)
+{
+#pragma unused(p, cred)
+       return (0);
+}
+
+/// System calls
+
+int
+necp_open(struct proc *p, struct necp_open_args *uap, int *retval)
+{
+#pragma unused(retval)
+       int error = 0;
+       struct necp_fd_data *fd_data = NULL;
+       struct fileproc *fp = NULL;
+       int fd = -1;
+
+       if (uap->flags & NECP_OPEN_FLAG_OBSERVER) {
+               if (necp_skywalk_priv_check_cred(p, kauth_cred_get()) != 0 &&
+                   priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NETWORK_STATISTICS, 0) != 0) {
+                       NECPLOG0(LOG_ERR, "Client does not hold necessary entitlement to observe other NECP clients");
+                       error = EACCES;
+                       goto done;
+               }
+       }
+
+       error = falloc(p, &fp, &fd, vfs_context_current());
+       if (error != 0) {
+               goto done;
+       }
+
+       if ((fd_data = _MALLOC(sizeof(struct necp_fd_data), M_NECP,
+                                                  M_WAITOK | M_ZERO)) == NULL) {
+               error = ENOMEM;
+               goto done;
+       }
+
+       fd_data->flags = uap->flags;
+       LIST_INIT(&fd_data->clients);
+       lck_mtx_init(&fd_data->fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr);
+       klist_init(&fd_data->si.si_note);
+       fd_data->proc_pid = proc_pid(p);
+
+       fp->f_fglob->fg_flag = FREAD;
+       fp->f_fglob->fg_ops = &necp_fd_ops;
+       fp->f_fglob->fg_data = fd_data;
+
+       proc_fdlock(p);
+
+       *fdflags(p, fd) |= (UF_EXCLOSE | UF_FORKCLOSE);
+       procfdtbl_releasefd(p, fd, NULL);
+       fp_drop(p, fd, fp, 1);
+       proc_fdunlock(p);
+
+       *retval = fd;
+
+       lck_rw_lock_exclusive(&necp_fd_lock);
+       LIST_INSERT_HEAD(&necp_fd_list, fd_data, chain);
+       lck_rw_done(&necp_fd_lock);
+
+done:
+       if (error != 0) {
+               if (fp != NULL) {
+                       fp_free(p, fd, fp);
+                       fp = NULL;
+               }
+               if (fd_data != NULL) {
+                       FREE(fd_data, M_NECP);
+                       fd_data = NULL;
+               }
+       }
+
+       return (error);
+}
+
+static int
+necp_client_add(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       struct necp_client *client = NULL;
+
+       if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) ||
+               uap->buffer_size == 0 || uap->buffer_size > NECP_MAX_CLIENT_PARAMETERS_SIZE || uap->buffer == 0) {
+               error = EINVAL;
+               goto done;
+       }
+
+       if ((client = _MALLOC(sizeof(struct necp_client) + uap->buffer_size, M_NECP,
+                                                 M_WAITOK | M_ZERO)) == NULL) {
+               error = ENOMEM;
+               goto done;
+       }
+
+       error = copyin(uap->buffer, client->parameters, uap->buffer_size);
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_add parameters copyin error (%d)", error);
+               goto done;
+       }
+
+       client->parameters_length = uap->buffer_size;
+
+       uuid_generate_random(client->client_id);
+       LIST_INIT(&client->assertion_list);
+
+       error = copyout(client->client_id, uap->client_id, sizeof(uuid_t));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_add client_id copyout error (%d)", error);
+               goto done;
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       LIST_INSERT_HEAD(&fd_data->clients, client, chain);
+
+       // Prime the client result
+       (void)necp_update_client_result(current_proc(), client);
+       lck_mtx_unlock(&fd_data->fd_lock);
+done:
+       if (error != 0) {
+               if (client != NULL) {
+                       FREE(client, M_NECP);
+                       client = NULL;
+               }
+       }
+       *retval = error;
+
+       return (error);
+}
+
+static int
+necp_client_remove(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       struct necp_client *client = NULL;
+       struct necp_client *temp_client = NULL;
+       uuid_t client_id;
+
+       if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t)) {
+               error = EINVAL;
+               goto done;
+       }
+
+       error = copyin(uap->client_id, client_id, sizeof(uuid_t));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_remove copyin client_id error (%d)", error);
+               goto done;
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       LIST_FOREACH_SAFE(client, &fd_data->clients, chain, temp_client) {
+               if (uuid_compare(client->client_id, client_id) == 0) {
+                       necp_destroy_client(client);
+               }
+       }
+       lck_mtx_unlock(&fd_data->fd_lock);
+done:
+       *retval = error;
+
+       return (error);
+}
+
+static int
+necp_client_copy_internal(struct necp_client *client, bool client_is_observed, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       // Copy results out
+       if (uap->action == NECP_CLIENT_ACTION_COPY_PARAMETERS) {
+               if (uap->buffer_size < client->parameters_length) {
+                       error = EINVAL;
+                       goto done;
+               }
+               error = copyout(client->parameters, uap->buffer, client->parameters_length);
+               if (error) {
+                       NECPLOG(LOG_ERR, "necp_client_copy parameters copyout error (%d)", error);
+                       goto done;
+               }
+               *retval = client->parameters_length;
+       } else if (uap->action == NECP_CLIENT_ACTION_COPY_RESULT) {
+               if (uap->buffer_size < (client->result_length + client->assigned_results_length)) {
+                       error = EINVAL;
+                       goto done;
+               }
+               error = copyout(client->result, uap->buffer, client->result_length);
+               if (error) {
+                       NECPLOG(LOG_ERR, "necp_client_copy result copyout error (%d)", error);
+                       goto done;
+               }
+               if (client->assigned_results_length && client->assigned_results) {
+                       error = copyout(client->assigned_results, uap->buffer + client->result_length, client->assigned_results_length);
+                       if (error) {
+                               NECPLOG(LOG_ERR, "necp_client_copy assigned results copyout error (%d)", error);
+                               goto done;
+                       }
+                       *retval = client->result_length + client->assigned_results_length;
+               } else {
+                       *retval = client->result_length;
+               }
+
+               if (!client_is_observed) {
+                       client->result_read = TRUE;
+                       client->assigned_result_read = TRUE;
+               }
+       }
+
+done:
+       return (error);
+}
+
+static int
+necp_client_copy(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       struct necp_client *find_client = NULL;
+       struct necp_client *client = NULL;
+       uuid_t client_id;
+       uuid_clear(client_id);
+
+       *retval = 0;
+
+       if (uap->buffer_size == 0 || uap->buffer == 0) {
+               error = EINVAL;
+               goto done;
+       }
+
+       if (uap->action != NECP_CLIENT_ACTION_COPY_PARAMETERS &&
+               uap->action != NECP_CLIENT_ACTION_COPY_RESULT) {
+               error = EINVAL;
+               goto done;
+       }
+
+       if (uap->client_id) {
+               if (uap->client_id_len != sizeof(uuid_t)) {
+                       NECPLOG(LOG_ERR, "Incorrect length (got %d, expected %d)", uap->client_id_len, sizeof(uuid_t));
+                       error = ERANGE;
+                       goto done;
+               }
+
+               error = copyin(uap->client_id, client_id, sizeof(uuid_t));
+               if (error) {
+                       NECPLOG(LOG_ERR, "necp_client_copy client_id copyin error (%d)", error);
+                       goto done;
+               }
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       LIST_FOREACH(find_client, &fd_data->clients, chain) {
+               if (uap->action == NECP_CLIENT_ACTION_COPY_RESULT &&
+                       uuid_is_null(client_id)) {
+                       if (!find_client->result_read || !find_client->assigned_result_read) {
+                               client = find_client;
+                               break;
+                       }
+               } else if (uuid_compare(find_client->client_id, client_id) == 0) {
+                       client = find_client;
+                       break;
+               }
+       }
+
+       if (client != NULL) {
+               error = necp_client_copy_internal(client, FALSE, uap, retval);
+       }
+
+       // Unlock our own client before moving on or returning
+       lck_mtx_unlock(&fd_data->fd_lock);
+
+       if (client == NULL) {
+               if (fd_data->flags & NECP_OPEN_FLAG_OBSERVER) {
+                       // Observers are allowed to lookup clients on other fds
+
+                       // Lock list
+                       lck_rw_lock_shared(&necp_fd_lock);
+                       struct necp_fd_data *client_fd = NULL;
+                       LIST_FOREACH(client_fd, &necp_fd_list, chain) {
+                               // Lock client
+                               lck_mtx_lock(&client_fd->fd_lock);
+                               find_client = NULL;
+                               LIST_FOREACH(find_client, &client_fd->clients, chain) {
+                                       if (uuid_compare(find_client->client_id, client_id) == 0) {
+                                               client = find_client;
+                                               break;
+                                       }
+                               }
+
+                               if (client != NULL) {
+                                       // Matched, copy out data
+                                       error = necp_client_copy_internal(client, TRUE, uap, retval);
+                               }
+
+                               // Unlock client
+                               lck_mtx_unlock(&client_fd->fd_lock);
+
+                               if (client != NULL) {
+                                       break;
+                               }
+                       }
+
+                       // Unlock list
+                       lck_rw_done(&necp_fd_lock);
+
+                       // No client found, fail
+                       if (client == NULL) {
+                               error = ENOENT;
+                               goto done;
+                       }
+               } else {
+                       // No client found, and not allowed to search other fds, fail
+                       error = ENOENT;
+                       goto done;
+               }
+       }
+
+done:
+       return (error);
+}
+
+static int
+necp_client_list(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       struct necp_client *find_client = NULL;
+       uuid_t *list = NULL;
+       u_int32_t requested_client_count = 0;
+       u_int32_t client_count = 0;
+
+       if (uap->buffer_size < sizeof(requested_client_count) || uap->buffer == 0) {
+               error = EINVAL;
+               goto done;
+       }
+
+       if (!(fd_data->flags & NECP_OPEN_FLAG_OBSERVER)) {
+               NECPLOG0(LOG_ERR, "Client does not hold necessary entitlement to list other NECP clients");
+               error = EACCES;
+               goto done;
+       }
+
+       error = copyin(uap->buffer, &requested_client_count, sizeof(requested_client_count));
+       if (error) {
+               goto done;
+       }
+
+       if (uap->buffer_size != (sizeof(requested_client_count) + requested_client_count * sizeof(uuid_t))) {
+               error = EINVAL;
+               goto done;
+       }
+
+       if (requested_client_count > 0) {
+               if ((list = _MALLOC(requested_client_count * sizeof(uuid_t), M_NECP, M_WAITOK | M_ZERO)) == NULL) {
+                       error = ENOMEM;
+                       goto done;
+               }
+       }
+
+       // Lock list
+       lck_rw_lock_shared(&necp_fd_lock);
+       struct necp_fd_data *client_fd = NULL;
+       LIST_FOREACH(client_fd, &necp_fd_list, chain) {
+               // Lock client
+               lck_mtx_lock(&client_fd->fd_lock);
+               find_client = NULL;
+               LIST_FOREACH(find_client, &client_fd->clients, chain) {
+                       if (!uuid_is_null(find_client->client_id)) {
+                               if (client_count < requested_client_count) {
+                                       uuid_copy(list[client_count], find_client->client_id);
+                               }
+                               client_count++;
+                       }
+               }
+               lck_mtx_unlock(&client_fd->fd_lock);
+       }
+
+       // Unlock list
+       lck_rw_done(&necp_fd_lock);
+
+       error = copyout(&client_count, uap->buffer, sizeof(client_count));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_list buffer copyout error (%d)", error);
+               goto done;
+       }
+
+       if (requested_client_count > 0 &&
+               client_count > 0 &&
+               list != NULL) {
+               error = copyout(list, uap->buffer + sizeof(client_count), requested_client_count * sizeof(uuid_t));
+               if (error) {
+                       NECPLOG(LOG_ERR, "necp_client_list client count copyout error (%d)", error);
+                       goto done;
+               }
+       }
+done:
+       if (list != NULL) {
+               FREE(list, M_NECP);
+       }
+       *retval = error;
+       
+       return (error);
+}
+
+static int
+necp_client_request_nexus(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       struct necp_client *client = NULL;
+       uuid_t client_id;
+       bool requested_nexus = FALSE;
+
+       if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t)) {
+               error = EINVAL;
+               goto done;
+       }
+
+       error = copyin(uap->client_id, client_id, sizeof(uuid_t));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_request_nexus copyin client_id error (%d)", error);
+               goto done;
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       LIST_FOREACH(client, &fd_data->clients, chain) {
+               if (uuid_compare(client->client_id, client_id) == 0) {
+                       // Request from nexus agent
+                       if (!uuid_is_null(client->nexus_agent)) {
+                               error = netagent_client_message(client->nexus_agent, client->client_id,
+                                                                                               NETAGENT_MESSAGE_TYPE_REQUEST_NEXUS);
+                               if (error == 0) {
+                                       requested_nexus = TRUE;
+                               }
+                       }
+                       break;
+               }
+       }
+       lck_mtx_unlock(&fd_data->fd_lock);
+
+       if (!requested_nexus &&
+               error == 0) {
+               error = ENOENT;
+       }
+done:
+       *retval = error;
+
+       return (error);
+}
+
+static void
+necp_client_add_assertion(struct necp_client *client, uuid_t netagent_uuid)
+{
+       struct necp_client_assertion *new_assertion = NULL;
+
+       MALLOC(new_assertion, struct necp_client_assertion *, sizeof(*new_assertion), M_NECP, M_WAITOK);
+       if (new_assertion == NULL) {
+               NECPLOG0(LOG_ERR, "Failed to allocate assertion");
+               return;
+       }
+
+       uuid_copy(new_assertion->asserted_netagent, netagent_uuid);
+
+       LIST_INSERT_HEAD(&client->assertion_list, new_assertion, assertion_chain);
+}
+
+static bool
+necp_client_remove_assertion(struct necp_client *client, uuid_t netagent_uuid)
+{
+       struct necp_client_assertion *found_assertion = NULL;
+       struct necp_client_assertion *search_assertion = NULL;
+       LIST_FOREACH(search_assertion, &client->assertion_list, assertion_chain) {
+               if (uuid_compare(search_assertion->asserted_netagent, netagent_uuid) == 0) {
+                       found_assertion = search_assertion;
+                       break;
+               }
+       }
+
+       if (found_assertion == NULL) {
+               NECPLOG0(LOG_ERR, "Netagent uuid not previously asserted");
+               return false;
+       }
+
+       LIST_REMOVE(found_assertion, assertion_chain);
+       FREE(found_assertion, M_NECP);
+       return true;
+}
+
+static int
+necp_client_agent_action(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       struct necp_client *matched_client = NULL;
+       struct necp_client *client = NULL;
+       uuid_t client_id;
+       bool acted_on_agent = FALSE;
+       u_int8_t *parameters = NULL;
+       size_t parameters_size = uap->buffer_size;
+
+       if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) ||
+               uap->buffer_size == 0 || uap->buffer == 0) {
+               error = EINVAL;
+               goto done;
+       }
+
+       error = copyin(uap->client_id, client_id, sizeof(uuid_t));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_agent_action copyin client_id error (%d)", error);
+               goto done;
+       }
+
+       if ((parameters = _MALLOC(uap->buffer_size, M_NECP, M_WAITOK | M_ZERO)) == NULL) {
+               error = ENOMEM;
+               goto done;
+       }
+
+       error = copyin(uap->buffer, parameters, uap->buffer_size);
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_agent_action parameters copyin error (%d)", error);
+               goto done;
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       LIST_FOREACH(client, &fd_data->clients, chain) {
+               if (uuid_compare(client->client_id, client_id) == 0) {
+                       matched_client = client;
+                       break;
+               }
+       }
+       if (matched_client) {
+               size_t offset = 0;
+               while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= parameters_size) {
+                       u_int8_t type = necp_buffer_get_tlv_type(parameters, offset);
+                       u_int32_t length = necp_buffer_get_tlv_length(parameters, offset);
+
+                       if (length > 0 && (offset + sizeof(u_int8_t) + sizeof(u_int32_t) + length) <= parameters_size) {
+                               u_int8_t *value = necp_buffer_get_tlv_value(parameters, offset, NULL);
+                               if (length >= sizeof(uuid_t) &&
+                                       value != NULL &&
+                                       (type == NECP_CLIENT_PARAMETER_TRIGGER_AGENT ||
+                                        type == NECP_CLIENT_PARAMETER_ASSERT_AGENT ||
+                                        type == NECP_CLIENT_PARAMETER_UNASSERT_AGENT)) {
+
+                                               uuid_t agent_uuid;
+                                               uuid_copy(agent_uuid, value);
+                                               u_int8_t netagent_message_type = 0;
+                                               if (type == NECP_CLIENT_PARAMETER_TRIGGER_AGENT) {
+                                                       netagent_message_type = NETAGENT_MESSAGE_TYPE_CLIENT_TRIGGER;
+                                               } else if (type == NECP_CLIENT_PARAMETER_ASSERT_AGENT) {
+                                                       netagent_message_type = NETAGENT_MESSAGE_TYPE_CLIENT_ASSERT;
+                                               } else if (type == NECP_CLIENT_PARAMETER_UNASSERT_AGENT) {
+                                                       netagent_message_type = NETAGENT_MESSAGE_TYPE_CLIENT_UNASSERT;
+                                               }
+
+                                               // Before unasserting, verify that the assertion was already taken
+                                               if (type == NECP_CLIENT_PARAMETER_UNASSERT_AGENT) {
+                                                       if (!necp_client_remove_assertion(client, agent_uuid)) {
+                                                               error = ENOENT;
+                                                               break;
+                                                       }
+                                               }
+
+                                               error = netagent_client_message(agent_uuid, client_id,
+                                                                                                               netagent_message_type);
+                                               if (error == 0) {
+                                                       acted_on_agent = TRUE;
+                                               } else {
+                                                       break;
+                                               }
+
+                                               // Only save the assertion if the action succeeded
+                                               if (type == NECP_CLIENT_PARAMETER_ASSERT_AGENT) {
+                                                       necp_client_add_assertion(client, agent_uuid);
+                                               }
+                                       }
+                       }
+
+                       offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length;
+               }
+       }
+       lck_mtx_unlock(&fd_data->fd_lock);
+
+       if (!acted_on_agent &&
+               error == 0) {
+               error = ENOENT;
+       }
+done:
+       *retval = error;
+       if (parameters != NULL) {
+               FREE(parameters, M_NECP);
+               parameters = NULL;
+       }
+       
+       return (error);
+}
+
+static int
+necp_client_copy_agent(__unused struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       uuid_t agent_uuid;
+
+       if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) ||
+               uap->buffer_size == 0 || uap->buffer == 0) {
+               NECPLOG0(LOG_ERR, "necp_client_copy_agent bad input");
+               error = EINVAL;
+               goto done;
+       }
+
+       error = copyin(uap->client_id, agent_uuid, sizeof(uuid_t));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_copy_agent copyin agent_uuid error (%d)", error);
+               goto done;
+       }
+
+       error = netagent_copyout(agent_uuid, uap->buffer, uap->buffer_size);
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_copy_agent netagent_copyout error (%d)", error);
+               goto done;
+       }
+done:
+       *retval = error;
+       
+       return (error);
+}
+
+static int
+necp_client_copy_interface(__unused struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       u_int32_t interface_index = 0;
+       struct necp_interface_details interface_details;
+
+       if (uap->client_id == 0 || uap->client_id_len != sizeof(u_int32_t) ||
+               uap->buffer_size < sizeof(interface_details) || uap->buffer == 0) {
+               NECPLOG0(LOG_ERR, "necp_client_copy_interface bad input");
+               error = EINVAL;
+               goto done;
+       }
+
+       error = copyin(uap->client_id, &interface_index, sizeof(u_int32_t));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_copy_interface copyin interface_index error (%d)", error);
+               goto done;
+       }
+
+       if (interface_index == 0) {
+               error = ENOENT;
+               NECPLOG(LOG_ERR, "necp_client_copy_interface bad interface_index (%d)", interface_index);
+               goto done;
+       }
+
+       memset(&interface_details, 0, sizeof(interface_details));
+
+       ifnet_head_lock_shared();
+       ifnet_t interface = NULL;
+       if (interface_index != IFSCOPE_NONE && (int)interface_index <= if_index) {
+               interface = ifindex2ifnet[interface_index];
+       }
+
+       if (interface != NULL) {
+               if (interface->if_xname != NULL) {
+                       strlcpy((char *)&interface_details.name, interface->if_xname, sizeof(interface_details.name));
+               }
+               interface_details.index = interface->if_index;
+               interface_details.generation = ifnet_get_generation(interface);
+               if (interface->if_delegated.ifp != NULL) {
+                       interface_details.delegate_index = interface->if_delegated.ifp->if_index;
+               }
+               interface_details.functional_type = if_functional_type(interface, TRUE);
+               if (IFNET_IS_EXPENSIVE(interface)) {
+                       interface_details.flags |= NECP_INTERFACE_FLAG_EXPENSIVE;
+               }
+               interface_details.mtu = interface->if_mtu;
+
+               u_int8_t ipv4_signature_len = sizeof(interface_details.ipv4_signature);
+               u_int16_t ipv4_signature_flags;
+               ifnet_get_netsignature(interface, AF_INET, &ipv4_signature_len, &ipv4_signature_flags,
+                                                          (u_int8_t *)&interface_details.ipv4_signature);
+
+               u_int8_t ipv6_signature_len = sizeof(interface_details.ipv6_signature);
+               u_int16_t ipv6_signature_flags;
+               ifnet_get_netsignature(interface, AF_INET6, &ipv6_signature_len, &ipv6_signature_flags,
+                                                          (u_int8_t *)&interface_details.ipv6_signature);
+       }
+
+       ifnet_head_done();
+
+       error = copyout(&interface_details, uap->buffer, sizeof(interface_details));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_copy_interface copyout error (%d)", error);
+               goto done;
+       }
+done:
+       *retval = error;
+
+       return (error);
+}
+
+static int
+necp_client_stats_action(struct necp_client *client, user_addr_t buffer, user_size_t buffer_size)
+{
+       int error = 0;
+       struct necp_stats_hdr *stats_hdr = NULL;
+
+       if (client->stats_area) {
+               // Close old stats if required.
+               if ((client->stats_uaddr != buffer) || (client->stats_ulen != buffer_size)) {
+                       necp_destroy_client_stats(client);
+               }
+       }
+
+       if ((buffer == 0) || (buffer_size == 0)) {
+               goto done;
+       }
+
+       if (client->stats_area) {
+               // An update
+               error = copyin(client->stats_uaddr, client->stats_area, client->stats_ulen);
+               if (error) {
+                       NECPLOG(LOG_ERR, "necp_client_stats_action copyin error on update (%d)", error);
+               } else {
+                       // Future use - check 
+                       stats_hdr = (necp_stats_hdr *)client->stats_area;
+                       if (stats_hdr->necp_stats_event != 0) {
+                               ntstat_userland_stats_event(client->stats_handler_context, (userland_stats_event_t)stats_hdr->necp_stats_event);
+                       }
+               }
+               goto done;
+       }
+
+       // A create
+       if ((buffer_size > sizeof(necp_all_stats)) || (buffer_size < sizeof(necp_stats_hdr))) {
+               error = EINVAL;
+               goto done;
+       }
+
+       if ((stats_hdr = _MALLOC(buffer_size, M_NECP, M_WAITOK | M_ZERO)) == NULL) {
+               error = ENOMEM;
+               goto done;
+       }
+
+       client->stats_handler_context = NULL;
+       client->stats_uaddr = buffer;
+       client->stats_ulen = buffer_size;
+       client->stats_area = stats_hdr;
+       error = copyin(client->stats_uaddr, client->stats_area, client->stats_ulen);
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_stats_action copyin error on create (%d)", error);
+               goto done;
+       }
+
+       switch (stats_hdr->necp_stats_type) {
+               case NECP_CLIENT_STATISTICS_TYPE_TCP: {
+                       if (stats_hdr->necp_stats_ver == NECP_CLIENT_STATISTICS_TYPE_TCP_VER_1) {
+                               client->stats_handler_context = ntstat_userland_stats_open((userland_stats_provider_context *)client,
+                                                                                                                                                       NSTAT_PROVIDER_TCP_USERLAND, 0, necp_request_tcp_netstats);
+                               if (client->stats_handler_context == NULL) {
+                                       error = EIO;
+                               }
+                       } else {
+                               error = ENOTSUP;
+                       }
+                       break;
+               }
+               case NECP_CLIENT_STATISTICS_TYPE_UDP: {
+                       if (stats_hdr->necp_stats_ver != NECP_CLIENT_STATISTICS_TYPE_UDP_VER_1) {
+                               client->stats_handler_context = ntstat_userland_stats_open((userland_stats_provider_context *)client,
+                                                                                                                                                       NSTAT_PROVIDER_UDP_USERLAND, 0, necp_request_udp_netstats);
+                               if (client->stats_handler_context == NULL) {
+                                       error = EIO;
+                               }
+                       } else {
+                               error = ENOTSUP;
+                       }
+                       break;
+               }
+               default: {
+                       error = ENOTSUP;
+                       break;
+               }
+       }
+done:
+       if ((error) && (stats_hdr != NULL)) {
+               FREE(stats_hdr, M_NECP);
+               client->stats_area = NULL;
+               client->stats_handler_context = NULL;
+               client->stats_uaddr = 0;
+               client->stats_ulen = 0;
+       }
+
+       return (error);
+}
+
+static int
+necp_client_set_statistics(__unused struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval)
+{
+       int error = 0;
+       struct necp_client *find_client = NULL;
+       struct necp_client *client = NULL;
+       uuid_t client_id;
+
+       if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t)) {
+               error = EINVAL;
+               goto done;
+       }
+
+       error = copyin(uap->client_id, client_id, sizeof(uuid_t));
+       if (error) {
+               NECPLOG(LOG_ERR, "necp_client_set_statistics copyin client_id error (%d)", error);
+               goto done;
+       }
+
+       lck_mtx_lock(&fd_data->fd_lock);
+       LIST_FOREACH(find_client, &fd_data->clients, chain) {
+               if (uuid_compare(find_client->client_id, client_id) == 0) {
+                       client = find_client;
+                       break;
+               }
+       }
+
+       if (client) {
+               error = necp_client_stats_action(client, uap->buffer, uap->buffer_size);
+       } else {
+               error = ENOENT;
+       }
+       lck_mtx_unlock(&fd_data->fd_lock);
+done:
+       *retval = error;
+       return (error);
+}
+
+int
+necp_client_action(struct proc *p, struct necp_client_action_args *uap, int *retval)
+{
+#pragma unused(p)
+       int error = 0;
+       int return_value = 0;
+       struct necp_fd_data *fd_data = NULL;
+       error = necp_find_fd_data(uap->necp_fd, &fd_data);
+       if (error != 0) {
+               NECPLOG(LOG_ERR, "necp_client_action find fd error (%d)", error);
+               return (error);
+       }
+
+       u_int32_t action = uap->action;
+       switch (action) {
+               case NECP_CLIENT_ACTION_ADD: {
+                       return_value = necp_client_add(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_REMOVE: {
+                       return_value = necp_client_remove(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_COPY_PARAMETERS:
+               case NECP_CLIENT_ACTION_COPY_RESULT: {
+                       return_value = necp_client_copy(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_COPY_LIST: {
+                       return_value = necp_client_list(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_REQUEST_NEXUS_INSTANCE: {
+                       return_value = necp_client_request_nexus(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_AGENT: {
+                       return_value = necp_client_agent_action(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_COPY_AGENT: {
+                       return_value = necp_client_copy_agent(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_COPY_INTERFACE: {
+                       return_value = necp_client_copy_interface(fd_data, uap, retval);
+                       break;
+               }
+               case NECP_CLIENT_ACTION_SET_STATISTICS: {
+                       return_value = necp_client_set_statistics(fd_data, uap, retval);
+                       break;
+               }
+               default: {
+                       NECPLOG(LOG_ERR, "necp_client_action unknown action (%u)", action);
+                       return_value = EINVAL;
+                       break;
+               }
+       }
+
+       file_drop(uap->necp_fd);
+
+       return (return_value);
+}
+
+#define NECP_MAX_MATCH_POLICY_PARAMETER_SIZE 1024
+
+int
+necp_match_policy(struct proc *p, struct necp_match_policy_args *uap, int32_t *retval)
+{
+#pragma unused(retval)
+       u_int8_t *parameters = NULL;
+       struct necp_aggregate_result returned_result;
+       int error = 0;
+
+       if (uap == NULL) {
+               error = EINVAL;
+               goto done;
+       }
+
+       if (uap->parameters == 0 || uap->parameters_size == 0 || uap->parameters_size > NECP_MAX_MATCH_POLICY_PARAMETER_SIZE || uap->returned_result == 0) {
+               error = EINVAL;
+               goto done;
+       }
+
+       MALLOC(parameters, u_int8_t *, uap->parameters_size, M_NECP, M_WAITOK | M_ZERO);
+       if (parameters == NULL) {
+               error = ENOMEM;
+               goto done;
+       }
+       // Copy parameters in
+       error = copyin(uap->parameters, parameters, uap->parameters_size);
+       if (error) {
+               goto done;
+       }
+
+       error = necp_application_find_policy_match_internal(p, parameters, uap->parameters_size, &returned_result, NULL, 0);
+       if (error) {
+               goto done;
+       }
+
+       // Copy return value back
+       error = copyout(&returned_result, uap->returned_result, sizeof(struct necp_aggregate_result));
+       if (error) {
+               goto done;
+       }
+done:
+       if (parameters != NULL) {
+               FREE(parameters, M_NECP);
+       }
+       return (error);
+}
+
+/// Socket operations
+#define NECP_MAX_SOCKET_ATTRIBUTE_STRING_LENGTH 253
+
+static bool
+necp_set_socket_attribute(u_int8_t *buffer, size_t buffer_length, u_int8_t type, char **buffer_p)
+{
+       int error = 0;
+       int cursor = 0;
+       size_t string_size = 0;
+       char *local_string = NULL;
+       u_int8_t *value = NULL;
+
+       cursor = necp_buffer_find_tlv(buffer, buffer_length, 0, type, 0);
+       if (cursor < 0) {
+               // This will clear out the parameter
+               goto done;
+       }
+
+       string_size = necp_buffer_get_tlv_length(buffer, cursor);
+       if (string_size == 0 || string_size > NECP_MAX_SOCKET_ATTRIBUTE_STRING_LENGTH) {
+               // This will clear out the parameter
+               goto done;
+       }
+
+       MALLOC(local_string, char *, string_size + 1, M_NECP, M_WAITOK | M_ZERO);
+       if (local_string == NULL) {
+               NECPLOG(LOG_ERR, "Failed to allocate a socket attribute buffer (size %d)", string_size);
+               goto fail;
+       }
+
+       value = necp_buffer_get_tlv_value(buffer, cursor, NULL);
+       if (value == NULL) {
+               NECPLOG0(LOG_ERR, "Failed to get socket attribute");
+               goto fail;
+       }
+
+       memcpy(local_string, value, string_size);
+       local_string[string_size] = 0;
+
+done:
+       if (*buffer_p != NULL) {
+               FREE(*buffer_p, M_NECP);
+               *buffer_p = NULL;
+       }
+
+       *buffer_p = local_string;
+       return (0);
+fail:
+       if (local_string != NULL) {
+               FREE(local_string, M_NECP);
+       }
+       return (error);
+}
+
+errno_t
+necp_set_socket_attributes(struct socket *so, struct sockopt *sopt)
+{
+       int error = 0;
+       u_int8_t *buffer = NULL;
+       struct inpcb *inp = NULL;
+
+       if ((SOCK_DOM(so) != PF_INET
+#if INET6
+                && SOCK_DOM(so) != PF_INET6
+#endif
+                )) {
+               error = EINVAL;
+               goto done;
+       }
+
+       inp = sotoinpcb(so);
+
+       size_t valsize = sopt->sopt_valsize;
+       if (valsize == 0 ||
+               valsize > ((sizeof(u_int8_t) + sizeof(u_int32_t) + NECP_MAX_SOCKET_ATTRIBUTE_STRING_LENGTH) * 2)) {
+               goto done;
+       }
+
+       MALLOC(buffer, u_int8_t *, valsize, M_NECP, M_WAITOK | M_ZERO);
+       if (buffer == NULL) {
+               goto done;
+       }
+
+       error = sooptcopyin(sopt, buffer, valsize, 0);
+       if (error) {
+               goto done;
+       }
+
+       error = necp_set_socket_attribute(buffer, valsize, NECP_TLV_ATTRIBUTE_DOMAIN, &inp->inp_necp_attributes.inp_domain);
+       if (error) {
+               NECPLOG0(LOG_ERR, "Could not set domain TLV for socket attributes");
+               goto done;
+       }
+
+       error = necp_set_socket_attribute(buffer, valsize, NECP_TLV_ATTRIBUTE_ACCOUNT, &inp->inp_necp_attributes.inp_account);
+       if (error) {
+               NECPLOG0(LOG_ERR, "Could not set account TLV for socket attributes");
+               goto done;
+       }
+
+       if (necp_debug) {
+               NECPLOG(LOG_DEBUG, "Set on socket: Domain %s, Account %s", inp->inp_necp_attributes.inp_domain, inp->inp_necp_attributes.inp_account);
+       }
+done:
+       if (buffer != NULL) {
+               FREE(buffer, M_NECP);
+       }
+
+       return (error);
+}
+
+errno_t
+necp_get_socket_attributes(struct socket *so, struct sockopt *sopt)
+{
+       int error = 0;
+       u_int8_t *buffer = NULL;
+       u_int8_t *cursor = NULL;
+       size_t valsize = 0;
+       struct inpcb *inp = sotoinpcb(so);
+
+       if (inp->inp_necp_attributes.inp_domain != NULL) {
+               valsize += sizeof(u_int8_t) + sizeof(u_int32_t) + strlen(inp->inp_necp_attributes.inp_domain);
+       }
+       if (inp->inp_necp_attributes.inp_account != NULL) {
+               valsize += sizeof(u_int8_t) + sizeof(u_int32_t) + strlen(inp->inp_necp_attributes.inp_account);
+       }
+       if (valsize == 0) {
+               goto done;
+       }
+
+       MALLOC(buffer, u_int8_t *, valsize, M_NECP, M_WAITOK | M_ZERO);
+       if (buffer == NULL) {
+               goto done;
+       }
+
+       cursor = buffer;
+       if (inp->inp_necp_attributes.inp_domain != NULL) {
+               cursor = necp_buffer_write_tlv(cursor, NECP_TLV_ATTRIBUTE_DOMAIN, strlen(inp->inp_necp_attributes.inp_domain), inp->inp_necp_attributes.inp_domain);
+       }
+
+       if (inp->inp_necp_attributes.inp_account != NULL) {
+               cursor = necp_buffer_write_tlv(cursor, NECP_TLV_ATTRIBUTE_ACCOUNT, strlen(inp->inp_necp_attributes.inp_account), inp->inp_necp_attributes.inp_account);
+       }
+
+       error = sooptcopyout(sopt, buffer, valsize);
+       if (error) {
+               goto done;
+       }
+done:
+       if (buffer != NULL) {
+               FREE(buffer, M_NECP);
+       }
+       
+       return (error);
+}
+
+void
+necp_inpcb_dispose(struct inpcb *inp)
+{
+       if (inp->inp_necp_attributes.inp_domain != NULL) {
+               FREE(inp->inp_necp_attributes.inp_domain, M_NECP);
+               inp->inp_necp_attributes.inp_domain = NULL;
+       }
+       if (inp->inp_necp_attributes.inp_account != NULL) {
+               FREE(inp->inp_necp_attributes.inp_account, M_NECP);
+               inp->inp_necp_attributes.inp_account = NULL;
+       }
+}
+
+/// Module init
+
+errno_t
+necp_client_init(void)
+{
+       errno_t result = 0;
+
+       necp_fd_grp_attr = lck_grp_attr_alloc_init();
+       if (necp_fd_grp_attr == NULL) {
+               NECPLOG0(LOG_ERR, "lck_grp_attr_alloc_init failed");
+               result = ENOMEM;
+               goto done;
+       }
+
+       necp_fd_mtx_grp = lck_grp_alloc_init("necp_fd", necp_fd_grp_attr);
+       if (necp_fd_mtx_grp == NULL) {
+               NECPLOG0(LOG_ERR, "lck_grp_alloc_init failed");
+               result = ENOMEM;
+               goto done;
+       }
+
+       necp_fd_mtx_attr = lck_attr_alloc_init();
+       if (necp_fd_mtx_attr == NULL) {
+               NECPLOG0(LOG_ERR, "lck_attr_alloc_init failed");
+               result = ENOMEM;
+               goto done;
+       }
+
+       necp_client_tcall = thread_call_allocate(necp_update_all_clients_callout, NULL);
+       if (necp_client_tcall == NULL) {
+               NECPLOG0(LOG_ERR, "thread_call_allocate failed");
+               result = ENOMEM;
+               goto done;
+       }
+
+       lck_rw_init(&necp_fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr);
+
+       LIST_INIT(&necp_fd_list);
+
+done:
+       if (result != 0) {
+               if (necp_fd_mtx_attr != NULL) {
+                       lck_attr_free(necp_fd_mtx_attr);
+                       necp_fd_mtx_attr = NULL;
+               }
+               if (necp_fd_mtx_grp != NULL) {
+                       lck_grp_free(necp_fd_mtx_grp);
+                       necp_fd_mtx_grp = NULL;
+               }
+               if (necp_fd_grp_attr != NULL) {
+                       lck_grp_attr_free(necp_fd_grp_attr);
+                       necp_fd_grp_attr = NULL;
+               }
+       }
+       return (result);
+}