From: Lennart Poettering Date: Fri, 1 Mar 2024 22:36:51 +0000 (+0100) Subject: resolved: properly decode NAPTR RRs X-Git-Tag: v256-rc1~624^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=176156764168ae55e3609f443a4515efee00b17b;p=thirdparty%2Fsystemd.git resolved: properly decode NAPTR RRs Fixes: #18126 --- diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index a7d04449b2f..e6267400532 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -1220,6 +1220,30 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL); break; + case DNS_TYPE_NAPTR: + r = dns_packet_append_uint16(p, rr->naptr.order, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->naptr.preference, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->naptr.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->naptr.services, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->naptr.regexp, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->naptr.replacement, /* allow_compression= */ false, /* canonical_candidate= */ true, NULL); + break; + case DNS_TYPE_OPT: case DNS_TYPE_OPENPGPKEY: case _DNS_TYPE_INVALID: /* unparsable */ @@ -2247,6 +2271,30 @@ int dns_packet_read_rr( break; + case DNS_TYPE_NAPTR: + r = dns_packet_read_uint16(p, &rr->naptr.order, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &rr->naptr.preference, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->naptr.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->naptr.services, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->naptr.regexp, NULL); + if (r < 0) + return r; + + r = dns_packet_read_name(p, &rr->naptr.replacement, /* allow_compressed= */ false, NULL); + break; + case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ case DNS_TYPE_OPENPGPKEY: default: diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 7d824ee8065..ca30508ff4c 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -481,6 +481,13 @@ static DnsResourceRecord* dns_resource_record_free(DnsResourceRecord *rr) { free(rr->caa.value); break; + case DNS_TYPE_NAPTR: + free(rr->naptr.flags); + free(rr->naptr.services); + free(rr->naptr.regexp); + free(rr->naptr.replacement); + break; + case DNS_TYPE_OPENPGPKEY: default: if (!rr->unparsable) @@ -694,6 +701,17 @@ int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResou streq(a->caa.tag, b->caa.tag) && FIELD_EQUAL(a->caa, b->caa, value); + case DNS_TYPE_NAPTR: + r = dns_name_equal(a->naptr.replacement, b->naptr.replacement); + if (r <= 0) + return r; + + return a->naptr.order == b->naptr.order && + a->naptr.preference == b->naptr.preference && + streq(a->naptr.flags, b->naptr.flags) && + streq(a->naptr.services, b->naptr.services) && + streq(a->naptr.regexp, b->naptr.regexp); + case DNS_TYPE_OPENPGPKEY: default: return FIELD_EQUAL(a->generic, b->generic, data); @@ -1263,6 +1281,31 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) { return NULL; break; + case DNS_TYPE_NAPTR: { + _cleanup_free_ char *tt = NULL, *ttt = NULL; + + t = octescape(rr->naptr.flags, SIZE_MAX); + if (!t) + return NULL; + + tt = octescape(rr->naptr.services, SIZE_MAX); + if (!tt) + return NULL; + + ttt = octescape(rr->naptr.regexp, SIZE_MAX); + if (!ttt) + return NULL; + + if (asprintf(&s, "%" PRIu16 " %" PRIu16 " \"%s\" \"%s\" \"%s\" %s.", + rr->naptr.order, + rr->naptr.preference, + t, + tt, + ttt, + rr->naptr.replacement) < 0) + return NULL; + break; + } default: /* Format as documented in RFC 3597, Section 5 */ if (rr->generic.data_size == 0) @@ -1588,6 +1631,15 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash * siphash24_compress_safe(rr->caa.value, rr->caa.value_size, state); break; + case DNS_TYPE_NAPTR: + siphash24_compress_typesafe(rr->naptr.order, state); + siphash24_compress_typesafe(rr->naptr.preference, state); + string_hash_func(rr->naptr.flags, state); + string_hash_func(rr->naptr.services, state); + string_hash_func(rr->naptr.regexp, state); + dns_name_hash_func(rr->naptr.replacement, state); + break; + case DNS_TYPE_OPENPGPKEY: default: siphash24_compress_safe(rr->generic.data, rr->generic.data_size, state); @@ -1806,6 +1858,23 @@ DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) { return NULL; break; + case DNS_TYPE_NAPTR: + copy->naptr.order = rr->naptr.order; + copy->naptr.preference = rr->naptr.preference; + copy->naptr.flags = strdup(rr->naptr.flags); + if (!copy->naptr.flags) + return NULL; + copy->naptr.services = strdup(rr->naptr.services); + if (!copy->naptr.services) + return NULL; + copy->naptr.regexp = strdup(rr->naptr.regexp); + if (!copy->naptr.regexp) + return NULL; + copy->naptr.replacement = strdup(rr->naptr.replacement); + if (!copy->naptr.replacement) + return NULL; + break; + case DNS_TYPE_OPT: default: copy->generic.data = memdup(rr->generic.data, rr->generic.data_size); @@ -2352,6 +2421,18 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) { JSON_BUILD_PAIR("tag", JSON_BUILD_STRING(rr->caa.tag)), JSON_BUILD_PAIR("value", JSON_BUILD_OCTESCAPE(rr->caa.value, rr->caa.value_size)))); + case DNS_TYPE_NAPTR: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("order", JSON_BUILD_UNSIGNED(rr->naptr.order)), + JSON_BUILD_PAIR("preference", JSON_BUILD_UNSIGNED(rr->naptr.preference)), + /* NB: we name this flags field here naptrFlags, because there's already another "flags" field (for example in CAA) which has a different type */ + JSON_BUILD_PAIR("naptrFlags", JSON_BUILD_STRING(rr->naptr.flags)), + JSON_BUILD_PAIR("services", JSON_BUILD_STRING(rr->naptr.services)), + JSON_BUILD_PAIR("regexp", JSON_BUILD_STRING(rr->naptr.regexp)), + JSON_BUILD_PAIR("replacement", JSON_BUILD_STRING(rr->naptr.replacement)))); + default: /* Can't provide broken-down format */ *ret = NULL; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 961d3c78526..8ad4009ebf7 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -270,6 +270,16 @@ struct DnsResourceRecord { uint8_t flags; } caa; + + /* https://datatracker.ietf.org/doc/html/rfc2915 */ + struct { + uint16_t order; + uint16_t preference; + char *flags; + char *services; + char *regexp; + char *replacement; + } naptr; }; /* Note: fields should be ordered to minimize alignment gaps. Use pahole! */ diff --git a/src/resolve/test-resolved-packet.c b/src/resolve/test-resolved-packet.c index dd8c969b14b..8a65ea0f5e7 100644 --- a/src/resolve/test-resolved-packet.c +++ b/src/resolve/test-resolved-packet.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "hexdecoct.h" #include "log.h" #include "resolved-dns-packet.h" #include "tests.h" @@ -23,4 +24,190 @@ TEST(dns_packet_new) { assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, DNS_PACKET_SIZE_MAX + 1, DNS_PACKET_SIZE_MAX) == -EFBIG); } +TEST(naptr) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + + static const char twilio_reply[] = + "Sq+BgAABAAkAAAABBnR3aWxpbwNjb20AACMAAcAMACMAAQAABwgAMgAUAAoBUwdTSVArRDJUAARf" + "c2lwBF90Y3AEcHN0bgdpZTEtdG54BnR3aWxpbwNjb20AwAwAIwABAAAHCAAyAAoACgFTB1NJUCtE" + "MlUABF9zaXAEX3VkcARwc3RuB3VzMi10bngGdHdpbGlvA2NvbQDADAAjAAEAAAcIADQAFAAKAVMI" + "U0lQUytEMlQABV9zaXBzBF90Y3AEcHN0bgd1czEtdG54BnR3aWxpbwNjb20AwAwAIwABAAAHCAAy" + "AAoACgFTB1NJUCtEMlUABF9zaXAEX3VkcARwc3RuB2llMS10bngGdHdpbGlvA2NvbQDADAAjAAEA" + "AAcIADIAFAAKAVMHU0lQK0QyVAAEX3NpcARfdGNwBHBzdG4HdXMyLXRueAZ0d2lsaW8DY29tAMAM" + "ACMAAQAABwgANAAUAAoBUwhTSVBTK0QyVAAFX3NpcHMEX3RjcARwc3RuB3VzMi10bngGdHdpbGlv" + "A2NvbQDADAAjAAEAAAcIADQAFAAKAVMIU0lQUytEMlQABV9zaXBzBF90Y3AEcHN0bgdpZTEtdG54" + "BnR3aWxpbwNjb20AwAwAIwABAAAHCAAyAAoACgFTB1NJUCtEMlUABF9zaXAEX3VkcARwc3RuB3Vz" + "MS10bngGdHdpbGlvA2NvbQDADAAjAAEAAAcIADIAFAAKAVMHU0lQK0QyVAAEX3NpcARfdGNwBHBz" + "dG4HdXMxLXRueAZ0d2lsaW8DY29tAAAAKQIAAAAAAAAA"; + + static const char twilio_reply_string[] = + "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.ie1-tnx.twilio.com.\n" + "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.us2-tnx.twilio.com.\n" + "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.us1-tnx.twilio.com.\n" + "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.ie1-tnx.twilio.com.\n" + "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.us2-tnx.twilio.com.\n" + "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.us2-tnx.twilio.com.\n" + "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.ie1-tnx.twilio.com.\n" + "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.us1-tnx.twilio.com.\n" + "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.us1-tnx.twilio.com.\n"; + + static const char twilio_reply_json[] = + "[\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._tcp.pstn.ie1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 10,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2U\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._udp.pstn.us2-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIPS+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sips._tcp.pstn.us1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 10,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2U\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._udp.pstn.ie1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._tcp.pstn.us2-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIPS+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sips._tcp.pstn.us2-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIPS+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sips._tcp.pstn.ie1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 10,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2U\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._udp.pstn.us1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._tcp.pstn.us1-tnx.twilio.com\"\n" + " }\n" + "]\n"; + + _cleanup_free_ void *buf = NULL; + size_t sz = 0; + + assert_se(unbase64mem(twilio_reply, &buf, &sz) >= 0); + + assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, sz, DNS_PACKET_SIZE_MAX) == 0); + assert_se(p->allocated >= sz); + + memcpy(DNS_PACKET_DATA(p), buf, sz); + p->size = sz; + + assert_se(dns_packet_extract(p) >= 0); + + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL; + _cleanup_free_ char *joined = NULL; + DnsResourceRecord *rr; + DNS_ANSWER_FOREACH(rr, p->answer) { + const char *s; + + s = ASSERT_PTR(dns_resource_record_to_string(rr)); + printf("%s\n", s); + + assert_se(strextend(&joined, s, "\n")); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + assert_se(dns_resource_record_to_json(rr, &v) >= 0); + + assert_se(json_variant_append_array(&a, v) >= 0); + } + + assert(streq(joined, twilio_reply_string)); + + _cleanup_(json_variant_unrefp) JsonVariant *parsed = NULL; + assert_se(json_parse(twilio_reply_json, /* flags= */ 0, &parsed, /* ret_line= */ NULL, /* ret_column= */ NULL) >= 0); + + assert_se(json_variant_equal(parsed, a)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index ea21b437fa7..3ea97432db3 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -61,7 +61,13 @@ static VARLINK_DEFINE_STRUCT_TYPE( VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(target, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY)); + VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(order, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(preference, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(naptrFlags, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(services, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(regexp, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(replacement, VARLINK_STRING, VARLINK_NULLABLE)); static VARLINK_DEFINE_STRUCT_TYPE( ResourceRecordArray,