From 38d6f68de46037cdb4f408c97ba5d2f9e57ad7ea Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Tue, 23 Jul 2019 03:44:30 +1000 Subject: [PATCH] add dns_dns64_findprefix --- lib/dns/dns64.c | 159 +++++++++++++++++++++ lib/dns/include/dns/dns64.h | 21 +++ lib/dns/tests/Makefile.am | 1 + lib/dns/tests/dns64_test.c | 250 ++++++++++++++++++++++++++++++++++ lib/dns/win32/libdns.def.in | 1 + lib/isc/include/isc/netaddr.h | 5 + lib/isc/include/isc/types.h | 1 + util/copyrights | 1 + 8 files changed, 439 insertions(+) create mode 100644 lib/dns/tests/dns64_test.c diff --git a/lib/dns/dns64.c b/lib/dns/dns64.c index 67344a992f..cc99a1639e 100644 --- a/lib/dns/dns64.c +++ b/lib/dns/dns64.c @@ -321,3 +321,162 @@ done: } 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); +} diff --git a/lib/dns/include/dns/dns64.h b/lib/dns/include/dns/dns64.h index b3f6f42c40..c1960f0031 100644 --- a/lib/dns/include/dns/dns64.h +++ b/lib/dns/include/dns/dns64.h @@ -167,6 +167,27 @@ dns_dns64_aaaaok(const dns_dns64_t *dns64, const isc_netaddr_t *reqaddr, * if 'aaaaok' in non NULL. */ +isc_result_t +dns_dns64_findprefix(dns_rdataset_t *rdataset, isc_netprefix_t *prefix, + size_t *len); +/* + * Look through 'rdataset' for AAAA pairs which define encoded DNS64 prefixes. + * 'len' should be set to the number of entries in 'prefix' and returns + * the number of prefixes discovered. This may be bigger than those that + * can fit in 'prefix'. + * + * Requires + * 'rdataset' to be valid and to be for type AAAA and class IN. + * 'prefix' to be non NULL. + * 'len' to be non NULL and non zero. + * + * Returns + * ISC_R_SUCCESS + * ISC_R_NOSPACE if there are more prefixes discovered than can fit + * into 'prefix'. + * ISC_R_NOTFOUND no prefixes where found. + */ + ISC_LANG_ENDDECLS #endif /* DNS_DNS64_H */ diff --git a/lib/dns/tests/Makefile.am b/lib/dns/tests/Makefile.am index 04ef09c0f5..3322b89228 100644 --- a/lib/dns/tests/Makefile.am +++ b/lib/dns/tests/Makefile.am @@ -24,6 +24,7 @@ check_PROGRAMS = \ dbversion_test \ dh_test \ dispatch_test \ + dns64_test \ dst_test \ geoip_test \ keytable_test \ diff --git a/lib/dns/tests/dns64_test.c b/lib/dns/tests/dns64_test.c new file mode 100644 index 0000000000..450b09ac67 --- /dev/null +++ b/lib/dns/tests/dns64_test.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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. + */ + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +static void +multiple_prefixes(void) { + size_t i, count; + /* + * Two prefix, non consectutive. + */ + unsigned char aaaa[4][16] = { + { 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0, 0, 192, 0, 0, 171 }, + { 0, 0, 0, 0, 192, 55, 0, 170, 0, 0, 0, 0, 192, 0, 0, 170 }, + { 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0, 0, 192, 0, 0, 170 }, + { 0, 0, 0, 0, 192, 55, 0, 170, 0, 0, 0, 0, 192, 0, 0, 171 }, + }; + dns_rdataset_t rdataset; + dns_rdatalist_t rdatalist; + dns_rdata_t rdata[4] = { DNS_RDATA_INIT, DNS_RDATA_INIT, DNS_RDATA_INIT, + DNS_RDATA_INIT }; + isc_netprefix_t prefix[2]; + unsigned char p1[] = { 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0, 0 }; + unsigned char p2[] = { 0, 0, 0, 0, 192, 55, 0, 170, 0, 0, 0, 0 }; + isc_result_t result; + bool have_p1, have_p2; + + /* + * Construct AAAA rdataset containing 2 prefixes. + */ + dns_rdatalist_init(&rdatalist); + for (i = 0; i < 4; i++) { + isc_region_t region; + region.base = aaaa[i]; + region.length = 16; + dns_rdata_fromregion(&rdata[i], dns_rdataclass_in, + dns_rdatatype_aaaa, ®ion); + ISC_LIST_APPEND(rdatalist.rdata, &rdata[i], link); + } + rdatalist.type = rdata[0].type; + rdatalist.rdclass = rdata[0].rdclass; + rdatalist.ttl = 0; + dns_rdataset_init(&rdataset); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(result, ISC_R_SUCCESS); + + count = ARRAY_SIZE(prefix); + memset(&prefix, 0, sizeof(prefix)); + result = dns_dns64_findprefix(&rdataset, prefix, &count); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(count, 2); + have_p1 = have_p2 = false; + for (i = 0; i < count; i++) { + assert_int_equal(prefix[i].prefixlen, 96); + assert_int_equal(prefix[i].addr.family, AF_INET6); + if (memcmp(prefix[i].addr.type.in6.s6_addr, p1, 12) == 0) { + have_p1 = true; + } + if (memcmp(prefix[i].addr.type.in6.s6_addr, p2, 12) == 0) { + have_p2 = true; + } + } + assert_true(have_p1); + assert_true(have_p2); + + /* + * Check that insufficient prefix space returns ISC_R_NOSPACE + * and that the prefix is populated. + */ + count = 1; + memset(&prefix, 0, sizeof(prefix)); + result = dns_dns64_findprefix(&rdataset, prefix, &count); + assert_int_equal(result, ISC_R_NOSPACE); + assert_int_equal(count, 2); + have_p1 = have_p2 = false; + assert_int_equal(prefix[0].prefixlen, 96); + assert_int_equal(prefix[0].addr.family, AF_INET6); + if (memcmp(prefix[0].addr.type.in6.s6_addr, p1, 12) == 0) { + have_p1 = true; + } + if (memcmp(prefix[0].addr.type.in6.s6_addr, p2, 12) == 0) { + have_p2 = true; + } + if (!have_p2) { + assert_true(have_p1); + } + if (!have_p1) { + assert_true(have_p2); + } + assert_true(have_p1 != have_p2); +} + +static void +dns64_findprefix(void **state) { + unsigned int i, j, o; + isc_result_t result; + struct { + unsigned char prefix[12]; + unsigned int prefixlen; + isc_result_t result; + } tests[] = { + /* The WKP with various lengths. */ + { { 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 }, + 32, + ISC_R_SUCCESS }, + { { 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 }, + 40, + ISC_R_SUCCESS }, + { { 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 }, + 48, + ISC_R_SUCCESS }, + { { 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 }, + 56, + ISC_R_SUCCESS }, + { { 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 }, + 64, + ISC_R_SUCCESS }, + { { 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0 }, + 96, + ISC_R_SUCCESS }, + /* + * Prefix with the mapped addresses also appearing in the + * prefix. + */ + { { 0, 0, 0, 0, 192, 0, 0, 170, 0, 0, 0, 0 }, + 96, + ISC_R_SUCCESS }, + { { 0, 0, 0, 0, 192, 0, 0, 171, 0, 0, 0, 0 }, + 96, + ISC_R_SUCCESS }, + /* Bad prefix, MBZ != 0. */ + { { 0, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 1, 0, 0, 0 }, + 96, + ISC_R_NOTFOUND }, + }; + + UNUSED(state); + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + size_t count = 2; + dns_rdataset_t rdataset; + dns_rdatalist_t rdatalist; + dns_rdata_t rdata[2] = { DNS_RDATA_INIT, DNS_RDATA_INIT }; + struct in6_addr ina6[2]; + isc_netprefix_t prefix[2]; + unsigned char aa[] = { 192, 0, 0, 170 }; + unsigned char ab[] = { 192, 0, 0, 171 }; + isc_region_t region; + + /* + * Construct rdata. + */ + memset(ina6[0].s6_addr, 0, sizeof(ina6[0].s6_addr)); + memset(ina6[1].s6_addr, 0, sizeof(ina6[1].s6_addr)); + memmove(ina6[0].s6_addr, tests[i].prefix, 12); + memmove(ina6[1].s6_addr, tests[i].prefix, 12); + o = tests[i].prefixlen / 8; + for (j = 0; j < 4; j++) { + if ((o + j) == 8U) { + o++; /* skip mbz */ + } + ina6[0].s6_addr[j + o] = aa[j]; + ina6[1].s6_addr[j + o] = ab[j]; + } + region.base = ina6[0].s6_addr; + region.length = sizeof(ina6[0].s6_addr); + dns_rdata_fromregion(&rdata[0], dns_rdataclass_in, + dns_rdatatype_aaaa, ®ion); + region.base = ina6[1].s6_addr; + region.length = sizeof(ina6[1].s6_addr); + dns_rdata_fromregion(&rdata[1], dns_rdataclass_in, + dns_rdatatype_aaaa, ®ion); + + dns_rdatalist_init(&rdatalist); + rdatalist.type = rdata[0].type; + rdatalist.rdclass = rdata[0].rdclass; + rdatalist.ttl = 0; + ISC_LIST_APPEND(rdatalist.rdata, &rdata[0], link); + ISC_LIST_APPEND(rdatalist.rdata, &rdata[1], link); + dns_rdataset_init(&rdataset); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + assert_int_equal(result, ISC_R_SUCCESS); + + result = dns_dns64_findprefix(&rdataset, prefix, &count); + assert_int_equal(result, tests[i].result); + if (tests[i].result == ISC_R_SUCCESS) { + assert_int_equal(count, 1); + assert_int_equal(prefix[0].prefixlen, + tests[i].prefixlen); + assert_int_equal(prefix[0].addr.family, AF_INET6); + assert_memory_equal(prefix[0].addr.type.in6.s6_addr, + tests[i].prefix, + tests[i].prefixlen / 8); + } + } + + /* + * Test multiple prefixes. + */ + multiple_prefixes(); +} + +int +main(void) { + const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown( + dns64_findprefix, NULL, NULL) }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (0); +} + +#endif diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 63d9530f05..b9de1fee66 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -305,6 +305,7 @@ dns_dns64_aaaaok dns_dns64_append dns_dns64_create dns_dns64_destroy +dns_dns64_findprefix dns_dns64_next dns_dns64_unlink @IF NOTYET diff --git a/lib/isc/include/isc/netaddr.h b/lib/isc/include/isc/netaddr.h index 83b1ef4fb4..d4d417e852 100644 --- a/lib/isc/include/isc/netaddr.h +++ b/lib/isc/include/isc/netaddr.h @@ -40,6 +40,11 @@ struct isc_netaddr { uint32_t zone; }; +struct isc_netprefix { + isc_netaddr_t addr; + unsigned int prefixlen; +}; + bool isc_netaddr_equal(const isc_netaddr_t *a, const isc_netaddr_t *b); diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h index b82ff18d26..b3b66f358c 100644 --- a/lib/isc/include/isc/types.h +++ b/lib/isc/include/isc/types.h @@ -64,6 +64,7 @@ typedef struct isc_logmodule isc_logmodule_t; /*%< Log Module */ typedef struct isc_mem isc_mem_t; /*%< Memory */ typedef struct isc_mempool isc_mempool_t; /*%< Memory Pool */ typedef struct isc_netaddr isc_netaddr_t; /*%< Net Address */ +typedef struct isc_netprefix isc_netprefix_t; /*%< Net Prefix */ typedef struct isc_nm isc_nm_t; /*%< Network manager */ typedef struct isc_nmsocket isc_nmsocket_t; /*%< Network manager socket */ typedef struct isc_nmiface isc_nmiface_t; /*%< Network manager interface. */ diff --git a/util/copyrights b/util/copyrights index 161226d4de..5a50c6523f 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1637,6 +1637,7 @@ ./lib/dns/tests/dbversion_test.c C 2011,2012,2014,2015,2016,2018,2019,2020 ./lib/dns/tests/dh_test.c C 2014,2016,2018,2019,2020 ./lib/dns/tests/dispatch_test.c C 2012,2014,2016,2018,2019,2020 +./lib/dns/tests/dns64_test.c C 2019,2020 ./lib/dns/tests/dnstap_test.c C 2015,2016,2017,2018,2019,2020 ./lib/dns/tests/dnstest.c C 2011,2012,2013,2014,2015,2016,2017,2018,2019,2020 ./lib/dns/tests/dnstest.h C 2011,2012,2014,2015,2016,2017,2018,2019,2020