| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "alloc-util.h" |
| #include "env-util.h" |
| #include "hostname-util.h" |
| #include "local-addresses.h" |
| #include "missing_network.h" |
| #include "resolved-dns-synthesize.h" |
| |
| int dns_synthesize_family(uint64_t flags) { |
| |
| /* Picks an address family depending on set flags. This is |
| * purely for synthesized answers, where the family we return |
| * for the reply should match what was requested in the |
| * question, even though we are synthesizing the answer |
| * here. */ |
| |
| if (!(flags & SD_RESOLVED_DNS)) { |
| if (flags & (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_MDNS_IPV4)) |
| return AF_INET; |
| if (flags & (SD_RESOLVED_LLMNR_IPV6|SD_RESOLVED_MDNS_IPV6)) |
| return AF_INET6; |
| } |
| |
| return AF_UNSPEC; |
| } |
| |
| DnsProtocol dns_synthesize_protocol(uint64_t flags) { |
| |
| /* Similar as dns_synthesize_family() but does this for the |
| * protocol. If resolving via DNS was requested, we claim it |
| * was DNS. Similar, if nothing specific was |
| * requested. However, if only resolving via LLMNR was |
| * requested we return that. */ |
| |
| if (flags & SD_RESOLVED_DNS) |
| return DNS_PROTOCOL_DNS; |
| if (flags & SD_RESOLVED_LLMNR) |
| return DNS_PROTOCOL_LLMNR; |
| if (flags & SD_RESOLVED_MDNS) |
| return DNS_PROTOCOL_MDNS; |
| |
| return DNS_PROTOCOL_DNS; |
| } |
| |
| static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, DnsAnswer **answer) { |
| int r; |
| |
| assert(m); |
| assert(key); |
| assert(answer); |
| |
| r = dns_answer_reserve(answer, 2); |
| if (r < 0) |
| return r; |
| |
| if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; |
| |
| rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, dns_resource_key_name(key)); |
| if (!rr) |
| return -ENOMEM; |
| |
| rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); |
| |
| r = dns_answer_add(*answer, rr, LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED, NULL); |
| if (r < 0) |
| return r; |
| } |
| |
| if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY) && socket_ipv6_is_enabled()) { |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; |
| |
| rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, dns_resource_key_name(key)); |
| if (!rr) |
| return -ENOMEM; |
| |
| rr->aaaa.in6_addr = in6addr_loopback; |
| |
| r = dns_answer_add(*answer, rr, LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED, NULL); |
| if (r < 0) |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; |
| |
| rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); |
| if (!rr) |
| return -ENOMEM; |
| |
| rr->ptr.name = strdup(to); |
| if (!rr->ptr.name) |
| return -ENOMEM; |
| |
| return dns_answer_add(*answer, rr, ifindex, flags, NULL); |
| } |
| |
| static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, DnsAnswer **answer) { |
| int r; |
| |
| assert(m); |
| assert(key); |
| assert(answer); |
| |
| if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { |
| r = dns_answer_reserve(answer, 1); |
| if (r < 0) |
| return r; |
| |
| r = answer_add_ptr(answer, dns_resource_key_name(key), "localhost", LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED); |
| if (r < 0) |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static int answer_add_addresses_rr( |
| DnsAnswer **answer, |
| const char *name, |
| struct local_address *addresses, |
| unsigned n_addresses) { |
| |
| unsigned j; |
| int r; |
| |
| assert(answer); |
| assert(name); |
| |
| r = dns_answer_reserve(answer, n_addresses); |
| if (r < 0) |
| return r; |
| |
| for (j = 0; j < n_addresses; j++) { |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; |
| |
| r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); |
| if (r < 0) |
| return r; |
| |
| r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED, NULL); |
| if (r < 0) |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static int answer_add_addresses_ptr( |
| DnsAnswer **answer, |
| const char *name, |
| struct local_address *addresses, |
| unsigned n_addresses, |
| int af, const union in_addr_union *match) { |
| |
| bool added = false; |
| unsigned j; |
| int r; |
| |
| assert(answer); |
| assert(name); |
| |
| for (j = 0; j < n_addresses; j++) { |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; |
| |
| if (af != AF_UNSPEC) { |
| |
| if (addresses[j].family != af) |
| continue; |
| |
| if (match && !in_addr_equal(af, match, &addresses[j].address)) |
| continue; |
| } |
| |
| r = dns_answer_reserve(answer, 1); |
| if (r < 0) |
| return r; |
| |
| r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); |
| if (r < 0) |
| return r; |
| |
| r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED, NULL); |
| if (r < 0) |
| return r; |
| |
| added = true; |
| } |
| |
| return added; |
| } |
| |
| static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { |
| _cleanup_free_ struct local_address *addresses = NULL; |
| int n = 0, af; |
| |
| assert(m); |
| assert(key); |
| assert(answer); |
| |
| af = dns_type_to_af(key->type); |
| if (af >= 0) { |
| n = local_addresses(m->rtnl, ifindex, af, &addresses); |
| if (n < 0) |
| return n; |
| |
| if (n == 0) { |
| struct local_address buffer[2]; |
| |
| /* If we have no local addresses then use ::1 and 127.0.0.2 as local ones. */ |
| |
| if (IN_SET(af, AF_INET, AF_UNSPEC)) |
| buffer[n++] = (struct local_address) { |
| .family = AF_INET, |
| .ifindex = LOOPBACK_IFINDEX, |
| .address.in.s_addr = htobe32(INADDR_LOCALADDRESS), |
| }; |
| |
| if (IN_SET(af, AF_INET6, AF_UNSPEC) && socket_ipv6_is_enabled()) |
| buffer[n++] = (struct local_address) { |
| .family = AF_INET6, |
| .ifindex = LOOPBACK_IFINDEX, |
| .address.in6 = in6addr_loopback, |
| }; |
| |
| return answer_add_addresses_rr(answer, |
| dns_resource_key_name(key), |
| buffer, n); |
| } |
| } |
| |
| return answer_add_addresses_rr(answer, dns_resource_key_name(key), addresses, n); |
| } |
| |
| static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { |
| _cleanup_free_ struct local_address *addresses = NULL; |
| bool added = false; |
| int n, r; |
| |
| assert(m); |
| assert(address); |
| assert(answer); |
| |
| if (af == AF_INET && address->in.s_addr == htobe32(INADDR_LOCALADDRESS)) { |
| |
| /* Always map the IPv4 address 127.0.0.2 to the local hostname, in addition to "localhost": */ |
| |
| r = dns_answer_reserve(answer, 4); |
| if (r < 0) |
| return r; |
| |
| r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->full_hostname, LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED); |
| if (r < 0) |
| return r; |
| |
| r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED); |
| if (r < 0) |
| return r; |
| |
| r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED); |
| if (r < 0) |
| return r; |
| |
| r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED); |
| if (r < 0) |
| return r; |
| |
| return 1; |
| } |
| |
| n = local_addresses(m->rtnl, ifindex, af, &addresses); |
| if (n <= 0) |
| return n; |
| |
| r = answer_add_addresses_ptr(answer, m->full_hostname, addresses, n, af, address); |
| if (r < 0) |
| return r; |
| if (r > 0) |
| added = true; |
| |
| r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); |
| if (r < 0) |
| return r; |
| if (r > 0) |
| added = true; |
| |
| r = answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); |
| if (r < 0) |
| return r; |
| if (r > 0) |
| added = true; |
| |
| return added; |
| } |
| |
| static int synthesize_gateway_rr( |
| Manager *m, |
| const DnsResourceKey *key, |
| int ifindex, |
| int (*lookup)(sd_netlink *context, int ifindex, int af, struct local_address **ret), /* either local_gateways() or local_outbound() */ |
| DnsAnswer **answer) { |
| _cleanup_free_ struct local_address *addresses = NULL; |
| int n = 0, af, r; |
| |
| assert(m); |
| assert(key); |
| assert(lookup); |
| assert(answer); |
| |
| af = dns_type_to_af(key->type); |
| if (af >= 0) { |
| n = lookup(m->rtnl, ifindex, af, &addresses); |
| if (n < 0) /* < 0 means: error */ |
| return n; |
| |
| if (n == 0) { /* == 0 means we have no gateway */ |
| /* See if there's a gateway on the other protocol */ |
| if (af == AF_INET) |
| n = lookup(m->rtnl, ifindex, AF_INET6, NULL); |
| else { |
| assert(af == AF_INET6); |
| n = lookup(m->rtnl, ifindex, AF_INET, NULL); |
| } |
| if (n <= 0) /* error (if < 0) or really no gateway at all (if == 0) */ |
| return n; |
| |
| /* We have a gateway on the other protocol. Let's return > 0 without adding any RR to |
| * the answer, i.e. synthesize NODATA (and not NXDOMAIN!) */ |
| return 1; |
| } |
| } |
| |
| r = answer_add_addresses_rr(answer, dns_resource_key_name(key), addresses, n); |
| if (r < 0) |
| return r; |
| |
| return 1; /* > 0 means: we have some gateway */ |
| } |
| |
| static int synthesize_dns_stub_rr( |
| Manager *m, |
| const DnsResourceKey *key, |
| in_addr_t addr, |
| DnsAnswer **answer) { |
| |
| _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; |
| int r; |
| |
| assert(m); |
| assert(key); |
| assert(answer); |
| |
| if (!IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) |
| return 1; /* we still consider ourselves the owner of this name */ |
| |
| r = dns_answer_reserve(answer, 1); |
| if (r < 0) |
| return r; |
| |
| rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, dns_resource_key_name(key)); |
| if (!rr) |
| return -ENOMEM; |
| |
| rr->a.in_addr.s_addr = htobe32(addr); |
| |
| r = dns_answer_add(*answer, rr, LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED, NULL); |
| if (r < 0) |
| return r; |
| |
| return 1; |
| } |
| |
| static int synthesize_dns_stub_ptr( |
| Manager *m, |
| int af, |
| const union in_addr_union *address, |
| DnsAnswer **answer) { |
| |
| int r; |
| |
| assert(m); |
| assert(address); |
| assert(answer); |
| |
| if (af != AF_INET) |
| return 0; |
| |
| if (address->in.s_addr == htobe32(INADDR_DNS_STUB)) { |
| |
| r = dns_answer_reserve(answer, 1); |
| if (r < 0) |
| return r; |
| |
| r = answer_add_ptr(answer, "53.0.0.127.in-addr.arpa", "_localdnsstub", LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED); |
| if (r < 0) |
| return r; |
| |
| return 1; |
| } |
| |
| if (address->in.s_addr == htobe32(INADDR_DNS_PROXY_STUB)) { |
| |
| r = dns_answer_reserve(answer, 1); |
| if (r < 0) |
| return r; |
| |
| r = answer_add_ptr(answer, "54.0.0.127.in-addr.arpa", "_localdnsproxy", LOOPBACK_IFINDEX, DNS_ANSWER_AUTHENTICATED); |
| if (r < 0) |
| return r; |
| |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int synthesize_gateway_ptr( |
| Manager *m, |
| int af, |
| const union in_addr_union *address, |
| int ifindex, |
| DnsAnswer **answer) { |
| |
| _cleanup_free_ struct local_address *addresses = NULL; |
| int n; |
| |
| assert(m); |
| assert(address); |
| assert(answer); |
| |
| n = local_gateways(m->rtnl, ifindex, af, &addresses); |
| if (n <= 0) |
| return n; |
| |
| return answer_add_addresses_ptr(answer, "_gateway", addresses, n, af, address); |
| } |
| |
| int dns_synthesize_answer( |
| Manager *m, |
| DnsQuestion *q, |
| int ifindex, |
| DnsAnswer **ret) { |
| |
| _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; |
| DnsResourceKey *key; |
| bool found = false, nxdomain = false; |
| int r; |
| |
| assert(m); |
| assert(q); |
| |
| DNS_QUESTION_FOREACH(key, q) { |
| union in_addr_union address; |
| const char *name; |
| int af; |
| |
| if (!IN_SET(key->class, DNS_CLASS_IN, DNS_CLASS_ANY)) |
| continue; |
| |
| name = dns_resource_key_name(key); |
| |
| if (dns_name_is_root(name)) { |
| /* Do nothing. */ |
| |
| } else if (dns_name_dont_resolve(name)) { |
| /* Synthesize NXDOMAIN for some of the domains in RFC6303 + RFC6761 */ |
| nxdomain = true; |
| continue; |
| |
| } else if (is_localhost(name)) { |
| |
| r = synthesize_localhost_rr(m, key, &answer); |
| if (r < 0) |
| return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); |
| |
| } else if (manager_is_own_hostname(m, name)) { |
| |
| if (getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME") == 0) |
| continue; |
| r = synthesize_system_hostname_rr(m, key, ifindex, &answer); |
| if (r < 0) |
| return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); |
| |
| } else if (is_gateway_hostname(name)) { |
| |
| r = synthesize_gateway_rr(m, key, ifindex, local_gateways, &answer); |
| if (r < 0) |
| return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); |
| if (r == 0) { /* if we have no gateway return NXDOMAIN */ |
| nxdomain = true; |
| continue; |
| } |
| |
| } else if (is_outbound_hostname(name)) { |
| |
| r = synthesize_gateway_rr(m, key, ifindex, local_outbounds, &answer); |
| if (r < 0) |
| return log_error_errno(r, "Failed to synthesize outbound RRs: %m"); |
| if (r == 0) { /* if we have no gateway return NXDOMAIN */ |
| nxdomain = true; |
| continue; |
| } |
| |
| } else if (is_dns_stub_hostname(name)) { |
| |
| r = synthesize_dns_stub_rr(m, key, INADDR_DNS_STUB, &answer); |
| if (r < 0) |
| return log_error_errno(r, "Failed to synthesize local DNS stub RRs: %m"); |
| |
| } else if (is_dns_proxy_stub_hostname(name)) { |
| |
| r = synthesize_dns_stub_rr(m, key, INADDR_DNS_PROXY_STUB, &answer); |
| if (r < 0) |
| return log_error_errno(r, "Failed to synthesize local DNS stub RRs: %m"); |
| |
| } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && |
| dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0 && |
| dns_name_equal(name, "53.0.0.127.in-addr.arpa") == 0 && |
| dns_name_equal(name, "54.0.0.127.in-addr.arpa") == 0) || |
| dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) { |
| |
| r = synthesize_localhost_ptr(m, key, &answer); |
| if (r < 0) |
| return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); |
| |
| } else if (dns_name_address(name, &af, &address) > 0) { |
| int v, w, u; |
| |
| if (getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME") == 0) |
| continue; |
| |
| v = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); |
| if (v < 0) |
| return log_error_errno(v, "Failed to synthesize system hostname PTR RR: %m"); |
| |
| w = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); |
| if (w < 0) |
| return log_error_errno(w, "Failed to synthesize gateway hostname PTR RR: %m"); |
| |
| u = synthesize_dns_stub_ptr(m, af, &address, &answer); |
| if (u < 0) |
| return log_error_errno(u, "Failed to synthesize local stub hostname PTR PR: %m"); |
| |
| if (v == 0 && w == 0 && u == 0) /* This IP address is neither a local one, nor a gateway, nor a stub address */ |
| continue; |
| |
| /* Note that we never synthesize reverse PTR for _outbound, since those are local |
| * addresses and thus mapped to the local hostname anyway, hence they already have a |
| * mapping. */ |
| |
| } else |
| continue; |
| |
| found = true; |
| } |
| |
| if (found) { |
| |
| if (ret) |
| *ret = TAKE_PTR(answer); |
| |
| return 1; |
| } else if (nxdomain) |
| return -ENXIO; |
| |
| return 0; |
| } |