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