X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/7e41aa883dd258f888d0470250eead40a53ef1f5..3903760236c30e3b5ace7a4eefac3a269d68957c:/bsd/net/necp_client.c diff --git a/bsd/net/necp_client.c b/bsd/net/necp_client.c new file mode 100644 index 000000000..999fab3ee --- /dev/null +++ b/bsd/net/necp_client.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * NECP Client Architecture + * ------------------------------------------------ + * See 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); +}