1
0
Fork 0
bind9/lib/dns/dns64.c
Daniel Baumann f66ff7eae6
Adding upstream version 1:9.20.9.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:32:37 +02:00

484 lines
12 KiB
C

/*
* 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 && reqaddr != 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;
}