1 /* macos-ioloop.c
2 *
3 * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * Simple event dispatcher for DNS.
18 */
19
20 #define _GNU_SOURCE
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <unistd.h>
26 #include <sys/uio.h>
27 #include <errno.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33 #include <sys/time.h>
34 #include <signal.h>
35 #include <net/if.h>
36 #include <ifaddrs.h>
37 #include <dns_sd.h>
38
39 #include <dispatch/dispatch.h>
40
41 #include "srp.h"
42 #include "dns-msg.h"
43 #include "srp-crypto.h"
44 #include "ioloop.h"
45 #include "xpc_client_advertising_proxy.h"
46
47 static bool connection_write_now(comm_t *NONNULL connection);
48
49 dispatch_queue_t ioloop_main_queue;
50
51 // Forward references
52 static void tcp_start(comm_t *NONNULL connection);
53
54 int64_t
ioloop_timenow(void)55 ioloop_timenow(void)
56 {
57 int64_t now;
58 struct timeval tv;
59 gettimeofday(&tv, 0);
60 now = (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000;
61 return now;
62 }
63
64 static void
wakeup_event(void * context)65 wakeup_event(void *context)
66 {
67 wakeup_t *wakeup = context;
68
69 // All ioloop wakeups are one-shot.
70 ioloop_cancel_wake_event(wakeup);
71
72 // Call the callback, which mustn't be null.
73 wakeup->wakeup(wakeup->context);
74 }
75
76 static void
wakeup_finalize(void * context)77 wakeup_finalize(void *context)
78 {
79 wakeup_t *wakeup = context;
80 if (wakeup->ref_count == 0) {
81 if (wakeup->dispatch_source != NULL) {
82 dispatch_release(wakeup->dispatch_source);
83 wakeup->dispatch_source = NULL;
84 }
85 if (wakeup->finalize != NULL) {
86 wakeup->finalize(wakeup->context);
87 }
88 free(wakeup);
89 }
90 }
91
92 void
ioloop_wakeup_retain_(wakeup_t * wakeup,const char * file,int line)93 ioloop_wakeup_retain_(wakeup_t *wakeup, const char *file, int line)
94 {
95 (void)file; (void)line;
96 RETAIN(wakeup);
97 }
98
99 void
ioloop_wakeup_release_(wakeup_t * wakeup,const char * file,int line)100 ioloop_wakeup_release_(wakeup_t *wakeup, const char *file, int line)
101 {
102 (void)file; (void)line;
103 RELEASE(wakeup, wakeup_finalize);
104 }
105
106 wakeup_t *
ioloop_wakeup_create(void)107 ioloop_wakeup_create(void)
108 {
109 wakeup_t *ret = calloc(1, sizeof(*ret));
110 if (ret) {
111 RETAIN_HERE(ret);
112 }
113 return ret;
114 }
115
116 bool
ioloop_add_wake_event(wakeup_t * wakeup,void * context,wakeup_callback_t callback,wakeup_callback_t finalize,int milliseconds)117 ioloop_add_wake_event(wakeup_t *wakeup, void *context, wakeup_callback_t callback, wakeup_callback_t finalize,
118 int milliseconds)
119 {
120 if (callback == NULL) {
121 ERROR("ioloop_add_wake_event called with null callback");
122 return false;
123 }
124 if (wakeup->dispatch_source != NULL) {
125 ioloop_cancel_wake_event(wakeup);
126 }
127 wakeup->wakeup = callback;
128 wakeup->context = context;
129 wakeup->finalize = finalize;
130
131 wakeup->dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, ioloop_main_queue);
132 if (wakeup->dispatch_source == NULL) {
133 ERROR("dispatch_source_create failed in ioloop_add_wake_event().");
134 return false;
135 }
136 dispatch_source_set_event_handler_f(wakeup->dispatch_source, wakeup_event);
137 dispatch_set_context(wakeup->dispatch_source, wakeup);
138
139 // libdispatch doesn't allow events that are scheduled to happen right now. But it is actually useful to be
140 // able to trigger an event to happen immediately, and this is the easiest way to do it from ioloop-we
141 // can't rely on just scheduling an asynchronous event on an event loop because that's specific to Mac.
142 if (milliseconds <= 0) {
143 ERROR("ioloop_add_wake_event: milliseconds = %d", milliseconds);
144 milliseconds = 10;
145 }
146 dispatch_source_set_timer(wakeup->dispatch_source,
147 dispatch_time(DISPATCH_TIME_NOW, (uint64_t)milliseconds * NSEC_PER_SEC / 1000),
148 (uint64_t)milliseconds * NSEC_PER_SEC / 1000, NSEC_PER_SEC / 100);
149 dispatch_resume(wakeup->dispatch_source);
150
151 return true;
152 }
153
154 void
ioloop_cancel_wake_event(wakeup_t * wakeup)155 ioloop_cancel_wake_event(wakeup_t *wakeup)
156 {
157 if (wakeup->dispatch_source != NULL) {
158 dispatch_source_cancel(wakeup->dispatch_source);
159 dispatch_release(wakeup->dispatch_source);
160 wakeup->dispatch_source = NULL;
161 }
162 }
163
164 bool
ioloop_init(void)165 ioloop_init(void)
166 {
167 ioloop_main_queue = dispatch_get_main_queue();
168 dispatch_retain(ioloop_main_queue);
169 return true;
170 }
171
172 int
ioloop(void)173 ioloop(void)
174 {
175 dispatch_main();
176 return 0;
177 }
178
179 #define connection_cancel(conn) connection_cancel_(conn, __FILE__, __LINE__)
180 static void
connection_cancel_(nw_connection_t connection,const char * file,int line)181 connection_cancel_(nw_connection_t connection, const char *file, int line)
182 {
183 if (connection == NULL) {
184 INFO("connection_cancel: null connection at " PUB_S_SRP ":%d", file, line);
185 } else {
186 INFO("connection_cancel: " PUB_S_SRP ":%d", file, line);
187 nw_connection_cancel(connection);
188 }
189 }
190
191 static void
comm_finalize(comm_t * comm)192 comm_finalize(comm_t *comm)
193 {
194 ERROR("comm_finalize");
195 if (comm->connection != NULL) {
196 nw_release(comm->connection);
197 comm->connection = NULL;
198 }
199 if (comm->listener != NULL) {
200 nw_release(comm->listener);
201 comm->listener = NULL;
202 }
203 if (comm->parameters) {
204 nw_release(comm->parameters);
205 comm->parameters = NULL;
206 }
207 if (comm->pending_write != NULL) {
208 dispatch_release(comm->pending_write);
209 comm->pending_write = NULL;
210 }
211 // If there is an nw_connection_t or nw_listener_t outstanding, then we will get an asynchronous callback
212 // later on. So we can't actually free the data structure yet, but the good news is that comm_finalize() will
213 // be called again later when the last outstanding asynchronous cancel is done, and then all of the stuff
214 // that follows this will happen.
215 #ifndef __clang_analyzer__
216 if (comm->ref_count > 0) {
217 return;
218 }
219 #endif
220 if (comm->idle_timer != NULL) {
221 ioloop_cancel_wake_event(comm->idle_timer);
222 RELEASE_HERE(comm->idle_timer, wakeup_finalize);
223 }
224 if (comm->name != NULL) {
225 free(comm->name);
226 }
227 if (comm->finalize != NULL) {
228 comm->finalize(comm->context);
229 }
230 free(comm);
231 }
232
233 void
ioloop_comm_retain_(comm_t * comm,const char * file,int line)234 ioloop_comm_retain_(comm_t *comm, const char *file, int line)
235 {
236 (void)file; (void)line;
237 RETAIN(comm);
238 }
239
240 void
ioloop_comm_release_(comm_t * comm,const char * file,int line)241 ioloop_comm_release_(comm_t *comm, const char *file, int line)
242 {
243 (void)file; (void)line;
244 RELEASE(comm, comm_finalize);
245 }
246
247 static message_t *
message_create(size_t message_size)248 message_create(size_t message_size)
249 {
250 message_t *message;
251
252 // Never should have a message shorter than this.
253 if (message_size < DNS_HEADER_SIZE) {
254 return NULL;
255 }
256
257 message = (message_t *)malloc(message_size + (sizeof (message_t)) - (sizeof (dns_wire_t)));
258 if (message) {
259 memset(message, 0, (sizeof (message_t)) - (sizeof (dns_wire_t)));
260 RETAIN_HERE(message);
261 }
262 return message;
263 }
264
265 void
ioloop_comm_cancel(comm_t * connection)266 ioloop_comm_cancel(comm_t *connection)
267 {
268 if (connection->connection != NULL) {
269 connection_cancel(connection->connection);
270 }
271 }
272
273 static void
message_finalize(message_t * message)274 message_finalize(message_t *message)
275 {
276 free(message);
277 }
278
279 void
ioloop_message_retain_(message_t * message,const char * file,int line)280 ioloop_message_retain_(message_t *message, const char *file, int line)
281 {
282 (void)file; (void)line;
283 RETAIN(message);
284 }
285
286 void
ioloop_message_release_(message_t * message,const char * file,int line)287 ioloop_message_release_(message_t *message, const char *file, int line)
288 {
289 (void)file; (void)line;
290 RELEASE(message, message_finalize);
291 }
292
293 bool
ioloop_send_message(comm_t * connection,message_t * responding_to,struct iovec * iov,int iov_len)294 ioloop_send_message(comm_t *connection, message_t *responding_to, struct iovec *iov, int iov_len)
295 {
296 dispatch_data_t data = NULL, new_data, combined;
297 int i;
298 uint16_t len = 0;
299
300 // Not needed on OSX because UDP conversations are treated as "connections."
301 (void)responding_to;
302
303 if (connection->connection == NULL) {
304 return false;
305 }
306
307 // Create a dispatch_data_t object that contains the data in the iov.
308 for (i = 0; i < iov_len; i++) {
309 new_data = dispatch_data_create(iov->iov_base, iov->iov_len,
310 ioloop_main_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
311 len += iov->iov_len;
312 if (data != NULL) {
313 if (new_data != NULL) {
314 // Subsequent times through
315 combined = dispatch_data_create_concat(data, new_data);
316 dispatch_release(data);
317 dispatch_release(new_data);
318 data = combined;
319 } else {
320 // Fail
321 dispatch_release(data);
322 data = NULL;
323 }
324 } else {
325 // First time through
326 data = new_data;
327 }
328 if (data == NULL) {
329 ERROR("ioloop_send_message: no memory.");
330 return false;
331 }
332 }
333
334 if (len == 0) {
335 if (data) {
336 dispatch_release(data);
337 }
338 return false;
339 }
340
341 // TCP requires a length as well as the payload.
342 if (connection->tcp_stream) {
343 len = htons(len);
344 new_data = dispatch_data_create(&len, sizeof (len), ioloop_main_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
345 if (new_data == NULL) {
346 if (data != NULL) {
347 dispatch_release(data);
348 }
349 return false;
350 }
351 // Length is at beginning.
352 combined = dispatch_data_create_concat(new_data, data);
353 dispatch_release(data);
354 dispatch_release(new_data);
355 if (combined == NULL) {
356 return false;
357 }
358 data = combined;
359 }
360
361 if (connection->pending_write != NULL) {
362 ERROR("Dropping pending write on " PRI_S_SRP, connection->name ? connection->name : "<null>");
363 }
364 connection->pending_write = data;
365 if (connection->connection_ready) {
366 return connection_write_now(connection);
367 }
368 return true;
369 }
370
371 static bool
connection_write_now(comm_t * connection)372 connection_write_now(comm_t *connection)
373 {
374 // Retain the connection once for each write that's pending, so that it's never finalized while
375 // there's a write in progress.
376 connection->writes_pending++;
377 RETAIN_HERE(connection);
378 nw_connection_send(connection->connection, connection->pending_write, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true,
379 ^(nw_error_t _Nullable error) {
380 if (error != NULL) {
381 ERROR("ioloop_send_message: write failed: " PUB_S_SRP,
382 strerror(nw_error_get_error_code(error)));
383 connection_cancel(connection->connection);
384 }
385 if (connection->writes_pending > 0) {
386 connection->writes_pending--;
387 RELEASE_HERE(connection, comm_finalize);
388 } else {
389 ERROR("ioloop_send_message: write callback reached with no writes marked pending.");
390 }
391 });
392 // nw_connection_send should retain this, so let go of our reference to it.
393 dispatch_release(connection->pending_write);
394 connection->pending_write = NULL;
395 return true;
396 }
397
398 static bool
datagram_read(comm_t * connection,size_t length,dispatch_data_t content,nw_error_t error)399 datagram_read(comm_t *connection, size_t length, dispatch_data_t content, nw_error_t error)
400 {
401 message_t *message = NULL;
402 bool ret = true, *retp = &ret;
403
404 if (error != NULL) {
405 ERROR("datagram_read: " PUB_S_SRP, strerror(nw_error_get_error_code(error)));
406 ret = false;
407 goto out;
408 }
409 if (length > UINT16_MAX) {
410 ERROR("datagram_read: oversized datagram length %zd", length);
411 ret = false;
412 goto out;
413 }
414 message = message_create(length);
415 if (message == NULL) {
416 ERROR("datagram_read: unable to allocate message.");
417 ret = false;
418 goto out;
419 }
420 message->length = (uint16_t)length;
421 dispatch_data_apply(content,
422 ^bool (dispatch_data_t __unused region, size_t offset, const void *buffer, size_t size) {
423 if (message->length < offset + size) {
424 ERROR("datagram_read: data region %zd:%zd is out of range for message length %d",
425 offset, size, message->length);
426 *retp = false;
427 return false;
428 }
429 memcpy(((uint8_t *)&message->wire) + offset, buffer, size);
430 return true;
431 });
432 if (ret == true) {
433 // Process the message.
434 connection->datagram_callback(connection, message, connection->context);
435 }
436
437 out:
438 if (message != NULL) {
439 ioloop_message_release(message);
440 }
441 if (!ret) {
442 connection_cancel(connection->connection);
443 }
444 return ret;
445 }
446
447 static void
tcp_read(comm_t * connection,size_t length,dispatch_data_t content,nw_error_t error)448 tcp_read(comm_t *connection, size_t length, dispatch_data_t content, nw_error_t error)
449 {
450 if (error != NULL) {
451 connection_cancel(connection->connection);
452 return;
453 }
454 if (datagram_read(connection, length, content, error)) {
455 // Wait for the next frame
456 tcp_start(connection);
457 }
458 }
459
460 static void
tcp_read_length(comm_t * connection,dispatch_data_t content,nw_error_t error)461 tcp_read_length(comm_t *connection, dispatch_data_t content, nw_error_t error)
462 {
463 size_t length;
464 uint32_t bytes_to_read;
465 const uint8_t *lenbuf;
466 dispatch_data_t map;
467
468 if (error != NULL) {
469 ERROR("tcp_read_length: " PUB_S_SRP, strerror(nw_error_get_error_code(error)));
470 fail:
471 connection_cancel(connection->connection);
472 return;
473 }
474 if (connection->connection == NULL) {
475 return;
476 }
477 if (content == NULL) {
478 INFO("tcp_read_length: remote end closed connection.");
479 goto fail;
480 }
481
482 map = dispatch_data_create_map(content, (const void **)&lenbuf, &length);
483 if (map == NULL) {
484 ERROR("tcp_read_length: map create failed");
485 goto fail;
486 } else if (length != 2) {
487 ERROR("tcp_read_length: invalid length = %zu", length);
488 goto fail;
489 }
490 bytes_to_read = ((unsigned)(lenbuf[0]) << 8) | ((unsigned)lenbuf[1]);
491 nw_connection_receive(connection->connection, bytes_to_read, bytes_to_read,
492 ^(dispatch_data_t new_content, nw_content_context_t __unused new_context,
493 bool __unused is_complete, nw_error_t new_error) {
494 tcp_read(connection, bytes_to_read, new_content, new_error);
495 });
496 }
497
498 static void __unused
connection_idle_wakeup_callback(void * context)499 connection_idle_wakeup_callback(void *context)
500 {
501 comm_t *connection = context;
502 ERROR("Connection " PRI_S_SRP " has gone idle", connection->name);
503 connection_cancel(connection->connection);
504 }
505
506 static void __unused
connection_idle_wakeup_finalize(void * context)507 connection_idle_wakeup_finalize(void *context)
508 {
509 comm_t *connection = context;
510 connection->idle_timer = NULL;
511 }
512
513 static void
tcp_start(comm_t * connection)514 tcp_start(comm_t *connection)
515 {
516 if (connection->connection == NULL) {
517 return;
518 }
519 // We want to disconnect if the connection is idle for more than a short while.
520 if (connection->idle_timer == NULL) {
521 connection->idle_timer = ioloop_wakeup_create();
522 if (connection->idle_timer == NULL) {
523 // If we can't set up a timer, drop the connection now.
524 connection_cancel(connection->connection);
525 return;
526 }
527 }
528 ioloop_add_wake_event(connection->idle_timer, connection,
529 connection_idle_wakeup_callback, connection_idle_wakeup_finalize,
530 60 * 1000); // One minute
531 nw_connection_receive(connection->connection, 2, 2,
532 ^(dispatch_data_t content, nw_content_context_t __unused context,
533 bool is_complete, nw_error_t error) {
534 // For TCP connections, is_complete means the other end closed the connection.
535 if (is_complete || content == NULL) {
536 INFO("tcp_start: remote end closed connection.");
537 connection_cancel(connection->connection);
538 } else {
539 tcp_read_length(connection, content, error);
540 }
541 });
542 }
543
544 static void
udp_start(comm_t * connection)545 udp_start(comm_t *connection)
546 {
547 if (connection->connection == NULL) {
548 return;
549 }
550
551 // UDP is connectionless; the "connection" is just a placeholder that allows us to reply to the source.
552 // In principle, the five-tuple that is represented by the connection object should die as soon as the
553 // client is done retransmitting, since a later transaction should come from a different source port.
554 // Consequently, we set an idle timer: if we don't see any packets on this five-tuple after twenty seconds,
555 // it's unlikely that we will see any more, so it's time to collect the connection. If another packet
556 // does come in after this, a new connection will be created. The only risk is that if the cancel comes
557 // after a packet has arrived and been consumed by the nw_connection, but before we've called nw_connection_read,
558 // it will be lost. This should never happen for an existing SRP client, since the longest retry interval
559 // by default is 15 seconds; as the retry intervals get longer, it becomes safer to collect the connection
560 // and allow it to be recreated.
561 if (connection->server) {
562 if (connection->idle_timer == NULL) {
563 connection->idle_timer = ioloop_wakeup_create();
564 if (connection->idle_timer == NULL) {
565 // If we can't set up a timer, drop the connection now.
566 connection_cancel(connection->connection);
567 return;
568 }
569 }
570 ioloop_add_wake_event(connection->idle_timer, connection,
571 connection_idle_wakeup_callback, connection_idle_wakeup_finalize,
572 20 * 1000); // 20 seconds (15 seconds is the SRP client retry interval)
573 }
574
575 connection->read_pending = true; // When a read is pending, we have an extra refcount on the connection
576 RETAIN_HERE(connection);
577 nw_connection_receive_message(connection->connection,
578 ^(dispatch_data_t content, nw_content_context_t __unused context,
579 bool __unused is_complete, nw_error_t error) {
580 bool proceed = true;
581 if (content != NULL) {
582 proceed = datagram_read(connection, dispatch_data_get_size(content),
583 content, error);
584 }
585 if (content == NULL || error != NULL) {
586 connection_cancel(connection->connection);
587 }
588 // Once we have a five-tuple connection, we can't easily get rid of it, so keep
589 // reading.
590 else if (proceed) {
591 udp_start(connection);
592 }
593 RELEASE_HERE(connection, comm_finalize);
594 });
595 }
596
597 static void
connection_state_changed(comm_t * connection,nw_connection_state_t state,nw_error_t error)598 connection_state_changed(comm_t *connection, nw_connection_state_t state, nw_error_t error)
599 {
600 (void)error;
601 if (state == nw_connection_state_ready) {
602 INFO("connection_state_changed: " PRI_S_SRP " state is ready; error = %p",
603 connection->name != NULL ? connection->name : "<no name>", error);
604 // Set up a reader.
605 if (connection->tcp_stream) {
606 tcp_start(connection);
607 } else {
608 udp_start(connection);
609 }
610 connection->connection_ready = true;
611 // If there's a write pending, send it now.
612 if (connection->pending_write) {
613 connection_write_now(connection);
614 }
615 } else if (state == nw_connection_state_failed) {
616 INFO("connection_state_changed: " PRI_S_SRP " state is failed; error = %p",
617 connection->name != NULL ? connection->name : "<no name>", error);
618 connection_cancel(connection->connection);
619 } else if (state == nw_connection_state_cancelled) {
620 INFO("connection_state_changed: " PRI_S_SRP " state is canceled; error = %p",
621 connection->name != NULL ? connection->name : "<no name>", error);
622 // This releases the final reference to the connection object, which was held by the nw_connection_t.
623 RELEASE_HERE(connection, comm_finalize);
624 } else {
625 INFO("connection_state_changed: " PRI_S_SRP " state is %d; error = %p",
626 connection->name != NULL ? connection->name : "<no name>", state, error);
627 }
628 }
629
630 static void
connection_callback(comm_t * listener,nw_connection_t new_connection)631 connection_callback(comm_t *listener, nw_connection_t new_connection)
632 {
633 comm_t *connection = calloc(1, sizeof *connection);
634 if (connection == NULL) {
635 ERROR("Unable to receive connection: no memory.");
636 // Assuming that since we haven't retained the connection, it will be released?
637 // XXX RefCount Check.
638 return;
639 }
640
641 connection->connection = new_connection;
642 nw_retain(connection->connection);
643
644 connection->name = nw_connection_copy_description(connection->connection);
645 if (connection->name != NULL) {
646 INFO("Received connection from " PRI_S_SRP, connection->name);
647 } else {
648 ERROR("Unable to get description of new connection.");
649 }
650 connection->datagram_callback = listener->datagram_callback;
651 connection->tcp_stream = listener->tcp_stream;
652 connection->server = true;
653 nw_connection_set_state_changed_handler(connection->connection,
654 ^(nw_connection_state_t state, nw_error_t error)
655 { connection_state_changed(connection, state, error); });
656 nw_connection_set_queue(connection->connection, ioloop_main_queue);
657 nw_connection_start(connection->connection);
658 // new_connection holds a reference to the connection until it is canceled.
659 RETAIN_HERE(connection);
660 if (listener->connected != NULL) {
661 listener->connected(connection, listener->context);
662 }
663 }
664
665 static void
listener_finalize(comm_t * listener)666 listener_finalize(comm_t *listener)
667 {
668 if (listener->listener != NULL) {
669 nw_release(listener->listener);
670 listener->listener = NULL;
671 }
672 if (listener->name != NULL) {
673 free(listener->name);
674 }
675 if (listener->parameters) {
676 nw_release(listener->parameters);
677 }
678 if (listener->avoid_ports != NULL) {
679 free(listener->avoid_ports);
680 }
681 if (listener->finalize) {
682 listener->finalize(listener->context);
683 }
684 free(listener);
685 }
686
687 void
ioloop_listener_retain_(comm_t * listener,const char * file,int line)688 ioloop_listener_retain_(comm_t *listener, const char *file, int line)
689 {
690 RETAIN(listener);
691 }
692
693 void
ioloop_listener_release_(comm_t * listener,const char * file,int line)694 ioloop_listener_release_(comm_t *listener, const char *file, int line)
695 {
696 RELEASE(listener, listener_finalize);
697 }
698
699 void
ioloop_listener_cancel(comm_t * connection)700 ioloop_listener_cancel(comm_t *connection)
701 {
702 if (connection->listener != NULL) {
703 nw_listener_cancel(connection->listener);
704 // connection->listener will be released in ioloop_listener_state_changed_handler: nw_listener_state_cancelled.
705 }
706 }
707
708 static void
ioloop_listener_state_changed_handler(comm_t * listener,nw_listener_state_t state,nw_error_t error)709 ioloop_listener_state_changed_handler(comm_t *listener, nw_listener_state_t state, nw_error_t error)
710 {
711 int i;
712
713 #ifdef DEBUG_VERBOSE
714 if (listener->listener == NULL) {
715 if (state == nw_listener_state_cancelled) {
716 INFO("nw_listener gets released before the final nw_listener_state_cancelled event - name: " PRI_S_SRP,
717 listener->name);
718 } else {
719 ERROR("nw_listener gets released before the listener is canceled - name: " PRI_S_SRP ", state: %d",
720 listener->name, state);
721 }
722 }
723 #endif // DEBUG_VERBOSE
724
725 // Should never happen.
726 if (listener->listener == NULL && state != nw_listener_state_cancelled) {
727 return;
728 }
729
730 if (error != NULL) {
731 INFO("nw_listener_create:state changed: error");
732 } else {
733 if (state == nw_listener_state_waiting) {
734 INFO("nw_listener_create: waiting");
735 return;
736 } else if (state == nw_listener_state_failed) {
737 INFO("nw_listener_create: failed");
738 nw_listener_cancel(listener->listener);
739 } else if (state == nw_listener_state_ready) {
740 INFO("nw_listener_create: ready");
741 if (listener->avoiding) {
742 listener->listen_port = nw_listener_get_port(listener->listener);
743 if (listener->avoid_ports != NULL) {
744 for (i = 0; i < listener->num_avoid_ports; i++) {
745 if (listener->avoid_ports[i] == listener->listen_port) {
746 INFO("ioloop_listener_state_changed_handler: Got port %d, which we are avoiding.",
747 listener->listen_port);
748 listener->avoiding = true;
749 listener->listen_port = 0;
750 nw_listener_cancel(listener->listener);
751 return;
752 }
753 }
754 }
755 INFO("ioloop_listener_state_changed_handler: Got port %d.", listener->listen_port);
756 listener->avoiding = false;
757 if (listener->ready) {
758 listener->ready(listener->context, listener->listen_port);
759 }
760 }
761 } else if (state == nw_listener_state_cancelled) {
762 INFO("ioloop_listener_state_changed_handler: cancelled");
763 nw_release(listener->listener);
764 listener->listener = NULL;
765 if (listener->avoiding) {
766 listener->listener = nw_listener_create(listener->parameters);
767 if (listener->listener == NULL) {
768 ERROR("ioloop_listener_state_changed_handler: Unable to recreate listener.");
769 goto cancel;
770 } else {
771 RETAIN_HERE(listener);
772 nw_listener_set_state_changed_handler(listener->listener,
773 ^(nw_listener_state_t ev_state, nw_error_t ev_error) {
774 ioloop_listener_state_changed_handler(listener, ev_state, ev_error);
775 });
776 }
777 } else {
778 ;
779 cancel:
780 if (listener->cancel) {
781 listener->cancel(listener->context);
782 }
783 RELEASE_HERE(listener, listener_finalize);
784 }
785 }
786 }
787 }
788
789 comm_t *
ioloop_listener_create(bool stream,bool tls,uint16_t * avoid_ports,int num_avoid_ports,const addr_t * ip_address,const char * multicast,const char * name,datagram_callback_t datagram_callback,connect_callback_t connected,cancel_callback_t cancel,ready_callback_t ready,finalize_callback_t finalize,void * context)790 ioloop_listener_create(bool stream, bool tls, uint16_t *avoid_ports, int num_avoid_ports,
791 const addr_t *ip_address, const char *multicast, const char *name,
792 datagram_callback_t datagram_callback, connect_callback_t connected, cancel_callback_t cancel,
793 ready_callback_t ready, finalize_callback_t finalize, void *context)
794 {
795 comm_t *listener;
796 int family = (ip_address != NULL) ? ip_address->sa.sa_family : AF_UNSPEC;
797 uint16_t port;
798 char portbuf[10];
799 nw_endpoint_t endpoint;
800
801 if (ip_address == NULL) {
802 port = 0;
803 } else {
804 port = (family == AF_INET) ? ip_address->sin.sin_port : ip_address->sin6.sin6_port;
805 }
806
807 if (multicast != NULL) {
808 ERROR("ioloop_setup_listener: multicast not supported.");
809 return NULL;
810 }
811
812 if (datagram_callback == NULL) {
813 ERROR("ioloop_setup: no datagram callback provided.");
814 return NULL;
815 }
816
817 sprintf(portbuf, "%d", port);
818 listener = calloc(1, sizeof(*listener));
819 if (listener == NULL) {
820 if (ip_address == NULL) {
821 ERROR("No memory for listener on <NULL>#%d", port);
822 } else if (family == AF_INET) {
823 IPv4_ADDR_GEN_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf);
824 ERROR("No memory for listener on " PRI_IPv4_ADDR_SRP "#%d",
825 IPv4_ADDR_PARAM_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf), port);
826 } else if (family == AF_INET6) {
827 SEGMENTED_IPv6_ADDR_GEN_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
828 ERROR("No memory for listener on " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
829 SEGMENTED_IPv6_ADDR_PARAM_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf), port);
830 } else {
831 ERROR("No memory for listener on <family address other than AF_INET or AF_INET6: %d>#%d", family, port);
832 }
833 return NULL;
834 }
835 if (avoid_ports != NULL) {
836 listener->avoid_ports = malloc(num_avoid_ports * sizeof(uint16_t));
837 if (listener->avoid_ports == NULL) {
838 if (ip_address == NULL) {
839 ERROR("No memory for listener avoid_ports on <NULL>#%d", port);
840 } else if (family == AF_INET) {
841 IPv4_ADDR_GEN_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf);
842 ERROR("No memory for listener avoid_ports on " PRI_IPv4_ADDR_SRP "#%d",
843 IPv4_ADDR_PARAM_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf), port);
844 } else if (family == AF_INET6) {
845 SEGMENTED_IPv6_ADDR_GEN_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
846 ERROR("No memory for listener avoid_ports on " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
847 SEGMENTED_IPv6_ADDR_PARAM_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf), port);
848 } else {
849 ERROR("No memory for listener avoid_ports on <family address other than AF_INET or AF_INET6: %d>#%d",
850 family, port);
851 }
852 free(listener);
853 return NULL;
854 }
855 listener->num_avoid_ports = num_avoid_ports;
856 listener->avoiding = true;
857 }
858 RETAIN_HERE(listener);
859 if (port == 0) {
860 endpoint = NULL;
861 // Even though we don't have any ports to avoid, we still want the "avoiding" behavior in this case, since that
862 // is what triggers a call to the ready handler, which passes the port number that we got to it.
863 listener->avoiding = true;
864 } else {
865 listener->listen_port = port;
866 char ip_address_str[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
867 if (ip_address == NULL) {
868 if (family == AF_INET) {
869 snprintf(ip_address_str, sizeof(ip_address_str), "0.0.0.0");
870 } else {
871 // AF_INET6 or AF_UNSPEC
872 snprintf(ip_address_str, sizeof(ip_address_str), "::");
873 }
874 } else {
875 inet_ntop(family, ip_address->sa.sa_data, ip_address_str, sizeof(ip_address_str));
876 }
877 endpoint = nw_endpoint_create_host(ip_address_str, portbuf);
878 if (endpoint == NULL) {
879 ERROR("No memory for listener endpoint.");
880 RELEASE_HERE(listener, listener_finalize);
881 return NULL;
882 }
883 }
884
885 if (stream) {
886 listener->parameters = nw_parameters_create_secure_tcp(tls ? NW_PARAMETERS_DEFAULT_CONFIGURATION
887 : NW_PARAMETERS_DISABLE_PROTOCOL,
888 NW_PARAMETERS_DEFAULT_CONFIGURATION);
889 } else {
890 if (tls) {
891 ERROR("DTLS support not implemented.");
892 nw_release(endpoint);
893 RELEASE_HERE(listener, listener_finalize);
894 return NULL;
895 }
896 listener->parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL,
897 NW_PARAMETERS_DEFAULT_CONFIGURATION);
898 }
899 if (listener->parameters == NULL) {
900 ERROR("No memory for listener parameters.");
901 nw_release(endpoint);
902 RELEASE_HERE(listener, listener_finalize);
903 return NULL;
904 }
905
906 if (endpoint != NULL) {
907 nw_parameters_set_local_endpoint(listener->parameters, endpoint);
908 nw_release(endpoint);
909 }
910
911 if (tls) {
912 nw_protocol_options_t tls_options = nw_tls_create_options();
913 if (tls_options == NULL) {
914 ERROR("No memory for tls protocol options.");
915 RELEASE_HERE(listener, listener_finalize);
916 return NULL;
917 }
918 // XXX set up the listener certificate(s).
919 // XXX how to configure this onto the parameters object?
920 }
921
922 // Set SO_REUSEADDR.
923 nw_parameters_set_reuse_local_address(listener->parameters, true);
924
925 // Create the nw_listener_t.
926 listener->listener = nw_listener_create(listener->parameters);
927 if (listener->listener == NULL) {
928 ERROR("no memory for nw_listener object");
929 RELEASE_HERE(listener, listener_finalize);
930 return NULL;
931 }
932 nw_listener_set_new_connection_handler(listener->listener,
933 ^(nw_connection_t connection) { connection_callback(listener, connection); }
934 );
935
936 RETAIN_HERE(listener); // for the nw_listener_t
937 nw_listener_set_state_changed_handler(listener->listener, ^(nw_listener_state_t state, nw_error_t error) {
938 ioloop_listener_state_changed_handler(listener, state, error);
939 });
940
941 listener->name = strdup(name);
942 listener->datagram_callback = datagram_callback;
943 listener->cancel = cancel;
944 listener->ready = ready;
945 listener->finalize = finalize;
946 listener->context = context;
947 listener->connected = connected;
948 listener->tcp_stream = stream;
949
950 nw_listener_set_queue(listener->listener, ioloop_main_queue);
951 nw_listener_start(listener->listener);
952 // Listener has one refcount
953 return listener;
954 }
955
956 comm_t *
ioloop_connection_create(addr_t * NONNULL remote_address,bool tls,bool stream,datagram_callback_t datagram_callback,connect_callback_t connected,disconnect_callback_t disconnected,finalize_callback_t finalize,void * context)957 ioloop_connection_create(addr_t *NONNULL remote_address, bool tls, bool stream,
958 datagram_callback_t datagram_callback, connect_callback_t connected,
959 disconnect_callback_t disconnected, finalize_callback_t finalize, void *context)
960 {
961 comm_t *connection;
962 char portbuf[10];
963 nw_parameters_t parameters;
964 nw_endpoint_t endpoint;
965 char addrbuf[INET6_ADDRSTRLEN];
966
967 inet_ntop(remote_address->sa.sa_family, (remote_address->sa.sa_family == AF_INET
968 ? (void *)&remote_address->sin.sin_addr
969 : (void *)&remote_address->sin6.sin6_addr), addrbuf, sizeof addrbuf);
970 sprintf(portbuf, "%d", (remote_address->sa.sa_family == AF_INET
971 ? ntohs(remote_address->sin.sin_port)
972 : ntohs(remote_address->sin6.sin6_port)));
973 connection = calloc(1, sizeof(*connection));
974 if (connection == NULL) {
975 ERROR("No memory for connection");
976 return NULL;
977 }
978 // If we don't release this because of an error, this is the caller's reference to the comm_t.
979 RETAIN_HERE(connection);
980 endpoint = nw_endpoint_create_host(addrbuf, portbuf);
981 if (endpoint == NULL) {
982 ERROR("No memory for connection endpoint.");
983 RELEASE_HERE(connection, comm_finalize);
984 return NULL;
985 }
986
987 if (stream) {
988 parameters = nw_parameters_create_secure_tcp(tls ? NW_PARAMETERS_DEFAULT_CONFIGURATION
989 : NW_PARAMETERS_DISABLE_PROTOCOL,
990 NW_PARAMETERS_DEFAULT_CONFIGURATION);
991 } else {
992 if (tls) {
993 ERROR("DTLS support not implemented.");
994 nw_release(endpoint);
995 RELEASE_HERE(connection, comm_finalize);
996 return NULL;
997 }
998 parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL,
999 NW_PARAMETERS_DEFAULT_CONFIGURATION);
1000 }
1001 if (parameters == NULL) {
1002 ERROR("No memory for connection parameters.");
1003 nw_release(endpoint);
1004 RELEASE_HERE(connection, comm_finalize);
1005 return NULL;
1006 }
1007
1008 if (tls) {
1009 #ifdef NOTYET
1010 nw_protocol_options_t tls_options = nw_tls_create_options();
1011 if (tls_options == NULL) {
1012 ERROR("No memory for tls protocol options.");
1013 RELEASE_HERE(connection, comm_finalize);
1014 return NULL;
1015 }
1016 // XXX set up the connection certificate(s).
1017 // XXX how to configure this onto the parameters object?
1018 #endif
1019 }
1020
1021 connection->name = strdup(addrbuf);
1022
1023 // Create the nw_connection_t.
1024 connection->connection = nw_connection_create(endpoint, parameters);
1025 nw_release(endpoint);
1026 nw_release(parameters);
1027 if (connection->connection == NULL) {
1028 ERROR("no memory for nw_connection object");
1029 RELEASE_HERE(connection, comm_finalize);
1030 return NULL;
1031 }
1032
1033 connection->datagram_callback = datagram_callback;
1034 connection->connected = connected;
1035 connection->disconnected = disconnected;
1036 connection->finalize = finalize;
1037 connection->tcp_stream = stream;
1038 connection->context = context;
1039 nw_connection_set_state_changed_handler(connection->connection,
1040 ^(nw_connection_state_t state, nw_error_t error)
1041 { connection_state_changed(connection, state, error); });
1042 nw_connection_set_queue(connection->connection, ioloop_main_queue);
1043 // Until we get the canceled callback in connection_state_changed, the nw_connection_t holds a reference to this
1044 // comm_t object.
1045 RETAIN_HERE(connection);
1046 nw_connection_start(connection->connection);
1047 return connection;
1048 }
1049
1050 static void
subproc_finalize(subproc_t * subproc)1051 subproc_finalize(subproc_t *subproc)
1052 {
1053 int i;
1054 for (i = 0; i < subproc->argc; i++) {
1055 if (subproc->argv[i] != NULL) {
1056 free(subproc->argv[i]);
1057 subproc->argv[i] = NULL;
1058 }
1059 }
1060 if (subproc->dispatch_source != NULL) {
1061 dispatch_release(subproc->dispatch_source);
1062 }
1063 if (subproc->output_fd != NULL) {
1064 ioloop_file_descriptor_release(subproc->output_fd);
1065 }
1066 if (subproc->finalize != NULL) {
1067 subproc->finalize(subproc->context);
1068 }
1069 free(subproc);
1070 }
1071
subproc_cancel(void * context)1072 static void subproc_cancel(void *context)
1073 {
1074 subproc_t *subproc = context;
1075 subproc->dispatch_source = NULL;
1076 RELEASE_HERE(subproc, subproc_finalize);
1077 }
1078
1079 static void
subproc_event(void * context)1080 subproc_event(void *context)
1081 {
1082 subproc_t *subproc = context;
1083 pid_t pid;
1084 int status;
1085
1086 pid = waitpid(subproc->pid, &status, WNOHANG);
1087 if (pid <= 0) {
1088 return;
1089 }
1090 subproc->callback(subproc, status, NULL);
1091 if (!WIFSTOPPED(status)) {
1092 dispatch_source_cancel(subproc->dispatch_source);
1093 }
1094 }
1095
subproc_output_finalize(void * context)1096 static void subproc_output_finalize(void *context)
1097 {
1098 subproc_t *subproc = context;
1099 if (subproc->output_fd) {
1100 subproc->output_fd = NULL;
1101 }
1102 }
1103
1104 void
ioloop_subproc_release_(subproc_t * subproc,const char * file,int line)1105 ioloop_subproc_release_(subproc_t *subproc, const char *file, int line)
1106 {
1107 RELEASE(subproc, subproc_finalize);
1108 }
1109
1110 // Invoke the specified executable with the specified arguments. Call callback when it exits.
1111 // All failures are reported through the callback.
1112 subproc_t *
ioloop_subproc(const char * exepath,char * NULLABLE * argv,int argc,subproc_callback_t callback,io_callback_t output_callback,void * context)1113 ioloop_subproc(const char *exepath, char *NULLABLE *argv, int argc,
1114 subproc_callback_t callback, io_callback_t output_callback, void *context)
1115 {
1116 subproc_t *subproc;
1117 int i, rv;
1118 posix_spawn_file_actions_t actions;
1119 posix_spawnattr_t attrs;
1120
1121 if (callback == NULL) {
1122 ERROR("ioloop_add_wake_event called with null callback");
1123 return NULL;
1124 }
1125
1126 if (argc > MAX_SUBPROC_ARGS) {
1127 callback(NULL, 0, "too many subproc args");
1128 return NULL;
1129 }
1130
1131 subproc = calloc(1, sizeof *subproc);
1132 if (subproc == NULL) {
1133 callback(NULL, 0, "out of memory");
1134 return NULL;
1135 }
1136 RETAIN_HERE(subproc);
1137 if (output_callback != NULL) {
1138 rv = pipe(subproc->pipe_fds);
1139 if (rv < 0) {
1140 callback(NULL, 0, "unable to create pipe.");
1141 RELEASE_HERE(subproc, subproc_finalize);
1142 return NULL;
1143 }
1144 subproc->output_fd = ioloop_file_descriptor_create(subproc->pipe_fds[0], subproc, subproc_output_finalize);
1145 if (subproc->output_fd == NULL) {
1146 callback(NULL, 0, "out of memory.");
1147 close(subproc->pipe_fds[0]);
1148 close(subproc->pipe_fds[1]);
1149 RELEASE_HERE(subproc, subproc_finalize);
1150 return NULL;
1151 }
1152 }
1153
1154 subproc->argv[0] = strdup(exepath);
1155 if (subproc->argv[0] == NULL) {
1156 RELEASE_HERE(subproc, subproc_finalize);
1157 callback(NULL, 0, "out of memory");
1158 return NULL;
1159 }
1160 subproc->argc++;
1161 for (i = 0; i < argc; i++) {
1162 subproc->argv[i + 1] = strdup(argv[i]);
1163 if (subproc->argv[i + 1] == NULL) {
1164 RELEASE_HERE(subproc, subproc_finalize);
1165 callback(NULL, 0, "out of memory");
1166 return NULL;
1167 }
1168 subproc->argc++;
1169 }
1170
1171 // Set up for posix_spawn
1172 posix_spawn_file_actions_init(&actions);
1173 if (output_callback != NULL) {
1174 posix_spawn_file_actions_adddup2(&actions, subproc->pipe_fds[1], STDOUT_FILENO);
1175 posix_spawn_file_actions_addclose(&actions, subproc->pipe_fds[0]);
1176 posix_spawn_file_actions_addclose(&actions, subproc->pipe_fds[1]);
1177 }
1178 posix_spawnattr_init(&attrs);
1179 extern char **environ;
1180 rv = posix_spawn(&subproc->pid, exepath, &actions, &attrs, subproc->argv, environ);
1181 posix_spawn_file_actions_destroy(&actions);
1182 posix_spawnattr_destroy(&attrs);
1183 if (rv < 0) {
1184 ERROR("posix_spawn failed for " PUB_S_SRP ": " PUB_S_SRP, subproc->argv[0], strerror(errno));
1185 callback(subproc, 0, strerror(errno));
1186 RELEASE_HERE(subproc, subproc_finalize);
1187 return NULL;
1188 }
1189 subproc->callback = callback;
1190 subproc->context = context;
1191
1192 subproc->dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, subproc->pid, DISPATCH_PROC_EXIT,
1193 ioloop_main_queue);
1194 if (subproc->dispatch_source == NULL) {
1195 ERROR("dispatch_source_create failed in ioloop_add_wake_event().");
1196 return false;
1197 }
1198 dispatch_retain(subproc->dispatch_source);
1199 dispatch_source_set_event_handler_f(subproc->dispatch_source, subproc_event);
1200 dispatch_source_set_cancel_handler_f(subproc->dispatch_source, subproc_cancel);
1201 dispatch_set_context(subproc->dispatch_source, subproc);
1202 dispatch_activate(subproc->dispatch_source);
1203 RETAIN_HERE(subproc); // Dispatch has a reference
1204
1205 // Now that we have a viable subprocess, add the reader callback.
1206 if (output_callback != NULL && subproc->output_fd != NULL) {
1207 close(subproc->pipe_fds[1]);
1208 ioloop_add_reader(subproc->output_fd, output_callback);
1209 }
1210 return subproc;
1211 }
1212
1213 void
ioloop_dnssd_txn_cancel(dnssd_txn_t * txn)1214 ioloop_dnssd_txn_cancel(dnssd_txn_t *txn)
1215 {
1216 if (txn->sdref != NULL) {
1217 DNSServiceRefDeallocate(txn->sdref);
1218 txn->sdref = NULL;
1219 } else {
1220 INFO("ioloop_dnssd_txn_cancel: dead transaction.");
1221 }
1222 }
1223
1224 static void
dnssd_txn_finalize(dnssd_txn_t * txn)1225 dnssd_txn_finalize(dnssd_txn_t *txn)
1226 {
1227 if (txn->sdref != NULL) {
1228 ioloop_dnssd_txn_cancel(txn);
1229 }
1230 if (txn->finalize_callback) {
1231 txn->finalize_callback(txn->context);
1232 }
1233 free(txn);
1234 }
1235
1236 void
ioloop_dnssd_txn_retain_(dnssd_txn_t * dnssd_txn,const char * file,int line)1237 ioloop_dnssd_txn_retain_(dnssd_txn_t *dnssd_txn, const char *file, int line)
1238 {
1239 (void)file; (void)line;
1240 RETAIN(dnssd_txn);
1241 }
1242
1243 void
ioloop_dnssd_txn_release_(dnssd_txn_t * dnssd_txn,const char * file,int line)1244 ioloop_dnssd_txn_release_(dnssd_txn_t *dnssd_txn, const char *file, int line)
1245 {
1246 (void)file; (void)line;
1247 RELEASE(dnssd_txn, dnssd_txn_finalize);
1248 }
1249
1250 dnssd_txn_t *
ioloop_dnssd_txn_add_(DNSServiceRef ref,void * context,finalize_callback_t finalize_callback,const char * file,int line)1251 ioloop_dnssd_txn_add_(DNSServiceRef ref, void *context, finalize_callback_t finalize_callback, const char *file,
1252 int line)
1253 {
1254 dnssd_txn_t *txn = calloc(1, sizeof(*txn));
1255 (void)file; (void)line;
1256
1257 if (txn != NULL) {
1258 RETAIN(txn);
1259 txn->sdref = ref;
1260 txn->context = context;
1261 txn->finalize_callback = finalize_callback;
1262 DNSServiceSetDispatchQueue(ref, ioloop_main_queue);
1263 }
1264 return txn;
1265 }
1266
1267 void
ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t * NONNULL txn,void * aux_pointer)1268 ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t *NONNULL txn, void *aux_pointer)
1269 {
1270 txn->aux_pointer = aux_pointer;
1271 }
1272
1273 void *
ioloop_dnssd_txn_get_aux_pointer(dnssd_txn_t * NONNULL txn)1274 ioloop_dnssd_txn_get_aux_pointer(dnssd_txn_t *NONNULL txn)
1275 {
1276 return txn->aux_pointer;
1277 }
1278
1279 void *
ioloop_dnssd_txn_get_context(dnssd_txn_t * NONNULL txn)1280 ioloop_dnssd_txn_get_context(dnssd_txn_t *NONNULL txn)
1281 {
1282 return txn->context;
1283 }
1284
1285 static bool
ioloop_xpc_client_is_entitled(xpc_connection_t conn,const char * entitlement_name)1286 ioloop_xpc_client_is_entitled(xpc_connection_t conn, const char *entitlement_name)
1287 {
1288 bool entitled = false;
1289 xpc_object_t entitled_obj = xpc_connection_copy_entitlement_value(conn, entitlement_name);
1290
1291 if (entitled_obj) {
1292 if (xpc_get_type(entitled_obj) == XPC_TYPE_BOOL && xpc_bool_get_value(entitled_obj)) {
1293 entitled = true;
1294 }
1295 xpc_release(entitled_obj);
1296 } else {
1297 ERROR("ioloop_xpc_client_is_entitled: Client Entitlement is NULL");
1298 }
1299
1300 if (!entitled) {
1301 ERROR("ioloop_xpc_client_is_entitled: Client is missing Entitlement!");
1302 }
1303
1304 return entitled;
1305 }
1306
1307 static void
ioloop_xpc_accept(xpc_connection_t conn,const char * name,ioloop_xpc_callback_t callback)1308 ioloop_xpc_accept(xpc_connection_t conn, const char *name, ioloop_xpc_callback_t callback)
1309 {
1310 struct state {
1311 xpc_connection_t conn;
1312 ioloop_xpc_callback_t callback;
1313 } *state;
1314
1315 if (conn == NULL) {
1316 ERROR("ioloop_xpc_accept: listener has been canceled.");
1317 return;
1318 }
1319
1320 state = calloc(1, sizeof(*state));
1321 if (state == NULL) {
1322 ERROR("ioloop_xpc_accept: no memory for xpc connection state.");
1323 return;
1324 }
1325
1326 int pid = xpc_connection_get_pid(conn);
1327 int uid = xpc_connection_get_euid(conn);
1328
1329 if (!ioloop_xpc_client_is_entitled(conn, name)) {
1330 ERROR("ioloop_xpc_accept: connection from uid %d pid %d is missing entitlement " PUB_S_SRP ".", uid, pid, name);
1331 xpc_connection_cancel(conn);
1332 free(state);
1333 return;
1334 }
1335
1336 state->conn = conn;
1337 xpc_retain(conn);
1338 state->callback = callback;
1339 xpc_connection_set_target_queue(conn, ioloop_main_queue);
1340 xpc_connection_set_event_handler(conn, ^(xpc_object_t request) {
1341 xpc_type_t type = xpc_get_type(request);
1342
1343 if (request == XPC_ERROR_CONNECTION_INVALID) {
1344 INFO("ioloop_xpc_accept event handler: connection has been finalized.");
1345 if (state->callback != NULL) {
1346 state->callback(state->conn, NULL);
1347 }
1348 // We are guaranteed that this is the last callback, so we can safely free state.
1349 if (state->conn != NULL) {
1350 xpc_release(state->conn);
1351 state->conn = NULL;
1352 }
1353 free(state);
1354 } else if (type == XPC_TYPE_DICTIONARY) {
1355 // If the callback returns false, that means that we're done.
1356 if (state->callback != NULL) {
1357 if (!state->callback(state->conn, request)) {
1358 INFO("ioloop_xpc_accept event handler: callback indicated done.");
1359 xpc_connection_cancel(state->conn);
1360 state->callback = NULL;
1361 } else {
1362 INFO("ioloop_xpc_accept event handler: continuing.");
1363 }
1364 }
1365 } else {
1366 INFO("ioloop_xpc_accept event handler: client went away.");
1367 // Passing a null request to the callback means the client went away.
1368 xpc_connection_cancel(state->conn);
1369 if (state->callback != NULL) {
1370 callback(state->conn, NULL);
1371 }
1372 state->callback = NULL;
1373 }
1374 });
1375 xpc_connection_resume(conn);
1376 }
1377
1378 xpc_connection_t
ioloop_create_xpc_service(const char * name,ioloop_xpc_callback_t callback)1379 ioloop_create_xpc_service(const char *name, ioloop_xpc_callback_t callback)
1380 {
1381 xpc_connection_t listener = xpc_connection_create_mach_service(name, ioloop_main_queue,
1382 XPC_CONNECTION_MACH_SERVICE_LISTENER);
1383 if (listener == NULL || xpc_get_type(listener) != XPC_TYPE_CONNECTION) {
1384 ERROR("ioloop_create_xpc_service: " PUB_S_SRP ": unable to create listener %p", name, listener);
1385 if (listener != NULL) {
1386 xpc_release(listener);
1387 }
1388 return NULL;
1389 }
1390
1391 xpc_connection_set_event_handler(listener, ^(xpc_object_t eventmsg) {
1392 xpc_type_t type = xpc_get_type(eventmsg);
1393
1394 if (type == XPC_TYPE_CONNECTION) {
1395 INFO("ioloop_create_xpc_service: New " PUB_S_SRP " Client %p", name, eventmsg);
1396 ioloop_xpc_accept((xpc_connection_t)eventmsg, name, callback);
1397 }
1398 else if (type == XPC_TYPE_ERROR) // Ideally, we would never hit these cases
1399 {
1400 ERROR("ioloop_create_xpc_service: XPCError: " PUB_S_SRP,
1401 xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION));
1402 callback(NULL, NULL);
1403 }
1404 else
1405 {
1406 INFO("ioloop_create_xpc_service: Unknown EventMsg type");
1407 }
1408 });
1409 xpc_connection_resume(listener);
1410 return listener;
1411 }
1412
1413 static void
file_descriptor_finalize(void * context)1414 file_descriptor_finalize(void *context)
1415 {
1416 io_t *file_descriptor = context;
1417 if (file_descriptor->ref_count == 0) {
1418 if (file_descriptor->finalize) {
1419 file_descriptor->finalize(file_descriptor->context);
1420 }
1421 free(file_descriptor);
1422 }
1423 }
1424
1425 void
ioloop_file_descriptor_retain_(io_t * file_descriptor,const char * file,int line)1426 ioloop_file_descriptor_retain_(io_t *file_descriptor, const char *file, int line)
1427 {
1428 (void)file; (void)line;
1429 RETAIN(file_descriptor);
1430 }
1431
1432 void
ioloop_file_descriptor_release_(io_t * file_descriptor,const char * file,int line)1433 ioloop_file_descriptor_release_(io_t *file_descriptor, const char *file, int line)
1434 {
1435 (void)file; (void)line;
1436 RELEASE(file_descriptor, file_descriptor_finalize);
1437 }
1438
1439 io_t *
ioloop_file_descriptor_create_(int fd,void * context,finalize_callback_t finalize,const char * file,int line)1440 ioloop_file_descriptor_create_(int fd, void *context, finalize_callback_t finalize, const char *file, int line)
1441 {
1442 io_t *ret;
1443 ret = calloc(1, sizeof(*ret));
1444 if (ret) {
1445 ret->fd = fd;
1446 ret->context = context;
1447 ret->finalize = finalize;
1448 RETAIN(ret);
1449 }
1450 return ret;
1451 }
1452
1453 static void
ioloop_read_cancel(void * context)1454 ioloop_read_cancel(void *context)
1455 {
1456 io_t *io = context;
1457
1458 if (io->read_source != NULL) {
1459 dispatch_release(io->read_source);
1460 io->read_source = NULL;
1461 // Release the reference count that dispatch was holding.
1462 RELEASE_HERE(io, file_descriptor_finalize);
1463 }
1464 }
1465
1466 static void
ioloop_read_event(void * context)1467 ioloop_read_event(void *context)
1468 {
1469 io_t *io = context;
1470
1471 if (io->read_callback != NULL) {
1472 io->read_callback(io, io->context);
1473 }
1474 }
1475
1476 void
ioloop_close(io_t * io)1477 ioloop_close(io_t *io)
1478 {
1479 if (io->read_source != NULL) {
1480 dispatch_cancel(io->read_source);
1481 }
1482 if (io->write_source != NULL) {
1483 dispatch_cancel(io->write_source);
1484 }
1485 io->fd = -1;
1486 }
1487
1488 void
ioloop_add_reader(io_t * NONNULL io,io_callback_t NONNULL callback)1489 ioloop_add_reader(io_t *NONNULL io, io_callback_t NONNULL callback)
1490 {
1491 io->read_callback = callback;
1492 if (io->read_source == NULL) {
1493 io->read_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, io->fd, 0, ioloop_main_queue);
1494 }
1495 if (io->read_source == NULL) {
1496 ERROR("dispatch_source_create: unable to create read dispatch source.");
1497 return;
1498 }
1499 dispatch_source_set_event_handler_f(io->read_source, ioloop_read_event);
1500 dispatch_source_set_cancel_handler_f(io->read_source, ioloop_read_cancel);
1501 dispatch_set_context(io->read_source, io);
1502 RETAIN_HERE(io); // Dispatch will hold a reference.
1503 dispatch_resume(io->read_source);
1504 }
1505
1506 // Local Variables:
1507 // mode: C
1508 // tab-width: 4
1509 // c-file-style: "bsd"
1510 // c-basic-offset: 4
1511 // fill-column: 108
1512 // indent-tabs-mode: nil
1513 // End:
1514