From 1bd76a6217c0cd81972cd50b360cd5b8aa27f0ff Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Sun, 9 Jul 2023 08:00:00 +0000 Subject: [PATCH 1/3] resolved: keep track of first names listed for each address in /etc/hosts These names will be used later in responses as canonical names. --- src/resolve/resolved-etc-hosts.c | 11 ++++++++++- src/resolve/resolved-etc-hosts.h | 1 + src/resolve/test-resolved-etc-hosts.c | 24 ++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index 6acae48c2ba..aac7d986ba0 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -189,9 +189,18 @@ static int parse_line(EtcHosts *hosts, unsigned nr, const char *line) { return log_oom(); } - r = set_ensure_consume(&item->names, &dns_name_hash_ops_free, TAKE_PTR(name)); + r = set_ensure_put(&item->names, &dns_name_hash_ops_free, name); if (r < 0) return log_oom(); + if (r == 0) /* the name is already listed */ + continue; + /* + * Keep track of the first name listed for this address. + * This name will be used in responses as the canonical name. + */ + if (!item->canonical_name) + item->canonical_name = name; + TAKE_PTR(name); } if (!found) diff --git a/src/resolve/resolved-etc-hosts.h b/src/resolve/resolved-etc-hosts.h index e1a7249f298..805a09bb6d6 100644 --- a/src/resolve/resolved-etc-hosts.h +++ b/src/resolve/resolved-etc-hosts.h @@ -8,6 +8,7 @@ typedef struct EtcHostsItemByAddress { struct in_addr_data address; Set *names; + const char *canonical_name; } EtcHostsItemByAddress; typedef struct EtcHostsItemByName { diff --git a/src/resolve/test-resolved-etc-hosts.c b/src/resolve/test-resolved-etc-hosts.c index d46dbd30009..75f7db34828 100644 --- a/src/resolve/test-resolved-etc-hosts.c +++ b/src/resolve/test-resolved-etc-hosts.c @@ -27,11 +27,17 @@ TEST(parse_etc_hosts_system) { assert_se(etc_hosts_parse(&hosts, f) == 0); } +#define in_addr_4(_address_str) \ + (&(struct in_addr_data) { .family = AF_INET, .address.in = { .s_addr = inet_addr(_address_str) } }) + +#define in_addr_6(...) \ + (&(struct in_addr_data) { .family = AF_INET6, .address.in6 = { .s6_addr = __VA_ARGS__ } }) + #define has_4(_set, _address_str) \ - set_contains(_set, &(struct in_addr_data) { .family = AF_INET, .address.in = { .s_addr = inet_addr(_address_str) } }) + set_contains(_set, in_addr_4(_address_str)) #define has_6(_set, ...) \ - set_contains(_set, &(struct in_addr_data) { .family = AF_INET6, .address.in6 = { .s6_addr = __VA_ARGS__ } }) + set_contains(_set, in_addr_6(__VA_ARGS__)) TEST(parse_etc_hosts) { _cleanup_(unlink_tempfilep) char @@ -110,6 +116,20 @@ TEST(parse_etc_hosts) { assert_se(set_size(bn->addresses) == 1); assert_se(has_6(bn->addresses, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5})); + EtcHostsItemByAddress *ba; + assert_se(ba = hashmap_get(hosts.by_address, in_addr_4("1.2.3.6"))); + assert_se(set_size(ba->names) == 2); + assert_se(set_contains(ba->names, "dash")); + assert_se(set_contains(ba->names, "dash-dash.where-dash")); + assert_se(streq(ba->canonical_name, "dash")); + + assert_se(ba = hashmap_get(hosts.by_address, in_addr_6({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5}))); + assert_se(set_size(ba->names) == 3); + assert_se(set_contains(ba->names, "some.where")); + assert_se(set_contains(ba->names, "some.other")); + assert_se(set_contains(ba->names, "foobar.foo.foo")); + assert_se(streq(ba->canonical_name, "some.where")); + assert_se( set_contains(hosts.no_address, "some.where")); assert_se( set_contains(hosts.no_address, "some.other")); assert_se( set_contains(hosts.no_address, "deny.listed")); From 0ff8f2a33a8f7c225860388faf43fa83f106cfe3 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Mon, 10 Jul 2023 08:00:00 +0000 Subject: [PATCH 2/3] resolved: fix the canonical name returned by hosts lookup by address In etc_hosts_lookup_by_address(), make sure the canonical name of the given address is returned first in the list of names that address resolves to. Resolves: #25088 --- src/resolve/resolved-etc-hosts.c | 33 ++++++++++++++++++++++---------- test/units/testsuite-75.sh | 15 +++++++++++---- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index aac7d986ba0..fd5c6b78148 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -381,6 +381,20 @@ static int manager_etc_hosts_read(Manager *m) { return 1; } +static int answer_add_ptr(DnsAnswer *answer, DnsResourceKey *key, const char *name) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new(key); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(name); + if (!rr->ptr.name) + return -ENOMEM; + + return dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); +} + static int etc_hosts_lookup_by_address( EtcHosts *hosts, DnsQuestion *q, @@ -427,18 +441,17 @@ static int etc_hosts_lookup_by_address( if (r < 0) return r; + if (item->canonical_name) { + r = answer_add_ptr(*answer, found_ptr, item->canonical_name); + if (r < 0) + return r; + } + SET_FOREACH(n, item->names) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + if (n == item->canonical_name) + continue; - rr = dns_resource_record_new(found_ptr); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(n); - if (!rr->ptr.name) - return -ENOMEM; - - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); + r = answer_add_ptr(*answer, found_ptr, n); if (r < 0) return r; } diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh index ef0e42a81d5..504d1038e5e 100755 --- a/test/units/testsuite-75.sh +++ b/test/units/testsuite-75.sh @@ -160,10 +160,12 @@ ip link del hoge.foo ### SETUP ### # Configure network hostnamectl hostname ns1.unsigned.test -{ - echo "10.0.0.1 ns1.unsigned.test" - echo "fd00:dead:beef:cafe::1 ns1.unsigned.test" -} >>/etc/hosts +cat >>/etc/hosts </etc/systemd/network/dns0.netdev < Date: Tue, 11 Jul 2023 08:00:00 +0000 Subject: [PATCH 3/3] resolved: fix the canonical name returned by hosts lookup by name In etc_hosts_lookup_by_name(), return the canonical name of the resolved address instead of the name used to obtain that address. Resolves: #20158 --- src/resolve/resolved-etc-hosts.c | 44 ++++++++++++++++++++++++++++---- test/units/testsuite-75.sh | 9 +++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index fd5c6b78148..6af160a477f 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -395,6 +395,31 @@ static int answer_add_ptr(DnsAnswer *answer, DnsResourceKey *key, const char *na return dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); } +static int answer_add_cname(DnsAnswer *answer, const char *name, const char *cname) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_CNAME, name); + if (!rr) + return -ENOMEM; + + rr->cname.name = strdup(cname); + if (!rr->cname.name) + return -ENOMEM; + + return dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); +} + +static int answer_add_addr(DnsAnswer *answer, const char *name, const struct in_addr_data *a) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + int r; + + r = dns_resource_record_new_address(&rr, a->family, &a->address, name); + if (r < 0) + return r; + + return dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); +} + static int etc_hosts_lookup_by_address( EtcHosts *hosts, DnsQuestion *q, @@ -510,17 +535,26 @@ static int etc_hosts_lookup_by_name( } SET_FOREACH(a, item ? item->addresses : NULL) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + EtcHostsItemByAddress *item_by_addr; + const char *canonical_name; if ((!found_a && a->family == AF_INET) || (!found_aaaa && a->family == AF_INET6)) continue; - r = dns_resource_record_new_address(&rr, a->family, &a->address, item->name); - if (r < 0) - return r; + item_by_addr = hashmap_get(hosts->by_address, a); + if (item_by_addr && item_by_addr->canonical_name) + canonical_name = item_by_addr->canonical_name; + else + canonical_name = item->name; - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); + if (!streq(item->name, canonical_name)) { + r = answer_add_cname(*answer, item->name, canonical_name); + if (r < 0) + return r; + } + + r = answer_add_addr(*answer, canonical_name, a); if (r < 0) return r; } diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh index 504d1038e5e..5445c152c97 100755 --- a/test/units/testsuite-75.sh +++ b/test/units/testsuite-75.sh @@ -300,6 +300,15 @@ run getent -s resolve hosts 127.128.0.5 grep -qEx '127\.128\.0\.5\s+localhost5(\s+localhost5?\.localdomain[45]?){4}' "$RUN_OUT" [ "$(wc -l <"$RUN_OUT")" -eq 1 ] +# Issue: https://github.com/systemd/systemd/issues/20158 +run dig +noall +answer +additional localhost5. +grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT" +[ "$(wc -l <"$RUN_OUT")" -eq 1 ] +run dig +noall +answer +additional localhost5.localdomain4. +grep -qEx 'localhost5\.localdomain4\.\s+0\s+IN\s+CNAME\s+localhost5\.' "$RUN_OUT" +grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT" +[ "$(wc -l <"$RUN_OUT")" -eq 2 ] + : "--- Basic resolved tests ---" # Issue: https://github.com/systemd/systemd/issues/22229 # PR: https://github.com/systemd/systemd/pull/22231