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 int32_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 size_t 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
, uint32_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 size_t length_length
;
462 const uint8_t *lenbuf
;
463 dispatch_data_t map
= dispatch_data_create_map(content
, (const void **)&lenbuf
,
466 LogMsg("dso_read_message_length: map create failed");
468 } else if (length_length
!= 2) {
469 LogMsg("dso_read_message_length: invalid length = %d", length_length
);
470 dispatch_release(map
);
473 length
= ((unsigned)(lenbuf
[0]) << 8) | ((unsigned)lenbuf
[1]);
474 dispatch_release(map
);
475 dso_read_message(transport
, length
);
477 KQueueUnlock("dso_read_message_length completion routine");
481 void dso_read_message(dso_transport_t
*transport
, uint32_t length
)
483 const uint32_t serial
= transport
->dso
->serial
;
484 if (transport
->connection
== NULL
) {
485 LogMsg("dso_read_message called with null connection.");
488 nw_connection_receive(transport
->connection
, length
, length
,
489 ^(dispatch_data_t content
, nw_content_context_t __unused context
,
490 bool __unused is_complete
, nw_error_t error
) {
492 // Don't touch anything or look at anything until we have the lock.
494 dso
= dso_find_by_serial(serial
);
496 LogMsg("dso_read_message: read failed: %s", strerror(nw_error_get_error_code(error
)));
499 mDNS_Lock(&mDNSStorage
);
501 mDNS_Unlock(&mDNSStorage
);
503 } else if (content
== NULL
) {
504 LogMsg("dso_read_message: remote end closed connection");
508 const uint8_t *message
;
509 dispatch_data_t map
= dispatch_data_create_map(content
, (const void **)&message
, &bytes_read
);
511 LogMsg("dso_read_message_length: map create failed");
513 } else if (bytes_read
!= length
) {
514 LogMsg("dso_read_message_length: only %d of %d bytes read", bytes_read
, length
);
515 dispatch_release(map
);
518 // Process the message.
519 mDNS_Lock(&mDNSStorage
);
520 dns_message_received(dso
, message
, length
);
521 mDNS_Unlock(&mDNSStorage
);
523 // Release the map object now that we no longer need its buffers.
524 dispatch_release(map
);
526 // Now read the next message length.
527 dso_read_message_length(transport
);
529 KQueueUnlock("dso_read_message completion routine");
533 // Called whenever there's data available on a DSO connection
534 void dso_read_callback(TCPSocket
*sock
, void *context
, mDNSBool connection_established
, int err
)
536 dso_transport_t
*transport
= context
;
538 mDNSBool closed
= mDNSfalse
;
540 mDNS_Lock(&mDNSStorage
);
541 dso
= transport
->dso
;
543 // This shouldn't ever happen.
545 LogMsg("dso_read_callback: error %d", err
);
550 // Connection is already established by the time we set this up.
551 if (connection_established
) {
555 // This will be true either if we have never read a message or
556 // if the last thing we did was to finish reading a message and
558 if (transport
->message_length
== 0) {
559 transport
->need_length
= true;
560 transport
->inbufp
= transport
->inbuf
;
561 transport
->bytes_needed
= 2;
564 // Read up to bytes_needed bytes.
565 ssize_t count
= mDNSPlatformReadTCP(sock
, transport
->inbufp
, transport
->bytes_needed
, &closed
);
566 // LogMsg("read(%d, %p:%p, %d) -> %d", fd, dso->inbuf, dso->inbufp, dso->bytes_needed, count);
568 LogMsg("dso_read_callback: read from %s returned %d", dso
->remote_name
, errno
);
573 // If we get selected for read and there's nothing to read, the remote end has closed the
576 LogMsg("dso_read_callback: remote %s closed", dso
->remote_name
);
581 transport
->inbufp
+= count
;
582 transport
->bytes_needed
-= count
;
584 // If we read all the bytes we wanted, do what's next.
585 if (transport
->bytes_needed
== 0) {
586 // We just finished reading the complete length of a DNS-over-TCP message.
587 if (transport
->need_length
) {
588 // Get the number of bytes in this DNS message
589 transport
->bytes_needed
= (((int)transport
->inbuf
[0]) << 8) | transport
->inbuf
[1];
591 // Under no circumstances can length be zero.
592 if (transport
->bytes_needed
== 0) {
593 LogMsg("dso_read_callback: %s sent zero-length message.", dso
->remote_name
);
598 // The input buffer size is AbsoluteMaxDNSMessageData, which is around 9000 bytes on
599 // big platforms and around 1500 bytes on smaller ones. If the remote end has sent
600 // something larger than that, it's an error from which we can't recover.
601 if (transport
->bytes_needed
> transport
->inbuf_size
- 2) {
602 LogMsg("dso_read_callback: fatal: Proxy at %s sent a too-long (%ld bytes) message",
603 dso
->remote_name
, (long)transport
->bytes_needed
);
608 transport
->message_length
= transport
->bytes_needed
;
609 transport
->inbufp
= transport
->inbuf
+ 2;
610 transport
->need_length
= false;
612 // We just finished reading a complete DNS-over-TCP message.
614 dns_message_received(dso
, &transport
->inbuf
[2], transport
->message_length
);
615 transport
->message_length
= 0;
619 mDNS_Unlock(&mDNSStorage
);
621 #endif // DSO_USES_NETWORK_FRAMEWORK
623 #ifdef DSO_USES_NETWORK_FRAMEWORK
624 static dso_transport_t
*dso_transport_create(nw_connection_t connection
, bool is_server
, void *context
,
625 int max_outstanding_queries
, size_t outbuf_size_in
, const char *remote_name
,
626 dso_event_callback_t cb
, dso_state_t
*dso
)
628 dso_transport_t
*transport
;
630 const size_t outbuf_size
= outbuf_size_in
+ 256; // Space for additional TLVs
632 // We allocate everything in a single hunk so that we can free it together as well.
633 transp
= mallocL("dso_transport_create", (sizeof *transport
) + outbuf_size
);
634 if (transp
== NULL
) {
638 // Don't clear the buffers.
639 mDNSPlatformMemZero(transp
, sizeof (*transport
));
641 transport
= (dso_transport_t
*)transp
;
642 transp
+= sizeof *transport
;
644 transport
->outbuf
= transp
;
645 transport
->outbuf_size
= outbuf_size
;
648 transport
->dso
= dso_create(is_server
, max_outstanding_queries
, remote_name
, cb
, context
, transport
);
649 if (transport
->dso
== NULL
) {
650 mDNSPlatformMemFree(transport
);
655 transport
->dso
= dso
;
657 transport
->connection
= connection
;
658 nw_retain(transport
->connection
);
659 transport
->serial
= dso_transport_serial
++;
661 transport
->dso
->transport
= transport
;
662 transport
->dso
->transport_finalize
= dso_transport_finalize
;
663 transport
->next
= dso_transport_states
;
664 dso_transport_states
= transport
;
666 // Start looking for messages...
667 dso_read_message_length(transport
);
672 // Create a dso_transport_t structure
673 static dso_transport_t
*dso_transport_create(TCPSocket
*sock
, bool is_server
, void *context
, int max_outstanding_queries
,
674 size_t inbuf_size_in
, size_t outbuf_size_in
, const char *remote_name
,
675 dso_event_callback_t cb
, dso_state_t
*dso
)
677 dso_transport_t
*transport
;
683 // There's no point in a DSO that doesn't have a callback.
688 outbuf_size
= outbuf_size_in
+ 256; // Space for additional TLVs
689 inbuf_size
= inbuf_size_in
+ 2; // Space for length
691 // We allocate everything in a single hunk so that we can free it together as well.
692 transp
= mallocL("dso_transport_create", (sizeof *transport
) + inbuf_size
+ outbuf_size
);
693 if (transp
== NULL
) {
697 // Don't clear the buffers.
698 mDNSPlatformMemZero(transp
, sizeof (*transport
));
700 transport
= (dso_transport_t
*)transp
;
701 transp
+= sizeof *transport
;
703 transport
->inbuf
= transp
;
704 transport
->inbuf_size
= inbuf_size
;
705 transp
+= inbuf_size
;
707 transport
->outbuf
= transp
;
708 transport
->outbuf_size
= outbuf_size
;
711 transport
->dso
= dso_create(is_server
, max_outstanding_queries
, remote_name
, cb
, context
, transport
);
712 if (transport
->dso
== NULL
) {
713 mDNSPlatformMemFree(transport
);
718 transport
->dso
= dso
;
720 transport
->connection
= sock
;
722 status
= mDNSPlatformTCPSocketSetCallback(sock
, dso_read_callback
, transport
);
723 if (status
!= mStatus_NoError
) {
724 LogMsg("dso_create: unable to set callback: %d", status
);
725 dso_drop(transport
->dso
);
729 transport
->dso
->transport
= transport
;
730 transport
->dso
->transport_finalize
= dso_transport_finalize
;
731 transport
->next
= dso_transport_states
;
732 dso_transport_states
= transport
;
736 #endif // DSO_USES_NETWORK_FRAMEWORK
738 // This should all be replaced with Network Framework connection setup.
739 dso_connect_state_t
*dso_connect_state_create(const char *hostname
, mDNSAddr
*addr
, mDNSIPPort port
,
740 int max_outstanding_queries
, size_t inbuf_size
, size_t outbuf_size
,
741 dso_event_callback_t callback
, dso_state_t
*dso
, void *context
, const char *detail
)
743 size_t detlen
= strlen(detail
) + 1;
744 size_t hostlen
= hostname
== NULL
? 0 : strlen(hostname
) + 1;
746 dso_connect_state_t
*cs
;
748 char nbuf
[INET6_ADDRSTRLEN
+ 1];
749 dso_connect_state_t
**states
;
751 // Enforce Some Minimums (Xxx these are a bit arbitrary, maybe not worth doing?)
752 if (inbuf_size
< MaximumRDSize
|| outbuf_size
< 128 || max_outstanding_queries
< 1) {
756 // If we didn't get a hostname, make a presentation form of the IP address to use instead.
759 if (addr
->type
== mDNSAddrType_IPv4
) {
760 hostname
= inet_ntop(AF_INET
, &addr
->ip
.v4
, nbuf
, sizeof nbuf
);
762 hostname
= inet_ntop(AF_INET6
, &addr
->ip
.v6
, nbuf
, sizeof nbuf
);
764 if (hostname
!= NULL
) {
765 hostlen
= strlen(nbuf
);
769 // If we don't have a printable name, we won't proceed, because this means we don't know
770 // what to connect to.
775 len
= (sizeof *cs
) + detlen
+ hostlen
;
780 cs
= (dso_connect_state_t
*)csp
;
781 memset(cs
, 0, sizeof *cs
);
785 memcpy(cs
->detail
, detail
, detlen
);
788 memcpy(cs
->hostname
, hostname
, hostlen
);
790 cs
->config_port
= port
;
791 cs
->max_outstanding_queries
= max_outstanding_queries
;
792 cs
->outbuf_size
= outbuf_size
;
794 cs
->context
= context
;
795 } // else cs->context = NULL because of memset call above.
796 cs
->callback
= callback
;
797 cs
->connect_port
.NotAnInteger
= 0;
799 #ifdef DSO_USES_NETWORK_FRAMEWORK
800 cs
->serial
= dso_transport_serial
++;
802 cs
->inbuf_size
= inbuf_size
;
807 cs
->addresses
[0] = *addr
;
810 for (states
= &dso_connect_states
; *states
!= NULL
; states
= &(*states
)->next
)
816 #ifdef DSO_USES_NETWORK_FRAMEWORK
817 void dso_connect_state_use_tls(dso_connect_state_t
*cs
)
819 cs
->tls_enabled
= true;
823 void dso_connect_state_drop(dso_connect_state_t
*cs
)
825 dso_connect_state_t
**states
;
827 for (states
= &dso_connect_states
; *states
!= NULL
&& *states
!= cs
; states
= &(*states
)->next
)
832 LogMsg("dso_connect_state_drop: dropping a connect state that isn't recognized.");
834 #ifdef DSO_USES_NETWORK_FRAMEWORK
835 if (cs
->connection
!= NULL
) {
836 nw_connection_cancel(cs
->connection
);
837 nw_release(cs
->connection
);
838 cs
->connection
= NULL
;
841 mDNSPlatformMemFree(cs
);
844 #ifdef DSO_USES_NETWORK_FRAMEWORK
846 dso_connection_succeeded(dso_connect_state_t
*cs
)
848 // We got a connection.
849 dso_transport_t
*transport
=
850 dso_transport_create(cs
->connection
, false, cs
->context
, cs
->max_outstanding_queries
,
851 cs
->outbuf_size
, cs
->hostname
, cs
->callback
, cs
->dso
);
852 nw_release(cs
->connection
);
853 cs
->connection
= NULL
;
854 if (transport
== NULL
) {
855 // If dso_transport_create fails, there's no point in continuing to try to connect to new
857 LogMsg("dso_connection_succeeded: dso_create failed");
858 // XXX we didn't retain the connection, so we're done when it goes out of scope, right?
860 // Call the "we're connected" callback, which will start things up.
861 transport
->dso
->cb(cs
->context
, NULL
, transport
->dso
, kDSOEventType_Connected
);
866 // When the connection has succeeded, stop asking questions.
867 if (cs
->lookup
!= NULL
) {
868 mDNS
*m
= &mDNSStorage
;
869 DNSServiceRef ref
= cs
->lookup
;
871 mDNS_DropLockBeforeCallback();
872 DNSServiceRefDeallocate(ref
);
873 mDNS_ReclaimLockAfterCallback();
879 static void dso_connect_internal(dso_connect_state_t
*cs
)
881 uint32_t serial
= cs
->serial
;
883 cs
->last_event
= mDNSStorage
.timenow
;
885 if (cs
->num_addrs
<= cs
->cur_addr
) {
886 if (cs
->lookup
== NULL
) {
887 LogMsg("dso_connect_internal: %s: no more addresses to try", cs
->hostname
);
889 cs
->callback(cs
->context
, NULL
, NULL
, kDSOEventType_ConnectFailed
);
891 // Otherwise, we will get more callbacks when outstanding queries either fail or succeed.
895 char addrbuf
[INET6_ADDRSTRLEN
+ 1];
898 inet_ntop(cs
->addresses
[cs
->cur_addr
].type
== mDNSAddrType_IPv4
? AF_INET
: AF_INET6
,
899 cs
->addresses
[cs
->cur_addr
].type
== mDNSAddrType_IPv4
900 ? (void *)cs
->addresses
[cs
->cur_addr
].ip
.v4
.b
901 : (void *)cs
->addresses
[cs
->cur_addr
].ip
.v6
.b
, addrbuf
, sizeof addrbuf
);
902 snprintf(portbuf
, sizeof portbuf
, "%u", ntohs(cs
->ports
[cs
->cur_addr
].NotAnInteger
));
905 nw_endpoint_t endpoint
= nw_endpoint_create_host(addrbuf
, portbuf
);
906 if (endpoint
== NULL
) {
908 LogMsg("dso_connect_internal: no memory creating connection.");
911 nw_parameters_t parameters
= NULL
;
912 nw_parameters_configure_protocol_block_t configure_tls
= NW_PARAMETERS_DISABLE_PROTOCOL
;
913 if (cs
->tls_enabled
) {
914 // This sets up a block that's called when we get a TLS connection and want to verify
915 // the cert. Right now we only support opportunistic security, which means we have
916 // no way to validate the cert. Future work: add support for validating the cert
917 // using a TLSA record if one is present.
918 configure_tls
= ^(nw_protocol_options_t tls_options
) {
919 sec_protocol_options_t sec_options
= nw_tls_copy_sec_protocol_options(tls_options
);
920 sec_protocol_options_set_verify_block(sec_options
,
921 ^(sec_protocol_metadata_t __unused metadata
,
922 sec_trust_t __unused trust_ref
,
923 sec_protocol_verify_complete_t complete
) {
925 }, dso_dispatch_queue
);
928 parameters
= nw_parameters_create_secure_tcp(configure_tls
, NW_PARAMETERS_DEFAULT_CONFIGURATION
);
929 if (parameters
== NULL
) {
932 nw_connection_t connection
= nw_connection_create(endpoint
, parameters
);
933 if (connection
== NULL
) {
936 cs
->connection
= connection
;
938 LogMsg("dso_connect_internal: Attempting to connect to %s%%%s", addrbuf
, portbuf
);
939 nw_connection_set_queue(connection
, dso_dispatch_queue
);
940 nw_connection_set_state_changed_handler(
941 connection
, ^(nw_connection_state_t state
, nw_error_t error
) {
942 dso_connect_state_t
*ncs
;
944 ncs
= dso_connect_state_find(serial
); // Might have been freed.
946 LogMsg("forgotten connection is %s.",
947 state
== nw_connection_state_cancelled
? "canceled" :
948 state
== nw_connection_state_failed
? "failed" :
949 state
== nw_connection_state_waiting
? "canceled" :
950 state
== nw_connection_state_ready
? "ready" : "unknown");
951 if (state
!= nw_connection_state_cancelled
) {
952 nw_connection_cancel(connection
);
953 // Don't need to release it because only NW framework is holding a reference (XXX right?)
956 if (state
== nw_connection_state_waiting
) {
957 LogMsg("connection to %#a%%%d is waiting", &ncs
->addresses
[ncs
->cur_addr
], ncs
->ports
[ncs
->cur_addr
]);
959 // XXX the right way to do this is to just let NW Framework wait until we get a connection,
960 // but there are a bunch of problems with that right now. First, will we get "waiting" on
961 // every connection we try? We aren't relying on NW Framework for DNS lookups, so we are
962 // connecting to an IP address, not a host, which means in principle that a later IP address
963 // might be reachable. So we have to stop trying on this one to try that one. Oops.
964 // Once we get NW Framework to use internal calls to resolve names, we can fix this.
965 // Second, maybe we want to switch to polling if this happens. Probably not, but we need
966 // to think this through. So right now we're just using the semantics of regular sockets,
967 // which we /have/ thought through. So in the future we should do this think-through and
968 // try to use NW Framework as it's intended to work rather than as if it were just sockets.
969 ncs
->connecting
= mDNSfalse
;
970 nw_connection_cancel(connection
);
971 } else if (state
== nw_connection_state_failed
) {
972 // We tried to connect, but didn't succeed.
973 LogMsg("dso_connect_internal: failed to connect to %s on %#a%%%d: %s%s",
974 ncs
->hostname
, &ncs
->addresses
[ncs
->cur_addr
], ncs
->ports
[ncs
->cur_addr
],
975 strerror(nw_error_get_error_code(error
)), ncs
->detail
);
976 nw_release(ncs
->connection
);
977 ncs
->connection
= NULL
;
978 ncs
->connecting
= mDNSfalse
;
979 // This will do the work of figuring out if there are more addresses to try.
980 mDNS_Lock(&mDNSStorage
);
981 dso_connect_internal(ncs
);
982 mDNS_Unlock(&mDNSStorage
);
983 } else if (state
== nw_connection_state_ready
) {
984 ncs
->connecting
= mDNSfalse
;
985 mDNS_Lock(&mDNSStorage
);
986 dso_connection_succeeded(ncs
);
987 mDNS_Unlock(&mDNSStorage
);
988 } else if (state
== nw_connection_state_cancelled
) {
989 if (ncs
->connection
) {
990 nw_release(ncs
->connection
);
992 ncs
->connection
= NULL
;
993 ncs
->connecting
= mDNSfalse
;
994 // If we get here and cs exists, we are still trying to connect. So do the next step.
995 mDNS_Lock(&mDNSStorage
);
996 dso_connect_internal(ncs
);
997 mDNS_Unlock(&mDNSStorage
);
1000 KQueueUnlock("dso_connect_internal state change handler");
1002 nw_connection_start(connection
);
1003 cs
->connecting
= mDNStrue
;
1007 static void dso_connect_callback(TCPSocket
*sock
, void *context
, mDNSBool connected
, int err
)
1009 dso_connect_state_t
*cs
= context
;
1012 dso_transport_t
*transport
;
1013 mDNS
*m
= &mDNSStorage
;
1017 detail
= cs
->detail
;
1019 // If we had a socket open but the connect failed, close it and try the next address, if we have
1022 cs
->last_event
= m
->timenow
;
1024 cs
->connecting
= mDNSfalse
;
1025 if (err
!= mStatus_NoError
) {
1026 mDNSPlatformTCPCloseConnection(sock
);
1027 LogMsg("dso_connect_callback: connect %p failed (%d)", cs
, err
);
1030 // We got a connection.
1031 transport
= dso_transport_create(sock
, false, cs
->context
, cs
->max_outstanding_queries
,
1032 cs
->inbuf_size
, cs
->outbuf_size
, cs
->hostname
, cs
->callback
, cs
->dso
);
1033 if (transport
== NULL
) {
1034 // If dso_create fails, there's no point in continuing to try to connect to new
1037 LogMsg("dso_connect_callback: dso_create failed");
1038 mDNSPlatformTCPCloseConnection(sock
);
1040 // Call the "we're connected" callback, which will start things up.
1041 transport
->dso
->cb(cs
->context
, NULL
, transport
->dso
, kDSOEventType_Connected
);
1046 // When the connection has succeeded, stop asking questions.
1047 if (cs
->lookup
!= NULL
) {
1048 DNSServiceRef ref
= cs
->lookup
;
1050 mDNS_DropLockBeforeCallback();
1051 DNSServiceRefDeallocate(ref
);
1052 mDNS_ReclaimLockAfterCallback();
1059 // If there are no addresses to connect to, and there are no queries running, then we can give
1060 // up. Otherwise, we wait for one of the queries to deliver an answer.
1061 if (cs
->num_addrs
<= cs
->cur_addr
) {
1062 if (cs
->lookup
== NULL
) {
1063 LogMsg("dso_connect_callback: %s: no more addresses to try", cs
->hostname
);
1065 cs
->callback(cs
->context
, NULL
, NULL
, kDSOEventType_ConnectFailed
);
1067 // Otherwise, we will get more callbacks when outstanding queries either fail or succeed.
1072 sock
= mDNSPlatformTCPSocket(kTCPSocketFlags_Zero
, cs
->addresses
[cs
->cur_addr
].type
, NULL
, NULL
, mDNSfalse
);
1074 LogMsg("drConnectCallback: couldn't get a socket for %s: %s%s",
1075 cs
->hostname
, strerror(errno
), detail
);
1079 LogMsg("dso_connect_callback: Attempting to connect to %#a%%%d",
1080 &cs
->addresses
[cs
->cur_addr
], ntohs(cs
->ports
[cs
->cur_addr
].NotAnInteger
));
1082 status
= mDNSPlatformTCPConnect(sock
, &cs
->addresses
[cs
->cur_addr
], cs
->ports
[cs
->cur_addr
], NULL
,
1083 dso_connect_callback
, cs
);
1085 if (status
== mStatus_NoError
|| status
== mStatus_ConnEstablished
) {
1086 // This can't happen in practice on MacOS; we don't know about all other operating systems,
1087 // so we handle it just in case.
1088 LogMsg("dso_connect_callback: synchronous connect to %s", cs
->hostname
);
1090 } else if (status
== mStatus_ConnPending
) {
1091 LogMsg("dso_connect_callback: asynchronous connect to %s", cs
->hostname
);
1092 cs
->connecting
= mDNStrue
;
1093 // We should get called back when the connection succeeds or fails.
1097 LogMsg("dso_connect_callback: failed to connect to %s on %#a%d: %s%s",
1098 cs
->hostname
, &cs
->addresses
[cs
->cur_addr
],
1099 ntohs(cs
->ports
[cs
->cur_addr
].NotAnInteger
), strerror(errno
), detail
);
1103 static void dso_connect_internal(dso_connect_state_t
*cs
)
1105 dso_connect_callback(NULL
, cs
, false, mStatus_NoError
);
1107 #endif // DSO_USES_NETWORK_FRAMEWORK
1109 static void dso_inaddr_callback(DNSServiceRef sdRef
, DNSServiceFlags flags
, uint32_t interfaceIndex
,
1110 DNSServiceErrorType errorCode
, const char *fullname
, const struct sockaddr
*sa
,
1111 uint32_t ttl
, void *context
)
1113 dso_connect_state_t
*cs
= context
;
1114 char addrbuf
[INET6_ADDRSTRLEN
+ 1];
1115 mDNS
*m
= &mDNSStorage
;
1118 cs
->last_event
= m
->timenow
;
1119 inet_ntop(sa
->sa_family
, (sa
->sa_family
== AF_INET
1120 ? (void *)&((struct sockaddr_in
*)sa
)->sin_addr
1121 : (void *)&((struct sockaddr_in6
*)sa
)->sin6_addr
), addrbuf
, sizeof addrbuf
);
1122 LogMsg("dso_inaddr_callback: %s: flags %x index %d error %d fullname %s addr %s ttl %lu",
1123 cs
->hostname
, flags
, interfaceIndex
, errorCode
, fullname
, addrbuf
, (unsigned long)ttl
);
1125 if (errorCode
!= mStatus_NoError
) {
1129 if (cs
->num_addrs
== MAX_DSO_CONNECT_ADDRS
) {
1130 if (cs
->cur_addr
> 1) {
1131 memmove(&cs
->addresses
, &cs
->addresses
[cs
->cur_addr
],
1132 (MAX_DSO_CONNECT_ADDRS
- cs
->cur_addr
) * sizeof cs
->addresses
[0]);
1133 cs
->num_addrs
-= cs
->cur_addr
;
1136 LogMsg("dso_inaddr_callback: ran out of room for addresses.");
1141 if (sa
->sa_family
== AF_INET
) {
1142 cs
->addresses
[cs
->num_addrs
].type
= mDNSAddrType_IPv4
;
1143 mDNSPlatformMemCopy(&cs
->addresses
[cs
->num_addrs
].ip
.v4
,
1144 &((struct sockaddr_in
*)sa
)->sin_addr
, sizeof cs
->addresses
[cs
->num_addrs
].ip
.v4
);
1146 cs
->addresses
[cs
->num_addrs
].type
= mDNSAddrType_IPv6
;
1147 mDNSPlatformMemCopy(&cs
->addresses
[cs
->num_addrs
].ip
.v6
,
1148 &((struct sockaddr_in
*)sa
)->sin_addr
, sizeof cs
->addresses
[cs
->num_addrs
].ip
.v6
);
1151 cs
->ports
[cs
->num_addrs
] = cs
->config_port
;
1153 if (!cs
->connecting
) {
1154 LogMsg("dso_inaddr_callback: starting a new connection.");
1155 dso_connect_internal(cs
);
1157 LogMsg("dso_inaddr_callback: connection in progress, deferring new connect until it fails.");
1161 bool dso_connect(dso_connect_state_t
*cs
)
1164 struct in6_addr in6
;
1166 // If the connection state was created with an address, use that rather than hostname.
1167 if (cs
->num_addrs
> 0) {
1168 dso_connect_internal(cs
);
1170 // Else allow an IPv4 address literal string
1171 else if (inet_pton(AF_INET
, cs
->hostname
, &in
)) {
1173 cs
->addresses
[0].type
= mDNSAddrType_IPv4
;
1174 cs
->addresses
[0].ip
.v4
.NotAnInteger
= in
.s_addr
;
1175 cs
->ports
[0] = cs
->config_port
;
1176 dso_connect_internal(cs
);
1178 // ...or an IPv6 address literal string
1179 else if (inet_pton(AF_INET6
, cs
->hostname
, &in6
)) {
1181 cs
->addresses
[0].type
= mDNSAddrType_IPv6
;
1182 memcpy(&cs
->addresses
[0].ip
.v6
, &in6
, sizeof in6
);
1183 cs
->ports
[0] = cs
->config_port
;
1184 dso_connect_internal(cs
);
1186 // ...or else look it up.
1188 mDNS
*m
= &mDNSStorage
;
1190 mDNS_DropLockBeforeCallback();
1191 err
= DNSServiceGetAddrInfo(&cs
->lookup
, kDNSServiceFlagsReturnIntermediates
,
1192 kDNSServiceInterfaceIndexAny
, 0, cs
->hostname
, dso_inaddr_callback
, cs
);
1194 mDNS_ReclaimLockAfterCallback();
1195 if (err
!= mStatus_NoError
) {
1196 LogMsg("dso_connect: inaddr lookup query allocate failed for '%s': %d", cs
->hostname
, err
);
1203 #ifdef DSO_USES_NETWORK_FRAMEWORK
1204 // We don't need this for DNS Push, so it is being left as future work.
1205 int dso_listen(dso_connect_state_t
* __unused listen_context
)
1207 return mStatus_UnsupportedErr
;
1212 // Called whenever we get a connection on the DNS TCP socket
1213 static void dso_listen_callback(TCPSocket
*sock
, mDNSAddr
*addr
, mDNSIPPort
*port
,
1214 const char *remote_name
, void *context
)
1216 dso_connect_state_t
*lc
= context
;
1217 dso_transport_t
*transport
;
1219 mDNS_Lock(&mDNSStorage
);
1220 transport
= dso_transport_create(sock
, mDNStrue
, lc
->context
, lc
->max_outstanding_queries
,
1221 lc
->inbuf_size
, lc
->outbuf_size
, remote_name
, lc
->callback
, NULL
);
1222 if (transport
== NULL
) {
1223 mDNSPlatformTCPCloseConnection(sock
);
1224 LogMsg("No memory for new DSO connection from %s", remote_name
);
1228 transport
->remote_addr
= *addr
;
1229 transport
->remote_port
= ntohs(port
->NotAnInteger
);
1230 if (transport
->dso
->cb
) {
1231 transport
->dso
->cb(lc
->context
, 0, transport
->dso
, kDSOEventType_Connected
);
1233 LogMsg("DSO connection from %s", remote_name
);
1235 mDNS_Unlock(&mDNSStorage
);
1238 // Listen for connections; each time we get a connection, make a new dso_state_t object with the specified
1239 // parameters and call the callback. Port can be zero to leave it unspecified.
1241 int dso_listen(dso_connect_state_t
*listen_context
)
1243 char addrbuf
[INET6_ADDRSTRLEN
+ 1];
1245 mDNSBool reuseAddr
= mDNSfalse
;
1247 if (listen_context
->config_port
.NotAnInteger
) {
1248 port
= listen_context
->config_port
;
1249 reuseAddr
= mDNStrue
;
1251 listen_context
->listener
= mDNSPlatformTCPListen(mDNSAddrType_None
, &port
, NULL
, kTCPSocketFlags_Zero
,
1252 reuseAddr
, 5, dso_listen_callback
, listen_context
);
1253 if (!listen_context
->listener
) {
1254 return mStatus_UnknownErr
;
1256 listen_context
->connect_port
= port
;
1257 if (listen_context
->addresses
[0].type
== mDNSAddrType_IPv4
) {
1258 inet_ntop(AF_INET
, &listen_context
->addresses
[0].ip
.v4
, addrbuf
, sizeof addrbuf
);
1260 inet_ntop(AF_INET6
, &listen_context
->addresses
[0].ip
.v6
, addrbuf
, sizeof addrbuf
);
1263 LogMsg("DSOListen: Listening on %s%%%d", addrbuf
, ntohs(listen_context
->connect_port
.NotAnInteger
));
1264 return mStatus_NoError
;
1266 #endif // DSO_USES_NETWORK_FRAMEWORK
1271 // c-file-style: "bsd"
1272 // c-basic-offset: 4
1274 // indent-tabs-mode: nil