From: Lennart Poettering Date: Thu, 26 Feb 2026 14:50:26 +0000 (+0100) Subject: dns-rr: add dns_resource_record_from_json() X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2f1bbebeb96e6745bb13df70d8dbcc99d79b9cdd;p=thirdparty%2Fsystemd.git dns-rr: add dns_resource_record_from_json() 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. --- diff --git a/src/resolve/test-dns-rr.c b/src/resolve/test-dns-rr.c index e45f1d34238..2ded6b0ab96 100644 --- a/src/resolve/test-dns-rr.c +++ b/src/resolve/test-dns-rr.c @@ -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); diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 58d26e3609b..e807cef3638 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -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", diff --git a/src/shared/dns-rr.h b/src/shared/dns-rr.h index c30cd71cfa5..d747083aa8a 100644 --- a/src/shared/dns-rr.h +++ b/src/shared/dns-rr.h @@ -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);