]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dns-rr: add dns_resource_record_from_json()
authorLennart Poettering <lennart@amutable.com>
Thu, 26 Feb 2026 14:50:26 +0000 (15:50 +0100)
committerLennart Poettering <lennart@amutable.com>
Tue, 24 Mar 2026 20:24:47 +0000 (21:24 +0100)
This only parses a small subset of RR types for now, but we can add more
later.

Covered are the most important RR types: A, AAAA, PTR.

src/resolve/test-dns-rr.c
src/shared/dns-rr.c
src/shared/dns-rr.h

index e45f1d34238b090e685276fcd6af0569b08e4e8d..2ded6b0ab96f9871ab0ae1a5f0f5581b284c9636 100644 (file)
@@ -7,6 +7,16 @@
 #include "dns-type.h"
 #include "tests.h"
 
+static void test_to_json_from_json(DnsResourceRecord *rr) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
+        ASSERT_OK(dns_resource_record_to_json(rr, &j));
+
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr2 = NULL;
+        ASSERT_OK(dns_resource_record_from_json(j, &rr2));
+
+        ASSERT_TRUE(dns_resource_record_equal(rr, rr2));
+}
+
 /* ================================================================
  * DNS_RESOURCE_RECORD_RDATA()
  * ================================================================ */
@@ -802,6 +812,8 @@ TEST(dns_resource_record_new_address_ipv4) {
         ASSERT_EQ(rr->key->type, DNS_TYPE_A);
         ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com");
         ASSERT_EQ(rr->a.in_addr.s_addr, addr.in.s_addr);
+
+        test_to_json_from_json(rr);
 }
 
 TEST(dns_resource_record_new_address_ipv6) {
@@ -818,6 +830,8 @@ TEST(dns_resource_record_new_address_ipv6) {
         ASSERT_EQ(rr->key->type, DNS_TYPE_AAAA);
         ASSERT_STREQ(dns_resource_key_name(rr->key), "www.example.com");
         ASSERT_EQ(memcmp(&rr->aaaa.in6_addr, &addr.in6, sizeof(struct in6_addr)), 0);
+
+        test_to_json_from_json(rr);
 }
 
 /* ================================================================
@@ -1003,11 +1017,13 @@ TEST(dns_resource_record_equal_cname_copy) {
 
         a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, "www.example.com");
         ASSERT_NOT_NULL(a);
-        a->cname.name = strdup("example.com");
+        a->cname.name = ASSERT_PTR(strdup("example.com"));
 
         b = dns_resource_record_copy(a);
         ASSERT_NOT_NULL(b);
         ASSERT_TRUE(dns_resource_record_equal(a, b));
+
+        test_to_json_from_json(a);
 }
 
 TEST(dns_resource_record_equal_cname_fail) {
@@ -1220,11 +1236,13 @@ TEST(dns_resource_record_equal_ptr_copy) {
 
         a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, "127.1.168.192.in-addr-arpa");
         ASSERT_NOT_NULL(a);
-        a->ptr.name = strdup("example.com");
+        a->ptr.name = ASSERT_PTR(strdup("example.com"));
 
         b = dns_resource_record_copy(a);
         ASSERT_NOT_NULL(b);
         ASSERT_TRUE(dns_resource_record_equal(a, b));
+
+        test_to_json_from_json(a);
 }
 
 TEST(dns_resource_record_equal_ptr_fail) {
@@ -2461,4 +2479,25 @@ TEST(dns_resource_record_clamp_ttl_copy) {
         ASSERT_EQ(orig->ttl, 3600u);
 }
 
+static void test_from_json(const char *text, int expected) {
+        log_notice("Trying to parse as JSON RR: %s", text);
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
+        ASSERT_OK(sd_json_parse(text, /* flags= */ 0, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL));
+        ASSERT_EQ(dns_resource_record_from_json(j, NULL), expected);
+}
+
+TEST(from_bad_json) {
+        test_from_json("{}", -EBADMSG);
+        test_from_json("{\"key\":{}}", -ENXIO);
+        test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":9}}", -EOPNOTSUPP);
+        test_from_json("{\"key\":{\"name\":\"foobar\"}}", -ENXIO);
+        test_from_json("{\"key\":{\"type\":9}}", -ENXIO);
+        test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1}}", -ENXIO);
+        test_from_json("{\"key\":{\"name\":\"foobar\",\"type\":1},\"address\":[1,2,3,4]}", 0);
+        test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", 0);
+        test_from_json("{\"key\":{\"name\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG);
+        test_from_json("{\"key\":{\"name\":\"a.a\",\"type\":1},\"address\":[1,2,3,4]}", 0);
+        test_from_json("{\"key\":{\"name\":\"a..a\",\"type\":1},\"address\":[1,2,3,4]}", -EBADMSG);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);
index 58d26e3609b1b3f78b2eebe87528d8e4bb5e1328..e807cef3638df8bcb22be985b6570e333984a496 100644 (file)
@@ -2308,7 +2308,6 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) {
         int r;
 
         assert(rr);
-        assert(ret);
 
         r = dns_resource_key_to_json(rr->key, &k);
         if (r < 0)
@@ -2514,11 +2513,103 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) {
 
         default:
                 /* Can't provide broken-down format */
-                *ret = NULL;
+                if (ret)
+                        *ret = NULL;
                 return 0;
         }
 }
 
+int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret) {
+        int r;
+
+        assert(v);
+
+        sd_json_variant *k = sd_json_variant_by_key(v, "key");
+        if (!k)
+                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Resource record entry lacks key field, refusing.");
+
+        _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+        r = dns_resource_key_from_json(k, &key);
+        if (r < 0)
+                return r;
+
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+        rr = dns_resource_record_new(key);
+        if (!rr)
+                return log_oom_debug();
+
+        /* Note, for now we only support the most common subset of RRs for decoding here. Please send patches for more. */
+        switch (key->type) {
+
+        case DNS_TYPE_PTR:
+        case DNS_TYPE_NS:
+        case DNS_TYPE_CNAME:
+        case DNS_TYPE_DNAME: {
+                _cleanup_free_ char *name = NULL;
+
+                static const struct sd_json_dispatch_field table[] = {
+                        { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_MANDATORY },
+                        { "key",  SD_JSON_VARIANT_OBJECT, NULL,                    0, SD_JSON_MANDATORY },
+                        {}
+                };
+
+                r = sd_json_dispatch(v, table, /* flags= */ 0, &name);
+                if (r < 0)
+                        return r;
+
+                r = dns_name_is_valid(name);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EBADMSG;
+
+                rr->ptr.name = TAKE_PTR(name);
+                break;
+        }
+
+        case DNS_TYPE_A: {
+                struct in_addr addr = {};
+
+                static const struct sd_json_dispatch_field table[] = {
+                        { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr, 0, SD_JSON_MANDATORY },
+                        { "key",     SD_JSON_VARIANT_OBJECT,        NULL,                  0, SD_JSON_MANDATORY },
+                        {}
+                };
+
+                r = sd_json_dispatch(v, table, /* flags= */ 0, &addr);
+                if (r < 0)
+                        return r;
+
+                rr->a.in_addr = addr;
+                break;
+        }
+
+        case DNS_TYPE_AAAA: {
+                struct in6_addr addr = {};
+
+                static const struct sd_json_dispatch_field table[] = {
+                        { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in6_addr, 0, SD_JSON_MANDATORY },
+                        { "key",     SD_JSON_VARIANT_OBJECT,        NULL,                   0, SD_JSON_MANDATORY },
+                        {}
+                };
+
+                r = sd_json_dispatch(v, table, /* flags= */ 0, &addr);
+                if (r < 0)
+                        return r;
+
+                rr->aaaa.in6_addr = addr;
+                break;
+        }
+
+        default:
+                return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Decoding DNS type %s is currently not supported.", dns_type_to_string(key->type));
+        }
+
+        if (ret)
+                *ret = TAKE_PTR(rr);
+        return 0;
+}
+
 static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
         /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
         [DNSSEC_ALGORITHM_RSAMD5]             = "RSAMD5",
index c30cd71cfa5c7347830a1c62d3b591fd2d2f89f0..d747083aa8a811c38874334c28eddea7e8167e67 100644 (file)
@@ -419,6 +419,7 @@ int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data,
 int dns_resource_key_to_json(DnsResourceKey *key, sd_json_variant **ret);
 int dns_resource_key_from_json(sd_json_variant *v, DnsResourceKey **ret);
 int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret);
+int dns_resource_record_from_json(sd_json_variant *v, DnsResourceRecord **ret);
 
 void dns_resource_key_hash_func(const DnsResourceKey *k, struct siphash *state);
 int dns_resource_key_compare_func(const DnsResourceKey *x, const DnsResourceKey *y);