summaryrefslogtreecommitdiffstats
path: root/lib/dns/dns64.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dns/dns64.c')
-rw-r--r--lib/dns/dns64.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/lib/dns/dns64.c b/lib/dns/dns64.c
new file mode 100644
index 0000000..b575726
--- /dev/null
+++ b/lib/dns/dns64.c
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <isc/list.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/dns64.h>
+#include <dns/rdata.h>
+#include <dns/rdataset.h>
+
+struct dns_dns64 {
+ unsigned char bits[16]; /*
+ * Prefix + suffix bits.
+ */
+ dns_acl_t *clients; /*
+ * Which clients get mapped
+ * addresses.
+ */
+ dns_acl_t *mapped; /*
+ * IPv4 addresses to be mapped.
+ */
+ dns_acl_t *excluded; /*
+ * IPv6 addresses that are
+ * treated as not existing.
+ */
+ unsigned int prefixlen; /*
+ * Start of mapped address.
+ */
+ unsigned int flags;
+ isc_mem_t *mctx;
+ ISC_LINK(dns_dns64_t) link;
+};
+
+isc_result_t
+dns_dns64_create(isc_mem_t *mctx, const isc_netaddr_t *prefix,
+ unsigned int prefixlen, const isc_netaddr_t *suffix,
+ dns_acl_t *clients, dns_acl_t *mapped, dns_acl_t *excluded,
+ unsigned int flags, dns_dns64_t **dns64p) {
+ dns_dns64_t *dns64;
+ unsigned int nbytes = 16;
+
+ REQUIRE(prefix != NULL && prefix->family == AF_INET6);
+ /* Legal prefix lengths from rfc6052.txt. */
+ REQUIRE(prefixlen == 32 || prefixlen == 40 || prefixlen == 48 ||
+ prefixlen == 56 || prefixlen == 64 || prefixlen == 96);
+ REQUIRE(isc_netaddr_prefixok(prefix, prefixlen) == ISC_R_SUCCESS);
+ REQUIRE(dns64p != NULL && *dns64p == NULL);
+
+ if (suffix != NULL) {
+ static const unsigned char zeros[16];
+ REQUIRE(prefix->family == AF_INET6);
+ nbytes = prefixlen / 8 + 4;
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (prefixlen >= 32 && prefixlen <= 64) {
+ nbytes++;
+ }
+ REQUIRE(memcmp(suffix->type.in6.s6_addr, zeros, nbytes) == 0);
+ }
+
+ dns64 = isc_mem_get(mctx, sizeof(dns_dns64_t));
+ memset(dns64->bits, 0, sizeof(dns64->bits));
+ memmove(dns64->bits, prefix->type.in6.s6_addr, prefixlen / 8);
+ if (suffix != NULL) {
+ memmove(dns64->bits + nbytes, suffix->type.in6.s6_addr + nbytes,
+ 16 - nbytes);
+ }
+ dns64->clients = NULL;
+ if (clients != NULL) {
+ dns_acl_attach(clients, &dns64->clients);
+ }
+ dns64->mapped = NULL;
+ if (mapped != NULL) {
+ dns_acl_attach(mapped, &dns64->mapped);
+ }
+ dns64->excluded = NULL;
+ if (excluded != NULL) {
+ dns_acl_attach(excluded, &dns64->excluded);
+ }
+ dns64->prefixlen = prefixlen;
+ dns64->flags = flags;
+ ISC_LINK_INIT(dns64, link);
+ dns64->mctx = NULL;
+ isc_mem_attach(mctx, &dns64->mctx);
+ *dns64p = dns64;
+ return (ISC_R_SUCCESS);
+}
+
+void
+dns_dns64_destroy(dns_dns64_t **dns64p) {
+ dns_dns64_t *dns64;
+
+ REQUIRE(dns64p != NULL && *dns64p != NULL);
+
+ dns64 = *dns64p;
+ *dns64p = NULL;
+
+ REQUIRE(!ISC_LINK_LINKED(dns64, link));
+
+ if (dns64->clients != NULL) {
+ dns_acl_detach(&dns64->clients);
+ }
+ if (dns64->mapped != NULL) {
+ dns_acl_detach(&dns64->mapped);
+ }
+ if (dns64->excluded != NULL) {
+ dns_acl_detach(&dns64->excluded);
+ }
+ isc_mem_putanddetach(&dns64->mctx, dns64, sizeof(*dns64));
+}
+
+isc_result_t
+dns_dns64_aaaafroma(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner, dns_aclenv_t *env,
+ unsigned int flags, unsigned char *a, unsigned char *aaaa) {
+ unsigned int nbytes, i;
+ isc_result_t result;
+ int match;
+
+ if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 &&
+ (flags & DNS_DNS64_RECURSIVE) == 0)
+ {
+ return (DNS_R_DISALLOWED);
+ }
+
+ if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 &&
+ (flags & DNS_DNS64_DNSSEC) != 0)
+ {
+ return (DNS_R_DISALLOWED);
+ }
+
+ if (dns64->clients != NULL) {
+ result = dns_acl_match(reqaddr, reqsigner, dns64->clients, env,
+ &match, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (match <= 0) {
+ return (DNS_R_DISALLOWED);
+ }
+ }
+
+ if (dns64->mapped != NULL) {
+ struct in_addr ina;
+ isc_netaddr_t netaddr;
+
+ memmove(&ina.s_addr, a, 4);
+ isc_netaddr_fromin(&netaddr, &ina);
+ result = dns_acl_match(&netaddr, NULL, dns64->mapped, env,
+ &match, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (match <= 0) {
+ return (DNS_R_DISALLOWED);
+ }
+ }
+
+ nbytes = dns64->prefixlen / 8;
+ INSIST(nbytes <= 12);
+ /* Copy prefix. */
+ memmove(aaaa, dns64->bits, nbytes);
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (nbytes == 8) {
+ aaaa[nbytes++] = 0;
+ }
+ /* Copy mapped address. */
+ for (i = 0; i < 4U; i++) {
+ aaaa[nbytes++] = a[i];
+ /* Bits 64-71 are zeros. rfc6052.txt */
+ if (nbytes == 8) {
+ aaaa[nbytes++] = 0;
+ }
+ }
+ /* Copy suffix. */
+ memmove(aaaa + nbytes, dns64->bits + nbytes, 16 - nbytes);
+ return (ISC_R_SUCCESS);
+}
+
+dns_dns64_t *
+dns_dns64_next(dns_dns64_t *dns64) {
+ dns64 = ISC_LIST_NEXT(dns64, link);
+ return (dns64);
+}
+
+void
+dns_dns64_append(dns_dns64list_t *list, dns_dns64_t *dns64) {
+ ISC_LIST_APPEND(*list, dns64, link);
+}
+
+void
+dns_dns64_unlink(dns_dns64list_t *list, dns_dns64_t *dns64) {
+ ISC_LIST_UNLINK(*list, dns64, link);
+}
+
+bool
+dns_dns64_aaaaok(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner, dns_aclenv_t *env,
+ unsigned int flags, dns_rdataset_t *rdataset, bool *aaaaok,
+ size_t aaaaoklen) {
+ struct in6_addr in6;
+ isc_netaddr_t netaddr;
+ isc_result_t result;
+ int match;
+ bool answer = false;
+ bool found = false;
+ unsigned int i, ok;
+
+ REQUIRE(rdataset != NULL);
+ REQUIRE(rdataset->type == dns_rdatatype_aaaa);
+ REQUIRE(rdataset->rdclass == dns_rdataclass_in);
+ if (aaaaok != NULL) {
+ REQUIRE(aaaaoklen == dns_rdataset_count(rdataset));
+ }
+
+ for (; dns64 != NULL; dns64 = ISC_LIST_NEXT(dns64, link)) {
+ if ((dns64->flags & DNS_DNS64_RECURSIVE_ONLY) != 0 &&
+ (flags & DNS_DNS64_RECURSIVE) == 0)
+ {
+ continue;
+ }
+
+ if ((dns64->flags & DNS_DNS64_BREAK_DNSSEC) == 0 &&
+ (flags & DNS_DNS64_DNSSEC) != 0)
+ {
+ continue;
+ }
+ /*
+ * Work out if this dns64 structure applies to this client.
+ */
+ if (dns64->clients != NULL) {
+ result = dns_acl_match(reqaddr, reqsigner,
+ dns64->clients, env, &match,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (match <= 0) {
+ continue;
+ }
+ }
+
+ if (!found && aaaaok != NULL) {
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = false;
+ }
+ }
+ found = true;
+
+ /*
+ * If we are not excluding any addresses then any AAAA
+ * will do.
+ */
+ if (dns64->excluded == NULL) {
+ answer = true;
+ if (aaaaok == NULL) {
+ goto done;
+ }
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = true;
+ }
+ goto done;
+ }
+
+ i = 0;
+ ok = 0;
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ if (aaaaok == NULL || !aaaaok[i]) {
+ dns_rdataset_current(rdataset, &rdata);
+ memmove(&in6.s6_addr, rdata.data, 16);
+ isc_netaddr_fromin6(&netaddr, &in6);
+
+ result = dns_acl_match(&netaddr, NULL,
+ dns64->excluded, env,
+ &match, NULL);
+ if (result == ISC_R_SUCCESS && match <= 0) {
+ answer = true;
+ if (aaaaok == NULL) {
+ goto done;
+ }
+ aaaaok[i] = true;
+ ok++;
+ }
+ } else {
+ ok++;
+ }
+ i++;
+ }
+ /*
+ * Are all addresses ok?
+ */
+ if (aaaaok != NULL && ok == aaaaoklen) {
+ goto done;
+ }
+ }
+
+done:
+ if (!found && aaaaok != NULL) {
+ for (i = 0; i < aaaaoklen; i++) {
+ aaaaok[i] = true;
+ }
+ }
+ return (found ? answer : true);
+}
+
+/*
+ * Posible mapping of IPV4ONLY.ARPA A records into AAAA records
+ * for valid RFC6052 prefixes.
+ */
+static struct {
+ const unsigned char aa[16]; /* mapped version of 192.0.0.170 */
+ const unsigned char ab[16]; /* mapped version of 192.0.0.171 */
+ const unsigned char mask[16];
+ const unsigned int plen;
+} const prefixes[6] = {
+ { { 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 192, 0, 0, 171, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0 },
+ 32 },
+ { { 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0 },
+ 40 },
+ { { 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0 },
+ 48 },
+ { { 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 170, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 171, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0 },
+ 56 },
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 171, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0 },
+ 64 },
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 170 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 171 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255 },
+ 96 }
+};
+
+static unsigned int
+search(const dns_rdata_t *rd1, const dns_rdata_t *rd2, unsigned int plen) {
+ unsigned int i = 0, j;
+ const unsigned char *c, *m;
+
+ /*
+ * Resume looking for another aa match?
+ */
+ if (plen != 0U && rd2 == NULL) {
+ while (i < 6U) {
+ /* Post increment as we resume on next entry. */
+ if (prefixes[i++].plen == plen) {
+ break;
+ }
+ }
+ }
+
+ for (; i < 6U; i++) {
+ j = 0;
+ if (rd2 != NULL) {
+ /* Find the right entry. */
+ if (prefixes[i].plen != plen) {
+ continue;
+ }
+ /* Does the prefix match? */
+ while ((j * 8U) < plen) {
+ if (rd1->data[j] != rd2->data[j]) {
+ return (0);
+ }
+ j++;
+ }
+ }
+
+ /* Match well known mapped addresses. */
+ c = (rd2 == NULL) ? prefixes[i].aa : prefixes[i].ab;
+ m = prefixes[i].mask;
+ for (; j < 16U; j++) {
+ if ((rd1->data[j] & m[j]) != (c[j] & m[j])) {
+ break;
+ }
+ }
+ if (j == 16U) {
+ return (prefixes[i].plen);
+ }
+ if (rd2 != NULL) {
+ return (0);
+ }
+ }
+ return (0);
+}
+
+isc_result_t
+dns_dns64_findprefix(dns_rdataset_t *rdataset, isc_netprefix_t *prefix,
+ size_t *len) {
+ dns_rdataset_t outer, inner;
+ unsigned int oplen, iplen;
+ size_t count = 0;
+ struct in6_addr ina6;
+ isc_result_t result;
+
+ REQUIRE(prefix != NULL && len != NULL && *len != 0U);
+ REQUIRE(rdataset != NULL && rdataset->type == dns_rdatatype_aaaa);
+
+ dns_rdataset_init(&outer);
+ dns_rdataset_init(&inner);
+ dns_rdataset_clone(rdataset, &outer);
+ dns_rdataset_clone(rdataset, &inner);
+
+ for (result = dns_rdataset_first(&outer); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&outer))
+ {
+ dns_rdata_t rd1 = DNS_RDATA_INIT;
+ dns_rdataset_current(&outer, &rd1);
+ oplen = 0;
+ resume:
+ /* Look for a 192.0.0.170 match. */
+ oplen = search(&rd1, NULL, oplen);
+ if (oplen == 0) {
+ continue;
+ }
+
+ /* Look for the 192.0.0.171 match. */
+ for (result = dns_rdataset_first(&inner);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&inner))
+ {
+ dns_rdata_t rd2 = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&inner, &rd2);
+ iplen = search(&rd2, &rd1, oplen);
+ if (iplen == 0) {
+ continue;
+ }
+ INSIST(iplen == oplen);
+ if (count >= *len) {
+ count++;
+ break;
+ }
+
+ /* We have a prefix. */
+ memset(ina6.s6_addr, 0, sizeof(ina6.s6_addr));
+ memmove(ina6.s6_addr, rd1.data, oplen / 8);
+ isc_netaddr_fromin6(&prefix[count].addr, &ina6);
+ prefix[count].prefixlen = oplen;
+ count++;
+ break;
+ }
+ /* Didn't find a match look for a different prefix length. */
+ if (result == ISC_R_NOMORE) {
+ goto resume;
+ }
+ }
+ if (count == 0U) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (count > *len) {
+ *len = count;
+ return (ISC_R_NOSPACE);
+ }
+ *len = count;
+ return (ISC_R_SUCCESS);
+}