From cf7d1a30e44bf380027a2e73f9bf13f423a33cc1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 20:27:21 +0100 Subject: logind,machined: bump TasksMax= Issue #2388 suggests the current TasksMax= setting for user processes is to low. Bump it to 12K. Also, bump the container TasksMax= from 8K to 16K, so that it remains higher than the one for user processes. (Compare: the kernel default limit for processes system-wide is 32K). Fixes #2388 --- src/login/logind.c | 2 +- src/machine/machined-dbus.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/login/logind.c b/src/login/logind.c index 79ea5ddfcb..9723e2f4e0 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -70,7 +70,7 @@ static Manager *manager_new(void) { m->idle_action_not_before_usec = now(CLOCK_MONOTONIC); m->runtime_dir_size = PAGE_ALIGN((size_t) (physical_memory() / 10)); /* 10% */ - m->user_tasks_max = UINT64_C(4096); + m->user_tasks_max = UINT64_C(12288); m->devices = hashmap_new(&string_hash_ops); m->seats = hashmap_new(&string_hash_ops); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index e448dd2035..28134f61bf 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -1325,7 +1325,7 @@ int manager_start_scope( if (r < 0) return r; - r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", 8192); + r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); if (r < 0) return bus_log_create_error(r); -- cgit v1.2.3-54-g00ecf From ebc8a1066a1084f237512902a873652167a627e2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 20:30:03 +0100 Subject: resolved: fix RR key reduction logic Fixes #2380. --- src/resolve/resolved-dns-packet.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 9a5223ef01..032e719595 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -2094,7 +2094,7 @@ int dns_packet_extract(DnsPacket *p) { n = DNS_PACKET_RRCOUNT(p); if (n > 0) { - DnsResourceRecord *previous = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; bool bad_opt = false; answer = dns_answer_new(n); @@ -2150,12 +2150,12 @@ int dns_packet_extract(DnsPacket *p) { } if (has_rfc6975) { - /* OPT RR contains RFC6975 algorithm data, then this is indication that the - * server just copied the OPT it got from us (which contained that data) back - * into the reply. If so, then it doesn't properly support EDNS, as RFC6975 - * makes it very clear that the algorithm data should only be contained in - * questions, never in replies. Crappy Belkin copy the OPT data for example, - * hence let's detect this so that we downgrade early. */ + /* If the OPT RR contains RFC6975 algorithm data, then this is indication that + * the server just copied the OPT it got from us (which contained that data) + * back into the reply. If so, then it doesn't properly support EDNS, as + * RFC6975 makes it very clear that the algorithm data should only be contained + * in questions, never in replies. Crappy Belkin routers copy the OPT data for + * example, hence let's detect this so that we downgrade early. */ log_debug("OPT RR contained RFC6975 data, ignoring."); bad_opt = true; continue; @@ -2174,6 +2174,11 @@ int dns_packet_extract(DnsPacket *p) { if (r < 0) goto finish; } + + /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note + * that we only do this if we actually decided to keep the RR around. */ + dns_resource_record_unref(previous); + previous = dns_resource_record_ref(rr); } if (bad_opt) -- cgit v1.2.3-54-g00ecf From 55abd6dadd50e2d7d0e5fcc5bb49c21c52226d1b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 20:30:50 +0100 Subject: resolved: fix typo Noticed by @vcaputo --- src/resolve/resolved-link-bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index 20352a3e51..2ca7ece851 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -392,7 +392,7 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v if (r < 0) return r; if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search negative trust anchor domain: %s", *i); + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid negative trust anchor domain: %s", *i); } ns = set_new(&dns_name_hash_ops); -- cgit v1.2.3-54-g00ecf From 6955a3ba9dc5d36487724878333a5745987e6656 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 21:22:26 +0100 Subject: resolved: when checking whether a link is relevant, check kernel operstate This mimics what networkd is doing to detect a carrier. --- src/basic/missing.h | 11 +++++++++++ src/resolve/resolved-link.c | 11 +++++++++-- src/resolve/resolved-link.h | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/basic/missing.h b/src/basic/missing.h index c187afa287..6ed2133ed1 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -1149,3 +1149,14 @@ static inline key_serial_t request_key(const char *type, const char *description #ifndef PR_CAP_AMBIENT_CLEAR_ALL #define PR_CAP_AMBIENT_CLEAR_ALL 4 #endif + +/* The following two defines are actually available in the kernel headers for longer, but we define them here anyway, + * since that makes it easier to use them in conjunction with the glibc net/if.h header which conflicts with + * linux/if.h. */ +#ifndef IF_OPER_UNKNOWN +#define IF_OPER_UNKNOWN 0 +#endif + +#ifndef IF_OPER_UP +#define IF_OPER_UP 6 +#endif diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index b203f19dbb..e2f9c8b400 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -49,6 +49,7 @@ int link_new(Manager *m, Link **ret, int ifindex) { l->llmnr_support = RESOLVE_SUPPORT_YES; l->mdns_support = RESOLVE_SUPPORT_NO; l->dnssec_mode = _DNSSEC_MODE_INVALID; + l->operstate = IF_OPER_UNKNOWN; r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); if (r < 0) @@ -177,7 +178,8 @@ int link_update_rtnl(Link *l, sd_netlink_message *m) { if (r < 0) return r; - sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &l->operstate); if (sd_netlink_message_read_string(m, IFLA_IFNAME, &n) >= 0) { strncpy(l->name, n, sizeof(l->name)-1); @@ -514,7 +516,12 @@ bool link_relevant(Link *l, int family, bool multicast) { return false; } - sd_network_link_get_operational_state(l->ifindex, &state); + /* Check kernel operstate + * https://www.kernel.org/doc/Documentation/networking/operstates.txt */ + if (!IN_SET(l->operstate, IF_OPER_UNKNOWN, IF_OPER_UP)) + return false; + + (void) sd_network_link_get_operational_state(l->ifindex, &state); if (state && !STR_IN_SET(state, "unknown", "degraded", "routable")) return false; diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index 6544214b77..3b6aafb8f0 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -82,6 +82,7 @@ struct Link { char name[IF_NAMESIZE]; uint32_t mtu; + uint8_t operstate; }; int link_new(Manager *m, Link **ret, int ifindex); -- cgit v1.2.3-54-g00ecf From edbcc1fdd94355c5cf22263ba2c1cfa4ec2eb010 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 21:28:22 +0100 Subject: resolve: generate a nice clean error when clients try to resolve a name when the network is down --- src/libsystemd/sd-bus/bus-common-errors.c | 1 + src/libsystemd/sd-bus/bus-common-errors.h | 1 + src/resolve/resolved-bus.c | 3 +++ src/resolve/resolved-dns-query.c | 3 ++- src/resolve/resolved-dns-scope.c | 19 +++++++++++++++++++ src/resolve/resolved-dns-scope.h | 2 ++ src/resolve/resolved-dns-transaction.c | 9 +++++++++ src/resolve/resolved-dns-transaction.h | 1 + 8 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 9ddc9b5aaf..e344b3b77b 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -80,6 +80,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), + SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index e93b6ac448..130779e8e3 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -79,6 +79,7 @@ #define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" #define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" #define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" +#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." #define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 9110ea52a6..66c2c25dec 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -59,6 +59,9 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); + case DNS_TRANSACTION_NETWORK_DOWN: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); + case DNS_TRANSACTION_RCODE_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index fc5bf4020f..165a52fcd0 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -891,7 +891,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { DNS_TRANSACTION_RCODE_FAILURE, DNS_TRANSACTION_NO_SERVERS, DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)) + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_NETWORK_DOWN)) return 0; DNS_QUESTION_FOREACH(key, q->question_utf8) { diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 8a52d66fad..ac4887abea 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -1015,3 +1015,22 @@ bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { return dns_name_is_single_label(name); } + +bool dns_scope_network_good(DnsScope *s) { + Iterator i; + Link *l; + + /* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes + * bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global + * DNS scope we check whether there are any links that are up and have an address. */ + + if (s->link) + return true; + + HASHMAP_FOREACH(l, s->manager->links, i) { + if (link_relevant(l, AF_UNSPEC, false)) + return true; + } + + return false; +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index a0676bd30e..f9b63d56d9 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -107,3 +107,5 @@ void dns_scope_dump(DnsScope *s, FILE *f); DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); + +bool dns_scope_network_good(DnsScope *s); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 5640cd1d33..d485cd917d 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1094,6 +1094,14 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { dns_transaction_stop_timeout(t); + r = dns_scope_network_good(t->scope); + if (r < 0) + return r; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN); + return 0; + } + if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); return 0; @@ -2969,6 +2977,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", + [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", }; DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 76cf6e71db..80a2591fbc 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -40,6 +40,7 @@ enum DnsTransactionState { DNS_TRANSACTION_DNSSEC_FAILED, DNS_TRANSACTION_NO_TRUST_ANCHOR, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, + DNS_TRANSACTION_NETWORK_DOWN, _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -1 }; -- cgit v1.2.3-54-g00ecf From d07b43a18e2234e8abebc1cd4d93bfd78ab62001 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 21:30:59 +0100 Subject: resolved: move dns_type_to_af() to dns-type.c --- src/resolve/dns-type.c | 19 +++++++++++++++++++ src/resolve/dns-type.h | 1 + src/resolve/resolved-dns-query.c | 17 ----------------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index 058d14009a..56720646ca 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -19,6 +19,8 @@ along with systemd; If not, see . ***/ +#include + #include "dns-type.h" #include "string-util.h" @@ -183,6 +185,23 @@ bool dns_type_is_obsolete(uint16_t type) { DNS_TYPE_NULL); } +int dns_type_to_af(uint16_t t) { + switch (t) { + + case DNS_TYPE_A: + return AF_INET; + + case DNS_TYPE_AAAA: + return AF_INET6; + + case DNS_TYPE_ANY: + return AF_UNSPEC; + + default: + return -EINVAL; + } +} + const char *dns_class_to_string(uint16_t class) { switch (class) { diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index 78ff71b06e..60ff160383 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -133,6 +133,7 @@ bool dns_type_is_dnssec(uint16_t type); bool dns_type_is_obsolete(uint16_t type); bool dns_type_may_wildcard(uint16_t type); bool dns_type_apex_only(uint16_t type); +int dns_type_to_af(uint16_t t); bool dns_class_is_pseudo(uint16_t class); bool dns_class_is_valid_rr(uint16_t class); diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 165a52fcd0..47f1924702 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -595,23 +595,6 @@ static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) { return DNS_PROTOCOL_DNS; } -static int dns_type_to_af(uint16_t t) { - switch (t) { - - case DNS_TYPE_A: - return AF_INET; - - case DNS_TYPE_AAAA: - return AF_INET6; - - case DNS_TYPE_ANY: - return AF_UNSPEC; - - default: - return -EINVAL; - } -} - static int synthesize_localhost_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { int r; -- cgit v1.2.3-54-g00ecf From 839a4a20d8b4f6ebf3a342fd05c821358be5a313 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 22:06:26 +0100 Subject: resolved: split out RR synthesis logic into its own C file Also, while we are at it, set the "authenticated" bit for everything we synthesize --- Makefile.am | 2 + src/resolve/resolved-dns-query.c | 386 ++----------------------------- src/resolve/resolved-dns-synthesize.c | 418 ++++++++++++++++++++++++++++++++++ src/resolve/resolved-dns-synthesize.h | 26 +++ 4 files changed, 464 insertions(+), 368 deletions(-) create mode 100644 src/resolve/resolved-dns-synthesize.c create mode 100644 src/resolve/resolved-dns-synthesize.h diff --git a/Makefile.am b/Makefile.am index 264a769f71..fad5ff9d74 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5188,6 +5188,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-dns-packet.c \ src/resolve/resolved-dns-query.h \ src/resolve/resolved-dns-query.c \ + src/resolve/resolved-dns-synthesize.h \ + src/resolve/resolved-dns-synthesize.c \ src/resolve/resolved-dns-transaction.h \ src/resolve/resolved-dns-transaction.c \ src/resolve/resolved-dns-scope.h \ diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 47f1924702..2f9dd1c47b 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -24,6 +24,7 @@ #include "hostname-util.h" #include "local-addresses.h" #include "resolved-dns-query.h" +#include "resolved-dns-synthesize.h" #include "string-util.h" /* How long to wait for the query in total */ @@ -547,328 +548,15 @@ fail: return r; } -static int SYNTHESIZE_IFINDEX(int ifindex) { - - /* When the caller asked for resolving on a specific - * interface, we synthesize the answer for that - * interface. However, if nothing specific was claimed and we - * only return localhost RRs, we synthesize the answer for - * localhost. */ - - if (ifindex > 0) - return ifindex; - - return LOOPBACK_IFINDEX; -} - -static int 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) - return AF_INET; - if (flags & SD_RESOLVED_LLMNR_IPV6) - return AF_INET6; - } - - return AF_UNSPEC; -} - -static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) { - - /* Similar as 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; - - return DNS_PROTOCOL_DNS; -} - -static int synthesize_localhost_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - int r; - - assert(q); - 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, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { - _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, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); - 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); -} - -static int synthesize_localhost_ptr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - int r; - - assert(q); - 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", SYNTHESIZE_IFINDEX(q->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); - 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) { - - 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); - if (r < 0) - return r; - } - - return 0; -} - -static int synthesize_system_hostname_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(q); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_addresses(q->manager->rtnl, q->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 (af == AF_INET || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET, - .ifindex = SYNTHESIZE_IFINDEX(q->ifindex), - .address.in.s_addr = htobe32(0x7F000002), - }; - - if (af == AF_INET6 || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET6, - .ifindex = SYNTHESIZE_IFINDEX(q->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(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n, r; - - assert(q); - assert(address); - assert(answer); - - if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { - - /* Always map the IPv4 address 127.0.0.2 to the local - * hostname, in addition to "localhost": */ - - r = dns_answer_reserve(answer, 3); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - return 0; - } - - n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - r = answer_add_addresses_ptr(answer, q->manager->llmnr_hostname, addresses, n, af, address); - if (r < 0) - return r; - - return answer_add_addresses_ptr(answer, q->manager->mdns_hostname, addresses, n, af, address); -} - -static int synthesize_gateway_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(q); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - } - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); -} - -static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n; - - assert(q); - assert(address); - assert(answer); - - n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); -} - static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsResourceKey *key; - int r; + DnsProtocol protocol; + int family, r; assert(q); assert(state); - /* Tries to synthesize localhost RR replies where appropriate */ + /* Tries to synthesize localhost RR replies (and others) where appropriate */ if (!IN_SET(*state, DNS_TRANSACTION_RCODE_FAILURE, @@ -878,64 +566,26 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { DNS_TRANSACTION_NETWORK_DOWN)) return 0; - DNS_QUESTION_FOREACH(key, q->question_utf8) { - union in_addr_union address; - const char *name; - int af; - - if (key->class != DNS_CLASS_IN && - key->class != DNS_CLASS_ANY) - continue; - - name = DNS_RESOURCE_KEY_NAME(key); + r = dns_synthesize_answer( + q->manager, + q->question_utf8, + q->ifindex, + q->flags, + &answer, + &protocol, + &family); - if (is_localhost(name)) { - - r = synthesize_localhost_rr(q, key, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); - - } else if (manager_is_own_hostname(q->manager, name)) { - - r = synthesize_system_hostname_rr(q, key, &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(q, key, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway 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, "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(q, 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) { - - r = synthesize_system_hostname_ptr(q, af, &address, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); - - r = synthesize_gateway_ptr(q, af, &address, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); - } - } + if (r <= 0) + return r; - if (!answer) - return 0; + dns_query_reset_answer(q); - dns_answer_unref(q->answer); q->answer = answer; answer = NULL; - q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = SYNTHESIZE_PROTOCOL(q->flags); - q->answer_family = SYNTHESIZE_FAMILY(q->flags); + q->answer_protocol = protocol; + q->answer_family = family; + q->answer_authenticated = true; *state = DNS_TRANSACTION_SUCCESS; diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c new file mode 100644 index 0000000000..e12bb6e828 --- /dev/null +++ b/src/resolve/resolved-dns-synthesize.c @@ -0,0 +1,418 @@ +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "alloc-util.h" +#include "hostname-util.h" +#include "local-addresses.h" +#include "resolved-dns-synthesize.h" + +static int SYNTHESIZE_IFINDEX(int ifindex) { + + /* When the caller asked for resolving on a specific + * interface, we synthesize the answer for that + * interface. However, if nothing specific was claimed and we + * only return localhost RRs, we synthesize the answer for + * localhost. */ + + if (ifindex > 0) + return ifindex; + + return LOOPBACK_IFINDEX; +} + +static int 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; +} + +static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) { + + /* Similar as 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, int ifindex, 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, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { + _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, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + 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); +} + +static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, 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", SYNTHESIZE_IFINDEX(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); + 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) { + + 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); + if (r < 0) + return r; + } + + return 0; +} + +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 (af == AF_INET || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET, + .ifindex = SYNTHESIZE_IFINDEX(ifindex), + .address.in.s_addr = htobe32(0x7F000002), + }; + + if (af == AF_INET6 || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET6, + .ifindex = SYNTHESIZE_IFINDEX(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; + int n, r; + + assert(m); + assert(address); + assert(answer); + + if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { + + /* Always map the IPv4 address 127.0.0.2 to the local + * hostname, in addition to "localhost": */ + + r = dns_answer_reserve(answer, 3); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + return 0; + } + + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); + if (r < 0) + return r; + + return answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); +} + +static int synthesize_gateway_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_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +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, + uint64_t flags, + DnsAnswer **ret, + DnsProtocol *ret_protocol, + int *ret_family) { + + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsResourceKey *key; + int r; + + assert(m); + assert(q); + + DNS_QUESTION_FOREACH(key, q) { + union in_addr_union address; + const char *name; + int af; + + if (key->class != DNS_CLASS_IN && + key->class != DNS_CLASS_ANY) + continue; + + name = DNS_RESOURCE_KEY_NAME(key); + + if (is_localhost(name)) { + + r = synthesize_localhost_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); + + } else if (manager_is_own_hostname(m, name)) { + + 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, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway 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, "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, ifindex, &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) { + + r = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + + r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); + } + } + + r = dns_answer_size(answer) > 0; + + if (ret) { + *ret = answer; + answer = NULL; + } + + if (ret_protocol) + *ret_protocol = SYNTHESIZE_PROTOCOL(flags); + + if (ret_family) + *ret_family = SYNTHESIZE_FAMILY(flags); + + return r; +} diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h new file mode 100644 index 0000000000..e43d96cb2c --- /dev/null +++ b/src/resolve/resolved-dns-synthesize.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-manager.h" + +int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, uint64_t flags, DnsAnswer **ret, DnsProtocol *ret_protocol, int *ret_family); -- cgit v1.2.3-54-g00ecf From ce736ace37399f3dc45e6981852881a6f2448de2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 22:07:48 +0100 Subject: resolved: get rid of unnecessary if check DNS_ANSWER_FOREACH() can deal with NULL answers anyway, let's simplify our code here. --- src/resolve/resolved-bus.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 66c2c25dec..d6e9452766 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -136,8 +136,9 @@ static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifin static void bus_method_resolve_hostname_complete(DnsQuery *q) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsResourceRecord *rr; unsigned added = 0; - int r; + int ifindex, r; assert(q); @@ -164,30 +165,25 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { if (r < 0) goto finish; - if (q->answer) { - DnsResourceRecord *rr; - int ifindex; - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - DnsQuestion *question; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + DnsQuestion *question; - question = dns_query_question_for_protocol(q, q->answer_protocol); + question = dns_query_question_for_protocol(q, q->answer_protocol); - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - goto finish; - if (r == 0) - continue; + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (r == 0) + continue; - r = append_address(reply, rr, ifindex); - if (r < 0) - goto finish; + r = append_address(reply, rr, ifindex); + if (r < 0) + goto finish; - if (!canonical) - canonical = dns_resource_record_ref(rr); + if (!canonical) + canonical = dns_resource_record_ref(rr); - added ++; - } + added ++; } if (added <= 0) { -- cgit v1.2.3-54-g00ecf From b5eb973b39665d6a66f5a498fbdb511765e646e2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jan 2016 22:10:00 +0100 Subject: resolve: rename "systemd-resolve-host" tool to simply "systemd-resolve" The tool resolves way more than just hosts, hence give it a more generic name. This should be safe, as the tool is currently undocumented. Before we add documentation for it, let's get the name right. This also moves the C source into src/resolve/ (from src/resolve-host/), since the old name is a misnomer now. Also, since it links directly to many of the C files of resolved it really belongs into resolved's directory anyway. --- .gitignore | 2 +- Makefile.am | 10 +- src/resolve-host/Makefile | 1 - src/resolve-host/resolve-host.c | 1265 --------------------------------------- src/resolve/resolve-tool.c | 1265 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 1271 insertions(+), 1272 deletions(-) delete mode 120000 src/resolve-host/Makefile delete mode 100644 src/resolve-host/resolve-host.c create mode 100644 src/resolve/resolve-tool.c diff --git a/.gitignore b/.gitignore index 549dc8357f..c045ea4378 100644 --- a/.gitignore +++ b/.gitignore @@ -109,7 +109,7 @@ /systemd-remount-api-vfs /systemd-remount-fs /systemd-reply-password -/systemd-resolve-host +/systemd-resolve /systemd-resolved /systemd-rfkill /systemd-run diff --git a/Makefile.am b/Makefile.am index fad5ff9d74..5036a2bfc9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5267,8 +5267,8 @@ libnss_resolve_la_LIBADD = \ lib_LTLIBRARIES += \ libnss_resolve.la -systemd_resolve_host_SOURCES = \ - src/resolve-host/resolve-host.c \ +systemd_resolve_SOURCES = \ + src/resolve/resolve-tool.c \ src/resolve/resolved-dns-packet.c \ src/resolve/resolved-dns-packet.h \ src/resolve/resolved-dns-rr.c \ @@ -5280,15 +5280,15 @@ systemd_resolve_host_SOURCES = \ src/resolve/dns-type.c \ src/resolve/dns-type.h -nodist_systemd_resolve_host_SOURCES = \ +nodist_systemd_resolve_SOURCES = \ src/resolve/dns_type-from-name.h \ src/resolve/dns_type-to-name.h -systemd_resolve_host_LDADD = \ +systemd_resolve_LDADD = \ libshared.la rootlibexec_PROGRAMS += \ - systemd-resolve-host + systemd-resolve tests += \ test-dns-domain \ diff --git a/src/resolve-host/Makefile b/src/resolve-host/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/resolve-host/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c deleted file mode 100644 index 54a060ea5a..0000000000 --- a/src/resolve-host/resolve-host.c +++ /dev/null @@ -1,1265 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include - -#include "sd-bus.h" - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "escape.h" -#include "in-addr-util.h" -#include "parse-util.h" -#include "resolved-def.h" -#include "resolved-dns-packet.h" -#include "terminal-util.h" - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -static int arg_family = AF_UNSPEC; -static int arg_ifindex = 0; -static uint16_t arg_type = 0; -static uint16_t arg_class = 0; -static bool arg_legend = true; -static uint64_t arg_flags = 0; - -static enum { - MODE_RESOLVE_HOST, - MODE_RESOLVE_RECORD, - MODE_RESOLVE_SERVICE, - MODE_STATISTICS, - MODE_RESET_STATISTICS, -} arg_mode = MODE_RESOLVE_HOST; - -static void print_source(uint64_t flags, usec_t rtt) { - char rtt_str[FORMAT_TIMESTAMP_MAX]; - - if (!arg_legend) - return; - - if (flags == 0) - return; - - fputs("\n-- Information acquired via", stdout); - - if (flags != 0) - printf(" protocol%s%s%s%s%s", - flags & SD_RESOLVED_DNS ? " DNS" :"", - flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", - flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", - flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", - flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); - - assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); - - printf(" in %s", rtt_str); - - fputc('.', stdout); - fputc('\n', stdout); - - printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); -} - -static int resolve_host(sd_bus *bus, const char *name) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *canonical = NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned c = 0; - int r; - uint64_t flags; - usec_t ts; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - size_t sz; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s %s%s%s\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - pretty, - isempty(ifname) ? "" : "%", ifname); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) - printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); - - if (c == 0) { - log_error("%s: no addresses found", name); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *pretty = NULL; - char ifname[IF_NAMESIZE] = ""; - uint64_t flags; - unsigned c = 0; - usec_t ts; - int r; - - assert(bus); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(address); - - if (ifindex <= 0) - ifindex = arg_ifindex; - - r = in_addr_to_string(family, address, &pretty); - if (r < 0) - return log_oom(); - - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveAddress"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "ii", ifindex, family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "t", arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - return bus_log_create_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { - const char *n; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "is", &ifindex, &n); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%*s%*s%*s%s %s\n", - (int) strlen(pretty), c == 0 ? pretty : "", - isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", - (int) strlen(ifname), c == 0 ? ifname : "", - c == 0 ? ":" : " ", - n); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (c == 0) { - log_error("%s: no names found", pretty); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) { - const char *percent, *a; - int ifi = 0; - int r; - - percent = strchr(s, '%'); - if (percent) { - if (parse_ifindex(percent+1, &ifi) < 0) { - ifi = if_nametoindex(percent+1); - if (ifi <= 0) - return -EINVAL; - } - - a = strndupa(s, percent - s); - } else - a = s; - - r = in_addr_from_string_auto(a, family, address); - if (r < 0) - return r; - - *ifindex = ifi; - return 0; -} - -static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned n = 0; - uint64_t flags; - int r; - usec_t ts; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - const char *s; - uint16_t c, t; - int ifindex; - const void *d; - size_t l; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &d, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - return log_oom(); - - p->refuse_compression = true; - - r = dns_packet_append_blob(p, d, l, NULL); - if (r < 0) - return log_oom(); - - r = dns_packet_read_rr(p, &rr, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse RR: %m"); - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); - n++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (n == 0) { - log_error("%s: no records found", name); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_rfc4501(sd_bus *bus, const char *name) { - uint16_t type = 0, class = 0; - const char *p, *q, *n; - int r; - - assert(bus); - assert(name); - assert(startswith(name, "dns:")); - - /* Parse RFC 4501 dns: URIs */ - - p = name + 4; - - if (p[0] == '/') { - const char *e; - - if (p[1] != '/') - goto invalid; - - e = strchr(p + 2, '/'); - if (!e) - goto invalid; - - if (e != p + 2) - log_warning("DNS authority specification not supported; ignoring specified authority."); - - p = e + 1; - } - - q = strchr(p, '?'); - if (q) { - n = strndupa(p, q - p); - q++; - - for (;;) { - const char *f; - - f = startswith_no_case(q, "class="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (class != 0) { - log_error("DNS class specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_class_from_string(t); - if (r < 0) { - log_error("Unknown DNS class %s.", t); - return -EINVAL; - } - - class = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - f = startswith_no_case(q, "type="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (type != 0) { - log_error("DNS type specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_type_from_string(t); - if (r < 0) { - log_error("Unknown DNS type %s.", t); - return -EINVAL; - } - - type = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - goto invalid; - } - } else - n = p; - - if (type == 0) - type = arg_type; - if (type == 0) - type = DNS_TYPE_A; - - if (class == 0) - class = arg_class; - if (class == 0) - class = DNS_CLASS_IN; - - return resolve_record(bus, n, class, type); - -invalid: - log_error("Invalid DNS URI: %s", name); - return -EINVAL; -} - -static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { - const char *canonical_name, *canonical_type, *canonical_domain; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - size_t indent, sz; - uint64_t flags; - const char *p; - unsigned c; - usec_t ts; - int r; - - assert(bus); - assert(domain); - - if (isempty(name)) - name = NULL; - if (isempty(type)) - type = NULL; - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - if (name) - log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else if (type) - log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else - log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveService"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - return bus_log_parse_error(r); - - indent = - (name ? strlen(name) + 1 : 0) + - (type ? strlen(type) + 1 : 0) + - strlen(domain) + 2; - - c = 0; - while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { - uint16_t priority, weight, port; - const char *hostname, *canonical; - - r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); - if (r < 0) - return bus_log_parse_error(r); - - if (name) - printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); - if (type) - printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); - - printf("%*s%s %s:%u [priority=%u, weight=%u]\n", - (int) strlen(domain), c == 0 ? domain : "", - c == 0 ? ":" : " ", - hostname, port, - priority, weight); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "s", &canonical); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(hostname, canonical)) - printf("%*s(%s)\n", (int) indent, "", canonical); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'a', "ay"); - if (r < 0) - return bus_log_parse_error(r); - - c = 0; - while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { - _cleanup_free_ char *escaped = NULL; - - escaped = cescape_length(p, sz); - if (!escaped) - return log_oom(); - - printf("%*s%s\n", (int) indent, "", escaped); - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (isempty(canonical_name)) - canonical_name = NULL; - if (isempty(canonical_type)) - canonical_type = NULL; - - if (!streq_ptr(name, canonical_name) || - !streq_ptr(type, canonical_type) || - !streq_ptr(domain, canonical_domain)) { - - printf("%*s(", (int) indent, ""); - - if (canonical_name) - printf("%s/", canonical_name); - if (canonical_type) - printf("%s/", canonical_type); - - printf("%s)\n", canonical_domain); - } - - print_source(flags, ts); - - return 0; -} - -static int show_statistics(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint64_t n_current_transactions, n_total_transactions, - cache_size, n_cache_hit, n_cache_miss, - n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; - int r, dnssec_supported; - - assert(bus); - - r = sd_bus_get_property_trivial(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECSupported", - &error, - 'b', - &dnssec_supported); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); - - printf("DNSSEC supported by current servers: %s%s%s\n\n", - ansi_highlight(), - yes_no(dnssec_supported), - ansi_normal()); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "TransactionStatistics", - &error, - &reply, - "(tt)"); - if (r < 0) - return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tt)", - &n_current_transactions, - &n_total_transactions); - if (r < 0) - return bus_log_parse_error(r); - - printf("%sTransactions%s\n" - "Current Transactions: %" PRIu64 "\n" - " Total Transactions: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_current_transactions, - n_total_transactions); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "CacheStatistics", - &error, - &reply, - "(ttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(ttt)", - &cache_size, - &n_cache_hit, - &n_cache_miss); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sCache%s\n" - " Current Cache Size: %" PRIu64 "\n" - " Cache Hits: %" PRIu64 "\n" - " Cache Misses: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - cache_size, - n_cache_hit, - n_cache_miss); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECStatistics", - &error, - &reply, - "(tttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tttt)", - &n_dnssec_secure, - &n_dnssec_insecure, - &n_dnssec_bogus, - &n_dnssec_indeterminate); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sDNSSEC%s\n" - " Secure RRsets: %" PRIu64 "\n" - " Insecure RRsets: %" PRIu64 "\n" - " Bogus RRsets: %" PRIu64 "\n" - "Indeterminate RRsets: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_dnssec_secure, - n_dnssec_insecure, - n_dnssec_bogus, - n_dnssec_indeterminate); - - return 0; -} - -static int reset_statistics(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = sd_bus_call_method(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResetStatistics", - &error, - NULL, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); - - return 0; -} - -static void help_dns_types(void) { - int i; - const char *t; - - if (arg_legend) - puts("Known DNS RR types:"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - t = dns_type_to_string(i); - if (t) - puts(t); - } -} - -static void help_dns_classes(void) { - int i; - const char *t; - - if (arg_legend) - puts("Known DNS RR classes:"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - t = dns_class_to_string(i); - if (t) - puts(t); - } -} - -static void help(void) { - printf("%s [OPTIONS...] NAME...\n" - "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n" - "Resolve domain names, IPv4 or IPv6 addresses, resource records, and services.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTOCOL Look via protocol\n" - " -t --type=TYPE Query RR with DNS type\n" - " -c --class=CLASS Query RR with DNS class\n" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Do [not] resolve address for services\n" - " --service-txt=BOOL Do [not] resolve TXT records for services\n" - " --cname=BOOL Do [not] follow CNAME redirects\n" - " --search=BOOL Do [not] use search domains\n" - " --legend=BOOL Do [not] print column headers\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - , program_invocation_short_name, program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_SEARCH, - ARG_STATISTICS, - ARG_RESET_STATISTICS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "statistics", no_argument, NULL, ARG_STATISTICS, }, - { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) - switch(c) { - - case 'h': - help(); - return 0; /* done */; - - case ARG_VERSION: - return version(); - - case '4': - arg_family = AF_INET; - break; - - case '6': - arg_family = AF_INET6; - break; - - case 'i': { - int ifi; - - if (parse_ifindex(optarg, &ifi) >= 0) - arg_ifindex = ifi; - else { - ifi = if_nametoindex(optarg); - if (ifi <= 0) - return log_error_errno(errno, "Unknown interface %s: %m", optarg); - - arg_ifindex = ifi; - } - - break; - } - - case 't': - if (streq(optarg, "help")) { - help_dns_types(); - return 0; - } - - r = dns_type_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record type %s", optarg); - return r; - } - arg_type = (uint16_t) r; - assert((int) arg_type == r); - - arg_mode = MODE_RESOLVE_RECORD; - break; - - case 'c': - if (streq(optarg, "help")) { - help_dns_classes(); - return 0; - } - - r = dns_class_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record class %s", optarg); - return r; - } - arg_class = (uint16_t) r; - assert((int) arg_class == r); - - break; - - case ARG_LEGEND: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --legend= argument"); - - arg_legend = r; - break; - - case 'p': - if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else { - log_error("Unknown protocol specifier: %s", optarg); - return -EINVAL; - } - - break; - - case ARG_SERVICE: - arg_mode = MODE_RESOLVE_SERVICE; - break; - - case ARG_CNAME: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --cname= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_CNAME; - else - arg_flags &= ~SD_RESOLVED_NO_CNAME; - break; - - case ARG_SERVICE_ADDRESS: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-address= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_ADDRESS; - else - arg_flags &= ~SD_RESOLVED_NO_ADDRESS; - break; - - case ARG_SERVICE_TXT: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-txt= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_TXT; - else - arg_flags &= ~SD_RESOLVED_NO_TXT; - break; - - case ARG_SEARCH: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --search argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_SEARCH; - else - arg_flags &= ~SD_RESOLVED_NO_SEARCH; - break; - - case ARG_STATISTICS: - arg_mode = MODE_STATISTICS; - break; - - case ARG_RESET_STATISTICS: - arg_mode = MODE_RESET_STATISTICS; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_type == 0 && arg_class != 0) { - log_error("--class= may only be used in conjunction with --type=."); - return -EINVAL; - } - - if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { - log_error("--service and --type= may not be combined."); - return -EINVAL; - } - - if (arg_type != 0 && arg_class == 0) - arg_class = DNS_CLASS_IN; - - if (arg_class != 0 && arg_type == 0) - arg_type = DNS_TYPE_A; - - return 1 /* work to do */; -} - -int main(int argc, char **argv) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_error_errno(r, "sd_bus_open_system: %m"); - goto finish; - } - - switch (arg_mode) { - - case MODE_RESOLVE_HOST: - if (optind >= argc) { - log_error("No arguments passed"); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int family, ifindex, k; - union in_addr_union a; - - if (startswith(argv[optind], "dns:")) - k = resolve_rfc4501(bus, argv[optind]); - else { - k = parse_address(argv[optind], &family, &a, &ifindex); - if (k >= 0) - k = resolve_address(bus, family, &a, ifindex); - else - k = resolve_host(bus, argv[optind]); - } - - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_RECORD: - if (optind >= argc) { - log_error("No arguments passed"); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int k; - - k = resolve_record(bus, argv[optind], arg_class, arg_type); - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_SERVICE: - if (argc < optind + 1) { - log_error("Domain specification required."); - r = -EINVAL; - goto finish; - - } else if (argc == optind + 1) - r = resolve_service(bus, NULL, NULL, argv[optind]); - else if (argc == optind + 2) - r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); - else if (argc == optind + 3) - r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); - else { - log_error("Too many arguments"); - r = -EINVAL; - goto finish; - } - - break; - - case MODE_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = show_statistics(bus); - break; - - case MODE_RESET_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = reset_statistics(bus); - break; - } - -finish: - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c new file mode 100644 index 0000000000..54a060ea5a --- /dev/null +++ b/src/resolve/resolve-tool.c @@ -0,0 +1,1265 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "sd-bus.h" + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "escape.h" +#include "in-addr-util.h" +#include "parse-util.h" +#include "resolved-def.h" +#include "resolved-dns-packet.h" +#include "terminal-util.h" + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +static int arg_family = AF_UNSPEC; +static int arg_ifindex = 0; +static uint16_t arg_type = 0; +static uint16_t arg_class = 0; +static bool arg_legend = true; +static uint64_t arg_flags = 0; + +static enum { + MODE_RESOLVE_HOST, + MODE_RESOLVE_RECORD, + MODE_RESOLVE_SERVICE, + MODE_STATISTICS, + MODE_RESET_STATISTICS, +} arg_mode = MODE_RESOLVE_HOST; + +static void print_source(uint64_t flags, usec_t rtt) { + char rtt_str[FORMAT_TIMESTAMP_MAX]; + + if (!arg_legend) + return; + + if (flags == 0) + return; + + fputs("\n-- Information acquired via", stdout); + + if (flags != 0) + printf(" protocol%s%s%s%s%s", + flags & SD_RESOLVED_DNS ? " DNS" :"", + flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", + flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", + flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", + flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); + + assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); + + printf(" in %s", rtt_str); + + fputc('.', stdout); + fputc('\n', stdout); + + printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); +} + +static int resolve_host(sd_bus *bus, const char *name) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *canonical = NULL; + char ifname[IF_NAMESIZE] = ""; + unsigned c = 0; + int r; + uint64_t flags; + usec_t ts; + + assert(name); + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + size_t sz; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + r = in_addr_to_string(family, a, &pretty); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s %s%s%s\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + pretty, + isempty(ifname) ? "" : "%", ifname); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "st", &canonical, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(name, canonical)) + printf("%*s%s (%s)\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + canonical); + + if (c == 0) { + log_error("%s: no addresses found", name); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *pretty = NULL; + char ifname[IF_NAMESIZE] = ""; + uint64_t flags; + unsigned c = 0; + usec_t ts; + int r; + + assert(bus); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(address); + + if (ifindex <= 0) + ifindex = arg_ifindex; + + r = in_addr_to_string(family, address, &pretty); + if (r < 0) + return log_oom(); + + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveAddress"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "ii", ifindex, family); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "t", arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(is)"); + if (r < 0) + return bus_log_create_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { + const char *n; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "is", &ifindex, &n); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%*s%*s%*s%s %s\n", + (int) strlen(pretty), c == 0 ? pretty : "", + isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", + (int) strlen(ifname), c == 0 ? ifname : "", + c == 0 ? ":" : " ", + n); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (c == 0) { + log_error("%s: no names found", pretty); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) { + const char *percent, *a; + int ifi = 0; + int r; + + percent = strchr(s, '%'); + if (percent) { + if (parse_ifindex(percent+1, &ifi) < 0) { + ifi = if_nametoindex(percent+1); + if (ifi <= 0) + return -EINVAL; + } + + a = strndupa(s, percent - s); + } else + a = s; + + r = in_addr_from_string_auto(a, family, address); + if (r < 0) + return r; + + *ifindex = ifi; + return 0; +} + +static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char ifname[IF_NAMESIZE] = ""; + unsigned n = 0; + uint64_t flags; + int r; + usec_t ts; + + assert(name); + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + const char *s; + uint16_t c, t; + int ifindex; + const void *d; + size_t l; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &d, &l); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return log_oom(); + + p->refuse_compression = true; + + r = dns_packet_append_blob(p, d, l, NULL); + if (r < 0) + return log_oom(); + + r = dns_packet_read_rr(p, &rr, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse RR: %m"); + + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); + n++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (n == 0) { + log_error("%s: no records found", name); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int resolve_rfc4501(sd_bus *bus, const char *name) { + uint16_t type = 0, class = 0; + const char *p, *q, *n; + int r; + + assert(bus); + assert(name); + assert(startswith(name, "dns:")); + + /* Parse RFC 4501 dns: URIs */ + + p = name + 4; + + if (p[0] == '/') { + const char *e; + + if (p[1] != '/') + goto invalid; + + e = strchr(p + 2, '/'); + if (!e) + goto invalid; + + if (e != p + 2) + log_warning("DNS authority specification not supported; ignoring specified authority."); + + p = e + 1; + } + + q = strchr(p, '?'); + if (q) { + n = strndupa(p, q - p); + q++; + + for (;;) { + const char *f; + + f = startswith_no_case(q, "class="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (class != 0) { + log_error("DNS class specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_class_from_string(t); + if (r < 0) { + log_error("Unknown DNS class %s.", t); + return -EINVAL; + } + + class = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + f = startswith_no_case(q, "type="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (type != 0) { + log_error("DNS type specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_type_from_string(t); + if (r < 0) { + log_error("Unknown DNS type %s.", t); + return -EINVAL; + } + + type = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + goto invalid; + } + } else + n = p; + + if (type == 0) + type = arg_type; + if (type == 0) + type = DNS_TYPE_A; + + if (class == 0) + class = arg_class; + if (class == 0) + class = DNS_CLASS_IN; + + return resolve_record(bus, n, class, type); + +invalid: + log_error("Invalid DNS URI: %s", name); + return -EINVAL; +} + +static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { + const char *canonical_name, *canonical_type, *canonical_domain; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char ifname[IF_NAMESIZE] = ""; + size_t indent, sz; + uint64_t flags; + const char *p; + unsigned c; + usec_t ts; + int r; + + assert(bus); + assert(domain); + + if (isempty(name)) + name = NULL; + if (isempty(type)) + type = NULL; + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + if (name) + log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else if (type) + log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else + log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveService"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + return bus_log_parse_error(r); + + indent = + (name ? strlen(name) + 1 : 0) + + (type ? strlen(type) + 1 : 0) + + strlen(domain) + 2; + + c = 0; + while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { + uint16_t priority, weight, port; + const char *hostname, *canonical; + + r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); + if (r < 0) + return bus_log_parse_error(r); + + if (name) + printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); + if (type) + printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); + + printf("%*s%s %s:%u [priority=%u, weight=%u]\n", + (int) strlen(domain), c == 0 ? domain : "", + c == 0 ? ":" : " ", + hostname, port, + priority, weight); + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + r = in_addr_to_string(family, a, &pretty); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "s", &canonical); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(hostname, canonical)) + printf("%*s(%s)\n", (int) indent, "", canonical); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'a', "ay"); + if (r < 0) + return bus_log_parse_error(r); + + c = 0; + while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { + _cleanup_free_ char *escaped = NULL; + + escaped = cescape_length(p, sz); + if (!escaped) + return log_oom(); + + printf("%*s%s\n", (int) indent, "", escaped); + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (isempty(canonical_name)) + canonical_name = NULL; + if (isempty(canonical_type)) + canonical_type = NULL; + + if (!streq_ptr(name, canonical_name) || + !streq_ptr(type, canonical_type) || + !streq_ptr(domain, canonical_domain)) { + + printf("%*s(", (int) indent, ""); + + if (canonical_name) + printf("%s/", canonical_name); + if (canonical_type) + printf("%s/", canonical_type); + + printf("%s)\n", canonical_domain); + } + + print_source(flags, ts); + + return 0; +} + +static int show_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint64_t n_current_transactions, n_total_transactions, + cache_size, n_cache_hit, n_cache_miss, + n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; + int r, dnssec_supported; + + assert(bus); + + r = sd_bus_get_property_trivial(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECSupported", + &error, + 'b', + &dnssec_supported); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); + + printf("DNSSEC supported by current servers: %s%s%s\n\n", + ansi_highlight(), + yes_no(dnssec_supported), + ansi_normal()); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "TransactionStatistics", + &error, + &reply, + "(tt)"); + if (r < 0) + return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tt)", + &n_current_transactions, + &n_total_transactions); + if (r < 0) + return bus_log_parse_error(r); + + printf("%sTransactions%s\n" + "Current Transactions: %" PRIu64 "\n" + " Total Transactions: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_current_transactions, + n_total_transactions); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "CacheStatistics", + &error, + &reply, + "(ttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(ttt)", + &cache_size, + &n_cache_hit, + &n_cache_miss); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sCache%s\n" + " Current Cache Size: %" PRIu64 "\n" + " Cache Hits: %" PRIu64 "\n" + " Cache Misses: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + cache_size, + n_cache_hit, + n_cache_miss); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECStatistics", + &error, + &reply, + "(tttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tttt)", + &n_dnssec_secure, + &n_dnssec_insecure, + &n_dnssec_bogus, + &n_dnssec_indeterminate); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sDNSSEC%s\n" + " Secure RRsets: %" PRIu64 "\n" + " Insecure RRsets: %" PRIu64 "\n" + " Bogus RRsets: %" PRIu64 "\n" + "Indeterminate RRsets: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_dnssec_secure, + n_dnssec_insecure, + n_dnssec_bogus, + n_dnssec_indeterminate); + + return 0; +} + +static int reset_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResetStatistics", + &error, + NULL, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help_dns_types(void) { + int i; + const char *t; + + if (arg_legend) + puts("Known DNS RR types:"); + for (i = 0; i < _DNS_TYPE_MAX; i++) { + t = dns_type_to_string(i); + if (t) + puts(t); + } +} + +static void help_dns_classes(void) { + int i; + const char *t; + + if (arg_legend) + puts("Known DNS RR classes:"); + for (i = 0; i < _DNS_CLASS_MAX; i++) { + t = dns_class_to_string(i); + if (t) + puts(t); + } +} + +static void help(void) { + printf("%s [OPTIONS...] NAME...\n" + "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n" + "Resolve domain names, IPv4 or IPv6 addresses, resource records, and services.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -4 Resolve IPv4 addresses\n" + " -6 Resolve IPv6 addresses\n" + " -i --interface=INTERFACE Look on interface\n" + " -p --protocol=PROTOCOL Look via protocol\n" + " -t --type=TYPE Query RR with DNS type\n" + " -c --class=CLASS Query RR with DNS class\n" + " --service Resolve service (SRV)\n" + " --service-address=BOOL Do [not] resolve address for services\n" + " --service-txt=BOOL Do [not] resolve TXT records for services\n" + " --cname=BOOL Do [not] follow CNAME redirects\n" + " --search=BOOL Do [not] use search domains\n" + " --legend=BOOL Do [not] print column headers\n" + " --statistics Show resolver statistics\n" + " --reset-statistics Reset resolver statistics\n" + , program_invocation_short_name, program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_LEGEND, + ARG_SERVICE, + ARG_CNAME, + ARG_SERVICE_ADDRESS, + ARG_SERVICE_TXT, + ARG_SEARCH, + ARG_STATISTICS, + ARG_RESET_STATISTICS, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "class", required_argument, NULL, 'c' }, + { "legend", required_argument, NULL, ARG_LEGEND }, + { "interface", required_argument, NULL, 'i' }, + { "protocol", required_argument, NULL, 'p' }, + { "cname", required_argument, NULL, ARG_CNAME }, + { "service", no_argument, NULL, ARG_SERVICE }, + { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, + { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, + { "search", required_argument, NULL, ARG_SEARCH }, + { "statistics", no_argument, NULL, ARG_STATISTICS, }, + { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + switch(c) { + + case 'h': + help(); + return 0; /* done */; + + case ARG_VERSION: + return version(); + + case '4': + arg_family = AF_INET; + break; + + case '6': + arg_family = AF_INET6; + break; + + case 'i': { + int ifi; + + if (parse_ifindex(optarg, &ifi) >= 0) + arg_ifindex = ifi; + else { + ifi = if_nametoindex(optarg); + if (ifi <= 0) + return log_error_errno(errno, "Unknown interface %s: %m", optarg); + + arg_ifindex = ifi; + } + + break; + } + + case 't': + if (streq(optarg, "help")) { + help_dns_types(); + return 0; + } + + r = dns_type_from_string(optarg); + if (r < 0) { + log_error("Failed to parse RR record type %s", optarg); + return r; + } + arg_type = (uint16_t) r; + assert((int) arg_type == r); + + arg_mode = MODE_RESOLVE_RECORD; + break; + + case 'c': + if (streq(optarg, "help")) { + help_dns_classes(); + return 0; + } + + r = dns_class_from_string(optarg); + if (r < 0) { + log_error("Failed to parse RR record class %s", optarg); + return r; + } + arg_class = (uint16_t) r; + assert((int) arg_class == r); + + break; + + case ARG_LEGEND: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --legend= argument"); + + arg_legend = r; + break; + + case 'p': + if (streq(optarg, "dns")) + arg_flags |= SD_RESOLVED_DNS; + else if (streq(optarg, "llmnr")) + arg_flags |= SD_RESOLVED_LLMNR; + else if (streq(optarg, "llmnr-ipv4")) + arg_flags |= SD_RESOLVED_LLMNR_IPV4; + else if (streq(optarg, "llmnr-ipv6")) + arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else { + log_error("Unknown protocol specifier: %s", optarg); + return -EINVAL; + } + + break; + + case ARG_SERVICE: + arg_mode = MODE_RESOLVE_SERVICE; + break; + + case ARG_CNAME: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --cname= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_CNAME; + else + arg_flags &= ~SD_RESOLVED_NO_CNAME; + break; + + case ARG_SERVICE_ADDRESS: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-address= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_ADDRESS; + else + arg_flags &= ~SD_RESOLVED_NO_ADDRESS; + break; + + case ARG_SERVICE_TXT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-txt= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_TXT; + else + arg_flags &= ~SD_RESOLVED_NO_TXT; + break; + + case ARG_SEARCH: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --search argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_SEARCH; + else + arg_flags &= ~SD_RESOLVED_NO_SEARCH; + break; + + case ARG_STATISTICS: + arg_mode = MODE_STATISTICS; + break; + + case ARG_RESET_STATISTICS: + arg_mode = MODE_RESET_STATISTICS; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_type == 0 && arg_class != 0) { + log_error("--class= may only be used in conjunction with --type=."); + return -EINVAL; + } + + if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { + log_error("--service and --type= may not be combined."); + return -EINVAL; + } + + if (arg_type != 0 && arg_class == 0) + arg_class = DNS_CLASS_IN; + + if (arg_class != 0 && arg_type == 0) + arg_type = DNS_TYPE_A; + + return 1 /* work to do */; +} + +int main(int argc, char **argv) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error_errno(r, "sd_bus_open_system: %m"); + goto finish; + } + + switch (arg_mode) { + + case MODE_RESOLVE_HOST: + if (optind >= argc) { + log_error("No arguments passed"); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int family, ifindex, k; + union in_addr_union a; + + if (startswith(argv[optind], "dns:")) + k = resolve_rfc4501(bus, argv[optind]); + else { + k = parse_address(argv[optind], &family, &a, &ifindex); + if (k >= 0) + k = resolve_address(bus, family, &a, ifindex); + else + k = resolve_host(bus, argv[optind]); + } + + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_RECORD: + if (optind >= argc) { + log_error("No arguments passed"); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int k; + + k = resolve_record(bus, argv[optind], arg_class, arg_type); + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_SERVICE: + if (argc < optind + 1) { + log_error("Domain specification required."); + r = -EINVAL; + goto finish; + + } else if (argc == optind + 1) + r = resolve_service(bus, NULL, NULL, argv[optind]); + else if (argc == optind + 2) + r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); + else if (argc == optind + 3) + r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); + else { + log_error("Too many arguments"); + r = -EINVAL; + goto finish; + } + + break; + + case MODE_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = show_statistics(bus); + break; + + case MODE_RESET_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = reset_statistics(bus); + break; + } + +finish: + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} -- cgit v1.2.3-54-g00ecf From 54230826fd9cfaff5d49ab15cadd1f43105ffff2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 00:03:08 +0100 Subject: emacs: also configure nxml parameters in .dir-locals.el We configure the parameters we use for the docbook XML. --- .dir-locals.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dir-locals.el b/.dir-locals.el index eee106213b..9388bd66c2 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -10,4 +10,5 @@ (eval . (c-set-offset 'statement-case-open 0)) (eval . (c-set-offset 'case-label 0)) (eval . (c-set-offset 'arglist-intro '++)) - (eval . (c-set-offset 'arglist-close 0))))) + (eval . (c-set-offset 'arglist-close 0)))) + (nxml-mode . ((nxml-child-indent . 2)))) -- cgit v1.2.3-54-g00ecf From 624993ac85a58c27f8ae0adeaca7081bdb65cd3f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 00:04:19 +0100 Subject: man: document systemd-resolve(8) This also links up the new manpage from systemd-resolved.service(8), and makes a couple of unrelated additions. --- Makefile-man.am | 2 + man/systemd-resolve.xml | 272 +++++++++++++++++++++++++++++++++++++++ man/systemd-resolved.service.xml | 27 ++-- 3 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 man/systemd-resolve.xml diff --git a/Makefile-man.am b/Makefile-man.am index 98769fbee8..bcb67166ba 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -120,6 +120,7 @@ MANPAGES += \ man/systemd-nspawn.1 \ man/systemd-path.1 \ man/systemd-remount-fs.service.8 \ + man/systemd-resolve.1 \ man/systemd-run.1 \ man/systemd-sleep.conf.5 \ man/systemd-socket-proxyd.8 \ @@ -2601,6 +2602,7 @@ EXTRA_DIST += \ man/systemd-quotacheck.service.xml \ man/systemd-random-seed.service.xml \ man/systemd-remount-fs.service.xml \ + man/systemd-resolve.xml \ man/systemd-resolved.service.xml \ man/systemd-rfkill.service.xml \ man/systemd-run.xml \ diff --git a/man/systemd-resolve.xml b/man/systemd-resolve.xml new file mode 100644 index 0000000000..c5ad6a0e3e --- /dev/null +++ b/man/systemd-resolve.xml @@ -0,0 +1,272 @@ + + + + + + + + + systemd-resolve + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-resolve + 1 + + + + systemd-resolve + Resolve domain names, IPV4 and IPv6 addresses, DNS resource records, and services + + + + + systemd-resolve + OPTIONS + HOSTNAME + + + + systemd-resolve + OPTIONS + ADDRESS + + + + systemd-resolve + OPTIONS + --type=TYPE + RRDOMAIN + + + + systemd-resolve + OPTIONS + --service + NAME + TYPE DOMAIN + + + + systemd-resolve + OPTIONS + --statistics + + + + systemd-resolve + OPTIONS + --reset-statistics + + + + + + Description + + systemd-resolve may be used to resolve domain names, IPv4 and IPv6 addresses, DNS resource + records and services with the + systemd-resolved.service8 + resolver service. By default, the specified list of parameters will be resolved as hostnames, retrieving their IPv4 + and IPv6 addresses. If the parameters specified are formatted as IPv4 or IPv6 operation the reverse operation is + done, and a hostname is retrieved for the specified addresses. + + The switch may be used to specify a DNS resource record type (A, AAAA, SOA, MX, ...) in + order to request a specific DNS resource record, instead of the address or reverse address lookups. + + The switch may be used to resolve SRV and DNS-SD services (see below). In this mode, between one and three + arguments are required. If three parameters are passed the first is assumed to be the DNS-SD service name, the + second the SRV service type, and the third the domain to search in. In this case a full DNS-SD style SRV and TXT + lookup is executed. If only two parameters are specified, the first is assumed to be the SRV service type, and the + second the domain to look in. In this case no TXT RR is requested. Finally, if only one parameter is specified, it + is assumed to be a domain name, that is already prefixed with an SRV type, and an SRV lookup is done (no + TXT). + + The switch may be used to show resolver statistics, including information about + the number of succesful and failed DNSSEC validations. + + The may be used to reset various statistics counters maintained the + resolver, including those shown in the output. This operation requires root + privileges. + + + + Options + + + + + + By default, when resolving a hostname, both IPv4 and IPv6 + addresses are acquired. By specifying only IPv4 addresses are requested, by specifying + only IPv6 addresses are requested. + + + + + INTERFACE + INTERFACE + + Specifies the network interface to execute the query on. This may either be specified as numeric + interface index or as network interface string (e.g. en0). Note that this option has no + effect if system-wide DNS configuration (as configured in /etc/resolv.conf or + /etc/systemd/resolve.conf) in place of per-link configuration is used. + + + + PROTOCOL + PROTOCOL + + Specifies the network protocol for the query. May be one of dns + (i.e. classic unicast DNS), llmnr (Link-Local Multicast Name Resolution), + llmr-ipv4, llmnr-ipv6 (LLMNR via the indicated underlying IP + protocols). By default the lookup is done via all protocols suitable for the lookup. If used, limits the set of + protocols that may be used. Use this option multiple times to enable resolving via multiple protocols at the + same time. The setting llmnr is identical to specifying this switch once with + llmnr-ipv4 and once via llmnr-ipv6. Note that this option does not force + the service to resolve the operation with the specified protocol, as that might require a suitable network + interface and configuration. + + + + TYPE + TYPE + CLASS + CLASS + + Specifies the DNS resource record type (e.g. A, AAAA, MX, …) and class (e.g. IN, ANY, …) to + look up. If these options are used a DNS resource record set matching the specified class and type is + requested. The class defaults to IN if only a type is specified. + + + + + + Enables service resolution. This enables DNS-SD and simple SRV service resolution, dependending + on the specified list of parameters (see above). + + + + BOOL + + Takes a boolean parameter. If true (the default), when doing a service lookup with + the hostnames contained in the SRV resource records are resolved as well. + + + + BOOL + + Takes a boolean parameter. If true (the default), when doing a DNS-SD service lookup with + the TXT service metadata record is resolved as well. + + + + BOOL + + Takes a boolean parameter. If true (the default), DNS CNAME or DNAME redirections are + followed. Otherwise, if a CNAME or DNAME record is encountered while resolving, an error is + returned. + + + + BOOL + + Takes a boolean parameter. If true (the default), any specified single-label hostnames will be + searched in the domains configured in the search domain list, if it is non-empty. Otherwise, the search domain + logic is disabled. + + + + BOOL + + Takes a boolean parameter. If true (the default), column headers and meta information about the + query response are shown. Otherwise, this output is suppressed. + + + + + + If specified general resolver statistics are shown, including information whether DNSSEC is + enabled and available, as well as resolution and validation statistics. + + + + + + Resets the statistics counters shown in to zero. + + + + + + + + + Examples + + + Retrieve the addresses of the <literal>www.0pointer.net</literal> domain + + $ systemd-resolve www.0pointer.net + + + + Retrieve the domain of the <literal>85.214.157.71</literal> IP address + + $ systemd-resolve 85.214.157.71 + + + + Retrieve the MX record of the <literal>0pointer.net</literal> domain + + $ systemd-resolve -t MX 0pointer.net + + + + Resolve an SRV service + + $ systemd-resolve --service _xmpp-server._tcp gmail.com + + + + + + See Also + + systemd1, + systemd-resolved.service8 + + + diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 8e1ca1c092..ba36c5d9f0 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -56,15 +56,15 @@ Description - systemd-resolved is a system service that - manages network name resolution. It implements a caching DNS stub - resolver and an LLMNR resolver and responder. It also generates - /run/systemd/resolve/resolv.conf for - compatibility which may be symlinked from - /etc/resolv.conf. The glibc NSS module - nss-resolve8 - is necessary to allow libc's NSS resolver functions to resolve - host names via systemd-resolved. + systemd-resolved is a system service that provides network name resolution to local + applications. It implements a caching and validating DNS/DNSSEC stub resolver, as well as an LLMNR resolver and + responder. In addition it maintains the /run/systemd/resolve/resolv.conf file for + compatibility with traditional Linux programs. This file may be symlinked from + /etc/resolv.conf. + + The glibc NSS module + nss-resolve8 is required to + permit glibc's NSS resolver functions to resolve host names via systemd-resolved. The DNS servers contacted are determined from the global settings in /etc/systemd/resolved.conf, the @@ -104,7 +104,7 @@ Lookups for the special hostname localhost are never routed to the - network. + network. (A few other, special domains are handled the same way.) Single-label names are routed to all local interfaces capable of IP multicasting, using the LLMNR @@ -133,10 +133,8 @@ per-interface domains are exclusively routed to the matching interfaces. - Note that - /run/systemd/resolve/resolv.conf should not - be used directly, but only through a symlink from - /etc/resolv.conf. + Note that /run/systemd/resolve/resolv.conf should not be used directly by applications, + but only through a symlink from /etc/resolv.conf. @@ -146,6 +144,7 @@ resolved.conf5, dnssec-trust-anchors.d5, nss-resolve8, + systemd-resolve1, resolv.conf5, systemd.network5, systemd-networkd.service8 -- cgit v1.2.3-54-g00ecf From 300a716dc416c7b06e9743fcad37e44e73e30549 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 00:05:37 +0100 Subject: resolve: minor strings improvements --- src/resolve/resolve-tool.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index 54a060ea5a..60c0642b16 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -929,7 +929,7 @@ static void help_dns_classes(void) { static void help(void) { printf("%s [OPTIONS...] NAME...\n" "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n" - "Resolve domain names, IPv4 or IPv6 addresses, resource records, and services.\n\n" + "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" " -h --help Show this help\n" " --version Show package version\n" " -4 Resolve IPv4 addresses\n" @@ -943,7 +943,7 @@ static void help(void) { " --service-txt=BOOL Do [not] resolve TXT records for services\n" " --cname=BOOL Do [not] follow CNAME redirects\n" " --search=BOOL Do [not] use search domains\n" - " --legend=BOOL Do [not] print column headers\n" + " --legend=BOOL Do [not] print column headers and meta information\n" " --statistics Show resolver statistics\n" " --reset-statistics Reset resolver statistics\n" , program_invocation_short_name, program_invocation_short_name); @@ -1175,7 +1175,7 @@ int main(int argc, char **argv) { case MODE_RESOLVE_HOST: if (optind >= argc) { - log_error("No arguments passed"); + log_error("No arguments passed."); r = -EINVAL; goto finish; } @@ -1203,7 +1203,7 @@ int main(int argc, char **argv) { case MODE_RESOLVE_RECORD: if (optind >= argc) { - log_error("No arguments passed"); + log_error("No arguments passed."); r = -EINVAL; goto finish; } @@ -1232,7 +1232,7 @@ int main(int argc, char **argv) { else if (argc == optind + 3) r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); else { - log_error("Too many arguments"); + log_error("Too many arguments."); r = -EINVAL; goto finish; } -- cgit v1.2.3-54-g00ecf From 352af30838f130bf7aaa36dd6174945c11f39d29 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 00:58:49 +0100 Subject: resolve: use different bitmap checking rules when we find an exact NSEC3 match, or just a covering enclosure If we are looking for a DS RR we need to check the NSEC3 bitmap of the parent zone's NSEC3 RR, not the one from the child. For any other RR we need to look at the child's however, hence enforce this with the bitmaps. Note that not coverign checks only the lower zone's NSEC3 bitmaps matter, hence the existing check is fine. --- src/resolve/resolved-dns-dnssec.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 1f48f588ce..b71aee37d9 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1458,19 +1458,20 @@ found_zone: found_closest_encloser: /* We found a closest encloser in 'p'; next closer is 'pp' */ - /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) - return -EBADMSG; - - /* Ensure that this data is from the delegated domain - * (i.e. originates from the "lower" DNS server), and isn't - * just glue records (i.e. doesn't originate from the "upper" - * DNS server). */ - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && - !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) - return -EBADMSG; - if (!pp) { + /* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR + * from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are + * appropriately set. */ + + if (key->type == DNS_TYPE_DS) { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } else { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } + /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) *result = DNSSEC_NSEC_FOUND; @@ -1487,6 +1488,18 @@ found_closest_encloser: return 0; } + /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) + return -EBADMSG; + + /* Ensure that this data is from the delegated domain + * (i.e. originates from the "lower" DNS server), and isn't + * just glue records (i.e. doesn't originate from the "upper" + * DNS server). */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + /* Prove that there is no next closer and whether or not there is a wildcard domain. */ wildcard = strjoina("*.", p); -- cgit v1.2.3-54-g00ecf From 7aa8ce985537e7803e16d6f2adf5143df4537cf8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 01:06:28 +0100 Subject: resolved: also collect statistics about negative DNSSEC proofs We already maintain statistics about positive DNSSEC proofs, and count them up by 1 for each validated RRset. Now, update the same counters each time we validated a negative query, so that the statistics are the combined result of all validation checks, both positive and negative. --- src/resolve/resolved-dns-transaction.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index d485cd917d..6b465abe48 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -2900,6 +2900,12 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_dnssec_result = DNSSEC_VALIDATED; t->answer_rcode = DNS_RCODE_NXDOMAIN; t->answer_authenticated = authenticated; + + if (authenticated) + t->scope->manager->n_dnssec_secure++; + else + t->scope->manager->n_dnssec_insecure++; + break; case DNSSEC_NSEC_NODATA: @@ -2908,6 +2914,12 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_dnssec_result = DNSSEC_VALIDATED; t->answer_rcode = DNS_RCODE_SUCCESS; t->answer_authenticated = authenticated; + + if (authenticated) + t->scope->manager->n_dnssec_secure++; + else + t->scope->manager->n_dnssec_insecure++; + break; case DNSSEC_NSEC_OPTOUT: @@ -2915,6 +2927,8 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); t->answer_dnssec_result = DNSSEC_UNSIGNED; t->answer_authenticated = false; + + t->scope->manager->n_dnssec_insecure++; break; case DNSSEC_NSEC_NO_RR: @@ -2923,11 +2937,13 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { r = dns_transaction_requires_nsec(t); if (r < 0) return r; - if (r > 0) + if (r > 0) { t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; - else { + t->scope->manager->n_dnssec_indeterminate++; + } else { t->answer_dnssec_result = DNSSEC_UNSIGNED; t->answer_authenticated = false; + t->scope->manager->n_dnssec_insecure++; } break; @@ -2935,12 +2951,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: /* We don't know the NSEC3 algorithm used? */ t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; + t->scope->manager->n_dnssec_indeterminate++; break; case DNSSEC_NSEC_FOUND: case DNSSEC_NSEC_CNAME: /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; + t->scope->manager->n_dnssec_bogus++; break; default: -- cgit v1.2.3-54-g00ecf From fcfaff123506b8c2300038934eef46892576d2d2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 01:07:56 +0100 Subject: resolved: if we detect a message with incomplete DNSSEC data, consider this an invalid packet event --- src/resolve/resolved-dns-transaction.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 6b465abe48..018cfc7a63 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -675,6 +675,10 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { /* All our auxiliary DNSSEC transactions are complete now. Try * to validate our RRset now. */ r = dns_transaction_validate_dnssec(t); + if (r == -EBADMSG) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } if (r < 0) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return; -- cgit v1.2.3-54-g00ecf From f6618dcd96d437f2b8abeefd32a6a6790c04d026 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 01:24:30 +0100 Subject: resolved: never store NSEC/NSEC3 RRs from the upper zone of a zone cut in cache When using NSEC/NSEC3 RRs from the cache to derive existance of arbitrary RRs, we should not get confused by the fact that NSEC/NSEC3 RRs exist twice at zone cuts: once in the parent zone, and once in the child zone. For most RR types we should only consult the latter since that's where the beef is. However, for DS lookups we have to check the former. This change makes sure we never cache NSEC/NSEC3 RRs from any parent zone of a zone-cut. It also makes sure that when we look for a DS RR in the cache we never consider any cached NSEC RR, as those are now always from the child zone. --- src/resolve/resolved-dns-cache.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index fdb34d11df..9267b67f79 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -579,6 +579,28 @@ static void dns_cache_remove_previous( } } +static bool rr_eligible(DnsResourceRecord *rr) { + assert(rr); + + /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since + * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS + * existence from any cached NSEC/NSEC3, but that should be fine. */ + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec.types, DNS_TYPE_SOA); + + case DNS_TYPE_NSEC3: + return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA); + + default: + return true; + } +} + int dns_cache_put( DnsCache *c, DnsResourceKey *key, @@ -635,6 +657,12 @@ int dns_cache_put( if ((flags & DNS_ANSWER_CACHEABLE) == 0) continue; + r = rr_eligible(rr); + if (r < 0) + return r; + if (r == 0) + continue; + r = dns_cache_put_positive( c, rr, @@ -835,7 +863,10 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r have_non_authenticated = true; } - if (nsec && key->type != DNS_TYPE_NSEC) { + if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { + /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from + * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ + if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(key, &key_str); if (r < 0) -- cgit v1.2.3-54-g00ecf From 59c5b5974d106c5ebad080739b41d0e92ab74d29 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 02:21:58 +0100 Subject: resolved: log each time we increase the DNSSEC verdict counters Also, don't consider RRs that aren't primary to the lookups we do as relevant to the lookups. --- src/resolve/resolved-bus.c | 10 +++---- src/resolve/resolved-dns-dnssec.c | 8 +++++ src/resolve/resolved-dns-dnssec.h | 15 +++++++++- src/resolve/resolved-dns-transaction.c | 53 ++++++++++++++-------------------- src/resolve/resolved-manager.c | 16 ++++++++++ src/resolve/resolved-manager.h | 4 ++- 6 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index d6e9452766..bc2d6dd2fc 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -1292,10 +1292,10 @@ static int bus_property_get_dnssec_statistics( assert(m); return sd_bus_message_append(reply, "(tttt)", - (uint64_t) m->n_dnssec_secure, - (uint64_t) m->n_dnssec_insecure, - (uint64_t) m->n_dnssec_bogus, - (uint64_t) m->n_dnssec_indeterminate); + (uint64_t) m->n_dnssec_verdict[DNSSEC_SECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INSECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_BOGUS], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INDETERMINATE]); } static int bus_property_get_dnssec_supported( @@ -1326,7 +1326,7 @@ static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, s->cache.n_hit = s->cache.n_miss = 0; m->n_transactions_total = 0; - m->n_dnssec_secure = m->n_dnssec_insecure = m->n_dnssec_bogus = m->n_dnssec_indeterminate = 0; + zero(m->n_dnssec_verdict); return sd_bus_reply_method_return(message, NULL); } diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index b71aee37d9..e3b43e7e48 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -2142,3 +2142,11 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", }; DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); + +static const char* const dnssec_verdict_table[_DNSSEC_VERDICT_MAX] = { + [DNSSEC_SECURE] = "secure", + [DNSSEC_INSECURE] = "insecure", + [DNSSEC_BOGUS] = "bogus", + [DNSSEC_INDETERMINATE] = "indeterminate", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_verdict, DnssecVerdict); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index 955017e8cb..c99861b8e5 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -21,8 +21,8 @@ along with systemd; If not, see . ***/ -typedef enum DnssecMode DnssecMode; typedef enum DnssecResult DnssecResult; +typedef enum DnssecVerdict DnssecVerdict; #include "dns-domain.h" #include "resolved-dns-answer.h" @@ -50,6 +50,16 @@ enum DnssecResult { _DNSSEC_RESULT_INVALID = -1 }; +enum DnssecVerdict { + DNSSEC_SECURE, + DNSSEC_INSECURE, + DNSSEC_BOGUS, + DNSSEC_INDETERMINATE, + + _DNSSEC_VERDICT_MAX, + _DNSSEC_VERDICT_INVALID = -1 +}; + #define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) /* The longest digest we'll ever generate, of all digest algorithms we support */ @@ -90,3 +100,6 @@ int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *so const char* dnssec_result_to_string(DnssecResult m) _const_; DnssecResult dnssec_result_from_string(const char *s) _pure_; + +const char* dnssec_verdict_to_string(DnssecVerdict m) _const_; +DnssecVerdict dnssec_verdict_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 018cfc7a63..9ff8145ac1 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -2660,7 +2660,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_secure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; @@ -2700,10 +2700,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - if (authenticated) - t->scope->manager->n_dnssec_secure++; - else - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key); /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; @@ -2722,7 +2719,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); changed = true; break; } @@ -2744,7 +2741,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); changed = true; break; } @@ -2770,7 +2767,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); changed = true; break; } @@ -2792,20 +2789,12 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); changed = true; break; } } - if (IN_SET(result, - DNSSEC_INVALID, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_NO_SIGNATURE)) - t->scope->manager->n_dnssec_bogus++; - else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ - t->scope->manager->n_dnssec_indeterminate++; - r = dns_transaction_is_primary_response(t, rr); if (r < 0) return r; @@ -2823,6 +2812,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { } if (r == 0) { + if (IN_SET(result, + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_NO_SIGNATURE)) + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key); + else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key); + /* This is a primary response to our question, and it failed validation. That's * fatal. */ t->answer_dnssec_result = result; @@ -2905,11 +2902,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_rcode = DNS_RCODE_NXDOMAIN; t->answer_authenticated = authenticated; - if (authenticated) - t->scope->manager->n_dnssec_secure++; - else - t->scope->manager->n_dnssec_insecure++; - + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); break; case DNSSEC_NSEC_NODATA: @@ -2919,11 +2912,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_rcode = DNS_RCODE_SUCCESS; t->answer_authenticated = authenticated; - if (authenticated) - t->scope->manager->n_dnssec_secure++; - else - t->scope->manager->n_dnssec_insecure++; - + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); break; case DNSSEC_NSEC_OPTOUT: @@ -2932,7 +2921,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_dnssec_result = DNSSEC_UNSIGNED; t->answer_authenticated = false; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); break; case DNSSEC_NSEC_NO_RR: @@ -2943,11 +2932,11 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { return r; if (r > 0) { t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; - t->scope->manager->n_dnssec_indeterminate++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); } else { t->answer_dnssec_result = DNSSEC_UNSIGNED; t->answer_authenticated = false; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); } break; @@ -2955,14 +2944,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: /* We don't know the NSEC3 algorithm used? */ t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; - t->scope->manager->n_dnssec_indeterminate++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); break; case DNSSEC_NSEC_FOUND: case DNSSEC_NSEC_CNAME: /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; - t->scope->manager->n_dnssec_bogus++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); break; default: diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index d6d75a3f78..4ee19ca382 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1203,3 +1203,19 @@ bool manager_dnssec_supported(Manager *m) { return true; } + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { + + assert(verdict >= 0); + assert(verdict < _DNSSEC_VERDICT_MAX); + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *s = NULL; + + (void) dns_resource_key_to_string(key, &s); + + log_debug("Found verdict for lookup %s: %s", s ? strstrip(s) : "n/a", dnssec_verdict_to_string(verdict)); + } + + m->n_dnssec_verdict[verdict]++; +} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 8b13074298..5a8c2c8750 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -123,7 +123,7 @@ struct Manager { sd_event_source *sigusr1_event_source; unsigned n_transactions_total; - unsigned n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; + unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; }; /* Manager */ @@ -161,3 +161,5 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains); DnssecMode manager_get_dnssec_mode(Manager *m); bool manager_dnssec_supported(Manager *m); + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); -- cgit v1.2.3-54-g00ecf From 7405bb3ec09a07bd4b72c04e6ad015d3bfc97629 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jan 2016 02:27:44 +0100 Subject: resolve: fix wording in resolver statistics output Now that we count both negative and positive validation results, we shouldn't claim we just counted RRsets. --- src/resolve/resolve-tool.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index 60c0642b16..fdaeb8d926 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -867,11 +867,11 @@ static int show_statistics(sd_bus *bus) { if (r < 0) return bus_log_parse_error(r); - printf("\n%sDNSSEC%s\n" - " Secure RRsets: %" PRIu64 "\n" - " Insecure RRsets: %" PRIu64 "\n" - " Bogus RRsets: %" PRIu64 "\n" - "Indeterminate RRsets: %" PRIu64 "\n", + printf("\n%sDNSSEC Verdicts%s\n" + " Secure: %" PRIu64 "\n" + " Insecure: %" PRIu64 "\n" + " Bogus: %" PRIu64 "\n" + " Indeterminate: %" PRIu64 "\n", ansi_highlight(), ansi_normal(), n_dnssec_secure, -- cgit v1.2.3-54-g00ecf From 1c4e4227faa77026eeb633e2e575bedf1897daa3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 11:57:59 +0100 Subject: CODING_STYLE: make sure line break recommendation matches edit configuration In the .vimrc and .dir-locals.el we suggest a line width of 119. We should recommend the same in CODING_STYLE. --- CODING_STYLE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODING_STYLE b/CODING_STYLE index 0064303203..46e366898e 100644 --- a/CODING_STYLE +++ b/CODING_STYLE @@ -7,7 +7,7 @@ - Don't break code lines too eagerly. We do *not* force line breaks at 80ch, all of today's screens should be much larger than that. But - then again, don't overdo it, ~140ch should be enough really. + then again, don't overdo it, ~119ch should be enough really. - Variables and functions *must* be static, unless they have a prototype, and are supposed to be exported. -- cgit v1.2.3-54-g00ecf From 0aa5f6983fffb75c51f1581e139b846234f836f7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:01:04 +0100 Subject: README: drop link to systemd-commits ML Since we moved to github the commits ML is basically dead, hence don't advertise it anymore. --- README | 1 - 1 file changed, 1 deletion(-) diff --git a/README b/README index 2fd7f7d5ce..c46ac7e5de 100644 --- a/README +++ b/README @@ -15,7 +15,6 @@ GITWEB: MAILING LIST: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - http://lists.freedesktop.org/mailman/listinfo/systemd-commits IRC: #systemd on irc.freenode.org -- cgit v1.2.3-54-g00ecf From cc5fddef6b3e61fb0cecad4e6c14dea70183026a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:02:03 +0100 Subject: github: add a CONTRIBUTING.md file that github shows when a PR or issue is filed github links up CONTRIBUTING.md if it exists from the issue/PR filing form. Let's add one, in the hope that people have a look before filing a PR. --- CONTRIBUTING.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..ba4080cb6e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing + +We welcome contributions from everyone. However, please follow the following guidelines when posting a GitHub Pull +Request or filing a GitHub Issue on the systemd project: + +## Filing Issues + +* We use GitHub Issues **exclusively** for tracking **bugs** and **feature** **requests** of systemd. If you are + looking for help, please contact our [mailing list](http://lists.freedesktop.org/mailman/listinfo/systemd-devel) + instead. +* We only track bugs in the **two** **most** **recently** **released** **versions** of systemd in the GitHub Issue + tracker. If you are using an older version of systemd, please contact your distribution's bug tracker instead. +* When filing an issue, specify the **systemd** **version** you are experiencing the issue with. Also, indicate which + **distribution** you are using. +* Please include an explanation how to reproduce the issue you are pointing out. + +Following these guidelines makes it easier for us to process your issue, and ensures we won't close your issue +right-away for being misfiled. + +## Posting Pull Requests + +* Make sure to post PRs only relative to a very recent git master. +* Follow our [Coding Style](https://raw.githubusercontent.com/systemd/systemd/master/CODING_STYLE) when contributing + code. This is a requirement for all code we merge. +* Make sure to run "make check" locally, before posting your PR. We use a CI system, meaning we don't even look at your + PR, if the build and tests don't pass. + +* If you need to update the code in a existing PR, please consider opening a new PR (mentioning in it which old PR it + replaces) and closing the old PR. This is much preferable over force-pushing a new patch set into the PR's branch, as + commit comments aren't lost that way. That said, we don't follow this rule ourselves quite often, hence this is + really just a say as we say, not say as we do... + +## Final Words + +We'd like to apologize in advance if we are not able to process and reply to your issue or PR right-away. We have a lot +of work to do, but we are trying our best! + +Thank you very much for your contributions! -- cgit v1.2.3-54-g00ecf From 3731ad2703192dc904922443876d9b2ccff10f5e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:03:35 +0100 Subject: github: extend README.md a bit Link up more stuff on the GitHub landing page. --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5abe2073c2..dcd14ba94f 100644 --- a/README.md +++ b/README.md @@ -5,5 +5,14 @@ ## Details - * General information about systemd can be found in the [systemd Wiki](http://www.freedesktop.org/wiki/Software/systemd) - * Information about build requirements are provided in the [README file](../master/README) +General information about systemd can be found in the [systemd Wiki](http://www.freedesktop.org/wiki/Software/systemd). + +Information about build requirements are provided in the [README file](../master/README). + +Consult our [NEWS file](../master/NEWS) for information about what's new in the most recent systemd versions. + +Please see our [Contribution Guidelines](../master/CONTRIBUTING.md) for more information about filing GitHub Issues and posting GitHub Pull Requests. + +When preparing patches for systemd, please follow our [Coding Style Guidelines](../master/CODING_STYLE). + +If you are looking for support, please contact our [mailing list](http://lists.freedesktop.org/mailman/listinfo/systemd-devel) or join our [IRC channel](irc://irc.freenode.org/%23systemd). -- cgit v1.2.3-54-g00ecf From 3347dd5ea1dba5e27a677954b505bb6a2ee6cd75 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:04:36 +0100 Subject: resolved: fix rcode formatting string length Since we honour the edns rcode extension we need more than 4 bits to format it. To avoid further confusion, derive the right length from the type. --- src/resolve/resolved-bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index bc2d6dd2fc..2cb622885f 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -69,7 +69,7 @@ static int reply_query_state(DnsQuery *q) { sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); else { const char *rc, *n; - char p[3]; /* the rcode is 4 bits long */ + char p[DECIMAL_STR_MAX(q->answer_rcode)]; rc = dns_rcode_to_string(q->answer_rcode); if (!rc) { -- cgit v1.2.3-54-g00ecf From 7bb70b6e3d6600d4c448c016b71073706460a12e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:06:39 +0100 Subject: core: normalize error handling a bit, in setup_pam() Assign errno-style errors to a variable called "r" when they happen, the same way we do this in most other calls. It's bad enough that the error handling part of the function deals with two different error variables (pam_code and r) now, but before this fix it was even three! --- src/core/execute.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/core/execute.c b/src/core/execute.c index d70ba2be17..b9de2617a9 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -814,8 +814,7 @@ static int setup_pam( _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; pam_handle_t *handle = NULL; sigset_t old_ss; - int pam_code = PAM_SUCCESS; - int err = 0; + int pam_code = PAM_SUCCESS, r; char **e = NULL; bool close_session = false; pid_t pam_pid = 0, parent_pid; @@ -832,8 +831,8 @@ static int setup_pam( * daemon. We do things this way to ensure that the main PID * of the daemon is the one we initially fork()ed. */ - err = barrier_create(&barrier); - if (err < 0) + r = barrier_create(&barrier); + if (r < 0) goto fail; if (log_get_max_level() < LOG_DEBUG) @@ -875,12 +874,13 @@ static int setup_pam( parent_pid = getpid(); pam_pid = fork(); - if (pam_pid < 0) + if (pam_pid < 0) { + r = -errno; goto fail; + } if (pam_pid == 0) { - int sig; - int r = EXIT_PAM; + int sig, ret = EXIT_PAM; /* The child's job is to reset the PAM session on * termination */ @@ -945,11 +945,11 @@ static int setup_pam( goto child_finish; } - r = 0; + ret = 0; child_finish: pam_end(handle, pam_code | flags); - _exit(r); + _exit(ret); } barrier_set_role(&barrier, BARRIER_PARENT); @@ -978,10 +978,9 @@ static int setup_pam( fail: if (pam_code != PAM_SUCCESS) { log_error("PAM failed: %s", pam_strerror(handle, pam_code)); - err = -EPERM; /* PAM errors do not map to errno */ - } else { - err = log_error_errno(err < 0 ? err : errno, "PAM failed: %m"); - } + r = -EPERM; /* PAM errors do not map to errno */ + } else + log_error_errno(r, "PAM failed: %m"); if (handle) { if (close_session) @@ -993,7 +992,7 @@ fail: strv_free(e); closelog(); - return err; + return r; } #endif -- cgit v1.2.3-54-g00ecf From 0791110fbee9d7dfcabd6e338c290e90aeb79644 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:09:38 +0100 Subject: resolved: properly handle LLMNR/TCP connection errors The LLMNR spec suggests to do do reverse address lookups by doing direct LLMNR/TCP connections to the indicated address, instead of doing any LLMNR multicast queries. When we do this and the peer doesn't actually implement LLMNR this will result in a TCP connection error, which we need to handle. In contrast to most LLMNR lookups this will give us a quick response on whether we can find a suitable name. Report this as new transaction state, since this should mostly be treated like an NXDOMAIN rcode, except that it's not one. --- src/basic/fd-util.h | 3 ++- src/resolve/resolved-bus.c | 5 +++++ src/resolve/resolved-dns-query.c | 3 ++- src/resolve/resolved-dns-transaction.c | 14 ++++++++++++++ src/resolve/resolved-dns-transaction.h | 1 + 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 973413ff42..20890e3279 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -74,5 +74,6 @@ void cmsg_close_all(struct msghdr *mh); bool fdname_is_valid(const char *s); +/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ #define ERRNO_IS_DISCONNECT(r) \ - IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE) + IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 2cb622885f..3a21773dae 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -62,6 +62,11 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_NETWORK_DOWN: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); + case DNS_TRANSACTION_NOT_FOUND: + /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we + * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ + return sd_bus_reply_method_errorf(q->request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + case DNS_TRANSACTION_RCODE_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 2f9dd1c47b..734b6ff770 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -563,7 +563,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { DNS_TRANSACTION_NO_SERVERS, DNS_TRANSACTION_TIMEOUT, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, - DNS_TRANSACTION_NETWORK_DOWN)) + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND)) return 0; r = dns_synthesize_answer( diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 9ff8145ac1..43ee783ba9 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -432,6 +432,13 @@ static int on_stream_complete(DnsStream *s, int error) { if (ERRNO_IS_DISCONNECT(error)) { usec_t usec; + if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + /* If the LLMNR/TCP connection failed, the host doesn't support LLMNR, and we cannot answer the + * question on this scope. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + log_debug_errno(error, "Connection failure for DNS TCP stream: %m"); assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level, usec - t->start_usec); @@ -1463,6 +1470,12 @@ int dns_transaction_go(DnsTransaction *t) { dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); return 0; } + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { + /* On LLMNR, if we cannot connect to a host via TCP when doing revers lookups. This means we cannot + * answer this request with this protocol. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } if (r < 0) { if (t->scope->protocol != DNS_PROTOCOL_DNS) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); @@ -2989,6 +3002,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", + [DNS_TRANSACTION_NOT_FOUND] = "not-found", }; DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 80a2591fbc..ab5717c4d1 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -41,6 +41,7 @@ enum DnsTransactionState { DNS_TRANSACTION_NO_TRUST_ANCHOR, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -1 }; -- cgit v1.2.3-54-g00ecf From 528e685ebd3657f2531f8666c9e4bba6fbd38acd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:22:05 +0100 Subject: resolved: when synthesizing RR responses, own the name fully When we synthesize A/AAAA for domains like "localhost", then make sure we generate ENODATA if the user asks for RR types such a RP to be solved on the name. Previously, we'd pass the error back in that case that was generated from the usual lookup procedure. --- src/resolve/resolved-dns-synthesize.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index e12bb6e828..d93741b736 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -348,6 +348,7 @@ int dns_synthesize_answer( _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; DnsResourceKey *key; + bool found = false; int r; assert(m); @@ -398,10 +399,13 @@ int dns_synthesize_answer( r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); if (r < 0) return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); - } + } else + continue; + + found = true; } - r = dns_answer_size(answer) > 0; + r = found; if (ret) { *ret = answer; -- cgit v1.2.3-54-g00ecf From dd0bc0f1414cc1d0fa73a29470bd14944e4942d3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 12:24:20 +0100 Subject: resolved: synthesize RRs for data from /etc/hosts This way the difference between lookups via NSS and our native bus API should become minimal. --- Makefile.am | 2 + src/resolve/resolved-dns-query.c | 53 +++- src/resolve/resolved-dns-synthesize.c | 35 +-- src/resolve/resolved-dns-synthesize.h | 6 +- src/resolve/resolved-etc-hosts.c | 448 ++++++++++++++++++++++++++++++++++ src/resolve/resolved-etc-hosts.h | 28 +++ src/resolve/resolved-manager.c | 5 +- src/resolve/resolved-manager.h | 5 + 8 files changed, 549 insertions(+), 33 deletions(-) create mode 100644 src/resolve/resolved-etc-hosts.c create mode 100644 src/resolve/resolved-etc-hosts.h diff --git a/Makefile.am b/Makefile.am index 5036a2bfc9..4218a6dcdf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5208,6 +5208,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-dns-dnssec.c \ src/resolve/resolved-dns-trust-anchor.h \ src/resolve/resolved-dns-trust-anchor.c \ + src/resolve/resolved-etc-hosts.h \ + src/resolve/resolved-etc-hosts.c \ src/resolve/dns-type.c \ src/resolve/dns-type.h diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 734b6ff770..1fe37a6813 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -25,6 +25,7 @@ #include "local-addresses.h" #include "resolved-dns-query.h" #include "resolved-dns-synthesize.h" +#include "resolved-etc-hosts.h" #include "string-util.h" /* How long to wait for the query in total */ @@ -550,13 +551,14 @@ fail: static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsProtocol protocol; - int family, r; + int r; assert(q); assert(state); - /* Tries to synthesize localhost RR replies (and others) where appropriate */ + /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the + * the normal lookup finished. The data from the network hence takes precedence over the data we + * synthesize. (But note that many scopes refuse to resolve certain domain names) */ if (!IN_SET(*state, DNS_TRANSACTION_RCODE_FAILURE, @@ -571,10 +573,7 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { q->manager, q->question_utf8, q->ifindex, - q->flags, - &answer, - &protocol, - &family); + &answer); if (r <= 0) return r; @@ -584,8 +583,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { q->answer = answer; answer = NULL; q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = protocol; - q->answer_family = family; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); q->answer_authenticated = true; *state = DNS_TRANSACTION_SUCCESS; @@ -593,6 +592,34 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { return 1; } +static int dns_query_try_etc_hosts(DnsQuery *q) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(q); + + /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The + * data from /etc/hosts hence takes precedence over the network. */ + + r = manager_etc_hosts_lookup( + q->manager, + q->question_utf8, + &answer); + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + q->answer = answer; + answer = NULL; + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; + + return 1; +} + int dns_query_go(DnsQuery *q) { DnsScopeMatch found = DNS_SCOPE_NO; DnsScope *s, *first = NULL; @@ -604,6 +631,14 @@ int dns_query_go(DnsQuery *q) { if (q->state != DNS_TRANSACTION_NULL) return 0; + r = dns_query_try_etc_hosts(q); + if (r < 0) + return r; + if (r > 0) { + dns_query_complete(q, DNS_TRANSACTION_SUCCESS); + return 1; + } + LIST_FOREACH(scopes, s, q->manager->dns_scopes) { DnsScopeMatch match; const char *name; diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index d93741b736..f4a43dee8c 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -22,7 +22,7 @@ #include "local-addresses.h" #include "resolved-dns-synthesize.h" -static int SYNTHESIZE_IFINDEX(int ifindex) { +int dns_synthesize_ifindex(int ifindex) { /* When the caller asked for resolving on a specific * interface, we synthesize the answer for that @@ -36,7 +36,7 @@ static int SYNTHESIZE_IFINDEX(int ifindex) { return LOOPBACK_IFINDEX; } -static int SYNTHESIZE_FAMILY(uint64_t flags) { +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 @@ -54,9 +54,9 @@ static int SYNTHESIZE_FAMILY(uint64_t flags) { return AF_UNSPEC; } -static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) { +DnsProtocol dns_synthesize_protocol(uint64_t flags) { - /* Similar as SYNTHESIZE_FAMILY() but does this for the + /* 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 @@ -92,7 +92,7 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int if rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -106,7 +106,7 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int if rr->aaaa.in6_addr = in6addr_loopback; - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -140,7 +140,7 @@ static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int i if (r < 0) return r; - r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -243,14 +243,14 @@ static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, if (af == AF_INET || af == AF_UNSPEC) buffer[n++] = (struct local_address) { .family = AF_INET, - .ifindex = SYNTHESIZE_IFINDEX(ifindex), + .ifindex = dns_synthesize_ifindex(ifindex), .address.in.s_addr = htobe32(0x7F000002), }; if (af == AF_INET6 || af == AF_UNSPEC) buffer[n++] = (struct local_address) { .family = AF_INET6, - .ifindex = SYNTHESIZE_IFINDEX(ifindex), + .ifindex = dns_synthesize_ifindex(ifindex), .address.in6 = in6addr_loopback, }; @@ -278,15 +278,15 @@ static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_add if (r < 0) return r; - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(ifindex), DNS_ANSWER_AUTHENTICATED); + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; @@ -341,10 +341,7 @@ int dns_synthesize_answer( Manager *m, DnsQuestion *q, int ifindex, - uint64_t flags, - DnsAnswer **ret, - DnsProtocol *ret_protocol, - int *ret_family) { + DnsAnswer **ret) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; DnsResourceKey *key; @@ -412,11 +409,5 @@ int dns_synthesize_answer( answer = NULL; } - if (ret_protocol) - *ret_protocol = SYNTHESIZE_PROTOCOL(flags); - - if (ret_family) - *ret_family = SYNTHESIZE_FAMILY(flags); - return r; } diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h index e43d96cb2c..5d829bb2e7 100644 --- a/src/resolve/resolved-dns-synthesize.h +++ b/src/resolve/resolved-dns-synthesize.h @@ -23,4 +23,8 @@ #include "resolved-dns-question.h" #include "resolved-manager.h" -int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, uint64_t flags, DnsAnswer **ret, DnsProtocol *ret_protocol, int *ret_family); +int dns_synthesize_ifindex(int ifindex); +int dns_synthesize_family(uint64_t flags); +DnsProtocol dns_synthesize_protocol(uint64_t flags); + +int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c new file mode 100644 index 0000000000..467754774f --- /dev/null +++ b/src/resolve/resolved-etc-hosts.c @@ -0,0 +1,448 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "resolved-etc-hosts.h" +#include "resolved-dns-synthesize.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +/* Recheck /etc/hosts at most once every 2s */ +#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC) + +typedef struct EtcHostsItem { + int family; + union in_addr_union address; + + char **names; +} EtcHostsItem; + +typedef struct EtcHostsItemByName { + char *name; + + EtcHostsItem **items; + size_t n_items, n_allocated; +} EtcHostsItemByName; + +void manager_etc_hosts_flush(Manager *m) { + EtcHostsItem *item; + EtcHostsItemByName *bn; + + while ((item = set_steal_first(m->etc_hosts_by_address))) { + strv_free(item->names); + free(item); + } + + while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) { + free(bn->name); + free(bn->items); + free(bn); + } + + m->etc_hosts_by_address = set_free(m->etc_hosts_by_address); + m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name); + + m->etc_hosts_mtime = USEC_INFINITY; +} + +static void etc_hosts_item_hash_func(const void *p, struct siphash *state) { + const EtcHostsItem *item = p; + + siphash24_compress(&item->family, sizeof(item->family), state); + + if (item->family == AF_INET) + siphash24_compress(&item->address.in, sizeof(item->address.in), state); + else if (item->family == AF_INET6) + siphash24_compress(&item->address.in6, sizeof(item->address.in6), state); +} + +static int etc_hosts_item_compare_func(const void *a, const void *b) { + const EtcHostsItem *x = a, *y = b; + + if (x->family != x->family) + return x->family - y->family; + + if (x->family == AF_INET) + return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr)); + + if (x->family == AF_INET6) + return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr)); + + return trivial_compare_func(a, b); +} + +static const struct hash_ops etc_hosts_item_ops = { + .hash = etc_hosts_item_hash_func, + .compare = etc_hosts_item_compare_func, +}; + +static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) { + + EtcHostsItem key = { + .family = family, + .address = *address, + }; + EtcHostsItem *item; + char **n; + int r; + + assert(m); + assert(address); + + r = in_addr_is_null(family, address); + if (r < 0) + return r; + if (r > 0) + /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to + * nothing. */ + item = NULL; + else { + /* If this is a normal address, then, simply add entry mapping it to the specified names */ + + item = set_get(m->etc_hosts_by_address, &key); + if (item) { + r = strv_extend_strv(&item->names, names, true); + if (r < 0) + return log_oom(); + } else { + + r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops); + if (r < 0) + return log_oom(); + + item = new0(EtcHostsItem, 1); + if (!item) + return log_oom(); + + item->family = family; + item->address = *address; + item->names = names; + + r = set_put(m->etc_hosts_by_address, item); + if (r < 0) { + free(item); + return log_oom(); + } + } + } + + STRV_FOREACH(n, names) { + EtcHostsItemByName *bn; + + bn = hashmap_get(m->etc_hosts_by_name, *n); + if (!bn) { + r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + bn = new0(EtcHostsItemByName, 1); + if (!bn) + return log_oom(); + + bn->name = strdup(*n); + if (!bn->name) { + free(bn); + return log_oom(); + } + + r = hashmap_put(m->etc_hosts_by_name, bn->name, bn); + if (r < 0) { + free(bn->name); + free(bn); + return log_oom(); + } + } + + if (item) { + if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1)) + return log_oom(); + + bn->items[bn->n_items++] = item; + } + } + + return 0; +} + +static int parse_line(Manager *m, unsigned nr, const char *line) { + _cleanup_free_ char *address = NULL; + _cleanup_strv_free_ char **names = NULL; + union in_addr_union in; + bool suppressed = false; + int family, r; + + assert(m); + assert(line); + + r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr); + if (r == 0) { + log_error("Premature end of line, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + r = in_addr_from_string_auto(address, &family, &in); + if (r < 0) + return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr); + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r <= 0) + return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr); + + if (is_localhost(name)) { + /* Suppress the "localhost" line that is often seen */ + suppressed = true; + continue; + } + + r = strv_push(&names, name); + if (r < 0) + return log_oom(); + + name = NULL; + } + + if (strv_isempty(names)) { + + if (suppressed) + return 0; + + log_error("Line is missing any host names, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + /* Takes possession of the names strv */ + r = add_item(m, family, &in, names); + if (r < 0) + return r; + + names = NULL; + return r; +} + +int manager_etc_hosts_read(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + struct stat st; + usec_t ts; + unsigned nr = 0; + int r; + + assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* See if we checked /etc/hosts recently already */ + if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts) + return 0; + + m->etc_hosts_last = ts; + + if (m->etc_hosts_mtime != USEC_INFINITY) { + if (stat("/etc/hosts", &st) < 0) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); + } + + /* Did the mtime change? If not, there's no point in re-reading the file. */ + if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime) + return 0; + } + + f = fopen("/etc/hosts", "re"); + if (!f) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to open /etc/hosts: %m"); + } + + /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next + * invocation */ + r = fstat(fileno(f), &st); + if (r < 0) + return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m"); + + manager_etc_hosts_flush(m); + + FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) { + char *l; + + nr ++; + + l = strstrip(line); + if (isempty(l)) + continue; + if (l[0] == '#') + continue; + + r = parse_line(m, nr, l); + if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */ + goto clear; + } + + m->etc_hosts_mtime = timespec_load(&st.st_mtim); + m->etc_hosts_last = ts; + + return 1; + +clear: + manager_etc_hosts_flush(m); + return r; +} + +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { + bool found_a = false, found_aaaa = false; + EtcHostsItemByName *bn; + EtcHostsItem k = {}; + DnsResourceKey *t; + const char *name; + unsigned i; + int r; + + assert(m); + assert(q); + assert(answer); + + r = manager_etc_hosts_read(m); + if (r < 0) + return r; + + name = dns_question_first_name(q); + if (!name) + return 0; + + r = dns_name_address(name, &k.family, &k.address); + if (r > 0) { + EtcHostsItem *item; + DnsResourceKey *found_ptr = NULL; + + item = set_get(m->etc_hosts_by_address, &k); + if (!item) + return 0; + + /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data + * we'll only return if the request was for PTR. */ + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r > 0) { + found_ptr = t; + break; + } + } + + if (found_ptr) { + char **n; + + r = dns_answer_reserve(answer, strv_length(item->names)); + if (r < 0) + return r; + + STRV_FOREACH(n, item->names) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + 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); + if (r < 0) + return r; + } + } + + return 1; + } + + bn = hashmap_get(m->etc_hosts_by_name, name); + if (!bn) + return 0; + + r = dns_answer_reserve(answer, bn->n_items); + if (r < 0) + return r; + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r == 0) + continue; + + if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) + found_a = true; + if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + found_aaaa = true; + + if (found_a && found_aaaa) + break; + } + + for (i = 0; i < bn->n_items; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if ((found_a && bn->items[i]->family != AF_INET) && + (found_aaaa && bn->items[i]->family != AF_INET6)) + continue; + + r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 1; +} diff --git a/src/resolve/resolved-etc-hosts.h b/src/resolve/resolved-etc-hosts.h new file mode 100644 index 0000000000..9d5a175f18 --- /dev/null +++ b/src/resolve/resolved-etc-hosts.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "resolved-manager.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" + +void manager_etc_hosts_flush(Manager *m); +int manager_etc_hosts_read(Manager *m); +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 4ee19ca382..704f012c37 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -37,10 +37,11 @@ #include "random-util.h" #include "resolved-bus.h" #include "resolved-conf.h" +#include "resolved-etc-hosts.h" #include "resolved-llmnr.h" #include "resolved-manager.h" -#include "resolved-resolv-conf.h" #include "resolved-mdns.h" +#include "resolved-resolv-conf.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -485,6 +486,7 @@ int manager_new(Manager **ret) { m->dnssec_mode = DNSSEC_NO; m->read_resolv_conf = true; m->need_builtin_fallbacks = true; + m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; r = dns_trust_anchor_load(&m->trust_anchor); if (r < 0) @@ -594,6 +596,7 @@ Manager *manager_free(Manager *m) { free(m->mdns_hostname); dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); free(m); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 5a8c2c8750..a4291d57ff 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -124,6 +124,11 @@ struct Manager { unsigned n_transactions_total; unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; + + /* Data from /etc/hosts */ + Set* etc_hosts_by_address; + Hashmap* etc_hosts_by_name; + usec_t etc_hosts_last, etc_hosts_mtime; }; /* Manager */ -- cgit v1.2.3-54-g00ecf From 1e02e182f1e06fcbe389474175de228103be39cb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 13:39:31 +0100 Subject: resolved: log recognizably about DNSSEC downgrades If we downgrade from DNSSEC to non-DNSSEC mode, let's log about this in a recognizable way (i.e. with a message ID), after all, this is of major importance. --- src/resolve/resolved-dns-server.c | 18 ++++++++++++++++++ src/resolve/resolved-dns-server.h | 5 +++++ src/resolve/resolved-dns-transaction.c | 7 ++++++- src/systemd/sd-messages.h | 1 + 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 5a86661807..e1d2025863 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -19,6 +19,8 @@ along with systemd; If not, see . ***/ +#include + #include "alloc-util.h" #include "resolved-dns-server.h" #include "resolved-resolv-conf.h" @@ -547,6 +549,22 @@ bool dns_server_dnssec_supported(DnsServer *server) { return true; } +void dns_server_warn_downgrade(DnsServer *server) { + assert(server); + + if (server->warned_downgrade) + return; + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), + LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), + "DNS_SERVER=%s", dns_server_string(server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), + NULL); + + server->warned_downgrade = true; +} + static void dns_server_hash_func(const void *p, struct siphash *state) { const DnsServer *s = p; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 02bd3463a7..2a3c921678 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -82,6 +82,9 @@ struct DnsServer { usec_t verified_usec; usec_t features_grace_period_usec; + /* Whether we already warned about downgrading to non-DNSSEC mode for this server */ + bool warned_downgrade:1; + /* Used when GC'ing old DNS servers when configuration changes. */ bool marked:1; @@ -119,6 +122,8 @@ const char *dns_server_string(DnsServer *server); bool dns_server_dnssec_supported(DnsServer *server); +void dns_server_warn_downgrade(DnsServer *server); + DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); void dns_server_unlink_all(DnsServer *first); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 43ee783ba9..02269498c9 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -296,6 +296,8 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { "DNS_TRANSACTION=%" PRIu16, t->id, "DNS_QUESTION=%s", dns_transaction_key_string(t), "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), + "DNS_SERVER=%s", dns_server_string(t->server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level), NULL); /* Note that this call might invalidate the query. Callers @@ -708,6 +710,9 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { return; } + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER) + dns_server_warn_downgrade(t->server); + dns_transaction_cache_answer(t); if (t->answer_rcode == DNS_RCODE_SUCCESS) @@ -2568,7 +2573,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (!dns_transaction_dnssec_supported_full(t)) { /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */ t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; - log_debug("Not validating response, server lacks DNSSEC support."); + log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id); return 0; } diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 1183df6105..814263546b 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -88,6 +88,7 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) #define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) +#define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) _SD_END_DECLARATIONS; -- cgit v1.2.3-54-g00ecf From b35f360bbde7faa0436c28b9d914a3aa1bb43dbd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 16:16:33 +0100 Subject: catalog: fix line width to 79 chars Line breaks default to 119 characters for systemd sources now, configured through the .vimrc and .dir-local.el files. However, for the catalog files we really should stick to 79 chars, as they are regularly shown on terminal screens. --- catalog/systemd.catalog | 1 + 1 file changed, 1 insertion(+) diff --git a/catalog/systemd.catalog b/catalog/systemd.catalog index 4488c835a8..bdcfd42c88 100644 --- a/catalog/systemd.catalog +++ b/catalog/systemd.catalog @@ -1,3 +1,4 @@ +# -*- fill-column: 79; indent-tabs-mode: nil -*- # This file is part of systemd. # # Copyright 2012 Lennart Poettering -- cgit v1.2.3-54-g00ecf From f25f9e8d6084ab6c60fbbfb0e4c0fe31db0dadc8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 16:20:25 +0100 Subject: catalog: add DNSSEC log messages to message catalog --- catalog/systemd.catalog | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/catalog/systemd.catalog b/catalog/systemd.catalog index bdcfd42c88..696f4ed618 100644 --- a/catalog/systemd.catalog +++ b/catalog/systemd.catalog @@ -279,3 +279,42 @@ Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel The virtual machine @NAME@ with its leader PID @LEADER@ has been shut down. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: DNSSEC mode has been turned off, as server doesn't support it +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +The resolver service (systemd-resolved.service) has detected that the +configured DNS server does not support DNSSEC, and DNSSEC validation has been +turned off as result. + +This event will take place if DNSSEC=allow-downgrade is configured in +resolved.conf and the configured DNS server is incompatible with DNSSEC. Note +that using this mode permits DNSSEC downgrade attacks, as an attacker might be +able turn off DNSSEC validation on the system by inserting DNS replies in the +communication channel that result in a downgrade like this. + +This event might be indication that the DNS server is indeed incompatible with +DNSSEC or that an attacker has successfully managed to stage such a downgrade +attack. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: DNSSEC validation failed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNS query or resource record set failed DNSSEC validation. This is usually +indication that the communication channel used was tampered with. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: A DNSSEC trust anchor has been revoked +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNSSEC trust anchor has been revoked. A new trust anchor has to be +configured, or the operating system needs to be updated, to provide an updated +DNSSEC trust anchor. -- cgit v1.2.3-54-g00ecf From 7cc6ed7ba6c667caef9a92ba4d59e1ecdc3af8ff Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 17:22:23 +0100 Subject: resolved: replace DNS_TRANSACTION_RESOURCES by DNS_TRANSACTION_ERRNO Whenever we encounter an OS error we did not expect, we so far put the transaction into DNS_TRANSACTION_RESOURCES state. Rename this state to DNS_TRANSACTION_ERRNO, and save + propagate the actual system error to the caller. This should make error messages triggered by system errors much more readable by the user. --- src/resolve/resolved-bus.c | 4 +- src/resolve/resolved-dns-query.c | 31 +++++++++---- src/resolve/resolved-dns-query.h | 1 + src/resolve/resolved-dns-transaction.c | 83 ++++++++++++++++++---------------- src/resolve/resolved-dns-transaction.h | 3 +- 5 files changed, 73 insertions(+), 49 deletions(-) diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 3a21773dae..d8e8638327 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -43,8 +43,8 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_INVALID_REPLY: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - case DNS_TRANSACTION_RESOURCES: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources"); + case DNS_TRANSACTION_ERRNO: + return sd_bus_reply_method_errnof(q->request, q->answer_errno, "Lookup failed due to system error: %m"); case DNS_TRANSACTION_ABORTED: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 1fe37a6813..1d706f8a43 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -182,7 +182,7 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { assert(c); if (c->error_code != 0) - return DNS_TRANSACTION_RESOURCES; + return DNS_TRANSACTION_ERRNO; SET_FOREACH(t, c->transactions, i) { @@ -324,6 +324,7 @@ static void dns_query_reset_answer(DnsQuery *q) { q->answer = dns_answer_unref(q->answer); q->answer_rcode = 0; q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = 0; q->answer_authenticated = false; q->answer_protocol = _DNS_PROTOCOL_INVALID; q->answer_family = AF_UNSPEC; @@ -670,7 +671,10 @@ int dns_query_go(DnsQuery *q) { if (found == DNS_SCOPE_NO) { DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + return r; + dns_query_complete(q, state); return 1; } @@ -748,7 +752,10 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { assert(q); if (!c) { - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + dns_query_complete(q, state); return; } @@ -760,12 +767,11 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { case DNS_TRANSACTION_SUCCESS: { /* We found a successfuly reply, merge it into the answer */ r = dns_answer_extend(&q->answer, t->answer); - if (r < 0) { - dns_query_complete(q, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; q->answer_rcode = t->answer_rcode; + q->answer_errno = 0; if (t->answer_authenticated) { has_authenticated = true; @@ -796,6 +802,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { q->answer = dns_answer_unref(q->answer); q->answer_rcode = t->answer_rcode; q->answer_dnssec_result = t->answer_dnssec_result; + q->answer_errno = t->answer_errno; state = t->state; break; @@ -813,8 +820,16 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { dns_search_domain_unref(q->answer_search_domain); q->answer_search_domain = dns_search_domain_ref(c->search_domain); - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + dns_query_complete(q, state); + return; + +fail: + q->answer_errno = -r; + dns_query_complete(q, DNS_TRANSACTION_ERRNO); } void dns_query_ready(DnsQuery *q) { diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 9f618d6f6b..c7e4ce9a00 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -83,6 +83,7 @@ struct DnsQuery { DnsProtocol answer_protocol; int answer_family; DnsSearchDomain *answer_search_domain; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ /* Bus client information */ sd_bus_message *request; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 02269498c9..0aeb3b0f4a 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -24,6 +24,7 @@ #include "af-list.h" #include "alloc-util.h" #include "dns-domain.h" +#include "errno-list.h" #include "fd-util.h" #include "random-util.h" #include "resolved-dns-cache.h" @@ -43,6 +44,7 @@ static void dns_transaction_reset_answer(DnsTransaction *t) { t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; t->answer_authenticated = false; t->answer_nsec_ttl = (uint32_t) -1; + t->answer_errno = 0; } static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { @@ -285,6 +287,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { DnsZoneItem *z; DnsTransaction *d; Iterator i; + const char *st; assert(t); assert(!DNS_TRANSACTION_IS_LIVE(state)); @@ -304,13 +307,18 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { * should hence not attempt to access the query or transaction * after calling this function. */ + if (state == DNS_TRANSACTION_ERRNO) + st = errno_to_name(t->answer_errno); + else + st = dns_transaction_state_to_string(state); + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", t->id, dns_transaction_key_string(t), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->name : "*", t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), - dns_transaction_state_to_string(state), + st, t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), t->answer_authenticated ? "authenticated" : "unsigned"); @@ -393,8 +401,10 @@ static void dns_transaction_retry(DnsTransaction *t) { dns_scope_next_dns_server(t->scope); r = dns_transaction_go(t); - if (r < 0) - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + if (r < 0) { + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + } } static int dns_transaction_maybe_restart(DnsTransaction *t) { @@ -449,7 +459,8 @@ static int on_stream_complete(DnsStream *s, int error) { return 0; } if (error != 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + t->answer_errno = error; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); return 0; } @@ -664,20 +675,16 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ r = dns_transaction_dnssec_ready(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */ return; /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better * restart the lookup immediately. */ r = dns_transaction_maybe_restart(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r > 0) /* Transaction got restarted... */ return; @@ -688,10 +695,8 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; } - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && t->scope->dnssec_mode == DNSSEC_YES) { @@ -719,6 +724,12 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); } void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { @@ -862,10 +873,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { if (r < 0) { /* On LLMNR, if we cannot connect to the host, * we immediately give up */ - if (t->scope->protocol != DNS_PROTOCOL_DNS) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (t->scope->protocol != DNS_PROTOCOL_DNS) + goto fail; /* On DNS, couldn't send? Try immediately again, with a new server */ dns_transaction_retry(t); @@ -891,10 +900,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ r = dns_transaction_maybe_restart(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r > 0) /* Transaction got restarted... */ return; @@ -902,10 +909,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* Only consider responses with equivalent query section to the request */ r = dns_packet_is_reply_for(p, t->key); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r == 0) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; @@ -934,10 +939,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { * quickly. */ if (t->state != DNS_TRANSACTION_PENDING) return; - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r > 0) { /* There are DNSSEC transactions pending now. Update the state accordingly. */ t->state = DNS_TRANSACTION_VALIDATING; @@ -948,6 +951,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } dns_transaction_process_dnssec(t); + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); } static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -973,7 +981,8 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use return 0; } if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + t->answer_errno = -r; return 0; } @@ -1482,10 +1491,8 @@ int dns_transaction_go(DnsTransaction *t) { return 0; } if (r < 0) { - if (t->scope->protocol != DNS_PROTOCOL_DNS) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return 0; - } + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return r; /* Couldn't send? Try immediately again, with a new server */ dns_scope_next_dns_server(t->scope); @@ -3001,7 +3008,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_TIMEOUT] = "timeout", [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", - [DNS_TRANSACTION_RESOURCES] = "resources", + [DNS_TRANSACTION_ERRNO] = "errno", [DNS_TRANSACTION_ABORTED] = "aborted", [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index ab5717c4d1..b6c5b2861c 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -35,7 +35,7 @@ enum DnsTransactionState { DNS_TRANSACTION_TIMEOUT, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, DNS_TRANSACTION_INVALID_REPLY, - DNS_TRANSACTION_RESOURCES, + DNS_TRANSACTION_ERRNO, DNS_TRANSACTION_ABORTED, DNS_TRANSACTION_DNSSEC_FAILED, DNS_TRANSACTION_NO_TRUST_ANCHOR, @@ -84,6 +84,7 @@ struct DnsTransaction { DnssecResult answer_dnssec_result; DnsTransactionSource answer_source; uint32_t answer_nsec_ttl; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ /* Indicates whether the primary answer is authenticated, * i.e. whether the RRs from answer which directly match the -- cgit v1.2.3-54-g00ecf From a7bf2ada62db599efbb2fbabcc4dc45997b87415 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 17:24:05 +0100 Subject: resolved: properly propagate query candidate error We already properly propagate errors from transactions to queries. Make sure that errors that happened during handling of query candidates are propagated to the query, too. --- src/resolve/resolved-dns-query.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 1d706f8a43..11179d1926 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -760,6 +760,15 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { return; } + if (c->error_code != 0) { + /* If the candidate had an error condition of its own, start with that. */ + state = DNS_TRANSACTION_ERRNO; + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = c->error_code; + } + SET_FOREACH(t, c->transactions, i) { switch (t->state) { -- cgit v1.2.3-54-g00ecf From b8b143c5ff6f8dd91297f961553a3c24f0694ae9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 17:25:28 +0100 Subject: update DNSSEC TODO --- src/resolve/resolved-dns-dnssec.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index e3b43e7e48..eb217b1f2d 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -35,9 +35,6 @@ * * TODO: * - * - bus calls to override DNSEC setting per interface - * - log all DNSSEC downgrades - * - log all RRs that failed validation * - enable by default * - Allow clients to request DNSSEC even if DNSSEC is off * - make sure when getting an NXDOMAIN response through CNAME, we still process the first CNAMEs in the packet -- cgit v1.2.3-54-g00ecf From ab88b6d087c89fb052cedc6f145339fd24c1138e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 17:34:10 +0100 Subject: resolved: use dns_query_reset_answer() where we can --- src/resolve/resolved-dns-query.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 11179d1926..ef977b0948 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -703,10 +703,7 @@ int dns_query_go(DnsQuery *q) { goto fail; } - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = 0; - q->answer_family = AF_UNSPEC; - q->answer_protocol = _DNS_PROTOCOL_INVALID; + dns_query_reset_answer(q); r = sd_event_add_time( q->manager->event, -- cgit v1.2.3-54-g00ecf From cbd100ac7cb74d7d44c7e6dda09d26b2616776f7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Jan 2016 18:43:41 +0100 Subject: resolved: don't insist in RRSIG metadata for NSEC3 RRs that have not been authenticated In some cases we get NSEC3 RRs that have not been authenticated (because the chain of trust to the root is somewhere broken). We can use these for checking negative replies, as long as we don't claim they were ultimately authenticated. This means we need to be able to deal with NSEC3 RRs that lack RRSIG metadata. --- src/resolve/resolved-dns-dnssec.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index eb217b1f2d..8e3c78e7bf 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1267,11 +1267,12 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) return 0; - /* Ignore NSEC3 RRs generated from wildcards */ - if (rr->n_skip_labels_source != 0) + /* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this + * check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */ + if (rr->n_skip_labels_source != 0 && rr->n_skip_labels_source != (unsigned) -1) return 0; /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ - if (rr->n_skip_labels_signer != 1) + if (rr->n_skip_labels_signer != 1 && rr->n_skip_labels_signer != (unsigned) -1) return 0; if (!nsec3) -- cgit v1.2.3-54-g00ecf From 4cb94977ed8d384a0f476dd0b0ed7b51058a3bd4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jan 2016 15:48:36 +0100 Subject: resolved: don't consider NSEC/NSEC3 RRs as "pimary" for transactions So far, abritrary NSEC and NSEC3 RRs were implicitly consider "primary" for any transaction, meaning we'd abort the transaction immediately if we couldn't validate it. With this patch this logic is removed, and the NSEC/NSEC3 RRs will not be considered primary anymore. This has the effect that they will be dropped from the message if they don't validate, but processing continues. This is safe to do, as they are required anyway to validate positive wildcard and negative responses, and if they are missing then, then message will be considered unsigned, which hence means the outcome is effectively the same. This is benefical in case the server sends us NSEC/NSEC3 RRs that are not directly related to the lookup we did, but simply auxiliary information. Previously, if we couldn't authenticate those RRs we'd fail the entire lookup while with this change we'll simply drop the auxiliary information and proceed without it. --- src/resolve/resolved-dns-transaction.c | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 0aeb3b0f4a..77f9ef0a83 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -1716,33 +1716,13 @@ static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRec /* Check if the specified RR is the "primary" response, * i.e. either matches the question precisely or is a - * CNAME/DNAME for it, or is any kind of NSEC/NSEC3 RR */ + * CNAME/DNAME for it. */ r = dns_resource_key_match_rr(t->key, rr, NULL); if (r != 0) return r; - r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); - if (r != 0) - return r; - - if (rr->key->type == DNS_TYPE_NSEC3) { - const char *p; - - p = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r > 0) { - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), p); - if (r < 0) - return r; - if (r > 0) - return true; - } - } - - return rr->key->type == DNS_TYPE_NSEC; + return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); } static bool dns_transaction_dnssec_supported(DnsTransaction *t) { -- cgit v1.2.3-54-g00ecf From 6c1e69f9456d022f14dd00737126cfa4d9cca10c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jan 2016 17:16:27 +0100 Subject: udev: filter out non-sensically high onboard indexes reported by the kernel Let's not accept onboard interface indexes, that are so high that they are obviously non-sensical. Fixes: #2407 --- src/udev/udev-builtin-net_id.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index e83b8b1c12..104d5111c5 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -106,6 +106,8 @@ #include "string-util.h" #include "udev.h" +#define ONBOARD_INDEX_MAX (16*1024-1) + enum netname_type{ NET_UNDEF, NET_PCI, @@ -152,6 +154,13 @@ static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) { if (idx <= 0) return -EINVAL; + /* Some BIOSes report rubbish indexes that are excessively high (2^24-1 is an index VMware likes to report for + * example). Let's define a cut-off where we don't consider the index reliable anymore. We pick some arbitrary + * cut-off, which is somewhere beyond the realistic number of physical network interface a system might + * have. Ideally the kernel would already filter his crap for us, but it doesn't currently. */ + if (idx > ONBOARD_INDEX_MAX) + return -ENOENT; + /* kernel provided port index for multiple ports on a single PCI function */ attr = udev_device_get_sysattr_value(dev, "dev_port"); if (attr) -- cgit v1.2.3-54-g00ecf