1 /* dnssd-relay.c
2  *
3  * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * This is a Discovery Proxy module for the SRP gateway.
18  *
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.
24  *
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:
27  *
28  *            https://opensource.apple.com/tarballs/mDNSResponder/
29  */
30 
31 #define __APPLE_USE_RFC_3542
32 
33 #include <stdlib.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
41 #include <fcntl.h>
42 #include <sys/time.h>
43 #include <ctype.h>
44 #include <sys/types.h>
45 #include <ifaddrs.h>
46 #include <net/if.h>
47 
48 #include "dns_sd.h"
49 #include "srp.h"
50 #include "dns-msg.h"
51 #include "srp-crypto.h"
52 #define DNSMessageHeader dns_wire_t
53 #include "dso.h"
54 #include "ioloop.h"
55 #include "srp-tls.h"
56 #include "config-parse.h"
57 
58 // Enumerate the list of interfaces, map them to interface indexes, give each one a name
59 // Have a tree of subdomains for matching
60 
61 // Configuration file settings
62 uint16_t udp_port;
63 uint16_t tcp_port;
64 uint16_t tls_port;
65 #define MAX_ADDRS 10
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";
73 
74 // Code
75 
dso_transport_idle(void * context,int64_t now,int64_t next_event)76 int64_t dso_transport_idle(void *context, int64_t now, int64_t next_event)
77 {
78     return next_event;
79 }
80 
81 void
dp_simple_response(comm_t * comm,int rcode)82 dp_simple_response(comm_t *comm, int rcode)
83 {
84     if (comm->send_response) {
85         struct iovec iov;
86         dns_wire_t response;
87         memset(&response, 0, DNS_HEADER_SIZE);
88 
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);
99     }
100 }
101 
102 bool
dso_send_formerr(dso_state_t * dso,const dns_wire_t * header)103 dso_send_formerr(dso_state_t *dso, const dns_wire_t *header)
104 {
105     comm_t *transport = dso->transport;
106     (void)header;
107     dp_simple_response(transport, dns_rcode_formerr);
108     return true;
109 }
110 
dso_message(comm_t * comm,const dns_wire_t * header,dso_state_t * dso)111 static void dso_message(comm_t *comm, const dns_wire_t *header, dso_state_t *dso)
112 {
113     switch(dso->primary.opcode) {
114     case kDSOType_DNSPushSubscribe:
115         dns_push_subscription_change("DNS Push Subscribe", comm, header, dso);
116         break;
117     case kDSOType_DNSPushUnsubscribe:
118         dns_push_subscription_change("DNS Push Unsubscribe", comm, header, dso);
119         break;
120 
121     case kDSOType_DNSPushReconfirm:
122         dns_push_reconfirm(comm, header, dso);
123         break;
124 
125     case kDSOType_DNSPushUpdate:
126         INFO("dso_message: bogus push update message %d", dso->primary.opcode);
127         dso_drop(dso);
128         break;
129 
130     default:
131         INFO("dso_message: unexpected primary TLV %d", dso->primary.opcode);
132         dp_simple_response(comm, dns_rcode_dsotypeni);
133         break;
134     }
135     // XXX free the message if we didn't consume it.
136 }
137 
dns_push_callback(void * context,const void * event_context,dso_state_t * dso,dso_event_type_t eventType)138 static void dns_push_callback(void *context, const void *event_context,
139                               dso_state_t *dso, dso_event_type_t eventType)
140 {
141     const dns_wire_t *message;
142     switch(eventType)
143     {
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),
148              dso->remote_name);
149         break;
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),
154              dso->remote_name);
155         break;
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);
161         break;
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);
165         break;
166 
167     case kDSOEventType_Finalize:
168         INFO("dns_push_callback: Finalize");
169         break;
170 
171     case kDSOEventType_Connected:
172         INFO("dns_push_callback: Connected to " PRI_S_SRP, dso->remote_name);
173         break;
174 
175     case kDSOEventType_ConnectFailed:
176         INFO("dns_push_callback: Connection to " PRI_S_SRP " failed", dso->remote_name);
177         break;
178 
179     case kDSOEventType_Disconnected:
180         INFO("dns_push_callback: Connection to " PRI_S_SRP " disconnected", dso->remote_name);
181         break;
182     case kDSOEventType_ShouldReconnect:
183         INFO("dns_push_callback: Connection to " PRI_S_SRP " should reconnect (not for a server)", dso->remote_name);
184         break;
185     case kDSOEventType_Inactive:
186         INFO("dns_push_callback: Inactivity timer went off, closing connection.");
187         // XXX
188         break;
189     case kDSOEventType_Keepalive:
190         INFO("dns_push_callback: should send a keepalive now.");
191         break;
192     case kDSOEventType_KeepaliveRcvd:
193         INFO("dns_push_callback: keepalive received.");
194         break;
195     case kDSOEventType_RetryDelay:
196         INFO("dns_push_callback: keepalive received.");
197         break;
198     }
199 }
200 
201 void
dp_dns_query(comm_t * comm,dns_rr_t * question)202 dp_dns_query(comm_t *comm, dns_rr_t *question)
203 {
204     int rcode;
205     dnssd_query_t *query = dp_query_generate(comm, question, false, &rcode);
206     const char *failnote = NULL;
207     if (!query) {
208         dp_simple_response(comm, rcode);
209         return;
210     }
211 
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);
216 
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));
224     } else {
225         TOWIRE_CHECK("full name", &query->towire, dns_full_name_to_wire(NULL, &query->towire, query->name));
226     }
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);
231         goto fail;
232     }
233 
234     // We should check for OPT RR, but for now assume it's there.
235     query->is_edns0 = true;
236 
237     if (!dp_query_start(comm, query, &rcode, dns_query_callback)) {
238     fail:
239         dp_simple_response(comm, rcode);
240         free(query->name);
241         free(query);
242         return;
243     }
244 
245     // XXX make sure that finalize frees this.
246     if (comm->message) {
247         query->question = comm->message;
248         comm->message = NULL;
249     }
250 }
251 
dso_transport_finalize(comm_t * comm)252 void dso_transport_finalize(comm_t *comm)
253 {
254     dso_state_t *dso = comm->dso;
255     INFO("dso_transport_finalize: " PRI_S_SRP, dso->remote_name);
256     if (comm) {
257         ioloop_close(&comm->io);
258     }
259     free(dso);
260     comm->dso = NULL;
261 }
262 
dns_evaluate(comm_t * comm)263 void dns_evaluate(comm_t *comm)
264 {
265     dns_rr_t question;
266     unsigned offset = 0;
267 
268     // Drop incoming responses--we're a server, so we only accept queries.
269     if (dns_qr_get(&comm->message->wire) == dns_qr_response) {
270         return;
271     }
272 
273     // If this is a DSO message, see if we have a session yet.
274     switch(dns_opcode_get(&comm->message->wire)) {
275     case dns_opcode_dso:
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);
279             return;
280         }
281 
282         if (!comm->dso) {
283             comm->dso = dso_create(true, 0, comm->name, dns_push_callback, comm, comm);
284             if (!comm->dso) {
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);
288                 return;
289             }
290             comm->dso->transport_finalize = dso_transport_finalize;
291         }
292         dso_message_received(comm->dso, (uint8_t *)&comm->message->wire, comm->message->length);
293         break;
294 
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);
300             return;
301         }
302         if (!dns_rr_parse(&question, comm->message->wire.data, comm->message->length, &offset, 0)) {
303             dp_simple_response(comm, dns_rcode_formerr);
304             return;
305         }
306         dp_dns_query(comm, &question);
307         dns_rrdata_free(&question);
308         break;
309 
310         // No support for other opcodes yet.
311     default:
312         dp_simple_response(comm, dns_rcode_notimp);
313         break;
314     }
315 }
316 
dns_input(comm_t * comm)317 void dns_input(comm_t *comm)
318 {
319     dns_evaluate(comm);
320     if (comm->message != NULL) {
321         message_free(comm->message);
322         comm->message = NULL;
323     }
324 }
325 
326 static int
usage(const char * progname)327 usage(const char *progname)
328 {
329     ERROR("usage: " PUB_S_SRP, progname);
330     ERROR("ex: dnssd-proxy");
331     return 1;
332 }
333 
334 // Called whenever we get a connection.
335 void
connected(comm_t * comm)336 connected(comm_t *comm)
337 {
338     INFO("connection from " PRI_S_SRP, comm->name);
339     return;
340 }
341 
config_string_handler(char ** ret,const char * filename,const char * string,int lineno,bool tdot,bool ldot)342 static bool config_string_handler(char **ret, const char *filename, const char *string, int lineno, bool tdot,
343                                   bool ldot)
344 {
345     char *s;
346     int add_trailing_dot = 0;
347     int add_leading_dot = ldot ? 1 : 0;
348     int len = strlen(string);
349 
350     // Space for NUL and leading dot.
351     if (tdot && len > 0 && string[len - 1] != '.') {
352         add_trailing_dot = 1;
353     }
354     s = malloc(strlen(string) + add_leading_dot + add_trailing_dot + 1);
355     if (s == NULL) {
356         ERROR("Unable to allocate domain name " PRI_S_SRP, string);
357         return false;
358     }
359     *ret = s;
360     if (ldot) {
361         *s++ = '.';
362     }
363     strcpy(s, string);
364     if (add_trailing_dot) {
365         s[len] = '.';
366         s[len + 1] = 0;
367     }
368     return true;
369 }
370 
371 // Config file parsing...
interface_handler(void * context,const char * filename,char ** hunks,int num_hunks,int lineno)372 static bool interface_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
373 {
374     interface_t *interface = calloc(1, sizeof *interface);
375     if (interface == NULL) {
376         ERROR("Unable to allocate interface " PUB_S_SRP, hunks[1]);
377         return false;
378     }
379 
380     interface->name = strdup(hunks[1]);
381     if (interface->name == NULL) {
382         ERROR("Unable to allocate interface name " PUB_S_SRP, hunks[1]);
383         free(interface);
384         return false;
385     }
386 
387     if (!strcmp(hunks[0], "nopush")) {
388         interface->no_push = true;
389     }
390 
391     if (new_served_domain(interface, hunks[2]) == NULL) {
392         free(interface->name);
393         free(interface);
394         return false;
395     }
396     return true;
397 }
398 
port_handler(void * context,const char * filename,char ** hunks,int num_hunks,int lineno)399 static bool port_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
400 {
401     char *ep = NULL;
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]);
405         return false;
406     }
407     if (!strcmp(hunks[0], "udp-port")) {
408         udp_port = port;
409     } else if (!strcmp(hunks[0], "tcp-port")) {
410         tcp_port = port;
411     } else if (!strcmp(hunks[0], "tls-port")) {
412         tls_port = port;
413     }
414     return true;
415 }
416 
listen_addr_handler(void * context,const char * filename,char ** hunks,int num_hunks,int lineno)417 static bool listen_addr_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
418 {
419     if (num_listen_addrs == MAX_ADDRS) {
420         ERROR("Only %d IPv4 listen addresses can be configured.", MAX_ADDRS);
421         return false;
422     }
423     return config_string_handler(&listen_addrs[num_listen_addrs++], filename, hunks[1], lineno, false, false);
424 }
425 
tls_key_handler(void * context,const char * filename,char ** hunks,int num_hunks,int lineno)426 static bool tls_key_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
427 {
428     return config_string_handler(&tls_key_filename, filename, hunks[1], lineno, false, false);
429 }
430 
tls_cert_handler(void * context,const char * filename,char ** hunks,int num_hunks,int lineno)431 static bool tls_cert_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
432 {
433     return config_string_handler(&tls_cert_filename, filename, hunks[1], lineno, false, false);
434 }
435 
tls_cacert_handler(void * context,const char * filename,char ** hunks,int num_hunks,int lineno)436 static bool tls_cacert_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
437 {
438     return config_string_handler(&tls_cacert_filename, filename, hunks[1], lineno, false, false);
439 }
440 
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>
451 };
452 #define NUMCFVERBS ((sizeof dp_verbs) / sizeof (config_file_verb_t))
453 
454 int
main(int argc,char ** argv)455 main(int argc, char **argv)
456 {
457     int i;
458     comm_t *listener[4 + MAX_ADDRS];
459     int num_listeners = 0;
460 
461     udp_port = tcp_port = 53;
462     tls_port = 853;
463 
464     // Parse command line arguments
465     for (i = 1; i < argc; i++) {
466 	  return usage(argv[0]);
467     }
468 
469     // Read the config file
470     if (!config_parse(NULL, "/etc/dnssd-relay.cf", dp_verbs, NUMCFVERBS)) {
471         return 1;
472     }
473 
474     map_interfaces();
475 
476     if (!srp_tls_init()) {
477         return 1;
478     }
479 
480     if (!ioloop_init()) {
481         return 1;
482     }
483 
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,
487                                                         connected, 0);
488         if (listener[num_listeners] == NULL) {
489             ERROR("TLS4 listener: fail.");
490             return 1;
491         }
492         num_listeners++;
493 	}
494 
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.");
501             return 1;
502         }
503         num_listeners++;
504 
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.");
509             return 1;
510         }
511         num_listeners++;
512     }
513 
514     for (i = 0; i < num_listeners; i++) {
515         INFO("Started " PRI_S_SRP, listener[i]->name);
516     }
517 
518     do {
519         int something = 0;
520         something = ioloop_events(0);
521         INFO("dispatched %d events.", something);
522     } while (1);
523 }
524 
525 // Local Variables:
526 // mode: C
527 // tab-width: 4
528 // c-file-style: "bsd"
529 // c-basic-offset: 4
530 // fill-column: 108
531 // indent-tabs-mode: nil
532 // End:
533