3 * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 //*************************************************************************************************************
30 #include <netdb.h> // For gethostbyname()
31 #include <sys/socket.h> // For AF_INET, AF_INET6, etc.
32 #include <net/if.h> // For IF_NAMESIZE
33 #include <netinet/in.h> // For INADDR_NONE
34 #include <netinet/tcp.h> // For SOL_TCP, TCP_NOTSENT_LOWAT
35 #include <arpa/inet.h> // For inet_addr()
41 #include "DNSCommon.h"
42 #include "mDNSEmbeddedAPI.h"
44 #include "dso-transport.h"
46 #ifdef DSO_USES_NETWORK_FRAMEWORK
47 // Network Framework only works on MacOS X at the moment, and we need the locking primitives for
49 #include "mDNSMacOSX.h"
52 extern mDNS mDNSStorage;
54 static dso_connect_state_t *dso_connect_states; // DSO connect states that exist.
55 static dso_transport_t *dso_transport_states; // DSO transport states that exist.
56 #ifdef DSO_USES_NETWORK_FRAMEWORK
57 static uint32_t dso_transport_serial; // Serial number of next dso_transport_state_t or dso_connect_state_t.
58 static dispatch_queue_t dso_dispatch_queue;
60 static void dso_read_callback(TCPSocket *sock, void *context, mDNSBool connection_established,
65 dso_transport_init(void)
67 #ifdef DSO_USES_NETWORK_FRAMEWORK
68 // It's conceivable that we might want a separate queue, but we don't know yet, so for
69 // now we just use the main dispatch queue, which should be on the main dispatch thread,
70 // which is _NOT_ the kevent thread. So whenever we are doing anything on the dispatch
71 // queue (any completion functions for NW framework) we need to acquire the lock before
72 // we even look at any variables that could be changed by the other thread.
73 dso_dispatch_queue = dispatch_get_main_queue();
77 #ifdef DSO_USES_NETWORK_FRAMEWORK
78 static dso_connect_state_t *
79 dso_connect_state_find(uint32_t serial)
81 dso_connect_state_t *csp;
82 for (csp = dso_connect_states; csp; csp = csp->next) {
83 if (csp->serial == serial) {
92 dso_transport_finalize(dso_transport_t *transport)
94 dso_transport_t **tp = &dso_transport_states;
95 if (transport->connection != NULL) {
96 #ifdef DSO_USES_NETWORK_FRAMEWORK
97 nw_connection_cancel(transport->connection);
98 nw_release(transport->connection);
100 mDNSPlatformTCPCloseConnection(transport->connection);
102 transport->connection = NULL;
105 if (*tp == transport) {
106 *tp = transport->next;
108 tp = &transport->next;
114 // We do all of the finalization for the dso state object and any objects it depends on here in the
115 // dso_idle function because it avoids the possibility that some code on the way out to the event loop
116 // _after_ the DSO connection has been dropped might still write to the DSO structure or one of the
117 // dependent structures and corrupt the heap, or indeed in the unlikely event that this memory was
118 // freed and then reallocated before the exit to the event loop, there could be a bad pointer
121 // If there is a finalize function, that function MUST either free its own state that references the
122 // DSO state, or else must NULL out the pointer to the DSO state.
123 int64_t dso_transport_idle(void *context, int64_t now_in, int64_t next_timer_event)
125 dso_connect_state_t *cs, *cnext;
127 mDNSs32 now = (mDNSs32)now_in;
128 mDNSs32 next_event = (mDNSs32)next_timer_event;
130 // Notice if a DSO connection state is active but hasn't seen activity in a while.
131 for (cs = dso_connect_states; cs != NULL; cs = cnext) {
133 if (!cs->connecting && cs->last_event != 0) {
134 mDNSs32 expiry = cs->last_event + 90 * mDNSPlatformOneSecond;
135 if (now - expiry > 0) {
137 cs->callback(cs->context, NULL, NULL, kDSOEventType_ConnectFailed);
138 if (cs->lookup != NULL) {
139 DNSServiceRef ref = cs->lookup;
141 mDNS_DropLockBeforeCallback();
142 DNSServiceRefDeallocate(ref);
143 mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
146 if (next_timer_event - expiry > 0) {
147 next_timer_event = expiry;
150 } else if (!cs->connecting && cs->reconnect_time && now - cs->reconnect_time > 0) {
151 cs->reconnect_time = 0; // Don't try to immediately reconnect if it fails.
152 // If cs->dso->transport is non-null, we're already connected.
153 if (cs->dso && cs->dso->transport == NULL) {
154 cs->callback(cs->context, NULL, NULL, kDSOEventType_ShouldReconnect);
157 if (cs->reconnect_time != 0 && next_event - cs->reconnect_time > 0) {
158 next_event = cs->reconnect_time;
165 // Call to schedule a reconnect at a later time.
166 void dso_schedule_reconnect(mDNS *m, dso_connect_state_t *cs, mDNSs32 when)
168 cs->reconnect_time = when * mDNSPlatformOneSecond + m->timenow;
171 // If a DSO was created by an incoming connection, the creator of the listener can use this function
172 // to supply context and a callback for future events.
173 void dso_set_callback(dso_state_t *dso, void *context, dso_event_callback_t cb)
176 dso->context = context;
179 // This is called before writing a DSO message to the output buffer. length is the length of the message.
180 // Returns true if we have successfully selected for write (which means that we're under TCP_NOTSENT_LOWAT).
181 // Otherwise returns false. It is valid to write even if it returns false, but there is a risk that
182 // the write will return EWOULDBLOCK, at which point we'd have to blow away the connection. It is also
183 // valid to give up at this point and not write a message; as long as dso_write_finish isn't called, a later
184 // call to dso_write_start will overwrite the length that was stored by the previous invocation.
186 // The circumstance in which this would occur is that we have filled the kernel's TCP output buffer for this
187 // connection all the way up to TCP_NOTSENT_LOWAT, and then we get a query from the Discovery Proxy to which we
188 // need to respond. Because TCP_NOTSENT_LOWAT is fairly low, there should be a lot of room in the TCP output
189 // buffer for small responses; it would need to be the case that we are getting requests from the proxy at a
190 // high rate for us to fill the output buffer to the point where a write of a 12-byte response returns
191 // EWOULDBLOCK; in that case, things are so dysfunctional that killing the connection isn't any worse than
192 // allowing it to continue.
194 // An additional note about the motivation for this code: the idea originally was that we'd do scatter/gather
195 // I/O here: this lets us write everything out in a single sendmsg() call. This isn't used with the mDNSPlatformTCP
196 // code because it doesn't support scatter/gather. Network Framework does, however, and in principle we could
197 // write to the descriptor directly if that were really needed.
199 bool dso_write_start(dso_transport_t *transport, size_t length)
201 // The transport doesn't support messages outside of this range.
202 if (length < 12 || length > 65535) {
206 #ifdef DSO_USES_NETWORK_FRAMEWORK
209 if (transport->to_write != NULL) {
210 nw_release(transport->to_write);
211 transport->to_write = NULL;
213 lenbuf[0] = length >> 8;
214 lenbuf[1] = length & 255;
215 transport->to_write = dispatch_data_create(lenbuf, 2, dso_dispatch_queue,
216 DISPATCH_DATA_DESTRUCTOR_DEFAULT);
217 if (transport->to_write == NULL) {
218 transport->write_failed = true;
221 transport->bytes_to_write = length + 2;
223 // We don't have access to TCP_NOTSENT_LOWAT, so for now we track how many bytes we've written
224 // versus how many bytes that we've written have completed, and if that creeps above MAX_UNSENT_BYTES,
225 // we return false here to indicate that there is congestion.
226 if (transport->unsent_bytes > MAX_UNSENT_BYTES) {
232 transport->lenbuf[0] = length >> 8;
233 transport->lenbuf[1] = length & 255;
235 transport->to_write[0] = transport->lenbuf;
236 transport->write_lengths[0] = 2;
237 transport->num_to_write = 1;
239 return mDNSPlatformTCPWritable(transport->connection);
240 #endif // DSO_USES_NETWORK_FRAMEWORK
243 // Called to finish a write (dso_write_start .. dso_write .. [ dso_write ... ] dso_write_finish). The
244 // write must completely finish--if we get a partial write, this means that the connection is stalled, and
245 // so we drop it. Since this can call dso_drop, the caller must not reference the DSO state object
246 // after this call if the return value is false.
247 bool dso_write_finish(dso_transport_t *transport)
249 #ifdef DSO_USES_NETWORK_FRAMEWORK
250 uint32_t serial = transport->dso->serial;
251 int bytes_to_write = transport->bytes_to_write;
252 transport->bytes_to_write = 0;
253 if (transport->write_failed) {
254 dso_drop(transport->dso);
257 transport->unsent_bytes += bytes_to_write;
258 nw_connection_send(transport->connection, transport->to_write, NW_CONNECTION_DEFAULT_STREAM_CONTEXT, true,
259 ^(nw_error_t _Nullable error) {
262 dso = dso_find_by_serial(serial);
264 LogMsg("dso_write_finish: write failed: %s", strerror(nw_error_get_error_code(error)));
269 dso->transport->unsent_bytes -= bytes_to_write;
270 LogMsg("dso_write_finish completion routine: %d bytes written, %d bytes outstanding",
271 bytes_to_write, dso->transport->unsent_bytes);
273 KQueueUnlock("dso_write_finish completion routine");
275 nw_release(transport->to_write);
276 transport->to_write = NULL;
279 ssize_t result, total = 0;
282 if (transport->num_to_write > MAX_WRITE_HUNKS) {
283 LogMsg("dso_write_finish: fatal internal programming error: called %d times (more than limit of %d)",
284 transport->num_to_write, MAX_WRITE_HUNKS);
285 dso_drop(transport->dso);
289 // This is our ersatz scatter/gather I/O.
290 for (i = 0; i < transport->num_to_write; i++) {
291 result = mDNSPlatformWriteTCP(transport->connection, (const char *)transport->to_write[i], transport->write_lengths[i]);
292 if (result != transport->write_lengths[i]) {
294 LogMsg("dso_write_finish: fatal: mDNSPlatformWrite on %s returned %d", transport->dso->remote_name, errno);
296 LogMsg("dso_write_finish: fatal: mDNSPlatformWrite: short write on %s: %ld < %ld",
297 transport->dso->remote_name, (long)result, (long)total);
299 dso_drop(transport->dso);
307 // This function may only be called after a previous call to dso_write_start; it records the length of and
308 // pointer to the write buffer. These buffers must remain valid until dso_write_finish() is called. The
309 // caller is responsible for managing the memory they contain. The expected control flow for writing is:
310 // dso_write_start(); dso_write(); dso_write(); dso_write(); dso_write_finished(); There should be one or
311 // more calls to dso_write; these will ideally be translated into a single scatter/gather sendmsg call (or
312 // equivalent) to the kernel.
313 void dso_write(dso_transport_t *transport, const uint8_t *buf, size_t length)
319 #ifdef DSO_USES_NETWORK_FRAMEWORK
320 if (transport->write_failed) {
323 dispatch_data_t dpd = dispatch_data_create(buf, length, dso_dispatch_queue,
324 DISPATCH_DATA_DESTRUCTOR_DEFAULT);
326 transport->write_failed = true;
329 if (transport->to_write != NULL) {
330 dispatch_data_t dpc = dispatch_data_create_concat(transport->to_write, dpd);
331 dispatch_release(dpd);
332 dispatch_release(transport->to_write);
334 transport->to_write = NULL;
335 transport->write_failed = true;
338 transport->to_write = dpc;
341 // We'll report this in dso_write_finish();
342 if (transport->num_to_write >= MAX_WRITE_HUNKS) {
343 transport->num_to_write++;
347 transport->to_write[transport->num_to_write] = buf;
348 transport->write_lengths[transport->num_to_write] = length;
349 transport->num_to_write++;
353 // Write a DSO message
354 int dso_message_write(dso_state_t *dso, dso_message_t *msg, bool disregard_low_water)
356 dso_transport_t *transport = dso->transport;
357 if (transport->connection != NULL) {
358 if (dso_write_start(transport, dso_message_length(msg)) || disregard_low_water) {
359 dso_write(transport, msg->buf, msg->no_copy_bytes_offset);
360 dso_write(transport, msg->no_copy_bytes, msg->no_copy_bytes_len);
361 dso_write(transport, &msg->buf[msg->no_copy_bytes_offset], msg->cur - msg->no_copy_bytes_offset);
362 return dso_write_finish(transport);
365 return mStatus_NoMemoryErr;
368 // Replies to some message we were sent with a response code and no data.
369 // This is a convenience function for replies that do not require that a new
370 // packet be constructed. It takes advantage of the fact that the message
371 // to which this is a reply is still in the input buffer, and modifies that
372 // message in place to turn it into a response.
374 bool dso_send_simple_response(dso_state_t *dso, int rcode, const DNSMessageHeader *header, const char *pres)
376 dso_transport_t *transport = dso->transport;
377 (void)pres; // might want this later.
378 DNSMessageHeader response = *header;
380 // Just return the message, with no questions, answers, etc.
381 response.flags.b[1] = (response.flags.b[1] & ~kDNSFlag1_RC_Mask) | rcode;
382 response.flags.b[0] |= kDNSFlag0_QR_Response;
383 response.numQuestions = 0;
384 response.numAnswers = 0;
385 response.numAuthorities = 0;
386 response.numAdditionals = 0;
388 // Buffered write back to discovery proxy
389 (void)dso_write_start(transport, 12);
390 dso_write(transport, (uint8_t *)&response, 12);
391 if (!dso_write_finish(transport)) {
397 // DSO Message we received has a primary TLV that's not implemented.
398 // XXX is this what we're supposed to do here? check draft.
399 bool dso_send_not_implemented(dso_state_t *dso, const DNSMessageHeader *header)
401 return dso_send_simple_response(dso, kDNSFlag1_RC_DSOTypeNI, header, "DSOTYPENI");
404 // Non-DSO message we received is refused.
405 bool dso_send_refused(dso_state_t *dso, const DNSMessageHeader *header)
407 return dso_send_simple_response(dso, kDNSFlag1_RC_Refused, header, "REFUSED");
410 bool dso_send_formerr(dso_state_t *dso, const DNSMessageHeader *header)
412 return dso_send_simple_response(dso, kDNSFlag1_RC_FormErr, header, "FORMERR");
415 bool dso_send_servfail(dso_state_t *dso, const DNSMessageHeader *header)
417 return dso_send_simple_response(dso, kDNSFlag1_RC_ServFail, header, "SERVFAIL");
420 bool dso_send_name_error(dso_state_t *dso, const DNSMessageHeader *header)
422 return dso_send_simple_response(dso, kDNSFlag1_RC_NXDomain, header, "NXDOMAIN");
425 bool dso_send_no_error(dso_state_t *dso, const DNSMessageHeader *header)
427 return dso_send_simple_response(dso, kDNSFlag1_RC_NoErr, header, "NOERROR");
430 #ifdef DSO_USES_NETWORK_FRAMEWORK
431 static void dso_read_message(dso_transport_t *transport, size_t length);
433 static void dso_read_message_length(dso_transport_t *transport)
435 const uint32_t serial = transport->dso->serial;
436 if (transport->connection == NULL) {
437 LogMsg("dso_read_message_length called with null connection.");
440 nw_connection_receive(transport->connection, 2, 2,
441 ^(dispatch_data_t content, nw_content_context_t __unused context,
442 bool __unused is_complete, nw_error_t error) {
444 // Don't touch anything or look at anything until we have the lock.
446 dso = dso_find_by_serial(serial);
448 LogMsg("dso_read_message_length: read failed: %s",
449 strerror(nw_error_get_error_code(error)));
452 mDNS_Lock(&mDNSStorage);
454 mDNS_Unlock(&mDNSStorage);
456 } else if (content == NULL) {
457 LogMsg("dso_read_message_length: remote end closed connection.");
461 const uint8_t *lenbuf;
462 dispatch_data_t map = dispatch_data_create_map(content, (const void **)&lenbuf, &length);
464 LogMsg("dso_read_message_length: map create failed");
466 } else if (length != 2) {
467 LogMsg("dso_read_message_length: invalid length = %d", length);
470 length = ((unsigned)(lenbuf[0]) << 8) | ((unsigned)lenbuf[1]);
471 dso_read_message(transport, length);
473 KQueueUnlock("dso_read_message_length completion routine");
477 void dso_read_message(dso_transport_t *transport, size_t length)
479 const uint32_t serial = transport->dso->serial;
480 if (transport->connection == NULL) {
481 LogMsg("dso_read_message called with null connection.");
484 nw_connection_receive(transport->connection, length, length,
485 ^(dispatch_data_t content, nw_content_context_t __unused context,
486 bool __unused is_complete, nw_error_t error) {
488 // Don't touch anything or look at anything until we have the lock.
490 dso = dso_find_by_serial(serial);
492 LogMsg("dso_read_message: read failed: %s", strerror(nw_error_get_error_code(error)));
495 mDNS_Lock(&mDNSStorage);
497 mDNS_Unlock(&mDNSStorage);
499 } else if (content == NULL) {
500 LogMsg("dso_read_message: remote end closed connection");
504 const uint8_t *message;
505 dispatch_data_t map = dispatch_data_create_map(content, (const void **)&message, &bytes_read);
507 LogMsg("dso_read_message_length: map create failed");
509 } else if (bytes_read != length) {
510 LogMsg("dso_read_message_length: only %d of %d bytes read", bytes_read, length);
513 // Process the message.
514 mDNS_Lock(&mDNSStorage);
515 dns_message_received(dso, message, length);
516 mDNS_Unlock(&mDNSStorage);
518 // Now read the next message length.
519 dso_read_message_length(transport);
521 KQueueUnlock("dso_read_message completion routine");
525 // Called whenever there's data available on a DSO connection
526 void dso_read_callback(TCPSocket *sock, void *context, mDNSBool connection_established, int err)
528 dso_transport_t *transport = context;
530 mDNSBool closed = mDNSfalse;
532 mDNS_Lock(&mDNSStorage);
533 dso = transport->dso;
535 // This shouldn't ever happen.
537 LogMsg("dso_read_callback: error %d", err);
542 // Connection is already established by the time we set this up.
543 if (connection_established) {
547 // This will be true either if we have never read a message or
548 // if the last thing we did was to finish reading a message and
550 if (transport->message_length == 0) {
551 transport->need_length = true;
552 transport->inbufp = transport->inbuf;
553 transport->bytes_needed = 2;
556 // Read up to bytes_needed bytes.
557 ssize_t count = mDNSPlatformReadTCP(sock, transport->inbufp, transport->bytes_needed, &closed);
558 // LogMsg("read(%d, %p:%p, %d) -> %d", fd, dso->inbuf, dso->inbufp, dso->bytes_needed, count);
560 LogMsg("dso_read_callback: read from %s returned %d", dso->remote_name, errno);
565 // If we get selected for read and there's nothing to read, the remote end has closed the
568 LogMsg("dso_read_callback: remote %s closed", dso->remote_name);
573 transport->inbufp += count;
574 transport->bytes_needed -= count;
576 // If we read all the bytes we wanted, do what's next.
577 if (transport->bytes_needed == 0) {
578 // We just finished reading the complete length of a DNS-over-TCP message.
579 if (transport->need_length) {
580 // Get the number of bytes in this DNS message
581 transport->bytes_needed = (((int)transport->inbuf[0]) << 8) | transport->inbuf[1];
583 // Under no circumstances can length be zero.
584 if (transport->bytes_needed == 0) {
585 LogMsg("dso_read_callback: %s sent zero-length message.", dso->remote_name);
590 // The input buffer size is AbsoluteMaxDNSMessageData, which is around 9000 bytes on
591 // big platforms and around 1500 bytes on smaller ones. If the remote end has sent
592 // something larger than that, it's an error from which we can't recover.
593 if (transport->bytes_needed > transport->inbuf_size - 2) {
594 LogMsg("dso_read_callback: fatal: Proxy at %s sent a too-long (%ld bytes) message",
595 dso->remote_name, (long)transport->bytes_needed);
600 transport->message_length = transport->bytes_needed;
601 transport->inbufp = transport->inbuf + 2;
602 transport->need_length = false;
604 // We just finished reading a complete DNS-over-TCP message.
606 dns_message_received(dso, &transport->inbuf[2], transport->message_length);
607 transport->message_length = 0;
611 mDNS_Unlock(&mDNSStorage);
613 #endif // DSO_USES_NETWORK_FRAMEWORK
615 #ifdef DSO_USES_NETWORK_FRAMEWORK
616 static dso_transport_t *dso_transport_create(nw_connection_t connection, bool is_server, void *context,
617 int max_outstanding_queries, size_t outbuf_size_in, const char *remote_name,
618 dso_event_callback_t cb, dso_state_t *dso)
620 dso_transport_t *transport;
622 const size_t outbuf_size = outbuf_size_in + 256; // Space for additional TLVs
624 // We allocate everything in a single hunk so that we can free it together as well.
625 transp = mallocL("dso_transport_create", (sizeof *transport) + outbuf_size);
626 if (transp == NULL) {
630 // Don't clear the buffers.
631 mDNSPlatformMemZero(transp, sizeof (*transport));
633 transport = (dso_transport_t *)transp;
634 transp += sizeof *transport;
636 transport->outbuf = transp;
637 transport->outbuf_size = outbuf_size;
640 transport->dso = dso_create(is_server, max_outstanding_queries, remote_name, cb, context, transport);
641 if (transport->dso == NULL) {
642 mDNSPlatformMemFree(transport);
647 transport->dso = dso;
649 transport->connection = connection;
650 nw_retain(transport->connection);
651 transport->serial = dso_transport_serial++;
653 transport->dso->transport = transport;
654 transport->dso->transport_finalize = dso_transport_finalize;
655 transport->next = dso_transport_states;
656 dso_transport_states = transport;
658 // Start looking for messages...
659 dso_read_message_length(transport);
664 // Create a dso_transport_t structure
665 static dso_transport_t *dso_transport_create(TCPSocket *sock, bool is_server, void *context, int max_outstanding_queries,
666 size_t inbuf_size_in, size_t outbuf_size_in, const char *remote_name,
667 dso_event_callback_t cb, dso_state_t *dso)
669 dso_transport_t *transport;
675 // There's no point in a DSO that doesn't have a callback.
680 outbuf_size = outbuf_size_in + 256; // Space for additional TLVs
681 inbuf_size = inbuf_size_in + 2; // Space for length
683 // We allocate everything in a single hunk so that we can free it together as well.
684 transp = mallocL("dso_transport_create", (sizeof *transport) + inbuf_size + outbuf_size);
685 if (transp == NULL) {
689 // Don't clear the buffers.
690 mDNSPlatformMemZero(transp, sizeof (*transport));
692 transport = (dso_transport_t *)transp;
693 transp += sizeof *transport;
695 transport->inbuf = transp;
696 transport->inbuf_size = inbuf_size;
697 transp += inbuf_size;
699 transport->outbuf = transp;
700 transport->outbuf_size = outbuf_size;
703 transport->dso = dso_create(is_server, max_outstanding_queries, remote_name, cb, context, transport);
704 if (transport->dso == NULL) {
705 mDNSPlatformMemFree(transport);
710 transport->dso = dso;
712 transport->connection = sock;
714 status = mDNSPlatformTCPSocketSetCallback(sock, dso_read_callback, transport);
715 if (status != mStatus_NoError) {
716 LogMsg("dso_create: unable to set callback: %d", status);
717 dso_drop(transport->dso);
721 transport->dso->transport = transport;
722 transport->dso->transport_finalize = dso_transport_finalize;
723 transport->next = dso_transport_states;
724 dso_transport_states = transport;
728 #endif // DSO_USES_NETWORK_FRAMEWORK
730 // This should all be replaced with Network Framework connection setup.
731 dso_connect_state_t *dso_connect_state_create(const char *hostname, mDNSAddr *addr, mDNSIPPort port,
732 int max_outstanding_queries, size_t inbuf_size, size_t outbuf_size,
733 dso_event_callback_t callback, dso_state_t *dso, void *context, const char *detail)
735 int detlen = strlen(detail) + 1;
736 int hostlen = hostname == NULL ? 0 : strlen(hostname) + 1;
738 dso_connect_state_t *cs;
740 char nbuf[INET6_ADDRSTRLEN + 1];
741 dso_connect_state_t **states;
743 // Enforce Some Minimums (Xxx these are a bit arbitrary, maybe not worth doing?)
744 if (inbuf_size < MaximumRDSize || outbuf_size < 128 || max_outstanding_queries < 1) {
748 // If we didn't get a hostname, make a presentation form of the IP address to use instead.
751 if (addr->type == mDNSAddrType_IPv4) {
752 hostname = inet_ntop(AF_INET, &addr->ip.v4, nbuf, sizeof nbuf);
754 hostname = inet_ntop(AF_INET6, &addr->ip.v6, nbuf, sizeof nbuf);
756 if (hostname != NULL) {
757 hostlen = strlen(nbuf);
761 // If we don't have a printable name, we won't proceed, because this means we don't know
762 // what to connect to.
767 len = (sizeof *cs) + detlen + hostlen;
772 cs = (dso_connect_state_t *)csp;
773 memset(cs, 0, sizeof *cs);
777 memcpy(cs->detail, detail, detlen);
780 memcpy(cs->hostname, hostname, hostlen);
782 cs->config_port = port;
783 cs->max_outstanding_queries = max_outstanding_queries;
784 cs->outbuf_size = outbuf_size;
786 cs->context = context;
787 } // else cs->context = NULL because of memset call above.
788 cs->callback = callback;
789 cs->connect_port.NotAnInteger = 0;
791 #ifdef DSO_USES_NETWORK_FRAMEWORK
792 cs->serial = dso_transport_serial++;
794 cs->inbuf_size = inbuf_size;
799 cs->addresses[0] = *addr;
802 for (states = &dso_connect_states; *states != NULL; states = &(*states)->next)
808 #ifdef DSO_USES_NETWORK_FRAMEWORK
809 void dso_connect_state_use_tls(dso_connect_state_t *cs)
811 cs->tls_enabled = true;
815 void dso_connect_state_drop(dso_connect_state_t *cs)
817 dso_connect_state_t **states;
819 for (states = &dso_connect_states; *states != NULL && *states != cs; states = &(*states)->next)
824 LogMsg("dso_connect_state_drop: dropping a connect state that isn't recognized.");
826 #ifdef DSO_USES_NETWORK_FRAMEWORK
827 if (cs->connection != NULL) {
828 nw_connection_cancel(cs->connection);
829 nw_release(cs->connection);
830 cs->connection = NULL;
833 mDNSPlatformMemFree(cs);
836 #ifdef DSO_USES_NETWORK_FRAMEWORK
838 dso_connection_succeeded(dso_connect_state_t *cs)
840 // We got a connection.
841 dso_transport_t *transport =
842 dso_transport_create(cs->connection, false, cs->context, cs->max_outstanding_queries,
843 cs->outbuf_size, cs->hostname, cs->callback, cs->dso);
844 nw_release(cs->connection);
845 cs->connection = NULL;
846 if (transport == NULL) {
847 // If dso_transport_create fails, there's no point in continuing to try to connect to new
849 LogMsg("dso_connection_succeeded: dso_create failed");
850 // XXX we didn't retain the connection, so we're done when it goes out of scope, right?
852 // Call the "we're connected" callback, which will start things up.
853 transport->dso->cb(cs->context, NULL, transport->dso, kDSOEventType_Connected);
858 // When the connection has succeeded, stop asking questions.
859 if (cs->lookup != NULL) {
860 mDNS *m = &mDNSStorage;
861 DNSServiceRef ref = cs->lookup;
863 mDNS_DropLockBeforeCallback();
864 DNSServiceRefDeallocate(ref);
865 mDNS_ReclaimLockAfterCallback();
871 static void dso_connect_internal(dso_connect_state_t *cs)
873 uint32_t serial = cs->serial;
875 cs->last_event = mDNSStorage.timenow;
877 if (cs->num_addrs <= cs->cur_addr) {
878 if (cs->lookup == NULL) {
879 LogMsg("dso_connect_internal: %s: no more addresses to try", cs->hostname);
881 cs->callback(cs->context, NULL, NULL, kDSOEventType_ConnectFailed);
883 // Otherwise, we will get more callbacks when outstanding queries either fail or succeed.
887 char addrbuf[INET6_ADDRSTRLEN + 1];
890 inet_ntop(cs->addresses[cs->cur_addr].type == mDNSAddrType_IPv4 ? AF_INET : AF_INET6,
891 cs->addresses[cs->cur_addr].type == mDNSAddrType_IPv4
892 ? (void *)cs->addresses[cs->cur_addr].ip.v4.b
893 : (void *)cs->addresses[cs->cur_addr].ip.v6.b, addrbuf, sizeof addrbuf);
894 snprintf(portbuf, sizeof portbuf, "%u", ntohs(cs->ports[cs->cur_addr].NotAnInteger));
897 nw_endpoint_t endpoint = nw_endpoint_create_host(addrbuf, portbuf);
898 if (endpoint == NULL) {
900 LogMsg("dso_connect_internal: no memory creating connection.");
903 nw_parameters_t parameters = NULL;
904 nw_parameters_configure_protocol_block_t configure_tls = NW_PARAMETERS_DISABLE_PROTOCOL;
905 if (cs->tls_enabled) {
906 // This sets up a block that's called when we get a TLS connection and want to verify
907 // the cert. Right now we only support opportunistic security, which means we have
908 // no way to validate the cert. Future work: add support for validating the cert
909 // using a TLSA record if one is present.
910 configure_tls = ^(nw_protocol_options_t tls_options) {
911 sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
912 sec_protocol_options_set_verify_block(sec_options,
913 ^(sec_protocol_metadata_t __unused metadata,
914 sec_trust_t __unused trust_ref,
915 sec_protocol_verify_complete_t complete) {
917 }, dso_dispatch_queue);
920 parameters = nw_parameters_create_secure_tcp(configure_tls, NW_PARAMETERS_DEFAULT_CONFIGURATION);
921 if (parameters == NULL) {
924 nw_connection_t connection = nw_connection_create(endpoint, parameters);
925 if (connection == NULL) {
928 cs->connection = connection;
930 LogMsg("dso_connect_internal: Attempting to connect to %s%%%s", addrbuf, portbuf);
931 nw_connection_set_queue(connection, dso_dispatch_queue);
932 nw_connection_set_state_changed_handler(
933 connection, ^(nw_connection_state_t state, nw_error_t error) {
934 dso_connect_state_t *ncs;
936 ncs = dso_connect_state_find(serial); // Might have been freed.
938 LogMsg("forgotten connection is %s.",
939 state == nw_connection_state_cancelled ? "canceled" :
940 state == nw_connection_state_failed ? "failed" :
941 state == nw_connection_state_waiting ? "canceled" :
942 state == nw_connection_state_ready ? "ready" : "unknown");
943 if (state != nw_connection_state_cancelled) {
944 nw_connection_cancel(connection);
945 // Don't need to release it because only NW framework is holding a reference (XXX right?)
948 if (state == nw_connection_state_waiting) {
949 LogMsg("connection to %#a%%%d is waiting", &ncs->addresses[ncs->cur_addr], ncs->ports[ncs->cur_addr]);
951 // XXX the right way to do this is to just let NW Framework wait until we get a connection,
952 // but there are a bunch of problems with that right now. First, will we get "waiting" on
953 // every connection we try? We aren't relying on NW Framework for DNS lookups, so we are
954 // connecting to an IP address, not a host, which means in principle that a later IP address
955 // might be reachable. So we have to stop trying on this one to try that one. Oops.
956 // Once we get NW Framework to use internal calls to resolve names, we can fix this.
957 // Second, maybe we want to switch to polling if this happens. Probably not, but we need
958 // to think this through. So right now we're just using the semantics of regular sockets,
959 // which we /have/ thought through. So in the future we should do this think-through and
960 // try to use NW Framework as it's intended to work rather than as if it were just sockets.
961 ncs->connecting = mDNSfalse;
962 nw_connection_cancel(connection);
963 } else if (state == nw_connection_state_failed) {
964 // We tried to connect, but didn't succeed.
965 LogMsg("dso_connect_internal: failed to connect to %s on %#a%%%d: %s%s",
966 ncs->hostname, &ncs->addresses[ncs->cur_addr], ncs->ports[ncs->cur_addr],
967 strerror(nw_error_get_error_code(error)), ncs->detail);
968 nw_release(ncs->connection);
969 ncs->connection = NULL;
970 ncs->connecting = mDNSfalse;
971 // This will do the work of figuring out if there are more addresses to try.
972 mDNS_Lock(&mDNSStorage);
973 dso_connect_internal(ncs);
974 mDNS_Unlock(&mDNSStorage);
975 } else if (state == nw_connection_state_ready) {
976 ncs->connecting = mDNSfalse;
977 mDNS_Lock(&mDNSStorage);
978 dso_connection_succeeded(ncs);
979 mDNS_Unlock(&mDNSStorage);
980 } else if (state == nw_connection_state_cancelled) {
981 if (ncs->connection) {
982 nw_release(ncs->connection);
984 ncs->connection = NULL;
985 ncs->connecting = mDNSfalse;
986 // If we get here and cs exists, we are still trying to connect. So do the next step.
987 mDNS_Lock(&mDNSStorage);
988 dso_connect_internal(ncs);
989 mDNS_Unlock(&mDNSStorage);
992 KQueueUnlock("dso_connect_internal state change handler");
994 nw_connection_start(connection);
995 cs->connecting = mDNStrue;
999 static void dso_connect_callback(TCPSocket *sock, void *context, mDNSBool connected, int err)
1001 dso_connect_state_t *cs = context;
1004 dso_transport_t *transport;
1005 mDNS *m = &mDNSStorage;
1009 detail = cs->detail;
1011 // If we had a socket open but the connect failed, close it and try the next address, if we have
1014 cs->last_event = m->timenow;
1016 cs->connecting = mDNSfalse;
1017 if (err != mStatus_NoError) {
1018 mDNSPlatformTCPCloseConnection(sock);
1019 LogMsg("dso_connect_callback: connect %p failed (%d)", cs, err);
1022 // We got a connection.
1023 transport = dso_transport_create(sock, false, cs->context, cs->max_outstanding_queries,
1024 cs->inbuf_size, cs->outbuf_size, cs->hostname, cs->callback, cs->dso);
1025 if (transport == NULL) {
1026 // If dso_create fails, there's no point in continuing to try to connect to new
1029 LogMsg("dso_connect_callback: dso_create failed");
1030 mDNSPlatformTCPCloseConnection(sock);
1032 // Call the "we're connected" callback, which will start things up.
1033 transport->dso->cb(cs->context, NULL, transport->dso, kDSOEventType_Connected);
1038 // When the connection has succeeded, stop asking questions.
1039 if (cs->lookup != NULL) {
1040 DNSServiceRef ref = cs->lookup;
1042 mDNS_DropLockBeforeCallback();
1043 DNSServiceRefDeallocate(ref);
1044 mDNS_ReclaimLockAfterCallback();
1051 // If there are no addresses to connect to, and there are no queries running, then we can give
1052 // up. Otherwise, we wait for one of the queries to deliver an answer.
1053 if (cs->num_addrs <= cs->cur_addr) {
1054 if (cs->lookup == NULL) {
1055 LogMsg("dso_connect_callback: %s: no more addresses to try", cs->hostname);
1057 cs->callback(cs->context, NULL, NULL, kDSOEventType_ConnectFailed);
1059 // Otherwise, we will get more callbacks when outstanding queries either fail or succeed.
1064 sock = mDNSPlatformTCPSocket(kTCPSocketFlags_Zero, cs->addresses[cs->cur_addr].type, NULL, NULL, mDNSfalse);
1066 LogMsg("drConnectCallback: couldn't get a socket for %s: %s%s",
1067 cs->hostname, strerror(errno), detail);
1071 LogMsg("dso_connect_callback: Attempting to connect to %#a%%%d",
1072 &cs->addresses[cs->cur_addr], ntohs(cs->ports[cs->cur_addr].NotAnInteger));
1074 status = mDNSPlatformTCPConnect(sock, &cs->addresses[cs->cur_addr], cs->ports[cs->cur_addr], NULL,
1075 dso_connect_callback, cs);
1077 if (status == mStatus_NoError || status == mStatus_ConnEstablished) {
1078 // This can't happen in practice on MacOS; we don't know about all other operating systems,
1079 // so we handle it just in case.
1080 LogMsg("dso_connect_callback: synchronous connect to %s", cs->hostname);
1082 } else if (status == mStatus_ConnPending) {
1083 LogMsg("dso_connect_callback: asynchronous connect to %s", cs->hostname);
1084 cs->connecting = mDNStrue;
1085 // We should get called back when the connection succeeds or fails.
1089 LogMsg("dso_connect_callback: failed to connect to %s on %#a%d: %s%s",
1090 cs->hostname, &cs->addresses[cs->cur_addr],
1091 ntohs(cs->ports[cs->cur_addr].NotAnInteger), strerror(errno), detail);
1095 static void dso_connect_internal(dso_connect_state_t *cs)
1097 dso_connect_callback(NULL, cs, false, mStatus_NoError);
1099 #endif // DSO_USES_NETWORK_FRAMEWORK
1101 static void dso_inaddr_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
1102 DNSServiceErrorType errorCode, const char *fullname, const struct sockaddr *sa,
1103 uint32_t ttl, void *context)
1105 dso_connect_state_t *cs = context;
1106 char addrbuf[INET6_ADDRSTRLEN + 1];
1107 mDNS *m = &mDNSStorage;
1110 cs->last_event = m->timenow;
1111 inet_ntop(sa->sa_family, (sa->sa_family == AF_INET
1112 ? (void *)&((struct sockaddr_in *)sa)->sin_addr
1113 : (void *)&((struct sockaddr_in6 *)sa)->sin6_addr), addrbuf, sizeof addrbuf);
1114 LogMsg("dso_inaddr_callback: %s: flags %x index %d error %d fullname %s addr %s ttl %lu",
1115 cs->hostname, flags, interfaceIndex, errorCode, fullname, addrbuf, (unsigned long)ttl);
1117 if (errorCode != mStatus_NoError) {
1121 if (cs->num_addrs == MAX_DSO_CONNECT_ADDRS) {
1122 if (cs->cur_addr > 1) {
1123 memmove(&cs->addresses, &cs->addresses[cs->cur_addr],
1124 (MAX_DSO_CONNECT_ADDRS - cs->cur_addr) * sizeof cs->addresses[0]);
1125 cs->num_addrs -= cs->cur_addr;
1128 LogMsg("dso_inaddr_callback: ran out of room for addresses.");
1133 if (sa->sa_family == AF_INET) {
1134 cs->addresses[cs->num_addrs].type = mDNSAddrType_IPv4;
1135 mDNSPlatformMemCopy(&cs->addresses[cs->num_addrs].ip.v4,
1136 &((struct sockaddr_in *)sa)->sin_addr, sizeof cs->addresses[cs->num_addrs].ip.v4);
1138 cs->addresses[cs->num_addrs].type = mDNSAddrType_IPv6;
1139 mDNSPlatformMemCopy(&cs->addresses[cs->num_addrs].ip.v6,
1140 &((struct sockaddr_in *)sa)->sin_addr, sizeof cs->addresses[cs->num_addrs].ip.v6);
1143 cs->ports[cs->num_addrs] = cs->config_port;
1145 if (!cs->connecting) {
1146 LogMsg("dso_inaddr_callback: starting a new connection.");
1147 dso_connect_internal(cs);
1149 LogMsg("dso_inaddr_callback: connection in progress, deferring new connect until it fails.");
1153 bool dso_connect(dso_connect_state_t *cs)
1156 struct in6_addr in6;
1158 // If the connection state was created with an address, use that rather than hostname.
1159 if (cs->num_addrs > 0) {
1160 dso_connect_internal(cs);
1162 // Else allow an IPv4 address literal string
1163 else if (inet_pton(AF_INET, cs->hostname, &in)) {
1165 cs->addresses[0].type = mDNSAddrType_IPv4;
1166 cs->addresses[0].ip.v4.NotAnInteger = in.s_addr;
1167 cs->ports[0] = cs->config_port;
1168 dso_connect_internal(cs);
1170 // ...or an IPv6 address literal string
1171 else if (inet_pton(AF_INET6, cs->hostname, &in6)) {
1173 cs->addresses[0].type = mDNSAddrType_IPv6;
1174 memcpy(&cs->addresses[0].ip.v6, &in6, sizeof in6);
1175 cs->ports[0] = cs->config_port;
1176 dso_connect_internal(cs);
1178 // ...or else look it up.
1180 mDNS *m = &mDNSStorage;
1182 mDNS_DropLockBeforeCallback();
1183 err = DNSServiceGetAddrInfo(&cs->lookup, kDNSServiceFlagsReturnIntermediates,
1184 kDNSServiceInterfaceIndexAny, 0, cs->hostname, dso_inaddr_callback, cs);
1186 mDNS_ReclaimLockAfterCallback();
1187 if (err != mStatus_NoError) {
1188 LogMsg("dso_connect: inaddr lookup query allocate failed for '%s': %d", cs->hostname, err);
1195 #ifdef DSO_USES_NETWORK_FRAMEWORK
1196 // We don't need this for DNS Push, so it is being left as future work.
1197 int dso_listen(dso_connect_state_t * __unused listen_context)
1199 return mStatus_UnsupportedErr;
1204 // Called whenever we get a connection on the DNS TCP socket
1205 static void dso_listen_callback(TCPSocket *sock, mDNSAddr *addr, mDNSIPPort *port,
1206 const char *remote_name, void *context)
1208 dso_connect_state_t *lc = context;
1209 dso_transport_t *transport;
1211 mDNS_Lock(&mDNSStorage);
1212 transport = dso_transport_create(sock, mDNStrue, lc->context, lc->max_outstanding_queries,
1213 lc->inbuf_size, lc->outbuf_size, remote_name, lc->callback, NULL);
1214 if (transport == NULL) {
1215 mDNSPlatformTCPCloseConnection(sock);
1216 LogMsg("No memory for new DSO connection from %s", remote_name);
1220 transport->remote_addr = *addr;
1221 transport->remote_port = ntohs(port->NotAnInteger);
1222 if (transport->dso->cb) {
1223 transport->dso->cb(lc->context, 0, transport->dso, kDSOEventType_Connected);
1225 LogMsg("DSO connection from %s", remote_name);
1227 mDNS_Unlock(&mDNSStorage);
1230 // Listen for connections; each time we get a connection, make a new dso_state_t object with the specified
1231 // parameters and call the callback. Port can be zero to leave it unspecified.
1233 int dso_listen(dso_connect_state_t *listen_context)
1235 char addrbuf[INET6_ADDRSTRLEN + 1];
1237 mDNSBool reuseAddr = mDNSfalse;
1239 if (listen_context->config_port.NotAnInteger) {
1240 port = listen_context->config_port;
1241 reuseAddr = mDNStrue;
1243 listen_context->listener = mDNSPlatformTCPListen(mDNSAddrType_None, &port, NULL, kTCPSocketFlags_Zero,
1244 reuseAddr, 5, dso_listen_callback, listen_context);
1245 if (!listen_context->listener) {
1246 return mStatus_UnknownErr;
1248 listen_context->connect_port = port;
1249 if (listen_context->addresses[0].type == mDNSAddrType_IPv4) {
1250 inet_ntop(AF_INET, &listen_context->addresses[0].ip.v4, addrbuf, sizeof addrbuf);
1252 inet_ntop(AF_INET6, &listen_context->addresses[0].ip.v6, addrbuf, sizeof addrbuf);
1255 LogMsg("DSOListen: Listening on %s%%%d", addrbuf, ntohs(listen_context->connect_port.NotAnInteger));
1256 return mStatus_NoError;
1258 #endif // DSO_USES_NETWORK_FRAMEWORK
1263 // c-file-style: "bsd"
1264 // c-basic-offset: 4
1266 // indent-tabs-mode: nil