Implement DNS64 synthesis.
authorflorian <florian@openbsd.org>
Sun, 24 Jan 2021 18:29:15 +0000 (18:29 +0000)
committerflorian <florian@openbsd.org>
Sun, 24 Jan 2021 18:29:15 +0000 (18:29 +0000)
When unwind(8) learns new autoconf resolvers (from dhcp or router
advertisements) it checks if a DNS64 is present in this network
location and tries to recover the IPv6 prefix used according to
RFC7050.
The learned autoconf resolvers are then prevented from upgrading to
the validating state since DNS64 breaks DNSSEC.
unwind(8) can now perform its own synthesis. If a query for a AAAA
record results in no answer we re-send the query for A and if that
leads to an answer we synthesize an AAAA answer using the learned
prefixes.

Testing & OK kn

sbin/unwind/Makefile
sbin/unwind/dns64_synth.c [new file with mode: 0644]
sbin/unwind/dns64_synth.h [new file with mode: 0644]
sbin/unwind/frontend.c
sbin/unwind/frontend.h
sbin/unwind/resolver.c
sbin/unwind/unwind.h

index bd2bd7d..dab7293 100644 (file)
@@ -1,7 +1,8 @@
-#      $OpenBSD: Makefile,v 1.7 2019/12/13 14:38:25 otto Exp $
+#      $OpenBSD: Makefile,v 1.8 2021/01/24 18:29:15 florian Exp $
 
 PROG=  unwind
 SRCS=  control.c resolver.c frontend.c log.c unwind.c parse.y printconf.c
+SRCS+= dns64_synth.c
 MAN=   unwind.8 unwind.conf.5
 
 .include "${.CURDIR}/libunbound/Makefile.inc"
diff --git a/sbin/unwind/dns64_synth.c b/sbin/unwind/dns64_synth.c
new file mode 100644 (file)
index 0000000..b776cf9
--- /dev/null
@@ -0,0 +1,176 @@
+/*     $OpenBSD: dns64_synth.c,v 1.1 2021/01/24 18:29:15 florian Exp $ */
+
+/*
+ * dns64/dns64.h - DNS64 module
+ *
+ * Copyright (c) 2009, Viagénie. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2021 Florian Obser <florian@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <net/route.h>
+
+#include <string.h>
+
+#include "libunbound/config.h"
+#include "libunbound/util/regional.h"
+#include "libunbound/util/data/msgencode.h"
+#include "libunbound/util/data/msgparse.h"
+#include "libunbound/util/data/msgreply.h"
+
+#include "log.h"
+#include "unwind.h"
+#include "frontend.h"
+#include "dns64_synth.h"
+
+extern struct dns64_prefix     *dns64_prefixes;
+extern int                      dns64_prefix_count;
+
+static void
+synthesize_aaaa(const struct in6_addr *in6, int prefixlen, const uint8_t *a,
+    size_t a_len, uint8_t *aaaa, size_t aaaa_len)
+{
+       size_t i, pos;
+       memcpy(aaaa, in6->s6_addr, sizeof(in6->s6_addr));
+       for (i = 0, pos = prefixlen / 8; i < a_len && pos < aaaa_len;
+           i++, pos++) {
+               if (pos == 8)
+                       aaaa[pos++] = 0;
+               aaaa[pos] = a[i];
+       }
+}
+
+/*
+ * Copied from unbound/dns64/dns64.c and slightly extended to work with more
+ * than one IPv6 prefix.
+ */
+
+void
+dns64_synth_aaaa_data(const struct ub_packed_rrset_key* fk, const struct
+    packed_rrset_data* fd, struct ub_packed_rrset_key *dk, struct
+    packed_rrset_data **dd_out, struct regional *region)
+{
+       struct packed_rrset_data        *dd;
+       size_t                           i, pos;
+       int                              j;
+
+       /*
+        * Create synthesized AAAA RR set data. We need to allocated extra
+        * memory for the RRs themselves. Each RR has a length, TTL, pointer to
+        * wireformat data, 2 bytes of data length, and 16 bytes of IPv6
+        * address.
+        */
+       if(fd->count > RR_COUNT_MAX) {
+               *dd_out = NULL;
+               return; /* integer overflow protection in alloc */
+       }
+       if (!(dd = *dd_out = regional_alloc(region, sizeof(struct
+           packed_rrset_data) + fd->count * dns64_prefix_count *
+           (sizeof(size_t) + sizeof(time_t) + sizeof(uint8_t*) + 2 + 16)))) {
+               log_warnx("out of memory");
+               return;
+       }
+
+       /* Copy attributes from A RR set. */
+       dd->ttl = fd->ttl;
+       dd->count = fd->count * dns64_prefix_count;
+       dd->rrsig_count = 0;
+       dd->trust = fd->trust;
+       dd->security = fd->security;
+
+       /* Synthesize AAAA records. Adjust pointers in structure. */
+       dd->rr_len = (size_t*)((uint8_t*)dd + sizeof(struct packed_rrset_data));
+       dd->rr_data = (uint8_t**)&dd->rr_len[dd->count];
+       dd->rr_ttl = (time_t*)&dd->rr_data[dd->count];
+       for(i = 0, pos = 0; i < fd->count; ++i) {
+               if (fd->rr_len[i] != 6 || fd->rr_data[i][0] != 0 ||
+                   fd->rr_data[i][1] != 4) {
+                       *dd_out = NULL;
+                       return;
+               }
+               for (j = 0; j < dns64_prefix_count; j++, pos++) {
+                       dd->rr_len[pos] = 18;
+                       dd->rr_ttl[pos] = fd->rr_ttl[i];
+                       dd->rr_data[pos] = (uint8_t*)&dd->rr_ttl[dd->count] +
+                           18 * pos;
+                       dd->rr_data[pos][0] = 0;
+                       dd->rr_data[pos][1] = 16;
+                       synthesize_aaaa(&dns64_prefixes[j].in6,
+                           dns64_prefixes[j].prefixlen, &fd->rr_data[i][2],
+                           fd->rr_len[i]-2, &dd->rr_data[pos][2],
+                           dd->rr_len[pos]-2);
+               }
+       }
+
+       /*
+        * Create synthesized AAAA RR set key. This is mostly just bookkeeping,
+        * nothing interesting here.
+        */
+       if(!dk) {
+               log_warnx("no key");
+               *dd_out = NULL;
+               return;
+       }
+
+       dk->rk.dname = (uint8_t*)regional_alloc_init(region, fk->rk.dname,
+           fk->rk.dname_len);
+
+       if(!dk->rk.dname) {
+               log_warnx("out of memory");
+               *dd_out = NULL;
+               return;
+       }
+
+       dk->rk.type = htons(LDNS_RR_TYPE_AAAA);
+       memset(&dk->entry, 0, sizeof(dk->entry));
+       dk->entry.key = dk;
+       dk->entry.hash = rrset_key_hash(&dk->rk);
+       dk->entry.data = dd;
+}
diff --git a/sbin/unwind/dns64_synth.h b/sbin/unwind/dns64_synth.h
new file mode 100644 (file)
index 0000000..3e2a88a
--- /dev/null
@@ -0,0 +1,46 @@
+/*     $OpenBSD: dns64_synth.h,v 1.1 2021/01/24 18:29:15 florian Exp $ */
+
+/*
+ * dns64/dns64.h - DNS64 module
+ *
+ * Copyright (c) 2009, Viagénie. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DNS64_SYNTH_H
+#define DNS64_SYNTH_H
+
+void
+dns64_synth_aaaa_data(const struct ub_packed_rrset_key *, const struct
+    packed_rrset_data *, struct ub_packed_rrset_key *, struct
+    packed_rrset_data **, struct regional *);
+
+#endif /* DNS64_SYNTH_H */
index e563d95..50dab6c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: frontend.c,v 1.64 2021/01/19 16:52:40 florian Exp $   */
+/*     $OpenBSD: frontend.c,v 1.65 2021/01/24 18:29:15 florian Exp $   */
 
 /*
  * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -60,6 +60,7 @@
 #include "unwind.h"
 #include "frontend.h"
 #include "control.h"
+#include "dns64_synth.h"
 
 #define        ROUTE_SOCKET_BUF_SIZE   16384
 
@@ -106,6 +107,7 @@ struct pending_query {
        uint64_t                         imsg_id;
        int                              fd;
        int                              tcp;
+       int                              dns64_synthesize;
 };
 
 TAILQ_HEAD(, pending_query)     pending_queries;
@@ -129,6 +131,8 @@ void                         tcp_response(int, short, void *);
 void                    tcp_timeout(int, short, void *);
 int                     check_query(sldns_buffer*);
 void                    noerror_answer(struct pending_query *);
+void                    synthesize_dns64_answer(struct pending_query *);
+void                    resend_dns64_query(struct pending_query *);
 void                    chaos_answer(struct pending_query *);
 void                    error_answer(struct pending_query *, int rcode);
 void                    send_answer(struct pending_query *);
@@ -161,6 +165,9 @@ RB_HEAD(bl_tree, bl_node)    bl_head = RB_INITIALIZER(&bl_head);
 RB_PROTOTYPE(bl_tree, bl_node, entry, bl_cmp)
 RB_GENERATE(bl_tree, bl_node, entry, bl_cmp)
 
+struct dns64_prefix    *dns64_prefixes;
+int                     dns64_prefix_count;
+
 void
 frontend_sig_handler(int sig, short event, void *bula)
 {
@@ -469,6 +476,9 @@ frontend_dispatch_main(int fd, short event, void *bula)
 void
 frontend_dispatch_resolver(int fd, short event, void *bula)
 {
+       static struct dns64_prefix      *new_dns64_prefixes = NULL;
+       static int                       new_dns64_prefix_count = 0;
+       static int                       new_dns64_prefix_pos = 0;
        struct pending_query            *pq;
        struct imsgev                   *iev = bula;
        struct imsgbuf                  *ibuf = &iev->ibuf;
@@ -548,8 +558,17 @@ frontend_dispatch_resolver(int fd, short event, void *bula)
                        if (sldns_buffer_position(pq->abuf) ==
                            sldns_buffer_capacity(pq->abuf)) {
                                sldns_buffer_flip(pq->abuf);
-                               noerror_answer(pq);
-                               send_answer(pq);
+                               if (pq->dns64_synthesize) {
+                                       synthesize_dns64_answer(pq);
+                                       send_answer(pq);
+                               } else {
+                                       noerror_answer(pq);
+                                       if (pq->dns64_synthesize)
+                                               /* we did not find a answer */
+                                               resend_dns64_query(pq);
+                                       else
+                                               send_answer(pq);
+                               }
                        }
                        break;
                }
@@ -579,6 +598,42 @@ frontend_dispatch_resolver(int fd, short event, void *bula)
                        if (ta_fd != -1)
                                write_trust_anchors(&trust_anchors, ta_fd);
                        break;
+               case IMSG_NEW_DNS64_PREFIXES_START:
+                       if (IMSG_DATA_SIZE(imsg) !=
+                           sizeof(new_dns64_prefix_count))
+                               fatalx("%s: IMSG_NEW_DNS64_PREFIXES_START "
+                                   "wrong length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       memcpy(&new_dns64_prefix_count, imsg.data,
+                           sizeof(new_dns64_prefix_count));
+                       free(new_dns64_prefixes);
+                       new_dns64_prefixes = NULL;
+                       if (new_dns64_prefix_count > 0)
+                               new_dns64_prefixes =
+                                   calloc(new_dns64_prefix_count,
+                                   sizeof(struct dns64_prefix));
+                       new_dns64_prefix_pos = 0;
+                       break;
+               case IMSG_NEW_DNS64_PREFIX: {
+                       if (IMSG_DATA_SIZE(imsg) != sizeof(struct dns64_prefix))
+                               fatalx("%s: IMSG_NEW_DNS64_PREFIX wrong "
+                                   "length: %lu", __func__,
+                                   IMSG_DATA_SIZE(imsg));
+                       if (new_dns64_prefixes == NULL)
+                               break;
+                       if (new_dns64_prefix_pos >= new_dns64_prefix_count)
+                               fatalx("%s: IMSG_NEW_DNS64_PREFIX: too many "
+                                   "prefixes", __func__);
+                       memcpy(&new_dns64_prefixes[new_dns64_prefix_pos++],
+                           imsg.data, sizeof(struct dns64_prefix));
+                       break;
+               }
+               case IMSG_NEW_DNS64_PREFIXES_DONE:
+                       free(dns64_prefixes);
+                       dns64_prefixes = new_dns64_prefixes;
+                       dns64_prefix_count = new_dns64_prefix_count;
+                       new_dns64_prefixes = NULL;
+                       break;
                default:
                        log_debug("%s: error handling imsg %d", __func__,
                            imsg.hdr.type);
@@ -794,10 +849,12 @@ handle_query(struct pending_query *pq)
 void
 noerror_answer(struct pending_query *pq)
 {
-       struct query_info        skip, qinfo;
-       struct reply_info       *rinfo = NULL;
-       struct alloc_cache       alloc;
-       struct edns_data         edns;
+       struct query_info                skip, qinfo;
+       struct reply_info               *rinfo = NULL;
+       struct alloc_cache               alloc;
+       struct edns_data                 edns;
+       struct ub_packed_rrset_key      *an_rrset = NULL;
+       struct packed_rrset_data        *an_rrset_data = NULL;
 
        alloc_init(&alloc, NULL, 0);
        memset(&qinfo, 0, sizeof(qinfo));
@@ -808,9 +865,21 @@ noerror_answer(struct pending_query *pq)
        if (reply_info_parse(pq->abuf, &alloc, &qinfo, &rinfo, pq->region,
            &edns) != 0)
                goto srvfail;
+
+       if ((an_rrset = reply_find_answer_rrset(&qinfo, rinfo)) != NULL)
+               an_rrset_data = (struct packed_rrset_data*)an_rrset->entry.data;
+
        /* reply_info_parse() allocates memory */
        query_info_clear(&qinfo);
 
+       /* XXX check that there a no AAAA records in answer section? */
+       if ((an_rrset_data == NULL || an_rrset_data->count == 0) &&
+           !pq->dns64_synthesize && pq->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
+           pq->qinfo.qclass == LDNS_RR_CLASS_IN && dns64_prefix_count > 0) {
+               pq->dns64_synthesize = 1;
+               return;
+       }
+
        sldns_buffer_clear(pq->abuf);
        if (reply_info_encode(&pq->qinfo, rinfo, pq->qmsg->id, rinfo->flags,
            pq->abuf, 0, pq->region, pq->tcp ? UINT16_MAX : pq->edns.udp_size,
@@ -827,6 +896,199 @@ noerror_answer(struct pending_query *pq)
        error_answer(pq, LDNS_RCODE_SERVFAIL);
 }
 
+void
+synthesize_dns64_answer(struct pending_query *pq)
+{
+       struct query_info                skip, qinfo;
+       struct reply_info               *rinfo = NULL, *synth_rinfo = NULL;
+       struct alloc_cache               alloc;
+       struct edns_data                 edns;
+       size_t                           i;
+
+       pq->dns64_synthesize = 0;
+
+       alloc_init(&alloc, NULL, 0);
+       memset(&qinfo, 0, sizeof(qinfo));
+       /* read past query section, no memory is allocated */
+       if (!query_info_parse(&skip, pq->abuf))
+               goto srvfail;
+
+       if (reply_info_parse(pq->abuf, &alloc, &qinfo, &rinfo, pq->region,
+           &edns) != 0)
+               goto srvfail;
+
+       /* reply_info_parse() allocates memory */
+       query_info_clear(&qinfo);
+
+       synth_rinfo = construct_reply_info_base(pq->region, rinfo->flags,
+           rinfo->qdcount, rinfo->ttl, rinfo->prefetch_ttl,
+           rinfo->serve_expired_ttl, rinfo->an_numrrsets,
+           rinfo->ns_numrrsets, rinfo->ar_numrrsets, rinfo->rrset_count,
+           rinfo->security);
+
+       if (!synth_rinfo)
+               goto srvfail;
+
+       if(!reply_info_alloc_rrset_keys(synth_rinfo, NULL, pq->region))
+               goto srvfail;
+
+       for (i = 0; i < synth_rinfo->rrset_count; i++) {
+               struct ub_packed_rrset_key      *src_rrset_key, *dst_rrset_key;
+               struct packed_rrset_data        *src_rrset_data;
+               struct packed_rrset_data        *dst_rrset_data;
+
+               src_rrset_key = rinfo->rrsets[i];
+               src_rrset_data =
+                   (struct packed_rrset_data *)src_rrset_key->entry.data;
+               dst_rrset_key = synth_rinfo->rrsets[i];
+
+               dst_rrset_key->id = src_rrset_key->id;
+               dst_rrset_key->rk = src_rrset_key->rk;
+
+               if (i < rinfo->an_numrrsets && src_rrset_key->rk.type ==
+                   htons(LDNS_RR_TYPE_A)) {
+                       dns64_synth_aaaa_data(src_rrset_key, src_rrset_data,
+                           dst_rrset_key, &dst_rrset_data, pq->region);
+                       if (dst_rrset_data == NULL)
+                               goto srvfail;
+               } else {
+                       dst_rrset_key->entry.hash = src_rrset_key->entry.hash;
+                       dst_rrset_key->rk.dname = regional_alloc_init(
+                           pq->region, src_rrset_key->rk.dname,
+                           src_rrset_key->rk.dname_len);
+                       if (dst_rrset_key->rk.dname == NULL)
+                               goto srvfail;
+
+                       dst_rrset_data = regional_alloc_init(pq->region,
+                           src_rrset_data,
+                           packed_rrset_sizeof(src_rrset_data));
+                       if (dst_rrset_data == NULL)
+                               goto srvfail;
+               }
+
+               packed_rrset_ptr_fixup(dst_rrset_data);
+               dst_rrset_key->entry.data = dst_rrset_data;
+       }
+
+       if (!sldns_buffer_set_capacity(pq->abuf, pq->tcp ? UINT16_MAX :
+           pq->edns.udp_size))
+               goto srvfail;
+
+       sldns_buffer_clear(pq->abuf);
+
+       if (reply_info_encode(&pq->qinfo, synth_rinfo, pq->qmsg->id,
+           synth_rinfo->flags, pq->abuf, 0, pq->region,
+           pq->tcp ? UINT16_MAX : pq->edns.udp_size,
+           pq->edns.bits & EDNS_DO, MINIMIZE_ANSWER) == 0)
+               goto srvfail;
+
+       reply_info_parsedelete(rinfo, &alloc);
+       alloc_clear(&alloc);
+       return;
+
+ srvfail:
+       reply_info_parsedelete(rinfo, &alloc);
+       alloc_clear(&alloc);
+       error_answer(pq, LDNS_RCODE_SERVFAIL);
+}
+
+void
+resend_dns64_query(struct pending_query *opq) {
+       struct pending_query    *pq;
+       struct query_imsg        query_imsg;
+       int                      rcode;
+       char                     dname[LDNS_MAX_DOMAINLEN + 1];
+
+       if ((pq = calloc(1, sizeof(*pq))) == NULL) {
+               log_warn(NULL);
+               return;
+       }
+
+       do {
+               arc4random_buf(&pq->imsg_id, sizeof(pq->imsg_id));
+       } while(find_pending_query(pq->imsg_id) != NULL);
+
+       TAILQ_INSERT_TAIL(&pending_queries, pq, entry);
+
+       pq->from = opq->from;
+       pq->fd = opq->fd;
+       opq->fd = -1;
+       pq->tcp = opq->tcp;
+       pq->qbuf = sldns_buffer_new(sldns_buffer_capacity(opq->qbuf));
+       pq->abuf = sldns_buffer_new(sldns_buffer_capacity(opq->abuf));
+       pq->region = regional_create();
+       pq->qmsg = regional_alloc(pq->region, sizeof(*pq->qmsg));
+
+       if (!pq->qbuf || !pq->abuf || !pq->region || !pq->qmsg) {
+               log_warnx("out of memory");
+               free_pending_query(pq);
+               free_pending_query(opq);
+               return;
+       }
+
+       sldns_buffer_rewind(opq->qbuf);
+       sldns_buffer_write(pq->qbuf, sldns_buffer_current(opq->qbuf),
+           sldns_buffer_remaining(opq->qbuf));
+       sldns_buffer_flip(pq->qbuf);
+       memset(pq->qmsg, 0, sizeof(*pq->qmsg));
+
+       if (pq->tcp) {
+               struct timeval   timeout = {TCP_TIMEOUT, 0};
+
+               event_set(&pq->ev, pq->fd, EV_READ | EV_PERSIST, tcp_request,
+                   pq);
+               event_set(&pq->resp_ev, pq->fd, EV_WRITE | EV_PERSIST,
+                   tcp_response, pq);
+               evtimer_set(&pq->tmo_ev, tcp_timeout, pq);
+               evtimer_add(&pq->tmo_ev, &timeout);
+       }
+
+       if (!query_info_parse(&pq->qinfo, pq->qbuf)) {
+               log_warnx("query_info_parse failed");
+               goto drop;
+       }
+
+       sldns_buffer_rewind(pq->qbuf);
+
+       if (parse_packet(pq->qbuf, pq->qmsg, pq->region) !=
+           LDNS_RCODE_NOERROR) {
+               log_warnx("parse_packet failed");
+               goto drop;
+       }
+
+       rcode = parse_extract_edns(pq->qmsg, &pq->edns, pq->region);
+       if (rcode != LDNS_RCODE_NOERROR) {
+               error_answer(pq, rcode);
+               goto send_answer;
+       }
+
+       dname_str(pq->qinfo.qname, dname);
+       strlcpy(query_imsg.qname, dname, sizeof(query_imsg.qname));
+       query_imsg.id = pq->imsg_id;
+       query_imsg.t = LDNS_RR_TYPE_A;
+       query_imsg.c = pq->qinfo.qclass;
+
+       pq->dns64_synthesize = 1;
+
+       if (frontend_imsg_compose_resolver(IMSG_QUERY, 0, &query_imsg,
+           sizeof(query_imsg)) == -1) {
+               error_answer(pq, LDNS_RCODE_SERVFAIL);
+               goto send_answer;
+       }
+
+       free_pending_query(opq);
+       return;
+
+ send_answer:
+       free_pending_query(opq);
+       send_answer(pq);
+       return;
+
+ drop:
+       free_pending_query(opq);
+       free_pending_query(pq);
+}
+
 void
 chaos_answer(struct pending_query *pq)
 {
index 5e9c176..cd6c218 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: frontend.h,v 1.7 2021/01/19 16:52:12 florian Exp $    */
+/*     $OpenBSD: frontend.h,v 1.8 2021/01/24 18:29:15 florian Exp $    */
 
 /*
  * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -30,6 +30,12 @@ struct imsg_rdns_proposal {
        struct sockaddr_rtdns    rtdns;
 };
 
+struct dns64_prefix {
+       struct in6_addr  in6;
+       int              prefixlen;
+       int              flags;
+};
+
 void            frontend(int, int);
 void            frontend_dispatch_main(int, short, void *);
 void            frontend_dispatch_resolver(int, short, void *);
index ef75345..e800d38 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: resolver.c,v 1.133 2021/01/23 16:28:12 florian Exp $  */
+/*     $OpenBSD: resolver.c,v 1.134 2021/01/24 18:29:15 florian Exp $  */
 
 /*
  * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -97,6 +97,9 @@
 #define        BOGUS           1
 #define        SECURE          2
 
+#define        WKA1_FOUND      1
+#define        WKA2_FOUND      2
+
 struct uw_resolver {
        struct event             check_ev;
        struct event             free_ev;
@@ -196,6 +199,12 @@ int                        *resolvers_to_restart(struct uw_conf *,
                             struct uw_conf *);
 const char             *query_imsg2str(struct query_imsg *);
 char                   *gen_resolv_conf(void);
+void                    check_dns64(void);
+void                    check_dns64_done(struct asr_result *, void *);
+int                     dns64_prefixlen(const struct in6_addr *,
+                            const uint8_t *);
+void                    add_dns64_prefix(const struct in6_addr *, int,
+                            struct dns64_prefix *, int, int);
 
 struct uw_conf                 *resolver_conf;
 static struct imsgev           *iev_frontend;
@@ -220,6 +229,8 @@ struct rrset_cache          *unified_rrset_cache;
 struct key_cache               *unified_key_cache;
 struct val_neg_cache           *unified_neg_cache;
 
+int                             dns64_present;
+
 static const char * const       as112_zones[] = {
        /* RFC1918 */
        "10.in-addr.arpa. transparent",
@@ -1501,10 +1512,19 @@ check_resolver_done(struct uw_resolver *res, void *arg, int rcode,
        }
 
        if (sec == SECURE) {
-               if (prev_state != VALIDATING)
-                       new_resolver(checked_resolver->type, VALIDATING);
-               if (!(evtimer_pending(&trust_anchor_timer, NULL)))
-                       evtimer_add(&trust_anchor_timer, &tv);
+               if (dns64_present && (res->type == UW_RES_DHCP ||
+                   res->type == UW_RES_ODOT_DHCP)) {
+                       /* do not upgrade to validating, DNS64 breaks DNSSEC */
+                       if (prev_state != RESOLVING)
+                               new_resolver(checked_resolver->type,
+                                   RESOLVING);
+               } else {
+                       if (prev_state != VALIDATING)
+                               new_resolver(checked_resolver->type,
+                                   VALIDATING);
+                       if (!(evtimer_pending(&trust_anchor_timer, NULL)))
+                               evtimer_add(&trust_anchor_timer, &tv);
+               }
         } else if (rcode == LDNS_RCODE_NOERROR &&
            LDNS_RCODE_WIRE((uint8_t*)answer_packet) == LDNS_RCODE_NOERROR) {
                if (why_bogus) {
@@ -1989,6 +2009,7 @@ replace_autoconf_forwarders(struct imsg_rdns_proposal *rdns_proposal)
                new_resolver(UW_RES_ASR, UNKNOWN);
                new_resolver(UW_RES_DHCP, UNKNOWN);
                new_resolver(UW_RES_ODOT_DHCP, UNKNOWN);
+               check_dns64();
        } else {
                while ((tmp = TAILQ_FIRST(&new_forwarder_list)) != NULL) {
                        TAILQ_REMOVE(&new_forwarder_list, tmp, entry);
@@ -2157,3 +2178,211 @@ gen_resolv_conf()
        }
        return resolv_conf;
 }
+
+void
+check_dns64(void)
+{
+       struct asr_query        *aq = NULL;
+       char                    *resolv_conf;
+       void                    *asr_ctx;
+
+       if (TAILQ_EMPTY(&autoconf_forwarder_list))
+               return;
+
+       if ((resolv_conf = gen_resolv_conf()) == NULL) {
+               log_warnx("could not create asr context");
+               return;
+       }
+
+       if ((asr_ctx = asr_resolver_from_string(resolv_conf)) != NULL) {
+               if ((aq = res_query_async("ipv4only.arpa", LDNS_RR_CLASS_IN,
+                   LDNS_RR_TYPE_AAAA, asr_ctx)) == NULL) {
+                       log_warn("%s: res_query_async", __func__);
+                       asr_resolver_free(asr_ctx);
+               }
+               if (event_asr_run(aq, check_dns64_done, asr_ctx) == NULL) {
+                       log_warn("%s: event_asr_run", __func__);
+                       free(aq);
+                       asr_resolver_free(asr_ctx);
+               }
+       } else
+               log_warnx("%s: could not create asr context", __func__);
+
+       free(resolv_conf);
+}
+
+void
+check_dns64_done(struct asr_result *ar, void *arg)
+{
+       /* RFC 7050: ipv4only.arpa resolves to 192.0.0.170 and 192.9.0.171 */
+       const uint8_t                    wka1[] = {192, 0, 0, 170};
+       const uint8_t                    wka2[] = {192, 0, 0, 171};
+       struct query_info                skip, qinfo;
+       struct reply_info               *rinfo = NULL;
+       struct regional                 *region = NULL;
+       struct sldns_buffer             *buf = NULL;
+       struct ub_packed_rrset_key      *an_rrset = NULL;
+       struct packed_rrset_data        *an_rrset_data;
+       struct alloc_cache               alloc;
+       struct edns_data                 edns;
+       struct dns64_prefix             *prefixes = NULL;
+       size_t                           i;
+       int                              preflen, count = 0;
+       void                            *asr_ctx = arg;
+
+       memset(&qinfo, 0, sizeof(qinfo));
+       alloc_init(&alloc, NULL, 0);
+
+       if (ar->ar_datalen < LDNS_HEADER_SIZE) {
+               log_warnx("%s: bad packet: too short: %d", __func__,
+                   ar->ar_datalen);
+               goto out;
+       }
+
+       if (ar->ar_datalen > UINT16_MAX) {
+               log_warnx("%s: bad packet: too large: %d", __func__,
+                   ar->ar_datalen);
+               goto out;
+       }
+
+       if (ar->ar_rcode == LDNS_RCODE_NXDOMAIN) {
+               /* XXX this means that the dhcp resolver is broken */
+               log_debug("%s: NXDOMAIN", __func__);
+               goto out;
+       }
+
+       if ((buf = sldns_buffer_new(ar->ar_datalen)) == NULL)
+               goto out;
+
+       if ((region = regional_create()) == NULL)
+               goto out;
+
+       sldns_buffer_write(buf, ar->ar_data, ar->ar_datalen);
+       sldns_buffer_flip(buf);
+
+       /* read past query section, no memory is allocated */
+       if (!query_info_parse(&skip, buf))
+               goto out;
+
+       if (reply_info_parse(buf, &alloc, &qinfo, &rinfo, region, &edns) != 0)
+               goto out;
+
+       if ((an_rrset = reply_find_answer_rrset(&qinfo, rinfo)) == NULL)
+               goto out;
+
+       an_rrset_data = (struct packed_rrset_data*)an_rrset->entry.data;
+
+       prefixes = calloc(an_rrset_data->count, sizeof(struct dns64_prefix));
+       if (prefixes == NULL)
+               goto out;
+
+       for (i = 0; i < an_rrset_data->count; i++) {
+               struct in6_addr  in6;
+
+               /* check for AAAA record */
+               if (an_rrset_data->rr_len[i] != 18) /* 2 + 128/8 */
+                       continue;
+               if (an_rrset_data->rr_data[i][0] != 0 &&
+                   an_rrset_data->rr_data[i][1] != 16)
+                       continue;
+
+               memcpy(&in6, &an_rrset_data->rr_data[i][2],
+                   sizeof(in6));
+               if ((preflen = dns64_prefixlen(&in6, wka1)) != -1)
+                       add_dns64_prefix(&in6, preflen, prefixes,
+                           an_rrset_data->count, WKA1_FOUND);
+               if ((preflen = dns64_prefixlen(&in6, wka2)) != -1)
+                       add_dns64_prefix(&in6, preflen, prefixes,
+                           an_rrset_data->count, WKA2_FOUND);
+       }
+
+       for (i = 0; i < an_rrset_data->count && prefixes[i].flags != 0; i++)
+               if ((prefixes[i].flags & (WKA1_FOUND | WKA2_FOUND)) ==
+                   (WKA1_FOUND | WKA2_FOUND))
+                       count++;
+
+       dns64_present = count > 0;
+
+       if (dns64_present) {
+               /* downgrade DHCP resolvers, DNS64 breaks DNSSEC */
+               if (resolvers[UW_RES_DHCP] != NULL &&
+                   resolvers[UW_RES_DHCP]->state == VALIDATING)
+                       new_resolver(UW_RES_DHCP, RESOLVING);
+               if (resolvers[UW_RES_ODOT_DHCP] != NULL &&
+                   resolvers[UW_RES_ODOT_DHCP]->state == VALIDATING)
+                       new_resolver(UW_RES_ODOT_DHCP, RESOLVING);
+       }
+
+       resolver_imsg_compose_frontend(IMSG_NEW_DNS64_PREFIXES_START, 0,
+           &count, sizeof(count));
+       for (i = 0; i < an_rrset_data->count && prefixes[i].flags != 0; i++) {
+               if ((prefixes[i].flags & (WKA1_FOUND | WKA2_FOUND)) ==
+                   (WKA1_FOUND | WKA2_FOUND)) {
+                       resolver_imsg_compose_frontend(IMSG_NEW_DNS64_PREFIX,
+                           0, &prefixes[i], sizeof(struct dns64_prefix));
+               }
+       }
+       resolver_imsg_compose_frontend(IMSG_NEW_DNS64_PREFIXES_DONE, 0, NULL,
+           0);
+ out:
+       free(prefixes);
+       query_info_clear(&qinfo);
+       reply_info_parsedelete(rinfo, &alloc);
+       alloc_clear(&alloc);
+       regional_destroy(region);
+       sldns_buffer_free(buf);
+       free(ar->ar_data);
+       asr_resolver_free(asr_ctx);
+}
+
+int
+dns64_prefixlen(const struct in6_addr *in6, const uint8_t *wka)
+{
+       /* RFC 6052, 2.2 */
+       static const int         possible_prefixes[] = {32, 40, 48, 56, 64, 96};
+       size_t                   i, j;
+       int                      found, pos;
+
+       for (i = 0; i < nitems(possible_prefixes); i++) {
+               pos = possible_prefixes[i] / 8;
+               found = 1;
+               for (j = 0; j < 4 && found; j++, pos++) {
+                       if (pos == 8) {
+                               if (in6->s6_addr[pos] != 0)
+                                       found = 0;
+                               pos++;
+                       }
+                       if (in6->s6_addr[pos] != wka[j])
+                               found = 0;
+               }
+               if (found)
+                       return possible_prefixes[i];
+       }
+       return -1;
+}
+
+void
+add_dns64_prefix(const struct in6_addr *in6, int prefixlen,
+    struct dns64_prefix *prefixes, int prefixes_size, int flag)
+{
+       struct in6_addr  tmp;
+       int              i;
+
+       tmp = *in6;
+
+       for(i = prefixlen / 8; i < 16; i++)
+               tmp.s6_addr[i] = 0;
+
+       for (i = 0; i < prefixes_size; i++) {
+               if (prefixes[i].flags == 0) {
+                       prefixes[i].in6 = tmp;
+                       prefixes[i].prefixlen = prefixlen;
+                       prefixes[i].flags |= flag;
+                       break;
+               } else if (prefixes[i].prefixlen == prefixlen &&
+                   memcmp(&prefixes[i].in6, &tmp, sizeof(tmp)) == 0) {
+                       prefixes[i].flags |= flag;
+                       break;
+               }
+       }
+}
index f94e585..1f007cb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: unwind.h,v 1.51 2021/01/19 16:50:23 florian Exp $     */
+/*     $OpenBSD: unwind.h,v 1.52 2021/01/24 18:29:15 florian Exp $     */
 
 /*
  * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -119,6 +119,9 @@ enum imsg_type {
        IMSG_NETWORK_CHANGED,
        IMSG_BLFD,
        IMSG_REPLACE_DNS,
+       IMSG_NEW_DNS64_PREFIXES_START,
+       IMSG_NEW_DNS64_PREFIX,
+       IMSG_NEW_DNS64_PREFIXES_DONE,
 };
 
 struct uw_forwarder {