From 00b0420eeb2be90778f4af55c39a81a6de99b9c1 Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 24 Jan 2021 18:29:15 +0000 Subject: [PATCH] Implement DNS64 synthesis. 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 | 3 +- sbin/unwind/dns64_synth.c | 176 ++++++++++++++++++++++++ sbin/unwind/dns64_synth.h | 46 +++++++ sbin/unwind/frontend.c | 276 +++++++++++++++++++++++++++++++++++++- sbin/unwind/frontend.h | 8 +- sbin/unwind/resolver.c | 239 ++++++++++++++++++++++++++++++++- sbin/unwind/unwind.h | 5 +- 7 files changed, 738 insertions(+), 15 deletions(-) create mode 100644 sbin/unwind/dns64_synth.c create mode 100644 sbin/unwind/dns64_synth.h diff --git a/sbin/unwind/Makefile b/sbin/unwind/Makefile index bd2bd7d9890..dab729395d7 100644 --- a/sbin/unwind/Makefile +++ b/sbin/unwind/Makefile @@ -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 index 00000000000..b776cf9dd35 --- /dev/null +++ b/sbin/unwind/dns64_synth.c @@ -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 + * + * 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 +#include + +#include +#include + +#include + +#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 index 00000000000..3e2a88accd3 --- /dev/null +++ b/sbin/unwind/dns64_synth.h @@ -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 */ diff --git a/sbin/unwind/frontend.c b/sbin/unwind/frontend.c index e563d9511c1..50dab6c70ca 100644 --- a/sbin/unwind/frontend.c +++ b/sbin/unwind/frontend.c @@ -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 @@ -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) { diff --git a/sbin/unwind/frontend.h b/sbin/unwind/frontend.h index 5e9c176497c..cd6c21875af 100644 --- a/sbin/unwind/frontend.h +++ b/sbin/unwind/frontend.h @@ -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 @@ -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 *); diff --git a/sbin/unwind/resolver.c b/sbin/unwind/resolver.c index ef753454b2d..e800d38280a 100644 --- a/sbin/unwind/resolver.c +++ b/sbin/unwind/resolver.c @@ -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 @@ -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; + } + } +} diff --git a/sbin/unwind/unwind.h b/sbin/unwind/unwind.h index f94e5850c5c..1f007cbed0e 100644 --- a/sbin/unwind/unwind.h +++ b/sbin/unwind/unwind.h @@ -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 @@ -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 { -- 2.20.1