diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index 6acae48c2ba..6af160a477f 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) @@ -372,6 +381,45 @@ 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 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, @@ -418,18 +466,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; } @@ -488,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/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")); diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh index ef0e42a81d5..5445c152c97 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 <