]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
add API to parse and extract IP from PTR name
authorColin Vidal <colin@isc.org>
Mon, 31 Mar 2025 13:50:32 +0000 (15:50 +0200)
committerColin Vidal <colin@isc.org>
Wed, 1 Oct 2025 10:16:05 +0000 (12:16 +0200)
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.

lib/dns/byaddr.c
lib/dns/include/dns/byaddr.h
tests/dns/byaddr_test.c [new file with mode: 0644]
tests/dns/meson.build

index e2f5208ddb40ba7f39c474dbc89cec3aaa4b6a8a..2836434b2b0539abbaa902850a41f33635d11d02 100644 (file)
 
 #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;
+}
index ffd51093bb58093e972102dd6a74eb3bc80472e2..b6b9a5b16ab3b7d2b7a60a65add8951d02be2fb5 100644 (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
+ */
diff --git a/tests/dns/byaddr_test.c b/tests/dns/byaddr_test.c
new file mode 100644 (file)
index 0000000..2cb3ee9
--- /dev/null
@@ -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
index 55d4dee47c71394cd1955ba5feb72dfd6dad9909..8d765013a8ed0fcf19ed0340e5e580eded504d6b 100644 (file)
@@ -12,6 +12,7 @@
 dns_tests = [
     'acl',
     'badcache',
+    'byaddr',
     'db',
     'dbdiff',
     'dbiterator',