From: Colin Vidal Date: Mon, 31 Mar 2025 13:50:32 +0000 (+0200) Subject: add API to parse and extract IP from PTR name X-Git-Tag: v9.21.14~13^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b4568a85c1b98c7af88dcd6c88bcdf624ddfa817;p=thirdparty%2Fbind9.git add API to parse and extract IP from PTR name Add an API to parse and extract either an IPv4 or IPv6 address from a name using the reverse format. It takes care of family detection, and returns a generic error in case of syntax error. --- diff --git a/lib/dns/byaddr.c b/lib/dns/byaddr.c index e2f5208ddb4..2836434b2b0 100644 --- a/lib/dns/byaddr.c +++ b/lib/dns/byaddr.c @@ -15,8 +15,10 @@ #include +#include #include #include +#include #include #include #include @@ -82,3 +84,118 @@ dns_byaddr_createptrname(const isc_netaddr_t *address, dns_name_t *name) { isc_buffer_add(&buffer, len); return dns_name_fromtext(name, &buffer, dns_rootname, 0); } + +static isc_result_t +parseptrnamev4(const dns_name_t *name, isc_netaddr_t *addr) { + isc_buffer_t b; + static unsigned char inaddrarpa_data[] = "\007IN-ADDR\004ARPA"; + static dns_name_t const inaddrarpa = + DNS_NAME_INITABSOLUTE(inaddrarpa_data); + + if (!dns_name_issubdomain(name, &inaddrarpa)) { + return ISC_R_FAILURE; + } + + *addr = (isc_netaddr_t){ .family = AF_INET }; + isc_buffer_init(&b, &addr->type.in, sizeof(addr->type.in)); + + /* + * Parse the IP address by extracting z y x w labels in reverse + * order to put the IP blocks in the right order. + */ + for (int i = 3; i >= 0; i--) { + dns_label_t label; + char labelstr[4]; + uint8_t block; + + dns_name_getlabel(name, i, &label); + if (label.length > 4) { + return ISC_R_FAILURE; + } + + /* + * Skip the first byte of the label as it encodes the length + * of the label (name wire format). + */ + strncpy(labelstr, (char *)label.base + 1, label.length); + labelstr[label.length - 1] = 0; + if (isc_parse_uint8(&block, labelstr, 10) != ISC_R_SUCCESS) { + return ISC_R_FAILURE; + } + + isc_buffer_putuint8(&b, block); + } + + INSIST(isc_buffer_availablelength(&b) == 0); + return ISC_R_SUCCESS; +} + +static isc_result_t +parseptrnamev6(const dns_name_t *name, isc_netaddr_t *addr) { + isc_buffer_t b; + isc_hex_decodectx_t ctx; + static unsigned char ip6arpa_data[] = "\003IP6\004ARPA"; + static dns_name_t const ip6arpa = DNS_NAME_INITABSOLUTE(ip6arpa_data); + + if (!dns_name_issubdomain(name, &ip6arpa)) { + return ISC_R_FAILURE; + } + + *addr = (isc_netaddr_t){ .family = AF_INET6 }; + isc_buffer_init(&b, &addr->type.in6, sizeof(addr->type.in6)); + isc_hex_decodeinit(&ctx, isc_buffer_length(&b), &b); + + /* + * Parse the IP address by extracting labels in reverse order to + * put the IP blocks in the right order. + */ + for (int i = 31; i >= 0; i--) { + dns_label_t label; + + dns_name_getlabel(name, i, &label); + if (label.length != 2) { + return ISC_R_FAILURE; + } + + /* + * First byte is the label length + */ + if (isc_hex_decodechar(&ctx, label.base[1]) != ISC_R_SUCCESS) { + return ISC_R_FAILURE; + } + } + + if (isc_hex_decodefinish(&ctx) != ISC_R_SUCCESS) { + return ISC_R_FAILURE; + } + + INSIST(isc_buffer_availablelength(&b) == 0); + return ISC_R_SUCCESS; +} + +isc_result_t +dns_byaddr_parseptrname(const dns_name_t *name, isc_netaddr_t *addr) { + int result; + + REQUIRE(DNS_NAME_VALID(name)); + REQUIRE(addr != NULL); + REQUIRE(dns_name_isabsolute(name)); + + switch (dns_name_countlabels(name)) { + case 7: + /* z.y.x.w.in-addr.arpa. has 7 labels */ + result = parseptrnamev4(name, addr); + break; + case 35: + /* + * 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 + * .0.0.0.0.0.0.0.0.0.ip6.arpa. has 35 labels + */ + result = parseptrnamev6(name, addr); + break; + default: + result = ISC_R_FAILURE; + } + + return result; +} diff --git a/lib/dns/include/dns/byaddr.h b/lib/dns/include/dns/byaddr.h index ffd51093bb5..b6b9a5b16ab 100644 --- a/lib/dns/include/dns/byaddr.h +++ b/lib/dns/include/dns/byaddr.h @@ -52,3 +52,15 @@ dns_byaddr_createptrname(const isc_netaddr_t *address, dns_name_t *name); * \li 'address' is a valid address. * \li 'name' is a valid name with a dedicated buffer. */ + +isc_result_t +dns_byaddr_parseptrname(const dns_name_t *name, isc_netaddr_t *addr); +/*%< + * Parse a name in a PTR format and convert it into a isc_net_addr_t. Support + * both IPv4 and IPv6 reverse format. + * + * Requires: + * + * \li 'name' is a valid name. + * \li 'addr' is a valid object + */ diff --git a/tests/dns/byaddr_test.c b/tests/dns/byaddr_test.c new file mode 100644 index 00000000000..2cb3ee9c53e --- /dev/null +++ b/tests/dns/byaddr_test.c @@ -0,0 +1,98 @@ +/* + * 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 /* IWYU pragma: keep */ +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include +#include + +#include + +ISC_RUN_TEST_IMPL(byaddr_parseptrname) { + struct { + const char *ptrname; + const char *address; + } tests[] = { + { "1.0.168.192.in-addr.arpa.", "192.168.0.1" }, + { "ab.0.168.192.in-addr.arpa.", NULL }, + { "abcd.0.168.192.in-addr.arpa.", NULL }, + { "1111.0.168.192.in-addr.arpa.", NULL }, + { "1.0.168.192.in-addr.arp.", NULL }, + { "4.1.999.4.in-addr.arpa.", NULL }, + { "e.f.a.c.3.2.1.0.e.f.a.c.7.6.5.4.1.1.1.1.0.0.0.0.0.0.0." + "0.1.2.e.f.ip6.arpa.", + "fe21::1111:4567:cafe:123:cafe" }, + { "e.f.a.c.3.g.1.0.e.f.a.c.7.6.5.4.1.1.1.1.0.0.0.0.0.0.0." + "0.1.2.e.f.ip6.arpa.", + NULL }, + { "e.f.a.c.3.ee.1.0.e.f.a.c.7.6.5.4.1.1.1.1.0.0.0.0.0.0.0." + "0.1.2.e.f.ip6.arpa.", + NULL }, + { "e.f.a.c.3.2.1.0.e.f.a.c.7.6.5.4.1.1.1.1.0.0.0.0.0.0.0." + "0.1.2.e.f.ip6.arp.", + NULL }, + { "a::z.ip6.arpa.", NULL }, + { "ed.f.a.c.3.2.1.0.e.f.a.c.7.6.5.4.1.1.1.1.0.0.0.0.0.0.0." + "0.1.2.e.f.ip6.arpa.", + NULL }, + { "1.0. . " + ".0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + "ip6.arpa.", + NULL }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(tests); i++) { + int result; + char bdata[128]; + isc_buffer_t b; + isc_netaddr_t addr; + dns_name_t name; + + isc_buffer_init(&b, bdata, sizeof(bdata)); + dns_name_init(&name); + dns_name_setbuffer(&name, &b); + dns_name_fromstring(&name, tests[i].ptrname, NULL, 0, NULL); + + result = dns_byaddr_parseptrname(&name, &addr); + + if (tests[i].address) { + assert_int_equal(result, ISC_R_SUCCESS); + } else { + assert_int_not_equal(result, ISC_R_SUCCESS); + } + + dns_name_invalidate(&name); + isc_buffer_clear(&b); + isc_netaddr_totext(&addr, &b); + isc_buffer_putuint8(&b, 0); + + if (tests[i].address) { + result = strcmp(tests[i].address, b.base); + assert_int_equal(result, 0); + } + } +} + +ISC_TEST_LIST_START +ISC_TEST_ENTRY(byaddr_parseptrname) +ISC_TEST_LIST_END +ISC_TEST_MAIN diff --git a/tests/dns/meson.build b/tests/dns/meson.build index 55d4dee47c7..8d765013a8e 100644 --- a/tests/dns/meson.build +++ b/tests/dns/meson.build @@ -12,6 +12,7 @@ dns_tests = [ 'acl', 'badcache', + 'byaddr', 'db', 'dbdiff', 'dbiterator',