#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>
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);
static int necp_timeout_microseconds = 1000 * 100; static int necp_timeout_leeway_microseconds = 1000 * 500; extern int tvtohz(struct timeval *);
#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;
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);
KNOTE(&si->si_note, 1);
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;
}
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)) {
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);
}
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)
{
LIST_REMOVE(client, chain);
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);
}
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);
}
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)) {
return (applies);
}
if (flags & NETAGENT_FLAG_SPECIFIC_USE_ONLY) {
bool required = FALSE;
if (parameters != NULL) {
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) {
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 > (parameters_size - (offset + sizeof(u_int8_t) + sizeof(u_int32_t)))) {
NECPLOG(LOG_ERR, "Invalid TLV length (%u)", length);
break;
}
if (length > 0) {
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; 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; 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_client = TRUE;
if (uuid_compare(client->nexus_agent, netagent_uuid) == 0) {
if (client->assigned_results != NULL) {
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);
}
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);
}
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;
}
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 {
memset(&result, 0, sizeof(result));
}
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 && result.routed_interface_index <= (u_int32_t)if_index) {
direct_interface = ifindex2ifnet[result.routed_interface_index];
} else if (parsed_parameters.required_interface_index != IFSCOPE_NONE &&
parsed_parameters.required_interface_index <= (u_int32_t)if_index) {
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 &&
parsed_parameters.required_interface_index <= (u_int32_t)if_index) {
original_scoped_interface = ifindex2ifnet[parsed_parameters.required_interface_index];
}
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);
}
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) {
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) {
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;
break;
}
}
if (updated_result) {
necp_fd_notify(client_fd, true);
}
lck_mtx_unlock(&client_fd->fd_lock);
if (updated_result) {
break;
}
}
lck_rw_done(&necp_fd_lock);
}
#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;
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);
}
}
}
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);
ifnet_head_lock_shared();
if (!(parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_SCOPED_IFNET_FIELDS)) {
ifp = TAILQ_FIRST(&ifnet_ordered_head);
if (ifp && necp_ifnet_matches_parameters(ifp, parsed_parameters, NULL)) {
ifnet_head_done();
return (TRUE);
}
}
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) {
*return_ifindex = ifp->if_index;
best_preferred_count = preferred_count;
if (!has_preferred_fields) {
break;
}
}
}
}
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) {
continue;
}
if (necp_ifnet_matches_parameters(ifp, parsed_parameters, &preferred_count)) {
if (preferred_count > best_preferred_count ||
*return_ifindex == 0) {
*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) {
*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 > (parameters_size - (offset + sizeof(u_int8_t) + sizeof(u_int32_t)))) {
NECPLOG(LOG_ERR, "Invalid TLV length (%u)", length);
break;
}
if (length > 0) {
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; 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));
}
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;
necp_fillout_current_process_details(&desc->pid, &desc->upid, desc->uuid, desc->pname, sizeof(desc->pname));
necp_find_netstat_data(client, (union necp_sockaddr_union *)&desc->local, (union necp_sockaddr_union *)&desc->remote, &desc->ifindex, desc->euuid, &desc->traffic_class);
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;
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;
}
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;
necp_fillout_current_process_details(&desc->pid, &desc->upid, desc->uuid, desc->pname, sizeof(desc->pname));
necp_find_netstat_data(client, (union necp_sockaddr_union *)&desc->local, (union necp_sockaddr_union *)&desc->remote, &desc->ifindex, desc->euuid, &desc->traffic_class);
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);
}
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);
*retval = fd;
lck_rw_lock_exclusive(&necp_fd_lock);
LIST_INSERT_HEAD(&necp_fd_list, fd_data, chain);
lck_rw_done(&necp_fd_lock);
proc_fdunlock(p);
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);
(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;
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);
}
lck_mtx_unlock(&fd_data->fd_lock);
if (client == NULL) {
if (fd_data->flags & NECP_OPEN_FLAG_OBSERVER) {
lck_rw_lock_shared(&necp_fd_lock);
struct necp_fd_data *client_fd = NULL;
LIST_FOREACH(client_fd, &necp_fd_list, chain) {
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) {
error = necp_client_copy_internal(client, TRUE, uap, retval);
}
lck_mtx_unlock(&client_fd->fd_lock);
if (client != NULL) {
break;
}
}
lck_rw_done(&necp_fd_lock);
if (client == NULL) {
error = ENOENT;
goto done;
}
} else {
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;
}
}
lck_rw_lock_shared(&necp_fd_lock);
struct necp_fd_data *client_fd = NULL;
LIST_FOREACH(client_fd, &necp_fd_list, chain) {
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);
}
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) {
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 > (parameters_size - (offset + sizeof(u_int8_t) + sizeof(u_int32_t)))) {
NECPLOG(LOG_ERR, "Invalid TLV length (%u)", length);
break;
}
if (length > 0) {
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;
}
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;
}
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_agent_use(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;
struct necp_agent_use_parameters parameters;
if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) ||
uap->buffer_size != sizeof(parameters) || uap->buffer == 0) {
error = EINVAL;
goto done;
}
error = copyin(uap->client_id, client_id, sizeof(uuid_t));
if (error) {
NECPLOG(LOG_ERR, "Copyin client_id error (%d)", error);
goto done;
}
error = copyin(uap->buffer, ¶meters, uap->buffer_size);
if (error) {
NECPLOG(LOG_ERR, "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) {
error = netagent_use(parameters.agent_uuid, ¶meters.out_use_count);
} else {
error = ENOENT;
}
lck_mtx_unlock(&fd_data->fd_lock);
if (error == 0) {
error = copyout(¶meters, uap->buffer, uap->buffer_size);
if (error) {
NECPLOG(LOG_ERR, "Parameters 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 && interface_index <= (u_int32_t)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) {
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) {
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 {
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;
}
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_AGENT_USE: {
return_value = necp_client_agent_use(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;
}
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;
}
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);
}
#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) {
goto done;
}
string_size = necp_buffer_get_tlv_length(buffer, cursor);
if (string_size == 0 || string_size > NECP_MAX_SOCKET_ATTRIBUTE_STRING_LENGTH) {
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;
}
}
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);
}