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.
This commit is contained in:
Colin Vidal
2025-03-31 15:50:32 +02:00
parent e34dd2b73e
commit b4568a85c1
4 changed files with 228 additions and 0 deletions

View File

@@ -15,8 +15,10 @@
#include <stdbool.h>
#include <isc/hex.h>
#include <isc/mem.h>
#include <isc/netaddr.h>
#include <isc/parseint.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/util.h>
@@ -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;
}

View File

@@ -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
*/

98
tests/dns/byaddr_test.c Normal file
View File

@@ -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 <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/lib.h>
#include <isc/netaddr.h>
#include <dns/byaddr.h>
#include <dns/name.h>
#include <tests/isc.h>
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

View File

@@ -12,6 +12,7 @@
dns_tests = [
'acl',
'badcache',
'byaddr',
'db',
'dbdiff',
'dbiterator',