]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2015-2016 Apple Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | ||
29 | #include <string.h> | |
30 | #include <sys/systm.h> | |
31 | #include <sys/types.h> | |
32 | #include <sys/queue.h> | |
33 | #include <sys/malloc.h> | |
34 | #include <libkern/OSMalloc.h> | |
35 | #include <sys/kernel.h> | |
36 | #include <net/if.h> | |
37 | #include <sys/domain.h> | |
38 | #include <sys/protosw.h> | |
39 | #include <sys/socket.h> | |
40 | #include <sys/socketvar.h> | |
41 | #include <netinet/ip.h> | |
42 | #include <netinet/ip6.h> | |
43 | #include <netinet/in_pcb.h> | |
44 | #include <net/if_var.h> | |
45 | #include <netinet/tcp_cc.h> | |
46 | #include <net/ntstat.h> | |
47 | #include <sys/kauth.h> | |
48 | #include <sys/sysproto.h> | |
49 | #include <sys/priv.h> | |
50 | #include <net/network_agent.h> | |
51 | #include <net/necp.h> | |
52 | #include <sys/file_internal.h> | |
53 | #include <sys/poll.h> | |
54 | #include <kern/thread_call.h> | |
55 | ||
56 | /* | |
57 | * NECP Client Architecture | |
58 | * ------------------------------------------------ | |
59 | * See <net/necp.c> for a discussion on NECP database architecture. | |
60 | * | |
61 | * Each client of NECP provides a set of parameters for a connection or network state | |
62 | * evaluation, on which NECP policy evaluation is run. This produces a policy result | |
63 | * which can be accessed by the originating process, along with events for when policies | |
64 | * results have changed. | |
65 | * | |
66 | * ------------------------------------------------ | |
67 | * NECP Client FD | |
68 | * ------------------------------------------------ | |
69 | * A process opens an NECP file descriptor using necp_open(). This is a very simple | |
70 | * file descriptor, upon which the process may do the following operations: | |
71 | * - necp_client_action(...), to add/remove/query clients | |
72 | * - kqueue, to watch for readable events | |
73 | * - close(), to close the client session and release all clients | |
74 | * | |
75 | * Client objects are allocated structures that hang off of the file descriptor. Each | |
76 | * client contains: | |
77 | * - Client ID, a UUID that references the client across the system | |
78 | * - Parameters, a buffer of TLVs that describe the client's connection parameters, | |
79 | * such as the remote and local endpoints, interface requirements, etc. | |
80 | * - Result, a buffer of TLVs containing the current policy evaluation for the client. | |
81 | * This result will be updated whenever a network change occurs that impacts the | |
82 | * policy result for that client. | |
83 | * | |
84 | * +--------------+ | |
85 | * | NECP fd | | |
86 | * +--------------+ | |
87 | * || | |
88 | * ================================== | |
89 | * || || || | |
90 | * +--------------+ +--------------+ +--------------+ | |
91 | * | Client ID | | Client ID | | Client ID | | |
92 | * | ---- | | ---- | | ---- | | |
93 | * | Parameters | | Parameters | | Parameters | | |
94 | * | ---- | | ---- | | ---- | | |
95 | * | Result | | Result | | Result | | |
96 | * +--------------+ +--------------+ +--------------+ | |
97 | * | |
98 | * ------------------------------------------------ | |
99 | * Client Actions | |
100 | * ------------------------------------------------ | |
101 | * - Add. Input parameters as a buffer of TLVs, and output a client ID. Allocates a | |
102 | * new client structure on the file descriptor. | |
103 | * - Remove. Input a client ID. Removes a client structure from the file descriptor. | |
104 | * - Copy Parameters. Input a client ID, and output parameter TLVs. | |
105 | * - Copy Result. Input a client ID, and output result TLVs. Alternatively, input empty | |
106 | * client ID and get next unread client result. | |
107 | * - Copy List. List all client IDs. | |
108 | * | |
109 | * ------------------------------------------------ | |
110 | * Client Policy Evaluation | |
111 | * ------------------------------------------------ | |
112 | * Policies are evaluated for clients upon client creation, and upon update events, | |
113 | * which are network/agent/policy changes coalesced by a timer. | |
114 | * | |
115 | * The policy evaluation goes through the following steps: | |
116 | * 1. Parse client parameters. | |
117 | * 2. Select a scoped interface if applicable. This involves using require/prohibit | |
118 | * parameters, along with the local address, to select the most appropriate interface | |
119 | * if not explicitly set by the client parameters. | |
120 | * 3. Run NECP application-level policy evalution | |
121 | * 4. Set policy result into client result buffer. | |
122 | * | |
123 | * ------------------------------------------------ | |
124 | * Client Observers | |
125 | * ------------------------------------------------ | |
126 | * If necp_open() is called with the NECP_OPEN_FLAG_OBSERVER flag, and the process | |
127 | * passes the necessary privilege check, the fd is allowed to use necp_client_action() | |
128 | * to copy client state attached to the file descriptors of other processes, and to | |
129 | * list all client IDs on the system. | |
130 | */ | |
131 | ||
132 | extern u_int32_t necp_debug; | |
133 | ||
134 | static int noop_read(struct fileproc *, struct uio *, int, vfs_context_t); | |
135 | static int noop_write(struct fileproc *, struct uio *, int, vfs_context_t); | |
136 | static int noop_ioctl(struct fileproc *, unsigned long, caddr_t, | |
137 | vfs_context_t); | |
138 | static int necpop_select(struct fileproc *, int, void *, vfs_context_t); | |
139 | static int necpop_close(struct fileglob *, vfs_context_t); | |
140 | static int necpop_kqfilter(struct fileproc *, struct knote *, vfs_context_t); | |
141 | ||
142 | // Timer functions | |
143 | static int necp_timeout_microseconds = 1000 * 100; // 100ms | |
144 | static int necp_timeout_leeway_microseconds = 1000 * 500; // 500ms | |
145 | extern int tvtohz(struct timeval *); | |
146 | ||
147 | // Parsed parameters | |
148 | #define NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR 0x0001 | |
149 | #define NECP_PARSED_PARAMETERS_FIELD_REMOTE_ADDR 0x0002 | |
150 | #define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IF 0x0004 | |
151 | #define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF 0x0008 | |
152 | #define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE 0x0010 | |
153 | #define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE 0x0020 | |
154 | #define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT 0x0040 | |
155 | #define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT 0x0080 | |
156 | #define NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT 0x0100 | |
157 | #define NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE 0x0200 | |
158 | #define NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE 0x0400 | |
159 | #define NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE 0x0800 | |
160 | ||
161 | #define NECP_MAX_PARSED_PARAMETERS 16 | |
162 | struct necp_client_parsed_parameters { | |
163 | u_int32_t valid_fields; | |
164 | union necp_sockaddr_union local_addr; | |
165 | union necp_sockaddr_union remote_addr; | |
166 | u_int32_t required_interface_index; | |
167 | char prohibited_interfaces[IFXNAMSIZ][NECP_MAX_PARSED_PARAMETERS]; | |
168 | u_int8_t required_interface_types[NECP_MAX_PARSED_PARAMETERS]; | |
169 | u_int8_t prohibited_interface_types[NECP_MAX_PARSED_PARAMETERS]; | |
170 | struct necp_client_parameter_netagent_type required_netagent_types[NECP_MAX_PARSED_PARAMETERS]; | |
171 | struct necp_client_parameter_netagent_type prohibited_netagent_types[NECP_MAX_PARSED_PARAMETERS]; | |
172 | struct necp_client_parameter_netagent_type preferred_netagent_types[NECP_MAX_PARSED_PARAMETERS]; | |
173 | uuid_t required_netagents[NECP_MAX_PARSED_PARAMETERS]; | |
174 | uuid_t prohibited_netagents[NECP_MAX_PARSED_PARAMETERS]; | |
175 | uuid_t preferred_netagents[NECP_MAX_PARSED_PARAMETERS]; | |
176 | }; | |
177 | ||
178 | static bool necp_find_matching_interface_index(struct necp_client_parsed_parameters *parsed_parameters, u_int *return_ifindex); | |
179 | ||
180 | static const struct fileops necp_fd_ops = { | |
181 | .fo_type = DTYPE_NETPOLICY, | |
182 | .fo_read = noop_read, | |
183 | .fo_write = noop_write, | |
184 | .fo_ioctl = noop_ioctl, | |
185 | .fo_select = necpop_select, | |
186 | .fo_close = necpop_close, | |
187 | .fo_kqfilter = necpop_kqfilter, | |
188 | .fo_drain = NULL, | |
189 | }; | |
190 | ||
191 | struct necp_client_assertion { | |
192 | LIST_ENTRY(necp_client_assertion) assertion_chain; | |
193 | uuid_t asserted_netagent; | |
194 | }; | |
195 | ||
196 | struct necp_client { | |
197 | LIST_ENTRY(necp_client) chain; | |
198 | ||
199 | uuid_t client_id; | |
200 | bool result_read; | |
201 | bool assigned_result_read; | |
202 | ||
203 | size_t result_length; | |
204 | u_int8_t result[NECP_MAX_CLIENT_RESULT_SIZE]; | |
205 | ||
206 | uuid_t nexus_agent; | |
207 | size_t assigned_results_length; | |
208 | u_int8_t *assigned_results; | |
209 | ||
210 | LIST_HEAD(_necp_client_assertion_list, necp_client_assertion) assertion_list; | |
211 | ||
212 | user_addr_t stats_uaddr; | |
213 | user_size_t stats_ulen; | |
214 | nstat_userland_context stats_handler_context; | |
215 | necp_stats_hdr *stats_area; | |
216 | ||
217 | size_t parameters_length; | |
218 | u_int8_t parameters[0]; | |
219 | }; | |
220 | ||
221 | struct necp_fd_data { | |
222 | LIST_ENTRY(necp_fd_data) chain; | |
223 | LIST_HEAD(_clients, necp_client) clients; | |
224 | int flags; | |
225 | int proc_pid; | |
226 | decl_lck_mtx_data(, fd_lock); | |
227 | struct selinfo si; | |
228 | }; | |
229 | ||
230 | static LIST_HEAD(_necp_fd_list, necp_fd_data) necp_fd_list; | |
231 | ||
232 | static lck_grp_attr_t *necp_fd_grp_attr = NULL; | |
233 | static lck_attr_t *necp_fd_mtx_attr = NULL; | |
234 | static lck_grp_t *necp_fd_mtx_grp = NULL; | |
235 | decl_lck_rw_data(static, necp_fd_lock); | |
236 | ||
237 | static thread_call_t necp_client_tcall; | |
238 | ||
239 | /// NECP file descriptor functions | |
240 | ||
241 | static int | |
242 | noop_read(struct fileproc *fp, struct uio *uio, int flags, vfs_context_t ctx) | |
243 | { | |
244 | #pragma unused(fp, uio, flags, ctx) | |
245 | return (ENXIO); | |
246 | } | |
247 | ||
248 | static int | |
249 | noop_write(struct fileproc *fp, struct uio *uio, int flags, | |
250 | vfs_context_t ctx) | |
251 | { | |
252 | #pragma unused(fp, uio, flags, ctx) | |
253 | return (ENXIO); | |
254 | } | |
255 | ||
256 | static int | |
257 | noop_ioctl(struct fileproc *fp, unsigned long com, caddr_t data, | |
258 | vfs_context_t ctx) | |
259 | { | |
260 | #pragma unused(fp, com, data, ctx) | |
261 | return (ENOTTY); | |
262 | } | |
263 | ||
264 | static void | |
265 | necp_fd_notify(struct necp_fd_data *fd_data, bool locked) | |
266 | { | |
267 | struct selinfo *si = &fd_data->si; | |
268 | ||
269 | if (!locked) { | |
270 | lck_mtx_lock(&fd_data->fd_lock); | |
271 | } | |
272 | ||
273 | selwakeup(si); | |
274 | ||
275 | // use a non-zero hint to tell the notification from the | |
276 | // call done in kqueue_scan() which uses 0 | |
277 | KNOTE(&si->si_note, 1); // notification | |
278 | ||
279 | if (!locked) { | |
280 | lck_mtx_unlock(&fd_data->fd_lock); | |
281 | } | |
282 | } | |
283 | ||
284 | static int | |
285 | necp_fd_poll(struct necp_fd_data *fd_data, int events, void *wql, struct proc *p, int is_kevent) | |
286 | { | |
287 | #pragma unused(wql, p, is_kevent) | |
288 | u_int revents = 0; | |
289 | struct necp_client *client = NULL; | |
290 | bool has_unread_clients = FALSE; | |
291 | ||
292 | u_int want_rx = events & (POLLIN | POLLRDNORM); | |
293 | if (want_rx) { | |
294 | ||
295 | LIST_FOREACH(client, &fd_data->clients, chain) { | |
296 | if (!client->result_read || !client->assigned_result_read) { | |
297 | has_unread_clients = TRUE; | |
298 | break; | |
299 | } | |
300 | } | |
301 | ||
302 | if (has_unread_clients) { | |
303 | revents |= want_rx; | |
304 | } | |
305 | } | |
306 | ||
307 | return (revents); | |
308 | } | |
309 | ||
310 | static int | |
311 | necpop_select(struct fileproc *fp, int which, void *wql, vfs_context_t ctx) | |
312 | { | |
313 | #pragma unused(fp, which, wql, ctx) | |
314 | return (0); | |
315 | struct necp_fd_data *fd_data = NULL; | |
316 | int revents = 0; | |
317 | int events = 0; | |
318 | proc_t procp; | |
319 | ||
320 | fd_data = (struct necp_fd_data *)fp->f_fglob->fg_data; | |
321 | if (fd_data == NULL) { | |
322 | return (0); | |
323 | } | |
324 | ||
325 | procp = vfs_context_proc(ctx); | |
326 | ||
327 | switch (which) { | |
328 | case FREAD: { | |
329 | events = POLLIN; | |
330 | break; | |
331 | } | |
332 | ||
333 | default: { | |
334 | return (1); | |
335 | } | |
336 | } | |
337 | ||
338 | lck_mtx_lock(&fd_data->fd_lock); | |
339 | revents = necp_fd_poll(fd_data, events, wql, procp, 0); | |
340 | lck_mtx_unlock(&fd_data->fd_lock); | |
341 | ||
342 | return ((events & revents) ? 1 : 0); | |
343 | } | |
344 | ||
345 | static void | |
346 | necp_fd_knrdetach(struct knote *kn) | |
347 | { | |
348 | struct necp_fd_data *fd_data = (struct necp_fd_data *)kn->kn_hook; | |
349 | struct selinfo *si = &fd_data->si; | |
350 | ||
351 | lck_mtx_lock(&fd_data->fd_lock); | |
352 | KNOTE_DETACH(&si->si_note, kn); | |
353 | lck_mtx_unlock(&fd_data->fd_lock); | |
354 | } | |
355 | ||
356 | static int | |
357 | necp_fd_knread(struct knote *kn, long hint) | |
358 | { | |
359 | #pragma unused(kn, hint) | |
360 | return 1; /* assume we are ready */ | |
361 | } | |
362 | ||
363 | static int | |
364 | necp_fd_knrprocess(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev) | |
365 | { | |
366 | #pragma unused(data) | |
367 | struct necp_fd_data *fd_data; | |
368 | int revents; | |
369 | int res; | |
370 | ||
371 | fd_data = (struct necp_fd_data *)kn->kn_hook; | |
372 | ||
373 | lck_mtx_lock(&fd_data->fd_lock); | |
374 | revents = necp_fd_poll(fd_data, POLLIN, NULL, current_proc(), 1); | |
375 | res = ((revents & POLLIN) != 0); | |
376 | if (res) { | |
377 | *kev = kn->kn_kevent; | |
378 | } | |
379 | lck_mtx_unlock(&fd_data->fd_lock); | |
380 | return (res); | |
381 | } | |
382 | ||
383 | static int | |
384 | necp_fd_knrtouch(struct knote *kn, struct kevent_internal_s *kev) | |
385 | { | |
386 | #pragma unused(kev) | |
387 | struct necp_fd_data *fd_data; | |
388 | int revents; | |
389 | ||
390 | fd_data = (struct necp_fd_data *)kn->kn_hook; | |
391 | ||
392 | lck_mtx_lock(&fd_data->fd_lock); | |
393 | if ((kn->kn_status & KN_UDATA_SPECIFIC) == 0) | |
394 | kn->kn_udata = kev->udata; | |
395 | revents = necp_fd_poll(fd_data, POLLIN, NULL, current_proc(), 1); | |
396 | lck_mtx_unlock(&fd_data->fd_lock); | |
397 | ||
398 | return ((revents & POLLIN) != 0); | |
399 | } | |
400 | ||
401 | struct filterops necp_fd_rfiltops = { | |
402 | .f_isfd = 1, | |
403 | .f_detach = necp_fd_knrdetach, | |
404 | .f_event = necp_fd_knread, | |
405 | .f_touch = necp_fd_knrtouch, | |
406 | .f_process = necp_fd_knrprocess, | |
407 | }; | |
408 | ||
409 | static int | |
410 | necpop_kqfilter(struct fileproc *fp, struct knote *kn, vfs_context_t ctx) | |
411 | { | |
412 | #pragma unused(fp, ctx) | |
413 | struct necp_fd_data *fd_data = NULL; | |
414 | int revents; | |
415 | ||
416 | if (kn->kn_filter != EVFILT_READ) { | |
417 | NECPLOG(LOG_ERR, "bad filter request %d", kn->kn_filter); | |
418 | kn->kn_flags = EV_ERROR; | |
419 | kn->kn_data = EINVAL; | |
420 | return (0); | |
421 | } | |
422 | ||
423 | fd_data = (struct necp_fd_data *)kn->kn_fp->f_fglob->fg_data; | |
424 | if (fd_data == NULL) { | |
425 | NECPLOG0(LOG_ERR, "No channel for kqfilter"); | |
426 | kn->kn_flags = EV_ERROR; | |
427 | kn->kn_data = ENOENT; | |
428 | return (0); | |
429 | } | |
430 | ||
431 | lck_mtx_lock(&fd_data->fd_lock); | |
432 | kn->kn_filtid = EVFILTID_NECP_FD; | |
433 | kn->kn_hook = fd_data; | |
434 | KNOTE_ATTACH(&fd_data->si.si_note, kn); | |
435 | ||
436 | revents = necp_fd_poll(fd_data, POLLIN, NULL, current_proc(), 1); | |
437 | ||
438 | lck_mtx_unlock(&fd_data->fd_lock); | |
439 | ||
440 | return ((revents & POLLIN) != 0); | |
441 | } | |
442 | ||
443 | static void | |
444 | necp_destroy_client_stats(struct necp_client *client) | |
445 | { | |
446 | if ((client->stats_area != NULL) && | |
447 | (client->stats_handler_context != NULL) && | |
448 | (client->stats_uaddr != 0)) { | |
449 | // Close old stats if required. | |
450 | int error = copyin(client->stats_uaddr, client->stats_area, client->stats_ulen); | |
451 | if (error) { | |
452 | NECPLOG(LOG_ERR, "necp_destroy_client_stats copyin error on close (%d)", error); | |
453 | // Not much we can for an error on an obsolete address | |
454 | } | |
455 | ntstat_userland_stats_close(client->stats_handler_context); | |
456 | FREE(client->stats_area, M_NECP); | |
457 | client->stats_area = NULL; | |
458 | client->stats_handler_context = NULL; | |
459 | client->stats_uaddr = 0; | |
460 | client->stats_ulen = 0; | |
461 | } | |
462 | } | |
463 | ||
464 | static void | |
465 | necp_destroy_client(struct necp_client *client) | |
466 | { | |
467 | // Remove from list | |
468 | LIST_REMOVE(client, chain); | |
469 | ||
470 | // Remove nexus assignment | |
471 | if (client->assigned_results != NULL) { | |
472 | if (!uuid_is_null(client->nexus_agent)) { | |
473 | int netagent_error = netagent_client_message(client->nexus_agent, client->client_id, | |
474 | NETAGENT_MESSAGE_TYPE_CLOSE_NEXUS); | |
475 | if (netagent_error != 0) { | |
476 | NECPLOG(LOG_ERR, "necp_client_remove close nexus error (%d)", netagent_error); | |
477 | } | |
478 | } | |
479 | FREE(client->assigned_results, M_NETAGENT); | |
480 | } | |
481 | ||
482 | // Remove agent assertions | |
483 | struct necp_client_assertion *search_assertion = NULL; | |
484 | struct necp_client_assertion *temp_assertion = NULL; | |
485 | LIST_FOREACH_SAFE(search_assertion, &client->assertion_list, assertion_chain, temp_assertion) { | |
486 | int netagent_error = netagent_client_message(search_assertion->asserted_netagent, client->client_id, NETAGENT_MESSAGE_TYPE_CLIENT_UNASSERT); | |
487 | if (netagent_error != 0) { | |
488 | NECPLOG(LOG_ERR, "necp_client_remove unassert agent error (%d)", netagent_error); | |
489 | } | |
490 | LIST_REMOVE(search_assertion, assertion_chain); | |
491 | FREE(search_assertion, M_NECP); | |
492 | } | |
493 | necp_destroy_client_stats(client); | |
494 | ||
495 | FREE(client, M_NECP); | |
496 | } | |
497 | ||
498 | static int | |
499 | necpop_close(struct fileglob *fg, vfs_context_t ctx) | |
500 | { | |
501 | #pragma unused(fg, ctx) | |
502 | struct necp_fd_data *fd_data = NULL; | |
503 | int error = 0; | |
504 | ||
505 | fd_data = (struct necp_fd_data *)fg->fg_data; | |
506 | fg->fg_data = NULL; | |
507 | ||
508 | if (fd_data != NULL) { | |
509 | lck_rw_lock_exclusive(&necp_fd_lock); | |
510 | ||
511 | lck_mtx_lock(&fd_data->fd_lock); | |
512 | struct necp_client *client = NULL; | |
513 | struct necp_client *temp_client = NULL; | |
514 | LIST_FOREACH_SAFE(client, &fd_data->clients, chain, temp_client) { | |
515 | necp_destroy_client(client); | |
516 | } | |
517 | lck_mtx_unlock(&fd_data->fd_lock); | |
518 | ||
519 | selthreadclear(&fd_data->si); | |
520 | ||
521 | lck_mtx_destroy(&fd_data->fd_lock, necp_fd_mtx_grp); | |
522 | ||
523 | LIST_REMOVE(fd_data, chain); | |
524 | ||
525 | lck_rw_done(&necp_fd_lock); | |
526 | ||
527 | FREE(fd_data, M_NECP); | |
528 | fd_data = NULL; | |
529 | } | |
530 | ||
531 | return (error); | |
532 | } | |
533 | ||
534 | /// NECP client utilities | |
535 | ||
536 | static int | |
537 | necp_find_fd_data(int fd, struct necp_fd_data **fd_data) | |
538 | { | |
539 | proc_t p = current_proc(); | |
540 | struct fileproc *fp = NULL; | |
541 | int error = 0; | |
542 | ||
543 | proc_fdlock_spin(p); | |
544 | if ((error = fp_lookup(p, fd, &fp, 1)) != 0) { | |
545 | goto done; | |
546 | } | |
547 | if (fp->f_fglob->fg_ops->fo_type != DTYPE_NETPOLICY) { | |
548 | fp_drop(p, fd, fp, 1); | |
549 | error = ENODEV; | |
550 | goto done; | |
551 | } | |
552 | *fd_data = (struct necp_fd_data *)fp->f_fglob->fg_data; | |
553 | ||
554 | done: | |
555 | proc_fdunlock(p); | |
556 | return (error); | |
557 | } | |
558 | ||
559 | static bool | |
560 | necp_netagent_applies_to_client(__unused struct necp_client *client, struct necp_client_parsed_parameters *parameters, uuid_t netagent_uuid) | |
561 | { | |
562 | bool applies = FALSE; | |
563 | u_int32_t flags = netagent_get_flags(netagent_uuid); | |
564 | if (!(flags & NETAGENT_FLAG_REGISTERED)) { | |
565 | // Unregistered agents never apply | |
566 | return (applies); | |
567 | } | |
568 | ||
569 | if (flags & NETAGENT_FLAG_SPECIFIC_USE_ONLY) { | |
570 | // Specific use agents only apply when required | |
571 | bool required = FALSE; | |
572 | if (parameters != NULL) { | |
573 | // Check required agent UUIDs | |
574 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
575 | if (uuid_is_null(parameters->required_netagents[i])) { | |
576 | break; | |
577 | } | |
578 | if (uuid_compare(parameters->required_netagents[i], netagent_uuid) == 0) { | |
579 | required = TRUE; | |
580 | break; | |
581 | } | |
582 | } | |
583 | ||
584 | if (!required) { | |
585 | // Check required agent types | |
586 | bool fetched_type = FALSE; | |
587 | char netagent_domain[NETAGENT_DOMAINSIZE]; | |
588 | char netagent_type[NETAGENT_TYPESIZE]; | |
589 | memset(&netagent_domain, 0, NETAGENT_DOMAINSIZE); | |
590 | memset(&netagent_type, 0, NETAGENT_TYPESIZE); | |
591 | ||
592 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
593 | if (strlen(parameters->required_netagent_types[i].netagent_domain) == 0 || | |
594 | strlen(parameters->required_netagent_types[i].netagent_type) == 0) { | |
595 | break; | |
596 | } | |
597 | ||
598 | if (!fetched_type) { | |
599 | if (netagent_get_agent_domain_and_type(netagent_uuid, netagent_domain, netagent_type)) { | |
600 | fetched_type = TRUE; | |
601 | } else { | |
602 | break; | |
603 | } | |
604 | } | |
605 | ||
606 | if ((strlen(parameters->required_netagent_types[i].netagent_domain) == 0 || | |
607 | strncmp(netagent_domain, parameters->required_netagent_types[i].netagent_domain, NETAGENT_DOMAINSIZE) == 0) && | |
608 | (strlen(parameters->required_netagent_types[i].netagent_type) == 0 || | |
609 | strncmp(netagent_type, parameters->required_netagent_types[i].netagent_type, NETAGENT_TYPESIZE) == 0)) { | |
610 | required = TRUE; | |
611 | break; | |
612 | } | |
613 | } | |
614 | } | |
615 | } | |
616 | ||
617 | applies = required; | |
618 | } else { | |
619 | applies = TRUE; | |
620 | } | |
621 | ||
622 | if (applies && | |
623 | (flags & NETAGENT_FLAG_NEXUS_PROVIDER) && | |
624 | uuid_is_null(client->nexus_agent)) { | |
625 | uuid_copy(client->nexus_agent, netagent_uuid); | |
626 | } | |
627 | ||
628 | return (applies); | |
629 | } | |
630 | ||
631 | static int | |
632 | necp_client_parse_parameters(u_int8_t *parameters, | |
633 | u_int32_t parameters_size, | |
634 | struct necp_client_parsed_parameters *parsed_parameters) | |
635 | { | |
636 | int error = 0; | |
637 | size_t offset = 0; | |
638 | ||
639 | u_int32_t num_prohibited_interfaces = 0; | |
640 | u_int32_t num_required_interface_types = 0; | |
641 | u_int32_t num_prohibited_interface_types = 0; | |
642 | u_int32_t num_required_agents = 0; | |
643 | u_int32_t num_prohibited_agents = 0; | |
644 | u_int32_t num_preferred_agents = 0; | |
645 | u_int32_t num_required_agent_types = 0; | |
646 | u_int32_t num_prohibited_agent_types = 0; | |
647 | u_int32_t num_preferred_agent_types = 0; | |
648 | ||
649 | if (parsed_parameters == NULL) { | |
650 | return (EINVAL); | |
651 | } | |
652 | ||
653 | memset(parsed_parameters, 0, sizeof(struct necp_client_parsed_parameters)); | |
654 | ||
655 | while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= parameters_size) { | |
656 | u_int8_t type = necp_buffer_get_tlv_type(parameters, offset); | |
657 | u_int32_t length = necp_buffer_get_tlv_length(parameters, offset); | |
658 | ||
659 | if (length > (parameters_size - (offset + sizeof(u_int8_t) + sizeof(u_int32_t)))) { | |
660 | // If the length is larger than what can fit in the remaining parameters size, bail | |
661 | NECPLOG(LOG_ERR, "Invalid TLV length (%u)", length); | |
662 | break; | |
663 | } | |
664 | ||
665 | if (length > 0) { | |
666 | u_int8_t *value = necp_buffer_get_tlv_value(parameters, offset, NULL); | |
667 | if (value != NULL) { | |
668 | switch (type) { | |
669 | case NECP_CLIENT_PARAMETER_BOUND_INTERFACE: { | |
670 | if (length <= IFXNAMSIZ && length > 0) { | |
671 | ifnet_t bound_interface = NULL; | |
672 | char interface_name[IFXNAMSIZ]; | |
673 | memcpy(interface_name, value, length); | |
674 | interface_name[length - 1] = 0; // Make sure the string is NULL terminated | |
675 | if (ifnet_find_by_name(interface_name, &bound_interface) == 0) { | |
676 | parsed_parameters->required_interface_index = bound_interface->if_index; | |
677 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IF; | |
678 | ifnet_release(bound_interface); | |
679 | } | |
680 | } | |
681 | break; | |
682 | } | |
683 | case NECP_CLIENT_PARAMETER_LOCAL_ADDRESS: { | |
684 | if (length >= sizeof(struct necp_policy_condition_addr)) { | |
685 | struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value; | |
686 | if ((address_struct->address.sa.sa_family == AF_INET || | |
687 | address_struct->address.sa.sa_family == AF_INET6) && | |
688 | address_struct->address.sa.sa_len <= length) { | |
689 | memcpy(&parsed_parameters->local_addr, &address_struct->address, sizeof(address_struct->address)); | |
690 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR; | |
691 | } | |
692 | } | |
693 | break; | |
694 | } | |
695 | case NECP_CLIENT_PARAMETER_LOCAL_ENDPOINT: { | |
696 | if (length >= sizeof(struct necp_client_endpoint)) { | |
697 | struct necp_client_endpoint *endpoint = (struct necp_client_endpoint *)(void *)value; | |
698 | if ((endpoint->u.endpoint.endpoint_family == AF_INET || | |
699 | endpoint->u.endpoint.endpoint_family == AF_INET6) && | |
700 | endpoint->u.endpoint.endpoint_length <= length) { | |
701 | memcpy(&parsed_parameters->local_addr, &endpoint->u.sa, sizeof(union necp_sockaddr_union)); | |
702 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR; | |
703 | } | |
704 | } | |
705 | break; | |
706 | } | |
707 | case NECP_CLIENT_PARAMETER_REMOTE_ADDRESS: { | |
708 | if (length >= sizeof(struct necp_policy_condition_addr)) { | |
709 | struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value; | |
710 | if ((address_struct->address.sa.sa_family == AF_INET || | |
711 | address_struct->address.sa.sa_family == AF_INET6) && | |
712 | address_struct->address.sa.sa_len <= length) { | |
713 | memcpy(&parsed_parameters->remote_addr, &address_struct->address, sizeof(address_struct->address)); | |
714 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REMOTE_ADDR; | |
715 | } | |
716 | } | |
717 | break; | |
718 | } | |
719 | case NECP_CLIENT_PARAMETER_REMOTE_ENDPOINT: { | |
720 | if (length >= sizeof(struct necp_client_endpoint)) { | |
721 | struct necp_client_endpoint *endpoint = (struct necp_client_endpoint *)(void *)value; | |
722 | if ((endpoint->u.endpoint.endpoint_family == AF_INET || | |
723 | endpoint->u.endpoint.endpoint_family == AF_INET6) && | |
724 | endpoint->u.endpoint.endpoint_length <= length) { | |
725 | memcpy(&parsed_parameters->remote_addr, &endpoint->u.sa, sizeof(union necp_sockaddr_union)); | |
726 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REMOTE_ADDR; | |
727 | } | |
728 | } | |
729 | break; | |
730 | } | |
731 | case NECP_CLIENT_PARAMETER_PROHIBIT_INTERFACE: { | |
732 | if (num_prohibited_interfaces >= NECP_MAX_PARSED_PARAMETERS) { | |
733 | break; | |
734 | } | |
735 | if (length <= IFXNAMSIZ && length > 0) { | |
736 | memcpy(parsed_parameters->prohibited_interfaces[num_prohibited_interfaces], value, length); | |
737 | parsed_parameters->prohibited_interfaces[num_prohibited_interfaces][length - 1] = 0; // Make sure the string is NULL terminated | |
738 | num_prohibited_interfaces++; | |
739 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF; | |
740 | } | |
741 | break; | |
742 | } | |
743 | case NECP_CLIENT_PARAMETER_REQUIRE_IF_TYPE: { | |
744 | if (num_required_interface_types >= NECP_MAX_PARSED_PARAMETERS) { | |
745 | break; | |
746 | } | |
747 | if (length >= sizeof(u_int8_t)) { | |
748 | memcpy(&parsed_parameters->required_interface_types[num_required_interface_types], value, sizeof(u_int8_t)); | |
749 | num_required_interface_types++; | |
750 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE; | |
751 | } | |
752 | break; | |
753 | } | |
754 | case NECP_CLIENT_PARAMETER_PROHIBIT_IF_TYPE: { | |
755 | if (num_prohibited_interface_types >= NECP_MAX_PARSED_PARAMETERS) { | |
756 | break; | |
757 | } | |
758 | if (length >= sizeof(u_int8_t)) { | |
759 | memcpy(&parsed_parameters->prohibited_interface_types[num_prohibited_interface_types], value, sizeof(u_int8_t)); | |
760 | num_prohibited_interface_types++; | |
761 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE; | |
762 | } | |
763 | break; | |
764 | } | |
765 | case NECP_CLIENT_PARAMETER_REQUIRE_AGENT: { | |
766 | if (num_required_agents >= NECP_MAX_PARSED_PARAMETERS) { | |
767 | break; | |
768 | } | |
769 | if (length >= sizeof(uuid_t)) { | |
770 | memcpy(&parsed_parameters->required_netagents[num_required_agents], value, sizeof(uuid_t)); | |
771 | num_required_agents++; | |
772 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT; | |
773 | } | |
774 | break; | |
775 | } | |
776 | case NECP_CLIENT_PARAMETER_PROHIBIT_AGENT: { | |
777 | if (num_prohibited_agents >= NECP_MAX_PARSED_PARAMETERS) { | |
778 | break; | |
779 | } | |
780 | if (length >= sizeof(uuid_t)) { | |
781 | memcpy(&parsed_parameters->prohibited_netagents[num_prohibited_agents], value, sizeof(uuid_t)); | |
782 | num_prohibited_agents++; | |
783 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT; | |
784 | } | |
785 | break; | |
786 | } | |
787 | case NECP_CLIENT_PARAMETER_PREFER_AGENT: { | |
788 | if (num_preferred_agents >= NECP_MAX_PARSED_PARAMETERS) { | |
789 | break; | |
790 | } | |
791 | if (length >= sizeof(uuid_t)) { | |
792 | memcpy(&parsed_parameters->preferred_netagents[num_preferred_agents], value, sizeof(uuid_t)); | |
793 | num_preferred_agents++; | |
794 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT; | |
795 | } | |
796 | break; | |
797 | } | |
798 | case NECP_CLIENT_PARAMETER_REQUIRE_AGENT_TYPE: { | |
799 | if (num_required_agent_types >= NECP_MAX_PARSED_PARAMETERS) { | |
800 | break; | |
801 | } | |
802 | if (length >= sizeof(struct necp_client_parameter_netagent_type)) { | |
803 | memcpy(&parsed_parameters->required_netagent_types[num_required_agent_types], value, sizeof(struct necp_client_parameter_netagent_type)); | |
804 | num_required_agent_types++; | |
805 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE; | |
806 | } | |
807 | break; | |
808 | } | |
809 | case NECP_CLIENT_PARAMETER_PROHIBIT_AGENT_TYPE: { | |
810 | if (num_prohibited_agent_types >= NECP_MAX_PARSED_PARAMETERS) { | |
811 | break; | |
812 | } | |
813 | if (length >= sizeof(struct necp_client_parameter_netagent_type)) { | |
814 | memcpy(&parsed_parameters->prohibited_netagent_types[num_prohibited_agent_types], value, sizeof(struct necp_client_parameter_netagent_type)); | |
815 | num_prohibited_agent_types++; | |
816 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE; | |
817 | } | |
818 | break; | |
819 | } | |
820 | case NECP_CLIENT_PARAMETER_PREFER_AGENT_TYPE: { | |
821 | if (num_preferred_agent_types >= NECP_MAX_PARSED_PARAMETERS) { | |
822 | break; | |
823 | } | |
824 | if (length >= sizeof(struct necp_client_parameter_netagent_type)) { | |
825 | memcpy(&parsed_parameters->preferred_netagent_types[num_preferred_agent_types], value, sizeof(struct necp_client_parameter_netagent_type)); | |
826 | num_preferred_agent_types++; | |
827 | parsed_parameters->valid_fields |= NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE; | |
828 | } | |
829 | break; | |
830 | } | |
831 | default: { | |
832 | break; | |
833 | } | |
834 | } | |
835 | } | |
836 | } | |
837 | ||
838 | offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length; | |
839 | } | |
840 | ||
841 | return (error); | |
842 | } | |
843 | ||
844 | int | |
845 | necp_assign_client_result(uuid_t netagent_uuid, uuid_t client_id, | |
846 | u_int8_t *assigned_results, size_t assigned_results_length) | |
847 | { | |
848 | int error = 0; | |
849 | struct necp_fd_data *client_fd = NULL; | |
850 | bool found_client = FALSE; | |
851 | bool client_updated = FALSE; | |
852 | ||
853 | lck_rw_lock_shared(&necp_fd_lock); | |
854 | ||
855 | LIST_FOREACH(client_fd, &necp_fd_list, chain) { | |
856 | struct necp_client *client = NULL; | |
857 | lck_mtx_lock(&client_fd->fd_lock); | |
858 | LIST_FOREACH(client, &client_fd->clients, chain) { | |
859 | if (uuid_compare(client->client_id, client_id) == 0) { | |
860 | // Found the right client! | |
861 | found_client = TRUE; | |
862 | ||
863 | if (uuid_compare(client->nexus_agent, netagent_uuid) == 0) { | |
864 | // Verify that the client nexus agent matches | |
865 | if (client->assigned_results != NULL) { | |
866 | // Release prior result | |
867 | FREE(client->assigned_results, M_NETAGENT); | |
868 | } | |
869 | client->assigned_results = assigned_results; | |
870 | client->assigned_results_length = assigned_results_length; | |
871 | client->assigned_result_read = FALSE; | |
872 | client_updated = TRUE; | |
873 | } | |
874 | } | |
875 | } | |
876 | if (client_updated) { | |
877 | necp_fd_notify(client_fd, true); | |
878 | } | |
879 | lck_mtx_unlock(&client_fd->fd_lock); | |
880 | ||
881 | if (found_client) { | |
882 | break; | |
883 | } | |
884 | } | |
885 | ||
886 | lck_rw_done(&necp_fd_lock); | |
887 | ||
888 | if (!found_client) { | |
889 | error = ENOENT; | |
890 | } else if (!client_updated) { | |
891 | error = EINVAL; | |
892 | } | |
893 | ||
894 | return (error); | |
895 | } | |
896 | ||
897 | /// Client updating | |
898 | ||
899 | static bool | |
900 | necp_update_client_result(proc_t proc, | |
901 | struct necp_client *client) | |
902 | { | |
903 | struct necp_client_result_netagent netagent; | |
904 | struct necp_aggregate_result result; | |
905 | struct necp_client_parsed_parameters parsed_parameters; | |
906 | u_int32_t flags = 0; | |
907 | ||
908 | uuid_clear(client->nexus_agent); | |
909 | ||
910 | int error = necp_client_parse_parameters(client->parameters, (u_int32_t)client->parameters_length, &parsed_parameters); | |
911 | if (error != 0) { | |
912 | return (FALSE); | |
913 | } | |
914 | ||
915 | // Check parameters to find best interface | |
916 | u_int matching_if_index = 0; | |
917 | if (necp_find_matching_interface_index(&parsed_parameters, &matching_if_index)) { | |
918 | if (matching_if_index != 0) { | |
919 | parsed_parameters.required_interface_index = matching_if_index; | |
920 | } | |
921 | // Interface found or not needed, match policy. | |
922 | error = necp_application_find_policy_match_internal(proc, client->parameters, (u_int32_t)client->parameters_length, &result, &flags, matching_if_index); | |
923 | if (error != 0) { | |
924 | return (FALSE); | |
925 | } | |
926 | } else { | |
927 | // Interface not found. Clear out the whole result, make everything fail. | |
928 | memset(&result, 0, sizeof(result)); | |
929 | } | |
930 | ||
931 | // If the original request was scoped, and the policy result matches, make sure the result is scoped | |
932 | if ((result.routing_result == NECP_KERNEL_POLICY_RESULT_NONE || | |
933 | result.routing_result == NECP_KERNEL_POLICY_RESULT_PASS) && | |
934 | result.routed_interface_index != IFSCOPE_NONE && | |
935 | parsed_parameters.required_interface_index == result.routed_interface_index) { | |
936 | result.routing_result = NECP_KERNEL_POLICY_RESULT_SOCKET_SCOPED; | |
937 | result.routing_result_parameter.scoped_interface_index = result.routed_interface_index; | |
938 | } | |
939 | ||
940 | bool updated = FALSE; | |
941 | u_int8_t *cursor = client->result; | |
942 | const u_int8_t *max = client->result + NECP_MAX_CLIENT_RESULT_SIZE; | |
943 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_CLIENT_ID, sizeof(uuid_t), client->client_id, &updated); | |
944 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_POLICY_RESULT, sizeof(result.routing_result), &result.routing_result, &updated); | |
945 | if (result.routing_result_parameter.tunnel_interface_index != 0) { | |
946 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_POLICY_RESULT_PARAMETER, | |
947 | sizeof(result.routing_result_parameter), &result.routing_result_parameter, &updated); | |
948 | } | |
949 | if (result.filter_control_unit != 0) { | |
950 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_FILTER_CONTROL_UNIT, | |
951 | sizeof(result.filter_control_unit), &result.filter_control_unit, &updated); | |
952 | } | |
953 | if (result.routed_interface_index != 0) { | |
954 | u_int routed_interface_index = result.routed_interface_index; | |
955 | if (result.routing_result == NECP_KERNEL_POLICY_RESULT_IP_TUNNEL && | |
956 | parsed_parameters.required_interface_index != IFSCOPE_NONE && | |
957 | parsed_parameters.required_interface_index != result.routed_interface_index) { | |
958 | routed_interface_index = parsed_parameters.required_interface_index; | |
959 | } | |
960 | ||
961 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE_INDEX, | |
962 | sizeof(routed_interface_index), &routed_interface_index, &updated); | |
963 | } | |
964 | if (flags != 0) { | |
965 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_FLAGS, | |
966 | sizeof(flags), &flags, &updated); | |
967 | } | |
968 | for (int i = 0; i < NECP_MAX_NETAGENTS; i++) { | |
969 | if (uuid_is_null(result.netagents[i])) { | |
970 | break; | |
971 | } | |
972 | uuid_copy(netagent.netagent_uuid, result.netagents[i]); | |
973 | netagent.generation = netagent_get_generation(netagent.netagent_uuid); | |
974 | if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) { | |
975 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated); | |
976 | } | |
977 | } | |
978 | ||
979 | ifnet_head_lock_shared(); | |
980 | ifnet_t direct_interface = NULL; | |
981 | ifnet_t delegate_interface = NULL; | |
982 | ifnet_t original_scoped_interface = NULL; | |
983 | ||
984 | if (result.routed_interface_index != IFSCOPE_NONE && result.routed_interface_index <= (u_int32_t)if_index) { | |
985 | direct_interface = ifindex2ifnet[result.routed_interface_index]; | |
986 | } else if (parsed_parameters.required_interface_index != IFSCOPE_NONE && | |
987 | parsed_parameters.required_interface_index <= (u_int32_t)if_index) { | |
988 | // If the request was scoped, but the route didn't match, still grab the agents | |
989 | direct_interface = ifindex2ifnet[parsed_parameters.required_interface_index]; | |
990 | } else if (result.routed_interface_index == IFSCOPE_NONE && | |
991 | result.routing_result == NECP_KERNEL_POLICY_RESULT_SOCKET_SCOPED && | |
992 | result.routing_result_parameter.scoped_interface_index != IFSCOPE_NONE) { | |
993 | direct_interface = ifindex2ifnet[result.routing_result_parameter.scoped_interface_index]; | |
994 | } | |
995 | if (direct_interface != NULL) { | |
996 | delegate_interface = direct_interface->if_delegated.ifp; | |
997 | } | |
998 | if (result.routing_result == NECP_KERNEL_POLICY_RESULT_IP_TUNNEL && | |
999 | parsed_parameters.required_interface_index != IFSCOPE_NONE && | |
1000 | parsed_parameters.required_interface_index != result.routing_result_parameter.tunnel_interface_index && | |
1001 | parsed_parameters.required_interface_index <= (u_int32_t)if_index) { | |
1002 | original_scoped_interface = ifindex2ifnet[parsed_parameters.required_interface_index]; | |
1003 | } | |
1004 | // Add interfaces | |
1005 | if (original_scoped_interface != NULL) { | |
1006 | struct necp_client_result_interface interface_struct; | |
1007 | interface_struct.index = original_scoped_interface->if_index; | |
1008 | interface_struct.generation = ifnet_get_generation(original_scoped_interface); | |
1009 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE, sizeof(interface_struct), &interface_struct, &updated); | |
1010 | } | |
1011 | if (direct_interface != NULL) { | |
1012 | struct necp_client_result_interface interface_struct; | |
1013 | interface_struct.index = direct_interface->if_index; | |
1014 | interface_struct.generation = ifnet_get_generation(direct_interface); | |
1015 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE, sizeof(interface_struct), &interface_struct, &updated); | |
1016 | } | |
1017 | if (delegate_interface != NULL) { | |
1018 | struct necp_client_result_interface interface_struct; | |
1019 | interface_struct.index = delegate_interface->if_index; | |
1020 | interface_struct.generation = ifnet_get_generation(delegate_interface); | |
1021 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_INTERFACE, sizeof(interface_struct), &interface_struct, &updated); | |
1022 | } | |
1023 | // Add agents | |
1024 | if (original_scoped_interface != NULL) { | |
1025 | ifnet_lock_shared(original_scoped_interface); | |
1026 | if (original_scoped_interface->if_agentids != NULL) { | |
1027 | for (u_int32_t i = 0; i < original_scoped_interface->if_agentcount; i++) { | |
1028 | if (uuid_is_null(original_scoped_interface->if_agentids[i])) { | |
1029 | continue; | |
1030 | } | |
1031 | uuid_copy(netagent.netagent_uuid, original_scoped_interface->if_agentids[i]); | |
1032 | netagent.generation = netagent_get_generation(netagent.netagent_uuid); | |
1033 | if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) { | |
1034 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated); | |
1035 | } | |
1036 | } | |
1037 | } | |
1038 | ifnet_lock_done(original_scoped_interface); | |
1039 | } | |
1040 | if (direct_interface != NULL) { | |
1041 | ifnet_lock_shared(direct_interface); | |
1042 | if (direct_interface->if_agentids != NULL) { | |
1043 | for (u_int32_t i = 0; i < direct_interface->if_agentcount; i++) { | |
1044 | if (uuid_is_null(direct_interface->if_agentids[i])) { | |
1045 | continue; | |
1046 | } | |
1047 | uuid_copy(netagent.netagent_uuid, direct_interface->if_agentids[i]); | |
1048 | netagent.generation = netagent_get_generation(netagent.netagent_uuid); | |
1049 | if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) { | |
1050 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated); | |
1051 | } | |
1052 | } | |
1053 | } | |
1054 | ifnet_lock_done(direct_interface); | |
1055 | } | |
1056 | if (delegate_interface != NULL) { | |
1057 | ifnet_lock_shared(delegate_interface); | |
1058 | if (delegate_interface->if_agentids != NULL) { | |
1059 | for (u_int32_t i = 0; i < delegate_interface->if_agentcount; i++) { | |
1060 | if (uuid_is_null(delegate_interface->if_agentids[i])) { | |
1061 | continue; | |
1062 | } | |
1063 | uuid_copy(netagent.netagent_uuid, delegate_interface->if_agentids[i]); | |
1064 | netagent.generation = netagent_get_generation(netagent.netagent_uuid); | |
1065 | if (necp_netagent_applies_to_client(client, &parsed_parameters, netagent.netagent_uuid)) { | |
1066 | cursor = necp_buffer_write_tlv_if_different(cursor, max, NECP_CLIENT_RESULT_NETAGENT, sizeof(netagent), &netagent, &updated); | |
1067 | } | |
1068 | } | |
1069 | } | |
1070 | ifnet_lock_done(delegate_interface); | |
1071 | } | |
1072 | ifnet_head_done(); | |
1073 | ||
1074 | size_t new_result_length = (cursor - client->result); | |
1075 | if (new_result_length != client->result_length) { | |
1076 | client->result_length = new_result_length; | |
1077 | updated = TRUE; | |
1078 | } | |
1079 | if (updated) { | |
1080 | client->result_read = FALSE; | |
1081 | } | |
1082 | ||
1083 | return (updated); | |
1084 | } | |
1085 | ||
1086 | static void | |
1087 | necp_update_all_clients_callout(__unused thread_call_param_t dummy, | |
1088 | __unused thread_call_param_t arg) | |
1089 | { | |
1090 | #pragma unused(arg) | |
1091 | struct necp_fd_data *client_fd = NULL; | |
1092 | ||
1093 | lck_rw_lock_shared(&necp_fd_lock); | |
1094 | ||
1095 | LIST_FOREACH(client_fd, &necp_fd_list, chain) { | |
1096 | bool updated_result = FALSE; | |
1097 | struct necp_client *client = NULL; | |
1098 | proc_t proc = proc_find(client_fd->proc_pid); | |
1099 | if (proc == NULL) { | |
1100 | continue; | |
1101 | } | |
1102 | ||
1103 | lck_mtx_lock(&client_fd->fd_lock); | |
1104 | LIST_FOREACH(client, &client_fd->clients, chain) { | |
1105 | if (necp_update_client_result(proc, client)) { | |
1106 | updated_result = TRUE; | |
1107 | } | |
1108 | } | |
1109 | if (updated_result) { | |
1110 | necp_fd_notify(client_fd, true); | |
1111 | } | |
1112 | lck_mtx_unlock(&client_fd->fd_lock); | |
1113 | ||
1114 | proc_rele(proc); | |
1115 | } | |
1116 | ||
1117 | lck_rw_done(&necp_fd_lock); | |
1118 | } | |
1119 | ||
1120 | void | |
1121 | necp_update_all_clients(void) | |
1122 | { | |
1123 | if (necp_client_tcall == NULL) { | |
1124 | // Don't try to update clients if the module is not initialized | |
1125 | return; | |
1126 | } | |
1127 | ||
1128 | uint64_t deadline = 0; | |
1129 | uint64_t leeway = 0; | |
1130 | clock_interval_to_deadline(necp_timeout_microseconds, NSEC_PER_USEC, &deadline); | |
1131 | clock_interval_to_absolutetime_interval(necp_timeout_leeway_microseconds, NSEC_PER_USEC, &leeway); | |
1132 | ||
1133 | thread_call_enter_delayed_with_leeway(necp_client_tcall, NULL, | |
1134 | deadline, leeway, THREAD_CALL_DELAY_LEEWAY); | |
1135 | } | |
1136 | ||
1137 | static void | |
1138 | necp_client_remove_agent_from_result(struct necp_client *client, uuid_t netagent_uuid) | |
1139 | { | |
1140 | size_t offset = 0; | |
1141 | ||
1142 | u_int8_t *result_buffer = client->result; | |
1143 | while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= client->result_length) { | |
1144 | u_int8_t type = necp_buffer_get_tlv_type(result_buffer, offset); | |
1145 | u_int32_t length = necp_buffer_get_tlv_length(result_buffer, offset); | |
1146 | ||
1147 | size_t tlv_total_length = (sizeof(u_int8_t) + sizeof(u_int32_t) + length); | |
1148 | if (type == NECP_CLIENT_RESULT_NETAGENT && | |
1149 | length == sizeof(struct necp_client_result_netagent) && | |
1150 | (offset + tlv_total_length) <= client->result_length) { | |
1151 | struct necp_client_result_netagent *value = ((struct necp_client_result_netagent *)(void *) | |
1152 | necp_buffer_get_tlv_value(result_buffer, offset, NULL)); | |
1153 | if (uuid_compare(value->netagent_uuid, netagent_uuid) == 0) { | |
1154 | // Found a netagent to remove | |
1155 | // Shift bytes down to remove the tlv, and adjust total length | |
1156 | // Don't adjust the current offset | |
1157 | memmove(result_buffer + offset, | |
1158 | result_buffer + offset + tlv_total_length, | |
1159 | client->result_length - (offset + tlv_total_length)); | |
1160 | client->result_length -= tlv_total_length; | |
1161 | memset(result_buffer + client->result_length, 0, NECP_MAX_CLIENT_RESULT_SIZE - client->result_length); | |
1162 | continue; | |
1163 | } | |
1164 | } | |
1165 | ||
1166 | offset += tlv_total_length; | |
1167 | } | |
1168 | } | |
1169 | ||
1170 | void | |
1171 | necp_force_update_client(uuid_t client_id, uuid_t remove_netagent_uuid) | |
1172 | { | |
1173 | struct necp_fd_data *client_fd = NULL; | |
1174 | ||
1175 | lck_rw_lock_shared(&necp_fd_lock); | |
1176 | ||
1177 | LIST_FOREACH(client_fd, &necp_fd_list, chain) { | |
1178 | bool updated_result = FALSE; | |
1179 | struct necp_client *client = NULL; | |
1180 | lck_mtx_lock(&client_fd->fd_lock); | |
1181 | LIST_FOREACH(client, &client_fd->clients, chain) { | |
1182 | if (uuid_compare(client->client_id, client_id) == 0) { | |
1183 | if (!uuid_is_null(remove_netagent_uuid)) { | |
1184 | necp_client_remove_agent_from_result(client, remove_netagent_uuid); | |
1185 | } | |
1186 | client->assigned_result_read = FALSE; | |
1187 | updated_result = TRUE; | |
1188 | // Found the client, break | |
1189 | break; | |
1190 | } | |
1191 | } | |
1192 | if (updated_result) { | |
1193 | necp_fd_notify(client_fd, true); | |
1194 | } | |
1195 | lck_mtx_unlock(&client_fd->fd_lock); | |
1196 | if (updated_result) { | |
1197 | // Found the client, break | |
1198 | break; | |
1199 | } | |
1200 | } | |
1201 | ||
1202 | lck_rw_done(&necp_fd_lock); | |
1203 | } | |
1204 | ||
1205 | /// Interface matching | |
1206 | ||
1207 | #define NECP_PARSED_PARAMETERS_INTERESTING_IFNET_FIELDS (NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR | \ | |
1208 | NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF | \ | |
1209 | NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE | \ | |
1210 | NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE | \ | |
1211 | NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT | \ | |
1212 | NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT | \ | |
1213 | NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT | \ | |
1214 | NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE | \ | |
1215 | NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE | \ | |
1216 | NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE) | |
1217 | ||
1218 | #define NECP_PARSED_PARAMETERS_SCOPED_IFNET_FIELDS (NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR | \ | |
1219 | NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE | \ | |
1220 | NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT | \ | |
1221 | NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT | \ | |
1222 | NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE | \ | |
1223 | NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE) | |
1224 | ||
1225 | #define NECP_PARSED_PARAMETERS_PREFERRED_IFNET_FIELDS (NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT | \ | |
1226 | NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE) | |
1227 | ||
1228 | static bool | |
1229 | necp_ifnet_matches_type(struct ifnet *ifp, u_int8_t interface_type, bool check_delegates) | |
1230 | { | |
1231 | struct ifnet *check_ifp = ifp; | |
1232 | while (check_ifp) { | |
1233 | if (if_functional_type(check_ifp, TRUE) == interface_type) { | |
1234 | return (TRUE); | |
1235 | } | |
1236 | if (!check_delegates) { | |
1237 | break; | |
1238 | } | |
1239 | check_ifp = check_ifp->if_delegated.ifp; | |
1240 | ||
1241 | } | |
1242 | return (FALSE); | |
1243 | } | |
1244 | ||
1245 | static bool | |
1246 | necp_ifnet_matches_name(struct ifnet *ifp, const char *interface_name, bool check_delegates) | |
1247 | { | |
1248 | struct ifnet *check_ifp = ifp; | |
1249 | while (check_ifp) { | |
1250 | if (strncmp(check_ifp->if_xname, interface_name, IFXNAMSIZ) == 0) { | |
1251 | return (TRUE); | |
1252 | } | |
1253 | if (!check_delegates) { | |
1254 | break; | |
1255 | } | |
1256 | check_ifp = check_ifp->if_delegated.ifp; | |
1257 | } | |
1258 | return (FALSE); | |
1259 | } | |
1260 | ||
1261 | static bool | |
1262 | necp_ifnet_matches_agent(struct ifnet *ifp, uuid_t *agent_uuid, bool check_delegates) | |
1263 | { | |
1264 | struct ifnet *check_ifp = ifp; | |
1265 | ||
1266 | while (check_ifp != NULL) { | |
1267 | ifnet_lock_shared(check_ifp); | |
1268 | if (check_ifp->if_agentids != NULL) { | |
1269 | for (u_int32_t index = 0; index < check_ifp->if_agentcount; index++) { | |
1270 | if (uuid_compare(check_ifp->if_agentids[index], *agent_uuid) == 0) { | |
1271 | ifnet_lock_done(check_ifp); | |
1272 | return (TRUE); | |
1273 | } | |
1274 | } | |
1275 | } | |
1276 | ifnet_lock_done(check_ifp); | |
1277 | ||
1278 | if (!check_delegates) { | |
1279 | break; | |
1280 | } | |
1281 | check_ifp = check_ifp->if_delegated.ifp; | |
1282 | } | |
1283 | return (FALSE); | |
1284 | } | |
1285 | ||
1286 | static bool | |
1287 | necp_necp_ifnet_matches_agent_type(struct ifnet *ifp, const char *agent_domain, const char *agent_type, bool check_delegates) | |
1288 | { | |
1289 | struct ifnet *check_ifp = ifp; | |
1290 | ||
1291 | while (check_ifp != NULL) { | |
1292 | ifnet_lock_shared(check_ifp); | |
1293 | if (check_ifp->if_agentids != NULL) { | |
1294 | for (u_int32_t index = 0; index < check_ifp->if_agentcount; index++) { | |
1295 | if (uuid_is_null(check_ifp->if_agentids[index])) { | |
1296 | continue; | |
1297 | } | |
1298 | ||
1299 | char if_agent_domain[NETAGENT_DOMAINSIZE] = { 0 }; | |
1300 | char if_agent_type[NETAGENT_TYPESIZE] = { 0 }; | |
1301 | ||
1302 | if (netagent_get_agent_domain_and_type(check_ifp->if_agentids[index], if_agent_domain, if_agent_type)) { | |
1303 | if ((strlen(agent_domain) == 0 || | |
1304 | strncmp(if_agent_domain, agent_domain, NETAGENT_DOMAINSIZE) == 0) && | |
1305 | (strlen(agent_type) == 0 || | |
1306 | strncmp(if_agent_type, agent_type, NETAGENT_TYPESIZE) == 0)) { | |
1307 | ifnet_lock_done(check_ifp); | |
1308 | return (TRUE); | |
1309 | } | |
1310 | } | |
1311 | } | |
1312 | } | |
1313 | ifnet_lock_done(check_ifp); | |
1314 | ||
1315 | if (!check_delegates) { | |
1316 | break; | |
1317 | } | |
1318 | check_ifp = check_ifp->if_delegated.ifp; | |
1319 | } | |
1320 | return (FALSE); | |
1321 | } | |
1322 | ||
1323 | static bool | |
1324 | necp_ifnet_matches_local_address(struct ifnet *ifp, struct sockaddr *sa) | |
1325 | { | |
1326 | struct ifaddr *ifa = NULL; | |
1327 | bool matched_local_address = FALSE; | |
1328 | ||
1329 | // Transform sa into the ifaddr form | |
1330 | // IPv6 Scope IDs are always embedded in the ifaddr list | |
1331 | struct sockaddr_storage address; | |
1332 | u_int ifscope = IFSCOPE_NONE; | |
1333 | (void)sa_copy(sa, &address, &ifscope); | |
1334 | SIN(&address)->sin_port = 0; | |
1335 | if (address.ss_family == AF_INET6) { | |
1336 | SIN6(&address)->sin6_scope_id = 0; | |
1337 | } | |
1338 | ||
1339 | ifa = ifa_ifwithaddr_scoped_locked((struct sockaddr *)&address, ifp->if_index); | |
1340 | matched_local_address = (ifa != NULL); | |
1341 | ||
1342 | if (ifa) { | |
1343 | ifaddr_release(ifa); | |
1344 | } | |
1345 | ||
1346 | return (matched_local_address); | |
1347 | } | |
1348 | ||
1349 | static bool | |
1350 | necp_ifnet_matches_parameters(struct ifnet *ifp, | |
1351 | struct necp_client_parsed_parameters *parsed_parameters, | |
1352 | u_int32_t *preferred_count) | |
1353 | { | |
1354 | if (preferred_count) { | |
1355 | *preferred_count = 0; | |
1356 | } | |
1357 | ||
1358 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_LOCAL_ADDR) { | |
1359 | if (!necp_ifnet_matches_local_address(ifp, &parsed_parameters->local_addr.sa)) { | |
1360 | return (FALSE); | |
1361 | } | |
1362 | } | |
1363 | ||
1364 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE) { | |
1365 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1366 | if (parsed_parameters->required_interface_types[i] == 0) { | |
1367 | break; | |
1368 | } | |
1369 | ||
1370 | if (!necp_ifnet_matches_type(ifp, parsed_parameters->required_interface_types[i], FALSE)) { | |
1371 | return (FALSE); | |
1372 | } | |
1373 | } | |
1374 | } | |
1375 | ||
1376 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IFTYPE) { | |
1377 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1378 | if (parsed_parameters->prohibited_interface_types[i] == 0) { | |
1379 | break; | |
1380 | } | |
1381 | ||
1382 | if (necp_ifnet_matches_type(ifp, parsed_parameters->prohibited_interface_types[i], TRUE)) { | |
1383 | return (FALSE); | |
1384 | } | |
1385 | } | |
1386 | } | |
1387 | ||
1388 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_IF) { | |
1389 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1390 | if (strlen(parsed_parameters->prohibited_interfaces[i]) == 0) { | |
1391 | break; | |
1392 | } | |
1393 | ||
1394 | if (necp_ifnet_matches_name(ifp, parsed_parameters->prohibited_interfaces[i], TRUE)) { | |
1395 | return (FALSE); | |
1396 | } | |
1397 | } | |
1398 | } | |
1399 | ||
1400 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT) { | |
1401 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1402 | if (uuid_is_null(parsed_parameters->required_netagents[i])) { | |
1403 | break; | |
1404 | } | |
1405 | ||
1406 | if (!necp_ifnet_matches_agent(ifp, &parsed_parameters->required_netagents[i], FALSE)) { | |
1407 | return (FALSE); | |
1408 | } | |
1409 | } | |
1410 | } | |
1411 | ||
1412 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT) { | |
1413 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1414 | if (uuid_is_null(parsed_parameters->prohibited_netagents[i])) { | |
1415 | break; | |
1416 | } | |
1417 | ||
1418 | if (necp_ifnet_matches_agent(ifp, &parsed_parameters->prohibited_netagents[i], TRUE)) { | |
1419 | return (FALSE); | |
1420 | } | |
1421 | } | |
1422 | } | |
1423 | ||
1424 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_AGENT_TYPE) { | |
1425 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1426 | if (strlen(parsed_parameters->required_netagent_types[i].netagent_domain) == 0 && | |
1427 | strlen(parsed_parameters->required_netagent_types[i].netagent_type) == 0) { | |
1428 | break; | |
1429 | } | |
1430 | ||
1431 | 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)) { | |
1432 | return (FALSE); | |
1433 | } | |
1434 | } | |
1435 | } | |
1436 | ||
1437 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PROHIBITED_AGENT_TYPE) { | |
1438 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1439 | if (strlen(parsed_parameters->prohibited_netagent_types[i].netagent_domain) == 0 && | |
1440 | strlen(parsed_parameters->prohibited_netagent_types[i].netagent_type) == 0) { | |
1441 | break; | |
1442 | } | |
1443 | ||
1444 | 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)) { | |
1445 | return (FALSE); | |
1446 | } | |
1447 | } | |
1448 | } | |
1449 | ||
1450 | // Checked preferred properties | |
1451 | if (preferred_count) { | |
1452 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT) { | |
1453 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1454 | if (uuid_is_null(parsed_parameters->preferred_netagents[i])) { | |
1455 | break; | |
1456 | } | |
1457 | ||
1458 | if (necp_ifnet_matches_agent(ifp, &parsed_parameters->preferred_netagents[i], TRUE)) { | |
1459 | (*preferred_count)++; | |
1460 | } | |
1461 | } | |
1462 | } | |
1463 | ||
1464 | if (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_PREFERRED_AGENT_TYPE) { | |
1465 | for (int i = 0; i < NECP_MAX_PARSED_PARAMETERS; i++) { | |
1466 | if (strlen(parsed_parameters->preferred_netagent_types[i].netagent_domain) == 0 && | |
1467 | strlen(parsed_parameters->preferred_netagent_types[i].netagent_type) == 0) { | |
1468 | break; | |
1469 | } | |
1470 | ||
1471 | 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)) { | |
1472 | (*preferred_count)++; | |
1473 | } | |
1474 | } | |
1475 | } | |
1476 | } | |
1477 | ||
1478 | return (TRUE); | |
1479 | } | |
1480 | ||
1481 | static bool | |
1482 | necp_find_matching_interface_index(struct necp_client_parsed_parameters *parsed_parameters, u_int *return_ifindex) | |
1483 | { | |
1484 | struct ifnet *ifp = NULL; | |
1485 | u_int32_t best_preferred_count = 0; | |
1486 | bool has_preferred_fields = FALSE; | |
1487 | *return_ifindex = 0; | |
1488 | ||
1489 | if (parsed_parameters->required_interface_index != 0) { | |
1490 | *return_ifindex = parsed_parameters->required_interface_index; | |
1491 | return (TRUE); | |
1492 | } | |
1493 | ||
1494 | if (!(parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_INTERESTING_IFNET_FIELDS)) { | |
1495 | return (TRUE); | |
1496 | } | |
1497 | ||
1498 | has_preferred_fields = (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_PREFERRED_IFNET_FIELDS); | |
1499 | ||
1500 | // We have interesting parameters to parse and find a matching interface | |
1501 | ifnet_head_lock_shared(); | |
1502 | ||
1503 | if (!(parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_SCOPED_IFNET_FIELDS)) { | |
1504 | // We do have fields to match, but they are only prohibitory | |
1505 | // If the first interface in the list matches, we don't need to scope | |
1506 | ifp = TAILQ_FIRST(&ifnet_ordered_head); | |
1507 | if (ifp && necp_ifnet_matches_parameters(ifp, parsed_parameters, NULL)) { | |
1508 | // Don't set return_ifindex, so the client doesn't need to scope | |
1509 | ifnet_head_done(); | |
1510 | return (TRUE); | |
1511 | } | |
1512 | } | |
1513 | ||
1514 | // First check the ordered interface list | |
1515 | TAILQ_FOREACH(ifp, &ifnet_ordered_head, if_ordered_link) { | |
1516 | u_int32_t preferred_count = 0; | |
1517 | if (necp_ifnet_matches_parameters(ifp, parsed_parameters, &preferred_count)) { | |
1518 | if (preferred_count > best_preferred_count || | |
1519 | *return_ifindex == 0) { | |
1520 | ||
1521 | // Everything matched, and is most preferred. Return this interface. | |
1522 | *return_ifindex = ifp->if_index; | |
1523 | best_preferred_count = preferred_count; | |
1524 | ||
1525 | if (!has_preferred_fields) { | |
1526 | break; | |
1527 | } | |
1528 | } | |
1529 | } | |
1530 | } | |
1531 | ||
1532 | // Then check the remaining interfaces | |
1533 | if ((parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_SCOPED_IFNET_FIELDS) && | |
1534 | !(parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_FIELD_REQUIRED_IFTYPE) && | |
1535 | *return_ifindex == 0) { | |
1536 | TAILQ_FOREACH(ifp, &ifnet_head, if_link) { | |
1537 | u_int32_t preferred_count = 0; | |
1538 | if (ifp->if_ordered_link.tqe_next != NULL || | |
1539 | ifp->if_ordered_link.tqe_prev != NULL) { | |
1540 | // This interface was in the ordered list, skip | |
1541 | continue; | |
1542 | } | |
1543 | if (necp_ifnet_matches_parameters(ifp, parsed_parameters, &preferred_count)) { | |
1544 | if (preferred_count > best_preferred_count || | |
1545 | *return_ifindex == 0) { | |
1546 | ||
1547 | // Everything matched, and is most preferred. Return this interface. | |
1548 | *return_ifindex = ifp->if_index; | |
1549 | best_preferred_count = preferred_count; | |
1550 | ||
1551 | if (!has_preferred_fields) { | |
1552 | break; | |
1553 | } | |
1554 | } | |
1555 | } | |
1556 | } | |
1557 | } | |
1558 | ||
1559 | ifnet_head_done(); | |
1560 | ||
1561 | if ((parsed_parameters->valid_fields == (parsed_parameters->valid_fields & NECP_PARSED_PARAMETERS_PREFERRED_IFNET_FIELDS)) && | |
1562 | best_preferred_count == 0) { | |
1563 | // If only has preferred fields, and nothing was found, clear the interface index and return TRUE | |
1564 | *return_ifindex = 0; | |
1565 | return (TRUE); | |
1566 | } | |
1567 | ||
1568 | return (*return_ifindex != 0); | |
1569 | } | |
1570 | ||
1571 | static void | |
1572 | 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) | |
1573 | { | |
1574 | size_t offset = 0; | |
1575 | u_int8_t *parameters; | |
1576 | u_int32_t parameters_size; | |
1577 | ||
1578 | parameters = client->parameters; | |
1579 | parameters_size = (u_int32_t)client->parameters_length; | |
1580 | ||
1581 | while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= parameters_size) { | |
1582 | u_int8_t type = necp_buffer_get_tlv_type(parameters, offset); | |
1583 | u_int32_t length = necp_buffer_get_tlv_length(parameters, offset); | |
1584 | ||
1585 | if (length > (parameters_size - (offset + sizeof(u_int8_t) + sizeof(u_int32_t)))) { | |
1586 | // If the length is larger than what can fit in the remaining parameters size, bail | |
1587 | NECPLOG(LOG_ERR, "Invalid TLV length (%u)", length); | |
1588 | break; | |
1589 | } | |
1590 | ||
1591 | if (length > 0) { | |
1592 | u_int8_t *value = necp_buffer_get_tlv_value(parameters, offset, NULL); | |
1593 | if (value != NULL) { | |
1594 | switch (type) { | |
1595 | case NECP_CLIENT_PARAMETER_REAL_APPLICATION: { | |
1596 | if (length >= sizeof(uuid_t)) { | |
1597 | uuid_copy(euuid, value); | |
1598 | } | |
1599 | break; | |
1600 | } | |
1601 | case NECP_CLIENT_PARAMETER_TRAFFIC_CLASS: { | |
1602 | if (length >= sizeof(u_int32_t)) { | |
1603 | memcpy(traffic_class, value, sizeof(u_int32_t)); | |
1604 | } | |
1605 | break; | |
1606 | } | |
1607 | case NECP_CLIENT_PARAMETER_BOUND_INTERFACE: { | |
1608 | if (length <= IFXNAMSIZ && length > 0) { | |
1609 | ifnet_t bound_interface = NULL; | |
1610 | char interface_name[IFXNAMSIZ]; | |
1611 | memcpy(interface_name, value, length); | |
1612 | interface_name[length - 1] = 0; // Make sure the string is NULL terminated | |
1613 | if (ifnet_find_by_name(interface_name, &bound_interface) == 0) { | |
1614 | *ifindex = bound_interface->if_index; | |
1615 | ifnet_release(bound_interface); | |
1616 | } | |
1617 | } | |
1618 | break; | |
1619 | } | |
1620 | case NECP_CLIENT_PARAMETER_LOCAL_ADDRESS: { | |
1621 | if (length >= sizeof(struct necp_policy_condition_addr)) { | |
1622 | struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value; | |
1623 | memcpy(local, &address_struct->address, sizeof(address_struct->address)); | |
1624 | } | |
1625 | break; | |
1626 | } | |
1627 | case NECP_CLIENT_PARAMETER_REMOTE_ADDRESS: { | |
1628 | if (length >= sizeof(struct necp_policy_condition_addr)) { | |
1629 | struct necp_policy_condition_addr *address_struct = (struct necp_policy_condition_addr *)(void *)value; | |
1630 | memcpy(remote, &address_struct->address, sizeof(address_struct->address)); | |
1631 | } | |
1632 | break; | |
1633 | } | |
1634 | default: { | |
1635 | break; | |
1636 | } | |
1637 | } | |
1638 | } | |
1639 | } | |
1640 | offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length; | |
1641 | } | |
1642 | } | |
1643 | ||
1644 | static void | |
1645 | necp_fillout_current_process_details(u_int32_t *pid, u_int64_t *upid, unsigned char *uuid, char *pname, size_t len) | |
1646 | { | |
1647 | *pid = proc_selfpid(); | |
1648 | *upid = proc_uniqueid(current_proc()); | |
1649 | proc_selfname(pname, (int) len); | |
1650 | proc_getexecutableuuid(current_proc(), uuid, sizeof(uuid_t)); | |
1651 | } | |
1652 | ||
1653 | // Called from NetworkStatistics when it wishes to collect latest information for a TCP flow. | |
1654 | // It is a responsibility of NetworkStatistics to have previously zeroed any supplied memory. | |
1655 | static bool | |
1656 | necp_request_tcp_netstats(userland_stats_provider_context *ctx, | |
1657 | nstat_counts *countsp, | |
1658 | void *metadatap) | |
1659 | { | |
1660 | if (ctx == NULL) { | |
1661 | return false; | |
1662 | } | |
1663 | ||
1664 | struct necp_client *client = (struct necp_client *)ctx; | |
1665 | struct necp_tcp_stats *tcpstats = (struct necp_tcp_stats *)client->stats_area; | |
1666 | if (tcpstats == NULL) { | |
1667 | return false; | |
1668 | } | |
1669 | ||
1670 | if (countsp) { | |
1671 | *countsp = *((struct nstat_counts *)&tcpstats->necp_tcp_counts); | |
1672 | } | |
1673 | ||
1674 | if (metadatap) { | |
1675 | nstat_tcp_descriptor *desc = (nstat_tcp_descriptor *)metadatap; | |
1676 | ||
1677 | // Metadata for the process | |
1678 | necp_fillout_current_process_details(&desc->pid, &desc->upid, desc->uuid, desc->pname, sizeof(desc->pname)); | |
1679 | ||
1680 | // Metadata that the necp client should have in TLV format. | |
1681 | necp_find_netstat_data(client, (union necp_sockaddr_union *)&desc->local, (union necp_sockaddr_union *)&desc->remote, &desc->ifindex, desc->euuid, &desc->traffic_class); | |
1682 | ||
1683 | // Basic metadata | |
1684 | desc->rcvbufsize = tcpstats->necp_tcp_basic.rcvbufsize; | |
1685 | desc->rcvbufused = tcpstats->necp_tcp_basic.rcvbufused; | |
1686 | desc->eupid = tcpstats->necp_tcp_basic.eupid; | |
1687 | desc->epid = tcpstats->necp_tcp_basic.epid; | |
1688 | memcpy(desc->vuuid, tcpstats->necp_tcp_basic.vuuid, sizeof(desc->vuuid)); | |
1689 | desc->ifnet_properties = tcpstats->necp_tcp_basic.ifnet_properties; | |
1690 | ||
1691 | // Additional TCP specific data | |
1692 | desc->sndbufsize = tcpstats->necp_tcp_extra.sndbufsize; | |
1693 | desc->sndbufused = tcpstats->necp_tcp_extra.sndbufused; | |
1694 | desc->txunacked = tcpstats->necp_tcp_extra.txunacked; | |
1695 | desc->txwindow = tcpstats->necp_tcp_extra.txwindow; | |
1696 | desc->txcwindow = tcpstats->necp_tcp_extra.txcwindow; | |
1697 | desc->traffic_mgt_flags = tcpstats->necp_tcp_extra.traffic_mgt_flags; | |
1698 | ||
1699 | if (tcpstats->necp_tcp_extra.cc_alg_index < TCP_CC_ALGO_COUNT) { | |
1700 | strlcpy(desc->cc_algo, tcp_cc_algo_list[tcpstats->necp_tcp_extra.cc_alg_index]->name, sizeof(desc->cc_algo)); | |
1701 | } else { | |
1702 | strlcpy(desc->cc_algo, "unknown", sizeof(desc->cc_algo)); | |
1703 | } | |
1704 | ||
1705 | desc->connstatus.write_probe_failed = tcpstats->necp_tcp_extra.probestatus.write_probe_failed; | |
1706 | desc->connstatus.read_probe_failed = tcpstats->necp_tcp_extra.probestatus.read_probe_failed; | |
1707 | desc->connstatus.conn_probe_failed = tcpstats->necp_tcp_extra.probestatus.conn_probe_failed; | |
1708 | } | |
1709 | return true; | |
1710 | } | |
1711 | ||
1712 | // Called from NetworkStatistics when it wishes to collect latest information for a UDP flow. | |
1713 | static bool | |
1714 | necp_request_udp_netstats(userland_stats_provider_context *ctx, | |
1715 | nstat_counts *countsp, | |
1716 | void *metadatap) | |
1717 | { | |
1718 | if (ctx == NULL) { | |
1719 | return false; | |
1720 | } | |
1721 | ||
1722 | struct necp_client *client = (struct necp_client *)ctx; | |
1723 | struct necp_udp_stats *udpstats = (struct necp_udp_stats *)client->stats_area; | |
1724 | if (udpstats == NULL) { | |
1725 | return false; | |
1726 | } | |
1727 | ||
1728 | if (countsp) { | |
1729 | *countsp = *((struct nstat_counts *)&udpstats->necp_udp_counts); | |
1730 | } | |
1731 | ||
1732 | if (metadatap) { | |
1733 | nstat_udp_descriptor *desc = (nstat_udp_descriptor *)metadatap; | |
1734 | ||
1735 | // Metadata for the process | |
1736 | necp_fillout_current_process_details(&desc->pid, &desc->upid, desc->uuid, desc->pname, sizeof(desc->pname)); | |
1737 | ||
1738 | // Metadata that the necp client should have in TLV format. | |
1739 | necp_find_netstat_data(client, (union necp_sockaddr_union *)&desc->local, (union necp_sockaddr_union *)&desc->remote, &desc->ifindex, desc->euuid, &desc->traffic_class); | |
1740 | ||
1741 | // Basic metadata is all that is required for UDP | |
1742 | desc->rcvbufsize = udpstats->necp_udp_basic.rcvbufsize; | |
1743 | desc->rcvbufused = udpstats->necp_udp_basic.rcvbufused; | |
1744 | desc->eupid = udpstats->necp_udp_basic.eupid; | |
1745 | desc->epid = udpstats->necp_udp_basic.epid; | |
1746 | memcpy(desc->vuuid, udpstats->necp_udp_basic.vuuid, sizeof(desc->euuid)); | |
1747 | desc->ifnet_properties = udpstats->necp_udp_basic.ifnet_properties; | |
1748 | } | |
1749 | return true; | |
1750 | } | |
1751 | ||
1752 | static int | |
1753 | necp_skywalk_priv_check_cred(proc_t p, kauth_cred_t cred) | |
1754 | { | |
1755 | #pragma unused(p, cred) | |
1756 | return (0); | |
1757 | } | |
1758 | ||
1759 | /// System calls | |
1760 | ||
1761 | int | |
1762 | necp_open(struct proc *p, struct necp_open_args *uap, int *retval) | |
1763 | { | |
1764 | #pragma unused(retval) | |
1765 | int error = 0; | |
1766 | struct necp_fd_data *fd_data = NULL; | |
1767 | struct fileproc *fp = NULL; | |
1768 | int fd = -1; | |
1769 | ||
1770 | if (uap->flags & NECP_OPEN_FLAG_OBSERVER) { | |
1771 | if (necp_skywalk_priv_check_cred(p, kauth_cred_get()) != 0 && | |
1772 | priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NETWORK_STATISTICS, 0) != 0) { | |
1773 | NECPLOG0(LOG_ERR, "Client does not hold necessary entitlement to observe other NECP clients"); | |
1774 | error = EACCES; | |
1775 | goto done; | |
1776 | } | |
1777 | } | |
1778 | ||
1779 | error = falloc(p, &fp, &fd, vfs_context_current()); | |
1780 | if (error != 0) { | |
1781 | goto done; | |
1782 | } | |
1783 | ||
1784 | if ((fd_data = _MALLOC(sizeof(struct necp_fd_data), M_NECP, | |
1785 | M_WAITOK | M_ZERO)) == NULL) { | |
1786 | error = ENOMEM; | |
1787 | goto done; | |
1788 | } | |
1789 | ||
1790 | fd_data->flags = uap->flags; | |
1791 | LIST_INIT(&fd_data->clients); | |
1792 | lck_mtx_init(&fd_data->fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr); | |
1793 | klist_init(&fd_data->si.si_note); | |
1794 | fd_data->proc_pid = proc_pid(p); | |
1795 | ||
1796 | fp->f_fglob->fg_flag = FREAD; | |
1797 | fp->f_fglob->fg_ops = &necp_fd_ops; | |
1798 | fp->f_fglob->fg_data = fd_data; | |
1799 | ||
1800 | proc_fdlock(p); | |
1801 | ||
1802 | *fdflags(p, fd) |= (UF_EXCLOSE | UF_FORKCLOSE); | |
1803 | procfdtbl_releasefd(p, fd, NULL); | |
1804 | fp_drop(p, fd, fp, 1); | |
1805 | ||
1806 | *retval = fd; | |
1807 | ||
1808 | lck_rw_lock_exclusive(&necp_fd_lock); | |
1809 | LIST_INSERT_HEAD(&necp_fd_list, fd_data, chain); | |
1810 | lck_rw_done(&necp_fd_lock); | |
1811 | ||
1812 | proc_fdunlock(p); | |
1813 | ||
1814 | done: | |
1815 | if (error != 0) { | |
1816 | if (fp != NULL) { | |
1817 | fp_free(p, fd, fp); | |
1818 | fp = NULL; | |
1819 | } | |
1820 | if (fd_data != NULL) { | |
1821 | FREE(fd_data, M_NECP); | |
1822 | fd_data = NULL; | |
1823 | } | |
1824 | } | |
1825 | ||
1826 | return (error); | |
1827 | } | |
1828 | ||
1829 | static int | |
1830 | necp_client_add(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
1831 | { | |
1832 | int error = 0; | |
1833 | struct necp_client *client = NULL; | |
1834 | ||
1835 | if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) || | |
1836 | uap->buffer_size == 0 || uap->buffer_size > NECP_MAX_CLIENT_PARAMETERS_SIZE || uap->buffer == 0) { | |
1837 | error = EINVAL; | |
1838 | goto done; | |
1839 | } | |
1840 | ||
1841 | if ((client = _MALLOC(sizeof(struct necp_client) + uap->buffer_size, M_NECP, | |
1842 | M_WAITOK | M_ZERO)) == NULL) { | |
1843 | error = ENOMEM; | |
1844 | goto done; | |
1845 | } | |
1846 | ||
1847 | error = copyin(uap->buffer, client->parameters, uap->buffer_size); | |
1848 | if (error) { | |
1849 | NECPLOG(LOG_ERR, "necp_client_add parameters copyin error (%d)", error); | |
1850 | goto done; | |
1851 | } | |
1852 | ||
1853 | client->parameters_length = uap->buffer_size; | |
1854 | ||
1855 | uuid_generate_random(client->client_id); | |
1856 | LIST_INIT(&client->assertion_list); | |
1857 | ||
1858 | error = copyout(client->client_id, uap->client_id, sizeof(uuid_t)); | |
1859 | if (error) { | |
1860 | NECPLOG(LOG_ERR, "necp_client_add client_id copyout error (%d)", error); | |
1861 | goto done; | |
1862 | } | |
1863 | ||
1864 | lck_mtx_lock(&fd_data->fd_lock); | |
1865 | LIST_INSERT_HEAD(&fd_data->clients, client, chain); | |
1866 | ||
1867 | // Prime the client result | |
1868 | (void)necp_update_client_result(current_proc(), client); | |
1869 | lck_mtx_unlock(&fd_data->fd_lock); | |
1870 | done: | |
1871 | if (error != 0) { | |
1872 | if (client != NULL) { | |
1873 | FREE(client, M_NECP); | |
1874 | client = NULL; | |
1875 | } | |
1876 | } | |
1877 | *retval = error; | |
1878 | ||
1879 | return (error); | |
1880 | } | |
1881 | ||
1882 | static int | |
1883 | necp_client_remove(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
1884 | { | |
1885 | int error = 0; | |
1886 | struct necp_client *client = NULL; | |
1887 | struct necp_client *temp_client = NULL; | |
1888 | uuid_t client_id; | |
1889 | ||
1890 | if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t)) { | |
1891 | error = EINVAL; | |
1892 | goto done; | |
1893 | } | |
1894 | ||
1895 | error = copyin(uap->client_id, client_id, sizeof(uuid_t)); | |
1896 | if (error) { | |
1897 | NECPLOG(LOG_ERR, "necp_client_remove copyin client_id error (%d)", error); | |
1898 | goto done; | |
1899 | } | |
1900 | ||
1901 | lck_mtx_lock(&fd_data->fd_lock); | |
1902 | LIST_FOREACH_SAFE(client, &fd_data->clients, chain, temp_client) { | |
1903 | if (uuid_compare(client->client_id, client_id) == 0) { | |
1904 | necp_destroy_client(client); | |
1905 | } | |
1906 | } | |
1907 | lck_mtx_unlock(&fd_data->fd_lock); | |
1908 | done: | |
1909 | *retval = error; | |
1910 | ||
1911 | return (error); | |
1912 | } | |
1913 | ||
1914 | static int | |
1915 | necp_client_copy_internal(struct necp_client *client, bool client_is_observed, struct necp_client_action_args *uap, int *retval) | |
1916 | { | |
1917 | int error = 0; | |
1918 | // Copy results out | |
1919 | if (uap->action == NECP_CLIENT_ACTION_COPY_PARAMETERS) { | |
1920 | if (uap->buffer_size < client->parameters_length) { | |
1921 | error = EINVAL; | |
1922 | goto done; | |
1923 | } | |
1924 | error = copyout(client->parameters, uap->buffer, client->parameters_length); | |
1925 | if (error) { | |
1926 | NECPLOG(LOG_ERR, "necp_client_copy parameters copyout error (%d)", error); | |
1927 | goto done; | |
1928 | } | |
1929 | *retval = client->parameters_length; | |
1930 | } else if (uap->action == NECP_CLIENT_ACTION_COPY_RESULT) { | |
1931 | if (uap->buffer_size < (client->result_length + client->assigned_results_length)) { | |
1932 | error = EINVAL; | |
1933 | goto done; | |
1934 | } | |
1935 | error = copyout(client->result, uap->buffer, client->result_length); | |
1936 | if (error) { | |
1937 | NECPLOG(LOG_ERR, "necp_client_copy result copyout error (%d)", error); | |
1938 | goto done; | |
1939 | } | |
1940 | if (client->assigned_results_length && client->assigned_results) { | |
1941 | error = copyout(client->assigned_results, uap->buffer + client->result_length, client->assigned_results_length); | |
1942 | if (error) { | |
1943 | NECPLOG(LOG_ERR, "necp_client_copy assigned results copyout error (%d)", error); | |
1944 | goto done; | |
1945 | } | |
1946 | *retval = client->result_length + client->assigned_results_length; | |
1947 | } else { | |
1948 | *retval = client->result_length; | |
1949 | } | |
1950 | ||
1951 | if (!client_is_observed) { | |
1952 | client->result_read = TRUE; | |
1953 | client->assigned_result_read = TRUE; | |
1954 | } | |
1955 | } | |
1956 | ||
1957 | done: | |
1958 | return (error); | |
1959 | } | |
1960 | ||
1961 | static int | |
1962 | necp_client_copy(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
1963 | { | |
1964 | int error = 0; | |
1965 | struct necp_client *find_client = NULL; | |
1966 | struct necp_client *client = NULL; | |
1967 | uuid_t client_id; | |
1968 | uuid_clear(client_id); | |
1969 | ||
1970 | *retval = 0; | |
1971 | ||
1972 | if (uap->buffer_size == 0 || uap->buffer == 0) { | |
1973 | error = EINVAL; | |
1974 | goto done; | |
1975 | } | |
1976 | ||
1977 | if (uap->action != NECP_CLIENT_ACTION_COPY_PARAMETERS && | |
1978 | uap->action != NECP_CLIENT_ACTION_COPY_RESULT) { | |
1979 | error = EINVAL; | |
1980 | goto done; | |
1981 | } | |
1982 | ||
1983 | if (uap->client_id) { | |
1984 | if (uap->client_id_len != sizeof(uuid_t)) { | |
1985 | NECPLOG(LOG_ERR, "Incorrect length (got %d, expected %d)", uap->client_id_len, sizeof(uuid_t)); | |
1986 | error = ERANGE; | |
1987 | goto done; | |
1988 | } | |
1989 | ||
1990 | error = copyin(uap->client_id, client_id, sizeof(uuid_t)); | |
1991 | if (error) { | |
1992 | NECPLOG(LOG_ERR, "necp_client_copy client_id copyin error (%d)", error); | |
1993 | goto done; | |
1994 | } | |
1995 | } | |
1996 | ||
1997 | lck_mtx_lock(&fd_data->fd_lock); | |
1998 | LIST_FOREACH(find_client, &fd_data->clients, chain) { | |
1999 | if (uap->action == NECP_CLIENT_ACTION_COPY_RESULT && | |
2000 | uuid_is_null(client_id)) { | |
2001 | if (!find_client->result_read || !find_client->assigned_result_read) { | |
2002 | client = find_client; | |
2003 | break; | |
2004 | } | |
2005 | } else if (uuid_compare(find_client->client_id, client_id) == 0) { | |
2006 | client = find_client; | |
2007 | break; | |
2008 | } | |
2009 | } | |
2010 | ||
2011 | if (client != NULL) { | |
2012 | error = necp_client_copy_internal(client, FALSE, uap, retval); | |
2013 | } | |
2014 | ||
2015 | // Unlock our own client before moving on or returning | |
2016 | lck_mtx_unlock(&fd_data->fd_lock); | |
2017 | ||
2018 | if (client == NULL) { | |
2019 | if (fd_data->flags & NECP_OPEN_FLAG_OBSERVER) { | |
2020 | // Observers are allowed to lookup clients on other fds | |
2021 | ||
2022 | // Lock list | |
2023 | lck_rw_lock_shared(&necp_fd_lock); | |
2024 | struct necp_fd_data *client_fd = NULL; | |
2025 | LIST_FOREACH(client_fd, &necp_fd_list, chain) { | |
2026 | // Lock client | |
2027 | lck_mtx_lock(&client_fd->fd_lock); | |
2028 | find_client = NULL; | |
2029 | LIST_FOREACH(find_client, &client_fd->clients, chain) { | |
2030 | if (uuid_compare(find_client->client_id, client_id) == 0) { | |
2031 | client = find_client; | |
2032 | break; | |
2033 | } | |
2034 | } | |
2035 | ||
2036 | if (client != NULL) { | |
2037 | // Matched, copy out data | |
2038 | error = necp_client_copy_internal(client, TRUE, uap, retval); | |
2039 | } | |
2040 | ||
2041 | // Unlock client | |
2042 | lck_mtx_unlock(&client_fd->fd_lock); | |
2043 | ||
2044 | if (client != NULL) { | |
2045 | break; | |
2046 | } | |
2047 | } | |
2048 | ||
2049 | // Unlock list | |
2050 | lck_rw_done(&necp_fd_lock); | |
2051 | ||
2052 | // No client found, fail | |
2053 | if (client == NULL) { | |
2054 | error = ENOENT; | |
2055 | goto done; | |
2056 | } | |
2057 | } else { | |
2058 | // No client found, and not allowed to search other fds, fail | |
2059 | error = ENOENT; | |
2060 | goto done; | |
2061 | } | |
2062 | } | |
2063 | ||
2064 | done: | |
2065 | return (error); | |
2066 | } | |
2067 | ||
2068 | static int | |
2069 | necp_client_list(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
2070 | { | |
2071 | int error = 0; | |
2072 | struct necp_client *find_client = NULL; | |
2073 | uuid_t *list = NULL; | |
2074 | u_int32_t requested_client_count = 0; | |
2075 | u_int32_t client_count = 0; | |
2076 | ||
2077 | if (uap->buffer_size < sizeof(requested_client_count) || uap->buffer == 0) { | |
2078 | error = EINVAL; | |
2079 | goto done; | |
2080 | } | |
2081 | ||
2082 | if (!(fd_data->flags & NECP_OPEN_FLAG_OBSERVER)) { | |
2083 | NECPLOG0(LOG_ERR, "Client does not hold necessary entitlement to list other NECP clients"); | |
2084 | error = EACCES; | |
2085 | goto done; | |
2086 | } | |
2087 | ||
2088 | error = copyin(uap->buffer, &requested_client_count, sizeof(requested_client_count)); | |
2089 | if (error) { | |
2090 | goto done; | |
2091 | } | |
2092 | ||
2093 | if (uap->buffer_size != (sizeof(requested_client_count) + requested_client_count * sizeof(uuid_t))) { | |
2094 | error = EINVAL; | |
2095 | goto done; | |
2096 | } | |
2097 | ||
2098 | if (requested_client_count > 0) { | |
2099 | if ((list = _MALLOC(requested_client_count * sizeof(uuid_t), M_NECP, M_WAITOK | M_ZERO)) == NULL) { | |
2100 | error = ENOMEM; | |
2101 | goto done; | |
2102 | } | |
2103 | } | |
2104 | ||
2105 | // Lock list | |
2106 | lck_rw_lock_shared(&necp_fd_lock); | |
2107 | struct necp_fd_data *client_fd = NULL; | |
2108 | LIST_FOREACH(client_fd, &necp_fd_list, chain) { | |
2109 | // Lock client | |
2110 | lck_mtx_lock(&client_fd->fd_lock); | |
2111 | find_client = NULL; | |
2112 | LIST_FOREACH(find_client, &client_fd->clients, chain) { | |
2113 | if (!uuid_is_null(find_client->client_id)) { | |
2114 | if (client_count < requested_client_count) { | |
2115 | uuid_copy(list[client_count], find_client->client_id); | |
2116 | } | |
2117 | client_count++; | |
2118 | } | |
2119 | } | |
2120 | lck_mtx_unlock(&client_fd->fd_lock); | |
2121 | } | |
2122 | ||
2123 | // Unlock list | |
2124 | lck_rw_done(&necp_fd_lock); | |
2125 | ||
2126 | error = copyout(&client_count, uap->buffer, sizeof(client_count)); | |
2127 | if (error) { | |
2128 | NECPLOG(LOG_ERR, "necp_client_list buffer copyout error (%d)", error); | |
2129 | goto done; | |
2130 | } | |
2131 | ||
2132 | if (requested_client_count > 0 && | |
2133 | client_count > 0 && | |
2134 | list != NULL) { | |
2135 | error = copyout(list, uap->buffer + sizeof(client_count), requested_client_count * sizeof(uuid_t)); | |
2136 | if (error) { | |
2137 | NECPLOG(LOG_ERR, "necp_client_list client count copyout error (%d)", error); | |
2138 | goto done; | |
2139 | } | |
2140 | } | |
2141 | done: | |
2142 | if (list != NULL) { | |
2143 | FREE(list, M_NECP); | |
2144 | } | |
2145 | *retval = error; | |
2146 | ||
2147 | return (error); | |
2148 | } | |
2149 | ||
2150 | static int | |
2151 | necp_client_request_nexus(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
2152 | { | |
2153 | int error = 0; | |
2154 | struct necp_client *client = NULL; | |
2155 | uuid_t client_id; | |
2156 | bool requested_nexus = FALSE; | |
2157 | ||
2158 | if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t)) { | |
2159 | error = EINVAL; | |
2160 | goto done; | |
2161 | } | |
2162 | ||
2163 | error = copyin(uap->client_id, client_id, sizeof(uuid_t)); | |
2164 | if (error) { | |
2165 | NECPLOG(LOG_ERR, "necp_client_request_nexus copyin client_id error (%d)", error); | |
2166 | goto done; | |
2167 | } | |
2168 | ||
2169 | lck_mtx_lock(&fd_data->fd_lock); | |
2170 | LIST_FOREACH(client, &fd_data->clients, chain) { | |
2171 | if (uuid_compare(client->client_id, client_id) == 0) { | |
2172 | // Request from nexus agent | |
2173 | if (!uuid_is_null(client->nexus_agent)) { | |
2174 | error = netagent_client_message(client->nexus_agent, client->client_id, | |
2175 | NETAGENT_MESSAGE_TYPE_REQUEST_NEXUS); | |
2176 | if (error == 0) { | |
2177 | requested_nexus = TRUE; | |
2178 | } | |
2179 | } | |
2180 | break; | |
2181 | } | |
2182 | } | |
2183 | lck_mtx_unlock(&fd_data->fd_lock); | |
2184 | ||
2185 | if (!requested_nexus && | |
2186 | error == 0) { | |
2187 | error = ENOENT; | |
2188 | } | |
2189 | done: | |
2190 | *retval = error; | |
2191 | ||
2192 | return (error); | |
2193 | } | |
2194 | ||
2195 | static void | |
2196 | necp_client_add_assertion(struct necp_client *client, uuid_t netagent_uuid) | |
2197 | { | |
2198 | struct necp_client_assertion *new_assertion = NULL; | |
2199 | ||
2200 | MALLOC(new_assertion, struct necp_client_assertion *, sizeof(*new_assertion), M_NECP, M_WAITOK); | |
2201 | if (new_assertion == NULL) { | |
2202 | NECPLOG0(LOG_ERR, "Failed to allocate assertion"); | |
2203 | return; | |
2204 | } | |
2205 | ||
2206 | uuid_copy(new_assertion->asserted_netagent, netagent_uuid); | |
2207 | ||
2208 | LIST_INSERT_HEAD(&client->assertion_list, new_assertion, assertion_chain); | |
2209 | } | |
2210 | ||
2211 | static bool | |
2212 | necp_client_remove_assertion(struct necp_client *client, uuid_t netagent_uuid) | |
2213 | { | |
2214 | struct necp_client_assertion *found_assertion = NULL; | |
2215 | struct necp_client_assertion *search_assertion = NULL; | |
2216 | LIST_FOREACH(search_assertion, &client->assertion_list, assertion_chain) { | |
2217 | if (uuid_compare(search_assertion->asserted_netagent, netagent_uuid) == 0) { | |
2218 | found_assertion = search_assertion; | |
2219 | break; | |
2220 | } | |
2221 | } | |
2222 | ||
2223 | if (found_assertion == NULL) { | |
2224 | NECPLOG0(LOG_ERR, "Netagent uuid not previously asserted"); | |
2225 | return false; | |
2226 | } | |
2227 | ||
2228 | LIST_REMOVE(found_assertion, assertion_chain); | |
2229 | FREE(found_assertion, M_NECP); | |
2230 | return true; | |
2231 | } | |
2232 | ||
2233 | static int | |
2234 | necp_client_agent_action(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
2235 | { | |
2236 | int error = 0; | |
2237 | struct necp_client *matched_client = NULL; | |
2238 | struct necp_client *client = NULL; | |
2239 | uuid_t client_id; | |
2240 | bool acted_on_agent = FALSE; | |
2241 | u_int8_t *parameters = NULL; | |
2242 | size_t parameters_size = uap->buffer_size; | |
2243 | ||
2244 | if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) || | |
2245 | uap->buffer_size == 0 || uap->buffer == 0) { | |
2246 | error = EINVAL; | |
2247 | goto done; | |
2248 | } | |
2249 | ||
2250 | error = copyin(uap->client_id, client_id, sizeof(uuid_t)); | |
2251 | if (error) { | |
2252 | NECPLOG(LOG_ERR, "necp_client_agent_action copyin client_id error (%d)", error); | |
2253 | goto done; | |
2254 | } | |
2255 | ||
2256 | if ((parameters = _MALLOC(uap->buffer_size, M_NECP, M_WAITOK | M_ZERO)) == NULL) { | |
2257 | error = ENOMEM; | |
2258 | goto done; | |
2259 | } | |
2260 | ||
2261 | error = copyin(uap->buffer, parameters, uap->buffer_size); | |
2262 | if (error) { | |
2263 | NECPLOG(LOG_ERR, "necp_client_agent_action parameters copyin error (%d)", error); | |
2264 | goto done; | |
2265 | } | |
2266 | ||
2267 | lck_mtx_lock(&fd_data->fd_lock); | |
2268 | LIST_FOREACH(client, &fd_data->clients, chain) { | |
2269 | if (uuid_compare(client->client_id, client_id) == 0) { | |
2270 | matched_client = client; | |
2271 | break; | |
2272 | } | |
2273 | } | |
2274 | if (matched_client) { | |
2275 | size_t offset = 0; | |
2276 | while ((offset + sizeof(u_int8_t) + sizeof(u_int32_t)) <= parameters_size) { | |
2277 | u_int8_t type = necp_buffer_get_tlv_type(parameters, offset); | |
2278 | u_int32_t length = necp_buffer_get_tlv_length(parameters, offset); | |
2279 | ||
2280 | if (length > (parameters_size - (offset + sizeof(u_int8_t) + sizeof(u_int32_t)))) { | |
2281 | // If the length is larger than what can fit in the remaining parameters size, bail | |
2282 | NECPLOG(LOG_ERR, "Invalid TLV length (%u)", length); | |
2283 | break; | |
2284 | } | |
2285 | ||
2286 | if (length > 0) { | |
2287 | u_int8_t *value = necp_buffer_get_tlv_value(parameters, offset, NULL); | |
2288 | if (length >= sizeof(uuid_t) && | |
2289 | value != NULL && | |
2290 | (type == NECP_CLIENT_PARAMETER_TRIGGER_AGENT || | |
2291 | type == NECP_CLIENT_PARAMETER_ASSERT_AGENT || | |
2292 | type == NECP_CLIENT_PARAMETER_UNASSERT_AGENT)) { | |
2293 | ||
2294 | uuid_t agent_uuid; | |
2295 | uuid_copy(agent_uuid, value); | |
2296 | u_int8_t netagent_message_type = 0; | |
2297 | if (type == NECP_CLIENT_PARAMETER_TRIGGER_AGENT) { | |
2298 | netagent_message_type = NETAGENT_MESSAGE_TYPE_CLIENT_TRIGGER; | |
2299 | } else if (type == NECP_CLIENT_PARAMETER_ASSERT_AGENT) { | |
2300 | netagent_message_type = NETAGENT_MESSAGE_TYPE_CLIENT_ASSERT; | |
2301 | } else if (type == NECP_CLIENT_PARAMETER_UNASSERT_AGENT) { | |
2302 | netagent_message_type = NETAGENT_MESSAGE_TYPE_CLIENT_UNASSERT; | |
2303 | } | |
2304 | ||
2305 | // Before unasserting, verify that the assertion was already taken | |
2306 | if (type == NECP_CLIENT_PARAMETER_UNASSERT_AGENT) { | |
2307 | if (!necp_client_remove_assertion(client, agent_uuid)) { | |
2308 | error = ENOENT; | |
2309 | break; | |
2310 | } | |
2311 | } | |
2312 | ||
2313 | error = netagent_client_message(agent_uuid, client_id, | |
2314 | netagent_message_type); | |
2315 | if (error == 0) { | |
2316 | acted_on_agent = TRUE; | |
2317 | } else { | |
2318 | break; | |
2319 | } | |
2320 | ||
2321 | // Only save the assertion if the action succeeded | |
2322 | if (type == NECP_CLIENT_PARAMETER_ASSERT_AGENT) { | |
2323 | necp_client_add_assertion(client, agent_uuid); | |
2324 | } | |
2325 | } | |
2326 | } | |
2327 | ||
2328 | offset += sizeof(u_int8_t) + sizeof(u_int32_t) + length; | |
2329 | } | |
2330 | } | |
2331 | lck_mtx_unlock(&fd_data->fd_lock); | |
2332 | ||
2333 | if (!acted_on_agent && | |
2334 | error == 0) { | |
2335 | error = ENOENT; | |
2336 | } | |
2337 | done: | |
2338 | *retval = error; | |
2339 | if (parameters != NULL) { | |
2340 | FREE(parameters, M_NECP); | |
2341 | parameters = NULL; | |
2342 | } | |
2343 | ||
2344 | return (error); | |
2345 | } | |
2346 | ||
2347 | static int | |
2348 | necp_client_copy_agent(__unused struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
2349 | { | |
2350 | int error = 0; | |
2351 | uuid_t agent_uuid; | |
2352 | ||
2353 | if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) || | |
2354 | uap->buffer_size == 0 || uap->buffer == 0) { | |
2355 | NECPLOG0(LOG_ERR, "necp_client_copy_agent bad input"); | |
2356 | error = EINVAL; | |
2357 | goto done; | |
2358 | } | |
2359 | ||
2360 | error = copyin(uap->client_id, agent_uuid, sizeof(uuid_t)); | |
2361 | if (error) { | |
2362 | NECPLOG(LOG_ERR, "necp_client_copy_agent copyin agent_uuid error (%d)", error); | |
2363 | goto done; | |
2364 | } | |
2365 | ||
2366 | error = netagent_copyout(agent_uuid, uap->buffer, uap->buffer_size); | |
2367 | if (error) { | |
2368 | NECPLOG(LOG_ERR, "necp_client_copy_agent netagent_copyout error (%d)", error); | |
2369 | goto done; | |
2370 | } | |
2371 | done: | |
2372 | *retval = error; | |
2373 | ||
2374 | return (error); | |
2375 | } | |
2376 | ||
2377 | static int | |
2378 | necp_client_agent_use(struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
2379 | { | |
2380 | int error = 0; | |
2381 | struct necp_client *matched_client = NULL; | |
2382 | struct necp_client *client = NULL; | |
2383 | uuid_t client_id; | |
2384 | struct necp_agent_use_parameters parameters; | |
2385 | ||
2386 | if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t) || | |
2387 | uap->buffer_size != sizeof(parameters) || uap->buffer == 0) { | |
2388 | error = EINVAL; | |
2389 | goto done; | |
2390 | } | |
2391 | ||
2392 | error = copyin(uap->client_id, client_id, sizeof(uuid_t)); | |
2393 | if (error) { | |
2394 | NECPLOG(LOG_ERR, "Copyin client_id error (%d)", error); | |
2395 | goto done; | |
2396 | } | |
2397 | ||
2398 | error = copyin(uap->buffer, ¶meters, uap->buffer_size); | |
2399 | if (error) { | |
2400 | NECPLOG(LOG_ERR, "Parameters copyin error (%d)", error); | |
2401 | goto done; | |
2402 | } | |
2403 | ||
2404 | lck_mtx_lock(&fd_data->fd_lock); | |
2405 | LIST_FOREACH(client, &fd_data->clients, chain) { | |
2406 | if (uuid_compare(client->client_id, client_id) == 0) { | |
2407 | matched_client = client; | |
2408 | break; | |
2409 | } | |
2410 | } | |
2411 | ||
2412 | if (matched_client) { | |
2413 | error = netagent_use(parameters.agent_uuid, ¶meters.out_use_count); | |
2414 | } else { | |
2415 | error = ENOENT; | |
2416 | } | |
2417 | ||
2418 | lck_mtx_unlock(&fd_data->fd_lock); | |
2419 | ||
2420 | if (error == 0) { | |
2421 | error = copyout(¶meters, uap->buffer, uap->buffer_size); | |
2422 | if (error) { | |
2423 | NECPLOG(LOG_ERR, "Parameters copyout error (%d)", error); | |
2424 | goto done; | |
2425 | } | |
2426 | } | |
2427 | ||
2428 | done: | |
2429 | *retval = error; | |
2430 | ||
2431 | return (error); | |
2432 | } | |
2433 | ||
2434 | static int | |
2435 | necp_client_copy_interface(__unused struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
2436 | { | |
2437 | int error = 0; | |
2438 | u_int32_t interface_index = 0; | |
2439 | struct necp_interface_details interface_details; | |
2440 | ||
2441 | if (uap->client_id == 0 || uap->client_id_len != sizeof(u_int32_t) || | |
2442 | uap->buffer_size < sizeof(interface_details) || uap->buffer == 0) { | |
2443 | NECPLOG0(LOG_ERR, "necp_client_copy_interface bad input"); | |
2444 | error = EINVAL; | |
2445 | goto done; | |
2446 | } | |
2447 | ||
2448 | error = copyin(uap->client_id, &interface_index, sizeof(u_int32_t)); | |
2449 | if (error) { | |
2450 | NECPLOG(LOG_ERR, "necp_client_copy_interface copyin interface_index error (%d)", error); | |
2451 | goto done; | |
2452 | } | |
2453 | ||
2454 | if (interface_index == 0) { | |
2455 | error = ENOENT; | |
2456 | NECPLOG(LOG_ERR, "necp_client_copy_interface bad interface_index (%d)", interface_index); | |
2457 | goto done; | |
2458 | } | |
2459 | ||
2460 | memset(&interface_details, 0, sizeof(interface_details)); | |
2461 | ||
2462 | ifnet_head_lock_shared(); | |
2463 | ifnet_t interface = NULL; | |
2464 | if (interface_index != IFSCOPE_NONE && interface_index <= (u_int32_t)if_index) { | |
2465 | interface = ifindex2ifnet[interface_index]; | |
2466 | } | |
2467 | ||
2468 | if (interface != NULL) { | |
2469 | if (interface->if_xname != NULL) { | |
2470 | strlcpy((char *)&interface_details.name, interface->if_xname, sizeof(interface_details.name)); | |
2471 | } | |
2472 | interface_details.index = interface->if_index; | |
2473 | interface_details.generation = ifnet_get_generation(interface); | |
2474 | if (interface->if_delegated.ifp != NULL) { | |
2475 | interface_details.delegate_index = interface->if_delegated.ifp->if_index; | |
2476 | } | |
2477 | interface_details.functional_type = if_functional_type(interface, TRUE); | |
2478 | if (IFNET_IS_EXPENSIVE(interface)) { | |
2479 | interface_details.flags |= NECP_INTERFACE_FLAG_EXPENSIVE; | |
2480 | } | |
2481 | interface_details.mtu = interface->if_mtu; | |
2482 | ||
2483 | u_int8_t ipv4_signature_len = sizeof(interface_details.ipv4_signature); | |
2484 | u_int16_t ipv4_signature_flags; | |
2485 | ifnet_get_netsignature(interface, AF_INET, &ipv4_signature_len, &ipv4_signature_flags, | |
2486 | (u_int8_t *)&interface_details.ipv4_signature); | |
2487 | ||
2488 | u_int8_t ipv6_signature_len = sizeof(interface_details.ipv6_signature); | |
2489 | u_int16_t ipv6_signature_flags; | |
2490 | ifnet_get_netsignature(interface, AF_INET6, &ipv6_signature_len, &ipv6_signature_flags, | |
2491 | (u_int8_t *)&interface_details.ipv6_signature); | |
2492 | } | |
2493 | ||
2494 | ifnet_head_done(); | |
2495 | ||
2496 | error = copyout(&interface_details, uap->buffer, sizeof(interface_details)); | |
2497 | if (error) { | |
2498 | NECPLOG(LOG_ERR, "necp_client_copy_interface copyout error (%d)", error); | |
2499 | goto done; | |
2500 | } | |
2501 | done: | |
2502 | *retval = error; | |
2503 | ||
2504 | return (error); | |
2505 | } | |
2506 | ||
2507 | static int | |
2508 | necp_client_stats_action(struct necp_client *client, user_addr_t buffer, user_size_t buffer_size) | |
2509 | { | |
2510 | int error = 0; | |
2511 | struct necp_stats_hdr *stats_hdr = NULL; | |
2512 | ||
2513 | if (client->stats_area) { | |
2514 | // Close old stats if required. | |
2515 | if ((client->stats_uaddr != buffer) || (client->stats_ulen != buffer_size)) { | |
2516 | necp_destroy_client_stats(client); | |
2517 | } | |
2518 | } | |
2519 | ||
2520 | if ((buffer == 0) || (buffer_size == 0)) { | |
2521 | goto done; | |
2522 | } | |
2523 | ||
2524 | if (client->stats_area) { | |
2525 | // An update | |
2526 | error = copyin(client->stats_uaddr, client->stats_area, client->stats_ulen); | |
2527 | if (error) { | |
2528 | NECPLOG(LOG_ERR, "necp_client_stats_action copyin error on update (%d)", error); | |
2529 | } else { | |
2530 | // Future use - check | |
2531 | stats_hdr = (necp_stats_hdr *)client->stats_area; | |
2532 | if (stats_hdr->necp_stats_event != 0) { | |
2533 | ntstat_userland_stats_event(client->stats_handler_context, (userland_stats_event_t)stats_hdr->necp_stats_event); | |
2534 | } | |
2535 | } | |
2536 | goto done; | |
2537 | } | |
2538 | ||
2539 | // A create | |
2540 | if ((buffer_size > sizeof(necp_all_stats)) || (buffer_size < sizeof(necp_stats_hdr))) { | |
2541 | error = EINVAL; | |
2542 | goto done; | |
2543 | } | |
2544 | ||
2545 | if ((stats_hdr = _MALLOC(buffer_size, M_NECP, M_WAITOK | M_ZERO)) == NULL) { | |
2546 | error = ENOMEM; | |
2547 | goto done; | |
2548 | } | |
2549 | ||
2550 | client->stats_handler_context = NULL; | |
2551 | client->stats_uaddr = buffer; | |
2552 | client->stats_ulen = buffer_size; | |
2553 | client->stats_area = stats_hdr; | |
2554 | error = copyin(client->stats_uaddr, client->stats_area, client->stats_ulen); | |
2555 | if (error) { | |
2556 | NECPLOG(LOG_ERR, "necp_client_stats_action copyin error on create (%d)", error); | |
2557 | goto done; | |
2558 | } | |
2559 | ||
2560 | switch (stats_hdr->necp_stats_type) { | |
2561 | case NECP_CLIENT_STATISTICS_TYPE_TCP: { | |
2562 | if (stats_hdr->necp_stats_ver == NECP_CLIENT_STATISTICS_TYPE_TCP_VER_1) { | |
2563 | client->stats_handler_context = ntstat_userland_stats_open((userland_stats_provider_context *)client, | |
2564 | NSTAT_PROVIDER_TCP_USERLAND, 0, necp_request_tcp_netstats); | |
2565 | if (client->stats_handler_context == NULL) { | |
2566 | error = EIO; | |
2567 | } | |
2568 | } else { | |
2569 | error = ENOTSUP; | |
2570 | } | |
2571 | break; | |
2572 | } | |
2573 | case NECP_CLIENT_STATISTICS_TYPE_UDP: { | |
2574 | if (stats_hdr->necp_stats_ver != NECP_CLIENT_STATISTICS_TYPE_UDP_VER_1) { | |
2575 | client->stats_handler_context = ntstat_userland_stats_open((userland_stats_provider_context *)client, | |
2576 | NSTAT_PROVIDER_UDP_USERLAND, 0, necp_request_udp_netstats); | |
2577 | if (client->stats_handler_context == NULL) { | |
2578 | error = EIO; | |
2579 | } | |
2580 | } else { | |
2581 | error = ENOTSUP; | |
2582 | } | |
2583 | break; | |
2584 | } | |
2585 | default: { | |
2586 | error = ENOTSUP; | |
2587 | break; | |
2588 | } | |
2589 | } | |
2590 | done: | |
2591 | if ((error) && (stats_hdr != NULL)) { | |
2592 | FREE(stats_hdr, M_NECP); | |
2593 | client->stats_area = NULL; | |
2594 | client->stats_handler_context = NULL; | |
2595 | client->stats_uaddr = 0; | |
2596 | client->stats_ulen = 0; | |
2597 | } | |
2598 | ||
2599 | return (error); | |
2600 | } | |
2601 | ||
2602 | static int | |
2603 | necp_client_set_statistics(__unused struct necp_fd_data *fd_data, struct necp_client_action_args *uap, int *retval) | |
2604 | { | |
2605 | int error = 0; | |
2606 | struct necp_client *find_client = NULL; | |
2607 | struct necp_client *client = NULL; | |
2608 | uuid_t client_id; | |
2609 | ||
2610 | if (uap->client_id == 0 || uap->client_id_len != sizeof(uuid_t)) { | |
2611 | error = EINVAL; | |
2612 | goto done; | |
2613 | } | |
2614 | ||
2615 | error = copyin(uap->client_id, client_id, sizeof(uuid_t)); | |
2616 | if (error) { | |
2617 | NECPLOG(LOG_ERR, "necp_client_set_statistics copyin client_id error (%d)", error); | |
2618 | goto done; | |
2619 | } | |
2620 | ||
2621 | lck_mtx_lock(&fd_data->fd_lock); | |
2622 | LIST_FOREACH(find_client, &fd_data->clients, chain) { | |
2623 | if (uuid_compare(find_client->client_id, client_id) == 0) { | |
2624 | client = find_client; | |
2625 | break; | |
2626 | } | |
2627 | } | |
2628 | ||
2629 | if (client) { | |
2630 | error = necp_client_stats_action(client, uap->buffer, uap->buffer_size); | |
2631 | } else { | |
2632 | error = ENOENT; | |
2633 | } | |
2634 | lck_mtx_unlock(&fd_data->fd_lock); | |
2635 | done: | |
2636 | *retval = error; | |
2637 | return (error); | |
2638 | } | |
2639 | ||
2640 | int | |
2641 | necp_client_action(struct proc *p, struct necp_client_action_args *uap, int *retval) | |
2642 | { | |
2643 | #pragma unused(p) | |
2644 | int error = 0; | |
2645 | int return_value = 0; | |
2646 | struct necp_fd_data *fd_data = NULL; | |
2647 | error = necp_find_fd_data(uap->necp_fd, &fd_data); | |
2648 | if (error != 0) { | |
2649 | NECPLOG(LOG_ERR, "necp_client_action find fd error (%d)", error); | |
2650 | return (error); | |
2651 | } | |
2652 | ||
2653 | u_int32_t action = uap->action; | |
2654 | switch (action) { | |
2655 | case NECP_CLIENT_ACTION_ADD: { | |
2656 | return_value = necp_client_add(fd_data, uap, retval); | |
2657 | break; | |
2658 | } | |
2659 | case NECP_CLIENT_ACTION_REMOVE: { | |
2660 | return_value = necp_client_remove(fd_data, uap, retval); | |
2661 | break; | |
2662 | } | |
2663 | case NECP_CLIENT_ACTION_COPY_PARAMETERS: | |
2664 | case NECP_CLIENT_ACTION_COPY_RESULT: { | |
2665 | return_value = necp_client_copy(fd_data, uap, retval); | |
2666 | break; | |
2667 | } | |
2668 | case NECP_CLIENT_ACTION_COPY_LIST: { | |
2669 | return_value = necp_client_list(fd_data, uap, retval); | |
2670 | break; | |
2671 | } | |
2672 | case NECP_CLIENT_ACTION_REQUEST_NEXUS_INSTANCE: { | |
2673 | return_value = necp_client_request_nexus(fd_data, uap, retval); | |
2674 | break; | |
2675 | } | |
2676 | case NECP_CLIENT_ACTION_AGENT: { | |
2677 | return_value = necp_client_agent_action(fd_data, uap, retval); | |
2678 | break; | |
2679 | } | |
2680 | case NECP_CLIENT_ACTION_COPY_AGENT: { | |
2681 | return_value = necp_client_copy_agent(fd_data, uap, retval); | |
2682 | break; | |
2683 | } | |
2684 | case NECP_CLIENT_ACTION_AGENT_USE: { | |
2685 | return_value = necp_client_agent_use(fd_data, uap, retval); | |
2686 | break; | |
2687 | } | |
2688 | case NECP_CLIENT_ACTION_COPY_INTERFACE: { | |
2689 | return_value = necp_client_copy_interface(fd_data, uap, retval); | |
2690 | break; | |
2691 | } | |
2692 | case NECP_CLIENT_ACTION_SET_STATISTICS: { | |
2693 | return_value = necp_client_set_statistics(fd_data, uap, retval); | |
2694 | break; | |
2695 | } | |
2696 | default: { | |
2697 | NECPLOG(LOG_ERR, "necp_client_action unknown action (%u)", action); | |
2698 | return_value = EINVAL; | |
2699 | break; | |
2700 | } | |
2701 | } | |
2702 | ||
2703 | file_drop(uap->necp_fd); | |
2704 | ||
2705 | return (return_value); | |
2706 | } | |
2707 | ||
2708 | #define NECP_MAX_MATCH_POLICY_PARAMETER_SIZE 1024 | |
2709 | ||
2710 | int | |
2711 | necp_match_policy(struct proc *p, struct necp_match_policy_args *uap, int32_t *retval) | |
2712 | { | |
2713 | #pragma unused(retval) | |
2714 | u_int8_t *parameters = NULL; | |
2715 | struct necp_aggregate_result returned_result; | |
2716 | int error = 0; | |
2717 | ||
2718 | if (uap == NULL) { | |
2719 | error = EINVAL; | |
2720 | goto done; | |
2721 | } | |
2722 | ||
2723 | if (uap->parameters == 0 || uap->parameters_size == 0 || uap->parameters_size > NECP_MAX_MATCH_POLICY_PARAMETER_SIZE || uap->returned_result == 0) { | |
2724 | error = EINVAL; | |
2725 | goto done; | |
2726 | } | |
2727 | ||
2728 | MALLOC(parameters, u_int8_t *, uap->parameters_size, M_NECP, M_WAITOK | M_ZERO); | |
2729 | if (parameters == NULL) { | |
2730 | error = ENOMEM; | |
2731 | goto done; | |
2732 | } | |
2733 | // Copy parameters in | |
2734 | error = copyin(uap->parameters, parameters, uap->parameters_size); | |
2735 | if (error) { | |
2736 | goto done; | |
2737 | } | |
2738 | ||
2739 | error = necp_application_find_policy_match_internal(p, parameters, uap->parameters_size, &returned_result, NULL, 0); | |
2740 | if (error) { | |
2741 | goto done; | |
2742 | } | |
2743 | ||
2744 | // Copy return value back | |
2745 | error = copyout(&returned_result, uap->returned_result, sizeof(struct necp_aggregate_result)); | |
2746 | if (error) { | |
2747 | goto done; | |
2748 | } | |
2749 | done: | |
2750 | if (parameters != NULL) { | |
2751 | FREE(parameters, M_NECP); | |
2752 | } | |
2753 | return (error); | |
2754 | } | |
2755 | ||
2756 | /// Socket operations | |
2757 | #define NECP_MAX_SOCKET_ATTRIBUTE_STRING_LENGTH 253 | |
2758 | ||
2759 | static bool | |
2760 | necp_set_socket_attribute(u_int8_t *buffer, size_t buffer_length, u_int8_t type, char **buffer_p) | |
2761 | { | |
2762 | int error = 0; | |
2763 | int cursor = 0; | |
2764 | size_t string_size = 0; | |
2765 | char *local_string = NULL; | |
2766 | u_int8_t *value = NULL; | |
2767 | ||
2768 | cursor = necp_buffer_find_tlv(buffer, buffer_length, 0, type, 0); | |
2769 | if (cursor < 0) { | |
2770 | // This will clear out the parameter | |
2771 | goto done; | |
2772 | } | |
2773 | ||
2774 | string_size = necp_buffer_get_tlv_length(buffer, cursor); | |
2775 | if (string_size == 0 || string_size > NECP_MAX_SOCKET_ATTRIBUTE_STRING_LENGTH) { | |
2776 | // This will clear out the parameter | |
2777 | goto done; | |
2778 | } | |
2779 | ||
2780 | MALLOC(local_string, char *, string_size + 1, M_NECP, M_WAITOK | M_ZERO); | |
2781 | if (local_string == NULL) { | |
2782 | NECPLOG(LOG_ERR, "Failed to allocate a socket attribute buffer (size %d)", string_size); | |
2783 | goto fail; | |
2784 | } | |
2785 | ||
2786 | value = necp_buffer_get_tlv_value(buffer, cursor, NULL); | |
2787 | if (value == NULL) { | |
2788 | NECPLOG0(LOG_ERR, "Failed to get socket attribute"); | |
2789 | goto fail; | |
2790 | } | |
2791 | ||
2792 | memcpy(local_string, value, string_size); | |
2793 | local_string[string_size] = 0; | |
2794 | ||
2795 | done: | |
2796 | if (*buffer_p != NULL) { | |
2797 | FREE(*buffer_p, M_NECP); | |
2798 | *buffer_p = NULL; | |
2799 | } | |
2800 | ||
2801 | *buffer_p = local_string; | |
2802 | return (0); | |
2803 | fail: | |
2804 | if (local_string != NULL) { | |
2805 | FREE(local_string, M_NECP); | |
2806 | } | |
2807 | return (error); | |
2808 | } | |
2809 | ||
2810 | errno_t | |
2811 | necp_set_socket_attributes(struct socket *so, struct sockopt *sopt) | |
2812 | { | |
2813 | int error = 0; | |
2814 | u_int8_t *buffer = NULL; | |
2815 | struct inpcb *inp = NULL; | |
2816 | ||
2817 | if ((SOCK_DOM(so) != PF_INET | |
2818 | #if INET6 | |
2819 | && SOCK_DOM(so) != PF_INET6 | |
2820 | #endif | |
2821 | )) { | |
2822 | error = EINVAL; | |
2823 | goto done; | |
2824 | } | |
2825 | ||
2826 | inp = sotoinpcb(so); | |
2827 | ||
2828 | size_t valsize = sopt->sopt_valsize; | |
2829 | if (valsize == 0 || | |
2830 | valsize > ((sizeof(u_int8_t) + sizeof(u_int32_t) + NECP_MAX_SOCKET_ATTRIBUTE_STRING_LENGTH) * 2)) { | |
2831 | goto done; | |
2832 | } | |
2833 | ||
2834 | MALLOC(buffer, u_int8_t *, valsize, M_NECP, M_WAITOK | M_ZERO); | |
2835 | if (buffer == NULL) { | |
2836 | goto done; | |
2837 | } | |
2838 | ||
2839 | error = sooptcopyin(sopt, buffer, valsize, 0); | |
2840 | if (error) { | |
2841 | goto done; | |
2842 | } | |
2843 | ||
2844 | error = necp_set_socket_attribute(buffer, valsize, NECP_TLV_ATTRIBUTE_DOMAIN, &inp->inp_necp_attributes.inp_domain); | |
2845 | if (error) { | |
2846 | NECPLOG0(LOG_ERR, "Could not set domain TLV for socket attributes"); | |
2847 | goto done; | |
2848 | } | |
2849 | ||
2850 | error = necp_set_socket_attribute(buffer, valsize, NECP_TLV_ATTRIBUTE_ACCOUNT, &inp->inp_necp_attributes.inp_account); | |
2851 | if (error) { | |
2852 | NECPLOG0(LOG_ERR, "Could not set account TLV for socket attributes"); | |
2853 | goto done; | |
2854 | } | |
2855 | ||
2856 | if (necp_debug) { | |
2857 | NECPLOG(LOG_DEBUG, "Set on socket: Domain %s, Account %s", inp->inp_necp_attributes.inp_domain, inp->inp_necp_attributes.inp_account); | |
2858 | } | |
2859 | done: | |
2860 | if (buffer != NULL) { | |
2861 | FREE(buffer, M_NECP); | |
2862 | } | |
2863 | ||
2864 | return (error); | |
2865 | } | |
2866 | ||
2867 | errno_t | |
2868 | necp_get_socket_attributes(struct socket *so, struct sockopt *sopt) | |
2869 | { | |
2870 | int error = 0; | |
2871 | u_int8_t *buffer = NULL; | |
2872 | u_int8_t *cursor = NULL; | |
2873 | size_t valsize = 0; | |
2874 | struct inpcb *inp = sotoinpcb(so); | |
2875 | ||
2876 | if (inp->inp_necp_attributes.inp_domain != NULL) { | |
2877 | valsize += sizeof(u_int8_t) + sizeof(u_int32_t) + strlen(inp->inp_necp_attributes.inp_domain); | |
2878 | } | |
2879 | if (inp->inp_necp_attributes.inp_account != NULL) { | |
2880 | valsize += sizeof(u_int8_t) + sizeof(u_int32_t) + strlen(inp->inp_necp_attributes.inp_account); | |
2881 | } | |
2882 | if (valsize == 0) { | |
2883 | goto done; | |
2884 | } | |
2885 | ||
2886 | MALLOC(buffer, u_int8_t *, valsize, M_NECP, M_WAITOK | M_ZERO); | |
2887 | if (buffer == NULL) { | |
2888 | goto done; | |
2889 | } | |
2890 | ||
2891 | cursor = buffer; | |
2892 | if (inp->inp_necp_attributes.inp_domain != NULL) { | |
2893 | cursor = necp_buffer_write_tlv(cursor, NECP_TLV_ATTRIBUTE_DOMAIN, strlen(inp->inp_necp_attributes.inp_domain), inp->inp_necp_attributes.inp_domain); | |
2894 | } | |
2895 | ||
2896 | if (inp->inp_necp_attributes.inp_account != NULL) { | |
2897 | cursor = necp_buffer_write_tlv(cursor, NECP_TLV_ATTRIBUTE_ACCOUNT, strlen(inp->inp_necp_attributes.inp_account), inp->inp_necp_attributes.inp_account); | |
2898 | } | |
2899 | ||
2900 | error = sooptcopyout(sopt, buffer, valsize); | |
2901 | if (error) { | |
2902 | goto done; | |
2903 | } | |
2904 | done: | |
2905 | if (buffer != NULL) { | |
2906 | FREE(buffer, M_NECP); | |
2907 | } | |
2908 | ||
2909 | return (error); | |
2910 | } | |
2911 | ||
2912 | void | |
2913 | necp_inpcb_dispose(struct inpcb *inp) | |
2914 | { | |
2915 | if (inp->inp_necp_attributes.inp_domain != NULL) { | |
2916 | FREE(inp->inp_necp_attributes.inp_domain, M_NECP); | |
2917 | inp->inp_necp_attributes.inp_domain = NULL; | |
2918 | } | |
2919 | if (inp->inp_necp_attributes.inp_account != NULL) { | |
2920 | FREE(inp->inp_necp_attributes.inp_account, M_NECP); | |
2921 | inp->inp_necp_attributes.inp_account = NULL; | |
2922 | } | |
2923 | } | |
2924 | ||
2925 | /// Module init | |
2926 | ||
2927 | errno_t | |
2928 | necp_client_init(void) | |
2929 | { | |
2930 | errno_t result = 0; | |
2931 | ||
2932 | necp_fd_grp_attr = lck_grp_attr_alloc_init(); | |
2933 | if (necp_fd_grp_attr == NULL) { | |
2934 | NECPLOG0(LOG_ERR, "lck_grp_attr_alloc_init failed"); | |
2935 | result = ENOMEM; | |
2936 | goto done; | |
2937 | } | |
2938 | ||
2939 | necp_fd_mtx_grp = lck_grp_alloc_init("necp_fd", necp_fd_grp_attr); | |
2940 | if (necp_fd_mtx_grp == NULL) { | |
2941 | NECPLOG0(LOG_ERR, "lck_grp_alloc_init failed"); | |
2942 | result = ENOMEM; | |
2943 | goto done; | |
2944 | } | |
2945 | ||
2946 | necp_fd_mtx_attr = lck_attr_alloc_init(); | |
2947 | if (necp_fd_mtx_attr == NULL) { | |
2948 | NECPLOG0(LOG_ERR, "lck_attr_alloc_init failed"); | |
2949 | result = ENOMEM; | |
2950 | goto done; | |
2951 | } | |
2952 | ||
2953 | necp_client_tcall = thread_call_allocate(necp_update_all_clients_callout, NULL); | |
2954 | if (necp_client_tcall == NULL) { | |
2955 | NECPLOG0(LOG_ERR, "thread_call_allocate failed"); | |
2956 | result = ENOMEM; | |
2957 | goto done; | |
2958 | } | |
2959 | ||
2960 | lck_rw_init(&necp_fd_lock, necp_fd_mtx_grp, necp_fd_mtx_attr); | |
2961 | ||
2962 | LIST_INIT(&necp_fd_list); | |
2963 | ||
2964 | done: | |
2965 | if (result != 0) { | |
2966 | if (necp_fd_mtx_attr != NULL) { | |
2967 | lck_attr_free(necp_fd_mtx_attr); | |
2968 | necp_fd_mtx_attr = NULL; | |
2969 | } | |
2970 | if (necp_fd_mtx_grp != NULL) { | |
2971 | lck_grp_free(necp_fd_mtx_grp); | |
2972 | necp_fd_mtx_grp = NULL; | |
2973 | } | |
2974 | if (necp_fd_grp_attr != NULL) { | |
2975 | lck_grp_attr_free(necp_fd_grp_attr); | |
2976 | necp_fd_grp_attr = NULL; | |
2977 | } | |
2978 | } | |
2979 | return (result); | |
2980 | } |