3 * Copyright (c) 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.
17 * This is a Discovery Proxy module for the SRP gateway.
19 * The motivation here is that it makes sense to co-locate the SRP relay and the Discovery Proxy because
20 * these functions are likely to co-exist on the same node, listening on the same port. For homenet-style
21 * name resolution, we need a DNS proxy that implements DNSSD Discovery Proxy for local queries, but
22 * forwards other queries to an ISP resolver. The SRP gateway is already expecting to do this.
23 * This module implements the functions required to allow the SRP gateway to also do Discovery Relay.
25 * The Discovery Proxy relies on Apple's DNS-SD library and the mDNSResponder DNSSD server, which is included
26 * in Apple's open source mDNSResponder package, available here:
28 * https://opensource.apple.com/tarballs/mDNSResponder/
31 #define __APPLE_USE_RFC_3542
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
44 #include <sys/types.h>
51 #include "srp-crypto.h"
52 #define DNSMessageHeader dns_wire_t
56 #include "config-parse.h"
58 // Enumerate the list of interfaces, map them to interface indexes, give each one a name
59 // Have a tree of subdomains for matching
61 // Configuration file settings
66 char *listen_addrs
[MAX_ADDRS
];
67 int num_listen_addrs
= 0;
68 char *publish_addrs
[MAX_ADDRS
];
69 int num_publish_addrs
= 0;
70 char *tls_cacert_filename
= NULL
;
71 char *tls_cert_filename
= "/etc/dnssd-relay/server.crt";
72 char *tls_key_filename
= "/etc/dnssd-relay/server.key";
76 int64_t dso_transport_idle(void *context
, int64_t now
, int64_t next_event
)
82 dp_simple_response(comm_t
*comm
, int rcode
)
84 if (comm
->send_response
) {
87 memset(&response
, 0, DNS_HEADER_SIZE
);
89 // We take the ID and the opcode from the incoming message, because if the
90 // header has been mangled, we (a) wouldn't have gotten here and (b) don't
91 // have any better choice anyway.
92 response
.id
= comm
->message
->wire
.id
;
93 dns_qr_set(&response
, dns_qr_response
);
94 dns_opcode_set(&response
, dns_opcode_get(&comm
->message
->wire
));
95 dns_rcode_set(&response
, rcode
);
96 iov
.iov_base
= &response
;
97 iov
.iov_len
= DNS_HEADER_SIZE
; // No RRs
98 comm
->send_response(comm
, comm
->message
, &iov
, 1);
103 dso_send_formerr(dso_state_t
*dso
, const dns_wire_t
*header
)
105 comm_t
*transport
= dso
->transport
;
107 dp_simple_response(transport
, dns_rcode_formerr
);
111 static void dso_message(comm_t
*comm
, const dns_wire_t
*header
, dso_state_t
*dso
)
113 switch(dso
->primary
.opcode
) {
114 case kDSOType_DNSPushSubscribe
:
115 dns_push_subscription_change("DNS Push Subscribe", comm
, header
, dso
);
117 case kDSOType_DNSPushUnsubscribe
:
118 dns_push_subscription_change("DNS Push Unsubscribe", comm
, header
, dso
);
121 case kDSOType_DNSPushReconfirm
:
122 dns_push_reconfirm(comm
, header
, dso
);
125 case kDSOType_DNSPushUpdate
:
126 INFO("dso_message: bogus push update message %d", dso
->primary
.opcode
);
131 INFO("dso_message: unexpected primary TLV %d", dso
->primary
.opcode
);
132 dp_simple_response(comm
, dns_rcode_dsotypeni
);
135 // XXX free the message if we didn't consume it.
138 static void dns_push_callback(void *context
, const void *event_context
,
139 dso_state_t
*dso
, dso_event_type_t eventType
)
141 const dns_wire_t
*message
;
144 case kDSOEventType_DNSMessage
:
145 // We shouldn't get here because we already handled any DNS messages
146 message
= event_context
;
147 INFO("dns_push_callback: DNS Message (opcode=%d) received from " PRI_S_SRP
, dns_opcode_get(message
),
150 case kDSOEventType_DNSResponse
:
151 // We shouldn't get here because we already handled any DNS messages
152 message
= event_context
;
153 INFO("dns_push_callback: DNS Response (opcode=%d) received from " PRI_S_SRP
, dns_opcode_get(message
),
156 case kDSOEventType_DSOMessage
:
157 INFO("dns_push_callback: DSO Message (Primary TLV=%d) received from " PRI_S_SRP
,
158 dso
->primary
.opcode
, dso
->remote_name
);
159 message
= event_context
;
160 dso_message((comm_t
*)context
, message
, dso
);
162 case kDSOEventType_DSOResponse
:
163 INFO("dns_push_callback: DSO Response (Primary TLV=%d) received from " PRI_S_SRP
,
164 dso
->primary
.opcode
, dso
->remote_name
);
167 case kDSOEventType_Finalize
:
168 INFO("dns_push_callback: Finalize");
171 case kDSOEventType_Connected
:
172 INFO("dns_push_callback: Connected to " PRI_S_SRP
, dso
->remote_name
);
175 case kDSOEventType_ConnectFailed
:
176 INFO("dns_push_callback: Connection to " PRI_S_SRP
" failed", dso
->remote_name
);
179 case kDSOEventType_Disconnected
:
180 INFO("dns_push_callback: Connection to " PRI_S_SRP
" disconnected", dso
->remote_name
);
182 case kDSOEventType_ShouldReconnect
:
183 INFO("dns_push_callback: Connection to " PRI_S_SRP
" should reconnect (not for a server)", dso
->remote_name
);
185 case kDSOEventType_Inactive
:
186 INFO("dns_push_callback: Inactivity timer went off, closing connection.");
189 case kDSOEventType_Keepalive
:
190 INFO("dns_push_callback: should send a keepalive now.");
192 case kDSOEventType_KeepaliveRcvd
:
193 INFO("dns_push_callback: keepalive received.");
195 case kDSOEventType_RetryDelay
:
196 INFO("dns_push_callback: keepalive received.");
202 dp_dns_query(comm_t
*comm
, dns_rr_t
*question
)
205 dnssd_query_t
*query
= dp_query_generate(comm
, question
, false, &rcode
);
206 const char *failnote
= NULL
;
208 dp_simple_response(comm
, rcode
);
212 // For regular DNS queries, copy the ID, etc.
213 query
->response
->id
= comm
->message
->wire
.id
;
214 query
->response
->bitfield
= comm
->message
->wire
.bitfield
;
215 dns_rcode_set(query
->response
, dns_rcode_noerror
);
217 // For DNS queries, we need to return the question.
218 query
->response
->qdcount
= htons(1);
219 if (query
->iface
!= NULL
) {
220 TOWIRE_CHECK("name", &query
->towire
, dns_name_to_wire(NULL
, &query
->towire
, query
->name
));
221 TOWIRE_CHECK("enclosing_domain", &query
->towire
,
222 dns_full_name_to_wire(&query
->enclosing_domain_pointer
,
223 &query
->towire
, query
->iface
->domain
));
225 TOWIRE_CHECK("full name", &query
->towire
, dns_full_name_to_wire(NULL
, &query
->towire
, query
->name
));
227 TOWIRE_CHECK("TYPE", &query
->towire
, dns_u16_to_wire(&query
->towire
, question
->type
)); // TYPE
228 TOWIRE_CHECK("CLASS", &query
->towire
, dns_u16_to_wire(&query
->towire
, question
->qclass
)); // CLASS
229 if (failnote
!= NULL
) {
230 ERROR("dp_dns_query: failure encoding question: " PUB_S_SRP
, failnote
);
234 // We should check for OPT RR, but for now assume it's there.
235 query
->is_edns0
= true;
237 if (!dp_query_start(comm
, query
, &rcode
, dns_query_callback
)) {
239 dp_simple_response(comm
, rcode
);
245 // XXX make sure that finalize frees this.
247 query
->question
= comm
->message
;
248 comm
->message
= NULL
;
252 void dso_transport_finalize(comm_t
*comm
)
254 dso_state_t
*dso
= comm
->dso
;
255 INFO("dso_transport_finalize: " PRI_S_SRP
, dso
->remote_name
);
257 ioloop_close(&comm
->io
);
263 void dns_evaluate(comm_t
*comm
)
268 // Drop incoming responses--we're a server, so we only accept queries.
269 if (dns_qr_get(&comm
->message
->wire
) == dns_qr_response
) {
273 // If this is a DSO message, see if we have a session yet.
274 switch(dns_opcode_get(&comm
->message
->wire
)) {
276 if (!comm
->tcp_stream
) {
277 ERROR("DSO message received on non-tcp socket " PRI_S_SRP
, comm
->name
);
278 dp_simple_response(comm
, dns_rcode_notimp
);
283 comm
->dso
= dso_create(true, 0, comm
->name
, dns_push_callback
, comm
, comm
);
285 ERROR("Unable to create a dso context for " PRI_S_SRP
, comm
->name
);
286 dp_simple_response(comm
, dns_rcode_servfail
);
287 ioloop_close(&comm
->io
);
290 comm
->dso
->transport_finalize
= dso_transport_finalize
;
292 dso_message_received(comm
->dso
, (uint8_t *)&comm
->message
->wire
, comm
->message
->length
);
295 case dns_opcode_query
:
296 // In theory this is permitted but it can't really be implemented because there's no way
297 // to say "here's the answer for this, and here's why that failed.
298 if (ntohs(comm
->message
->wire
.qdcount
) != 1) {
299 dp_simple_response(comm
, dns_rcode_formerr
);
302 if (!dns_rr_parse(&question
, comm
->message
->wire
.data
, comm
->message
->length
, &offset
, 0)) {
303 dp_simple_response(comm
, dns_rcode_formerr
);
306 dp_dns_query(comm
, &question
);
307 dns_rrdata_free(&question
);
310 // No support for other opcodes yet.
312 dp_simple_response(comm
, dns_rcode_notimp
);
317 void dns_input(comm_t
*comm
)
320 if (comm
->message
!= NULL
) {
321 message_free(comm
->message
);
322 comm
->message
= NULL
;
327 usage(const char *progname
)
329 ERROR("usage: " PUB_S_SRP
, progname
);
330 ERROR("ex: dnssd-proxy");
334 // Called whenever we get a connection.
336 connected(comm_t
*comm
)
338 INFO("connection from " PRI_S_SRP
, comm
->name
);
342 static bool config_string_handler(char **ret
, const char *filename
, const char *string
, int lineno
, bool tdot
,
346 int add_trailing_dot
= 0;
347 int add_leading_dot
= ldot
? 1 : 0;
348 int len
= strlen(string
);
350 // Space for NUL and leading dot.
351 if (tdot
&& len
> 0 && string
[len
- 1] != '.') {
352 add_trailing_dot
= 1;
354 s
= malloc(strlen(string
) + add_leading_dot
+ add_trailing_dot
+ 1);
356 ERROR("Unable to allocate domain name " PRI_S_SRP
, string
);
364 if (add_trailing_dot
) {
371 // Config file parsing...
372 static bool interface_handler(void *context
, const char *filename
, char **hunks
, int num_hunks
, int lineno
)
374 interface_t
*interface
= calloc(1, sizeof *interface
);
375 if (interface
== NULL
) {
376 ERROR("Unable to allocate interface " PUB_S_SRP
, hunks
[1]);
380 interface
->name
= strdup(hunks
[1]);
381 if (interface
->name
== NULL
) {
382 ERROR("Unable to allocate interface name " PUB_S_SRP
, hunks
[1]);
387 if (!strcmp(hunks
[0], "nopush")) {
388 interface
->no_push
= true;
391 if (new_served_domain(interface
, hunks
[2]) == NULL
) {
392 free(interface
->name
);
399 static bool port_handler(void *context
, const char *filename
, char **hunks
, int num_hunks
, int lineno
)
402 long port
= strtol(hunks
[1], &ep
, 10);
403 if (port
< 0 || port
> 65535 || *ep
!= 0) {
404 ERROR("Invalid port number: " PUB_S_SRP
, hunks
[1]);
407 if (!strcmp(hunks
[0], "udp-port")) {
409 } else if (!strcmp(hunks
[0], "tcp-port")) {
411 } else if (!strcmp(hunks
[0], "tls-port")) {
417 static bool listen_addr_handler(void *context
, const char *filename
, char **hunks
, int num_hunks
, int lineno
)
419 if (num_listen_addrs
== MAX_ADDRS
) {
420 ERROR("Only %d IPv4 listen addresses can be configured.", MAX_ADDRS
);
423 return config_string_handler(&listen_addrs
[num_listen_addrs
++], filename
, hunks
[1], lineno
, false, false);
426 static bool tls_key_handler(void *context
, const char *filename
, char **hunks
, int num_hunks
, int lineno
)
428 return config_string_handler(&tls_key_filename
, filename
, hunks
[1], lineno
, false, false);
431 static bool tls_cert_handler(void *context
, const char *filename
, char **hunks
, int num_hunks
, int lineno
)
433 return config_string_handler(&tls_cert_filename
, filename
, hunks
[1], lineno
, false, false);
436 static bool tls_cacert_handler(void *context
, const char *filename
, char **hunks
, int num_hunks
, int lineno
)
438 return config_string_handler(&tls_cacert_filename
, filename
, hunks
[1], lineno
, false, false);
441 config_file_verb_t dp_verbs
[] = {
442 { "interface", 3, 3, interface_handler
}, // interface <name> <domain>
443 { "nopush", 3, 3, interface_handler
}, // nopush <name> <domain>
444 { "udp-port", 2, 2, port_handler
}, // udp-port <number>
445 { "tcp-port", 2, 2, port_handler
}, // tcp-port <number>
446 { "tls-port", 2, 2, port_handler
}, // tls-port <number>
447 { "tls-key", 2, 2, tls_key_handler
}, // tls-key <filename>
448 { "tls-cert", 2, 2, tls_cert_handler
}, // tls-cert <filename>
449 { "tls-cacert", 2, 2, tls_cacert_handler
}, // tls-cacert <filename>
450 { "listen-addr", 2, 2, listen_addr_handler
}, // listen-addr <IP address>
452 #define NUMCFVERBS ((sizeof dp_verbs) / sizeof (config_file_verb_t))
455 main(int argc
, char **argv
)
458 comm_t
*listener
[4 + MAX_ADDRS
];
459 int num_listeners
= 0;
461 udp_port
= tcp_port
= 53;
464 // Parse command line arguments
465 for (i
= 1; i
< argc
; i
++) {
466 return usage(argv
[0]);
469 // Read the config file
470 if (!config_parse(NULL
, "/etc/dnssd-relay.cf", dp_verbs
, NUMCFVERBS
)) {
476 if (!srp_tls_init()) {
480 if (!ioloop_init()) {
484 for (i
= 0; i
< num_listen_addrs
; i
++) {
485 listener
[num_listeners
] = setup_listener_socket(AF_UNSPEC
, IPPROTO_TCP
, true,
486 tls_port
, listen_addrs
[i
], "DNS TLS Listener", dns_input
,
488 if (listener
[num_listeners
] == NULL
) {
489 ERROR("TLS4 listener: fail.");
495 // If we haven't been given any addresses to listen on, listen on an IPv4 address and an IPv6 address.
496 if (num_listen_addrs
== 0) {
497 listener
[num_listeners
] = setup_listener_socket(AF_INET
, IPPROTO_TCP
, true, tls_port
, NULL
,
498 "IPv4 DNS TLS Listener", dns_input
, 0, 0);
499 if (listener
[num_listeners
] == NULL
) {
500 ERROR("UDP4 listener: fail.");
505 listener
[num_listeners
] = setup_listener_socket(AF_INET6
, IPPROTO_TCP
, true, tls_port
, NULL
,
506 "IPv6 DNS TLS Listener", dns_input
, 0, 0);
507 if (listener
[num_listeners
] == NULL
) {
508 ERROR("UDP6 listener: fail.");
514 for (i
= 0; i
< num_listeners
; i
++) {
515 INFO("Started " PRI_S_SRP
, listener
[i
]->name
);
520 something
= ioloop_events(0);
521 INFO("dispatched %d events.", something
);
528 // c-file-style: "bsd"
531 // indent-tabs-mode: nil