From 6f7da49d000637d164bb4b0b4d6964c3abb198de Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Jun 2016 22:30:11 +0200 Subject: resolved: make sure that route-only domains are never added to /etc/resolv.conf After all, /etc/resolv.conf doesn't know the concept of "route-only domains", hence the domains should really not appear there. --- src/resolve/resolved-manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/resolve/resolved-manager.h') diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index ef71202ef9..c9e6ac9d4f 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -162,7 +162,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); int manager_is_own_hostname(Manager *m, const char *name); int manager_compile_dns_servers(Manager *m, OrderedSet **servers); -int manager_compile_search_domains(Manager *m, OrderedSet **domains); +int manager_compile_search_domains(Manager *m, OrderedSet **domains, int filter_route); DnssecMode manager_get_dnssec_mode(Manager *m); bool manager_dnssec_supported(Manager *m); -- cgit v1.2.3 From 943ef07ce0aacbee93c721e461c02d651ee9ef6a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 15 Jun 2016 22:38:23 +0200 Subject: resolved: make sure DNS configuration pushed in by the user stays around on restarts Let's make sure that all settings pushed in stay around when systemd-resolved is restarted. --- src/resolve/resolved-link-bus.c | 11 ++ src/resolve/resolved-link.c | 271 +++++++++++++++++++++++++++++++++++++++- src/resolve/resolved-link.h | 11 +- src/resolve/resolved-manager.c | 65 +++++++++- src/resolve/resolved-manager.h | 2 + 5 files changed, 353 insertions(+), 7 deletions(-) (limited to 'src/resolve/resolved-manager.h') diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index 07726c7d9e..acce8682de 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -265,6 +265,7 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_ dns_server_unlink_marked(l->dns_servers); link_allocate_scopes(l); + (void) link_save_user(l); (void) manager_write_resolv_conf(l->manager); return sd_bus_reply_method_return(message, NULL); @@ -346,6 +347,7 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ dns_search_domain_unlink_marked(l->search_domains); + (void) link_save_user(l); (void) manager_write_resolv_conf(l->manager); return sd_bus_reply_method_return(message, NULL); @@ -384,6 +386,8 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er link_allocate_scopes(l); link_add_rrs(l, false); + (void) link_save_user(l); + return sd_bus_reply_method_return(message, NULL); } @@ -416,6 +420,8 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err link_allocate_scopes(l); link_add_rrs(l, false); + (void) link_save_user(l); + return sd_bus_reply_method_return(message, NULL); } @@ -446,6 +452,8 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e link_set_dnssec_mode(l, mode); + (void) link_save_user(l); + return sd_bus_reply_method_return(message, NULL); } @@ -489,6 +497,8 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v l->dnssec_negative_trust_anchors = ns; ns = NULL; + (void) link_save_user(l); + return sd_bus_reply_method_return(message, NULL); } @@ -507,6 +517,7 @@ int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error link_allocate_scopes(l); link_add_rrs(l, false); + (void) link_save_user(l); (void) manager_write_resolv_conf(l->manager); return sd_bus_reply_method_return(message, NULL); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index b321aefda9..ea4a007139 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -22,7 +22,10 @@ #include "sd-network.h" #include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" #include "missing.h" +#include "mkdir.h" #include "parse-util.h" #include "resolved-link.h" #include "string-util.h" @@ -49,6 +52,9 @@ int link_new(Manager *m, Link **ret, int ifindex) { l->dnssec_mode = _DNSSEC_MODE_INVALID; l->operstate = IF_OPER_UNKNOWN; + if (asprintf(&l->state_file, "/run/systemd/resolve/netif/%i", ifindex) < 0) + return -ENOMEM; + r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); if (r < 0) return r; @@ -93,6 +99,8 @@ Link *link_free(Link *l) { dns_scope_free(l->mdns_ipv4_scope); dns_scope_free(l->mdns_ipv6_scope); + free(l->state_file); + free(l); return NULL; } @@ -165,7 +173,7 @@ void link_add_rrs(Link *l, bool force_remove) { link_address_add_rrs(a, force_remove); } -int link_update_rtnl(Link *l, sd_netlink_message *m) { +int link_process_rtnl(Link *l, sd_netlink_message *m) { const char *n = NULL; int r; @@ -511,10 +519,11 @@ static void link_read_settings(Link *l) { log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); } -int link_update_monitor(Link *l) { +int link_update(Link *l) { assert(l); link_read_settings(l); + link_load_user(l); link_allocate_scopes(l); link_add_rrs(l, false); @@ -846,3 +855,261 @@ bool link_address_relevant(LinkAddress *a, bool local_multicast) { return true; } + +static bool link_needs_save(Link *l) { + assert(l); + + /* Returns true if any of the settings where set different from the default */ + + if (l->is_managed) + return false; + + if (l->llmnr_support != RESOLVE_SUPPORT_YES || + l->mdns_support != RESOLVE_SUPPORT_NO || + l->dnssec_mode != _DNSSEC_MODE_INVALID) + return true; + + if (l->dns_servers || + l->search_domains) + return true; + + if (!set_isempty(l->dnssec_negative_trust_anchors)) + return true; + + return false; +} + +int link_save_user(Link *l) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + const char *v; + int r; + + assert(l); + assert(l->state_file); + + if (!link_needs_save(l)) { + (void) unlink(l->state_file); + return 0; + } + + r = mkdir_parents(l->state_file, 0700); + if (r < 0) + goto fail; + + r = fopen_temporary(l->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + fputs("# This is private data. Do not parse.\n", f); + + v = resolve_support_to_string(l->llmnr_support); + if (v) + fprintf(f, "LLMNR=%s\n", v); + + v = resolve_support_to_string(l->mdns_support); + if (v) + fprintf(f, "MDNS=%s\n", v); + + v = dnssec_mode_to_string(l->dnssec_mode); + if (v) + fprintf(f, "DNSSEC=%s\n", v); + + if (l->dns_servers) { + DnsServer *server; + + fputs("SERVERS=", f); + LIST_FOREACH(servers, server, l->dns_servers) { + + if (server != l->dns_servers) + fputc(' ', f); + + v = dns_server_string(server); + if (!v) { + r = -ENOMEM; + goto fail; + } + + fputs(v, f); + } + fputc('\n', f); + } + + if (l->search_domains) { + DnsSearchDomain *domain; + + fputs("DOMAINS=", f); + LIST_FOREACH(domains, domain, l->search_domains) { + + if (domain != l->search_domains) + fputc(' ', f); + + if (domain->route_only) + fputc('~', f); + + fputs(DNS_SEARCH_DOMAIN_NAME(domain), f); + } + fputc('\n', f); + } + + if (!set_isempty(l->dnssec_negative_trust_anchors)) { + bool space = false; + Iterator i; + char *nta; + + fputs("NTAS=", f); + SET_FOREACH(nta, l->dnssec_negative_trust_anchors, i) { + + if (space) + fputc(' ', f); + + fputs(nta, f); + space = true; + } + fputc('\n', f); + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, l->state_file) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + (void) unlink(l->state_file); + + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save link data %s: %m", l->state_file); +} + +int link_load_user(Link *l) { + _cleanup_free_ char + *llmnr = NULL, + *mdns = NULL, + *dnssec = NULL, + *servers = NULL, + *domains = NULL, + *ntas = NULL; + + ResolveSupport s; + int r; + + assert(l); + assert(l->state_file); + + /* Try to load only a single time */ + if (l->loaded) + return 0; + l->loaded = true; + + if (l->is_managed) + return 0; /* if the device is managed, then networkd is our configuration source, not the bus API */ + + r = parse_env_file(l->state_file, NEWLINE, + "LLMNR", &llmnr, + "MDNS", &mdns, + "DNSSEC", &dnssec, + "SERVERS", &servers, + "DOMAINS", &domains, + "NTAS", &ntas, + NULL); + if (r == -ENOENT) + return 0; + if (r < 0) + goto fail; + + link_flush_settings(l); + + /* If we can't recognize the LLMNR or MDNS setting we don't override the default */ + s = resolve_support_from_string(llmnr); + if (s >= 0) + l->llmnr_support = s; + + s = resolve_support_from_string(mdns); + if (s >= 0) + l->mdns_support = s; + + /* If we can't recognize the DNSSEC setting, then set it to invalid, so that the daemon default is used. */ + l->dnssec_mode = dnssec_mode_from_string(dnssec); + + if (servers) { + const char *p = servers; + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r < 0) + goto fail; + if (r == 0) + break; + + r = link_update_dns_server_one(l, word); + if (r < 0) { + log_debug_errno(r, "Failed to load DNS server '%s', ignoring: %m", word); + continue; + } + } + } + + if (domains) { + const char *p = domains; + + for (;;) { + _cleanup_free_ char *word = NULL; + const char *n; + bool is_route; + + r = extract_first_word(&p, &word, NULL, 0); + if (r < 0) + goto fail; + if (r == 0) + break; + + is_route = word[0] == '~'; + n = is_route ? word + 1 : word; + + r = link_update_search_domain_one(l, n, is_route); + if (r < 0) { + log_debug_errno(r, "Failed to load search domain '%s', ignoring: %m", word); + continue; + } + } + } + + if (ntas) { + _cleanup_set_free_free_ Set *ns = NULL; + + ns = set_new(&dns_name_hash_ops); + if (!ns) { + r = -ENOMEM; + goto fail; + } + + r = set_put_strsplit(ns, ntas, NULL, 0); + if (r < 0) + goto fail; + + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + } + + return 0; + +fail: + return log_error_errno(r, "Failed to load link data %s: %m", l->state_file); +} + +void link_remove_user(Link *l) { + assert(l); + assert(l->state_file); + + (void) unlink(l->state_file); +} diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index f534c12824..6a2343f9f7 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -81,12 +81,15 @@ struct Link { char name[IF_NAMESIZE]; uint32_t mtu; uint8_t operstate; + + bool loaded; + char *state_file; }; int link_new(Manager *m, Link **ret, int ifindex); Link *link_free(Link *l); -int link_update_rtnl(Link *l, sd_netlink_message *m); -int link_update_monitor(Link *l); +int link_process_rtnl(Link *l, sd_netlink_message *m); +int link_update(Link *l); bool link_relevant(Link *l, int family, bool local_multicast); LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr); void link_add_rrs(Link *l, bool force_remove); @@ -102,6 +105,10 @@ void link_next_dns_server(Link *l); DnssecMode link_get_dnssec_mode(Link *l); bool link_dnssec_supported(Link *l); +int link_save_user(Link *l); +int link_load_user(Link *l); +void link_remove_user(Link *l); + int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr); LinkAddress *link_address_free(LinkAddress *a); int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 44abacb55a..e8811fa1d8 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -23,6 +23,7 @@ #include "af-list.h" #include "alloc-util.h" +#include "dirent-util.h" #include "dns-domain.h" #include "fd-util.h" #include "fileio-label.h" @@ -78,11 +79,11 @@ static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void * goto fail; } - r = link_update_rtnl(l, mm); + r = link_process_rtnl(l, mm); if (r < 0) goto fail; - r = link_update_monitor(l); + r = link_update(l); if (r < 0) goto fail; @@ -95,6 +96,7 @@ static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void * case RTM_DELLINK: if (l) { log_debug("Removing link %i/%s", l->ifindex, l->name); + link_remove_user(l); link_free(l); } @@ -279,7 +281,7 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void * sd_network_monitor_flush(m->network_monitor); HASHMAP_FOREACH(l, m->links, i) { - r = link_update_monitor(l); + r = link_update(l); if (r < 0) log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex); } @@ -540,6 +542,8 @@ int manager_new(Manager **ret) { (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); (void) sd_event_add_signal(m->event, &m->sigusr2_event_source, SIGUSR2, manager_sigusr2, m); + manager_cleanup_saved_user(m); + *ret = m; m = NULL; @@ -1269,3 +1273,58 @@ void manager_flush_caches(Manager *m) { log_info("Flushed all caches."); } + +void manager_cleanup_saved_user(Manager *m) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r; + + assert(m); + + /* Clean up all saved per-link files in /run/systemd/resolve/netif/ that don't have a matching interface + * anymore. These files are created to persist settings pushed in by the user via the bus, so that resolved can + * be restarted without losing this data. */ + + d = opendir("/run/systemd/resolve/netif/"); + if (!d) { + if (errno == ENOENT) + return; + + log_warning_errno(errno, "Failed to open interface directory: %m"); + return; + } + + FOREACH_DIRENT_ALL(de, d, log_error_errno(errno, "Failed to read interface directory: %m")) { + _cleanup_free_ char *p = NULL; + int ifindex; + Link *l; + + if (!IN_SET(de->d_type, DT_UNKNOWN, DT_REG)) + continue; + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + r = parse_ifindex(de->d_name, &ifindex); + if (r < 0) /* Probably some temporary file from a previous run. Delete it */ + goto rm; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) /* link vanished */ + goto rm; + + if (l->is_managed) /* now managed by networkd, hence the bus settings are useless */ + goto rm; + + continue; + + rm: + p = strappend("/run/systemd/resolve/netif/", de->d_name); + if (!p) { + log_oom(); + return; + } + + (void) unlink(p); + } +} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index c9e6ac9d4f..eee45c94ef 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -172,3 +172,5 @@ void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResource bool manager_routable(Manager *m, int family); void manager_flush_caches(Manager *m); + +void manager_cleanup_saved_user(Manager *m); -- cgit v1.2.3 From 0a0fc27b80796c603cb64728b38c55f8f2084f36 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 16 Jun 2016 18:50:14 +0200 Subject: resolved: drop unused permit_domain_search variable from Manager object --- src/resolve/resolved-manager.h | 1 - 1 file changed, 1 deletion(-) (limited to 'src/resolve/resolved-manager.h') diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index eee45c94ef..0821904e84 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -72,7 +72,6 @@ struct Manager { LIST_HEAD(DnsSearchDomain, search_domains); unsigned n_search_domains; - bool permit_domain_search; bool need_builtin_fallbacks:1; -- cgit v1.2.3 From b30bf55d5c9942f15f27a641c2c34bbb646ec981 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 21 Jun 2016 00:58:47 +0200 Subject: resolved: respond to local resolver requests on 127.0.0.53:53 In order to improve compatibility with local clients that speak DNS directly (and do not use NSS or our bus API) listen locally on 127.0.0.53:53 and process any queries made that way. Note that resolved does not implement a full DNS server on this port, but simply enough to allow normal, local clients to resolve RRs through resolved. Specifically it does not implement queries without the RD bit set (these are requests where recursive lookups are explicitly disabled), and neither queries with DNSSEC DO set in combination with DNSSEC CD (i.e. DNSSEC lookups with validation turned off). It also refuses zone transfers and obsolete RR types. All lookups done this way will be rejected with a clean error code, so that the client side can repeat the query with a reduced feature set. The code will set the DNSSEC AD flag however, depending on whether the data resolved has been validated (or comes from a local, trusted source). Lookups made via this mechanisms are propagated to LLMNR and mDNS as necessary, but this is only partially useful as DNS packets cannot carry IP scope data (i.e. the ifindex), and hence link-local addresses returned cannot be used properly (and given that LLMNR/mDNS are mostly about link-local communication this is quite a limitation). Also, given that DNS tends to use IDNA for non-ASCII names, while LLMNR/mDNS uses UTF-8 lookups cannot be mapped 1:1. In general this should improve compatibility with clients bypassing NSS but it is highly recommended for clients to instead use NSS or our native bus API. This patch also beefs up the DnsStream logic, as it reuses the code for local TCP listening. DnsStream now provides proper reference counting for its objects. In order to avoid feedback loops resolved will no silently ignore 127.0.0.53 specified as DNS server when reading configuration. resolved listens on 127.0.0.53:53 instead of 127.0.0.1:53 in order to leave the latter free for local, external DNS servers or forwarders. This also changes the "etc.conf" tmpfiles snippet to create a symlink from /etc/resolv.conf to /usr/lib/systemd/resolv.conf by default, thus making this stub the default mode of operation if /etc is not populated. --- Makefile.am | 8 +- src/resolve/resolv.conf | 11 + src/resolve/resolved-conf.c | 4 + src/resolve/resolved-dns-packet.c | 44 ++- src/resolve/resolved-dns-packet.h | 31 +- src/resolve/resolved-dns-query.c | 10 + src/resolve/resolved-dns-query.h | 4 + src/resolve/resolved-dns-rr.h | 7 + src/resolve/resolved-dns-scope.c | 37 ++- src/resolve/resolved-dns-server.c | 14 + src/resolve/resolved-dns-server.h | 2 + src/resolve/resolved-dns-stream.c | 29 +- src/resolve/resolved-dns-stream.h | 23 +- src/resolve/resolved-dns-stub.c | 572 +++++++++++++++++++++++++++++++++ src/resolve/resolved-dns-stub.h | 31 ++ src/resolve/resolved-dns-transaction.c | 13 +- src/resolve/resolved-link-bus.c | 3 + src/resolve/resolved-llmnr.c | 33 +- src/resolve/resolved-manager.c | 62 +++- src/resolve/resolved-manager.h | 9 +- src/resolve/resolved-resolv-conf.c | 11 +- src/resolve/resolved.c | 13 +- tmpfiles.d/etc.conf.m4 | 2 +- units/systemd-resolved.service.m4.in | 2 +- 24 files changed, 896 insertions(+), 79 deletions(-) create mode 100644 src/resolve/resolv.conf create mode 100644 src/resolve/resolved-dns-stub.c create mode 100644 src/resolve/resolved-dns-stub.h (limited to 'src/resolve/resolved-manager.h') diff --git a/Makefile.am b/Makefile.am index 3c13acf28d..c7e4c20c49 100644 --- a/Makefile.am +++ b/Makefile.am @@ -125,6 +125,7 @@ dist_systemunit_DATA_busnames = dist_sysusers_DATA = check_PROGRAMS = check_DATA = +dist_rootlibexec_DATA = tests= manual_tests = TEST_EXTENSIONS = .py @@ -5147,7 +5148,7 @@ systemd_export_LDADD = \ $(ZLIB_LIBS) \ -lbz2 -dist_rootlibexec_DATA = \ +dist_rootlibexec_DATA += \ src/import/import-pubring.gpg nodist_systemunit_DATA += \ @@ -5259,6 +5260,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-dns-stream.c \ src/resolve/resolved-dns-trust-anchor.h \ src/resolve/resolved-dns-trust-anchor.c \ + src/resolve/resolved-dns-stub.h \ + src/resolve/resolved-dns-stub.c \ src/resolve/resolved-etc-hosts.h \ src/resolve/resolved-etc-hosts.c \ src/shared/gcrypt-util.c \ @@ -5411,6 +5414,9 @@ EXTRA_DIST += \ units/systemd-resolved.service.m4.in \ src/resolve/resolved.conf.in +dist_rootlibexec_DATA += \ + src/resolve/resolv.conf + # ------------------------------------------------------------------------------ if ENABLE_NETWORKD rootlibexec_PROGRAMS += \ diff --git a/src/resolve/resolv.conf b/src/resolve/resolv.conf new file mode 100644 index 0000000000..b8034d6829 --- /dev/null +++ b/src/resolve/resolv.conf @@ -0,0 +1,11 @@ +# This is a static resolv.conf file for connecting local clients to +# systemd-resolved via its DNS stub listener on 127.0.0.53. +# +# Third party programs must not access this file directly, but only through the +# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way, +# replace this symlink by a static file or a different symlink. +# +# See systemd-resolved.service(8) for details about the supported modes of +# operation for /etc/resolv.conf. + +nameserver 127.0.0.53 diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index fecf7ecccf..dd233e7c4a 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -37,6 +37,10 @@ int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char if (r < 0) return r; + /* Silently filter out 0.0.0.0 and 127.0.0.53 (our own stub DNS listener) */ + if (!dns_server_address_valid(family, &address)) + return 0; + /* Filter out duplicates */ s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, ifindex); if (s) { diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 2cf07a628b..ea0be56d98 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -264,6 +264,7 @@ int dns_packet_validate_query(DnsPacket *p) { switch (p->protocol) { case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_DNS: /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ if (DNS_PACKET_QDCOUNT(p) != 1) return -EBADMSG; @@ -719,9 +720,8 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, in goto fail; /* RDLENGTH */ - - if (edns0_do) { - /* If DO is on, also append RFC6975 Algorithm data */ + if (edns0_do & !DNS_PACKET_QR(p)) { + /* If DO is on and this is not a reply, also append RFC6975 Algorithm data */ static const uint8_t rfc6975[] = { @@ -752,7 +752,6 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, in r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); } else r = dns_packet_append_uint16(p, 0, NULL); - if (r < 0) goto fail; @@ -2062,8 +2061,10 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { assert(rr->key->type == DNS_TYPE_OPT); /* Check that the version is 0 */ - if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) - return false; + if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) { + *rfc6975 = false; + return true; /* if it's not version 0, it's OK, but we will ignore the OPT field contents */ + } p = rr->opt.data; l = rr->opt.data_size; @@ -2186,16 +2187,27 @@ int dns_packet_extract(DnsPacket *p) { continue; } - if (has_rfc6975) { - /* 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; + if (DNS_PACKET_QR(p)) { + /* Additional checks for responses */ + + if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) { + /* If this is a reply and we don't know the EDNS version then something + * is weird... */ + log_debug("EDNS version newer that our request, bad server."); + return -EBADMSG; + } + + if (has_rfc6975) { + /* 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; + } } p->opt = dns_resource_record_ref(rr); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 1216bcb72d..7b7d4e14c9 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -118,6 +118,8 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { #define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1) #define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1) +#define DNS_PACKET_FLAG_TC (UINT16_C(1) << 9) + static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { uint16_t rcode; @@ -126,7 +128,34 @@ static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { else rcode = 0; - return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15); + return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 0xF); +} + +static inline uint16_t DNS_PACKET_PAYLOAD_SIZE_MAX(DnsPacket *p) { + + /* Returns the advertised maximum datagram size for replies, or the DNS default if there's nothing defined. */ + + if (p->opt) + return MAX(DNS_PACKET_UNICAST_SIZE_MAX, p->opt->key->class); + + return DNS_PACKET_UNICAST_SIZE_MAX; +} + +static inline bool DNS_PACKET_DO(DnsPacket *p) { + if (!p->opt) + return false; + + return !!(p->opt->ttl & (1U << 15)); +} + +static inline bool DNS_PACKET_VERSION_SUPPORTED(DnsPacket *p) { + /* Returns true if this packet is in a version we support. Which means either non-EDNS or EDNS(0), but not EDNS + * of any newer versions */ + + if (!p->opt) + return true; + + return DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(p->opt); } /* LLMNR defines some bits differently */ diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 8578774c37..c8af5579f0 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -404,6 +404,16 @@ DnsQuery *dns_query_free(DnsQuery *q) { sd_bus_message_unref(q->request); sd_bus_track_unref(q->bus_track); + dns_packet_unref(q->request_dns_packet); + + if (q->request_dns_stream) { + /* Detach the stream from our query, in case something else keeps a reference to it. */ + q->request_dns_stream->complete = NULL; + q->request_dns_stream->on_packet = NULL; + q->request_dns_stream->query = NULL; + dns_stream_unref(q->request_dns_stream); + } + free(q->request_address_string); if (q->manager) { diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 53f48d462b..49a35b846b 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -99,6 +99,10 @@ struct DnsQuery { unsigned block_all_complete; char *request_address_string; + /* DNS stub information */ + DnsPacket *request_dns_packet; + DnsStream *request_dns_stream; + /* Completion callback */ void (*complete)(DnsQuery* q); unsigned block_ready; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 8b2d4df9e7..42d39a1251 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -282,6 +282,13 @@ static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { return rr->wire_format_size - rr->wire_format_rdata_offset; } +static inline uint8_t DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(DnsResourceRecord *rr) { + assert(rr); + assert(rr->key->type == DNS_TYPE_OPT); + + return ((rr->ttl >> 16) & 0xFF) == 0; +} + DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 66e763cb7d..ed0c6aa105 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -232,7 +232,7 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { if (fd < 0) return fd; - r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); + r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, NULL, p); if (r < 0) return r; @@ -257,7 +257,7 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { if (fd < 0) return fd; - r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); + r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, NULL, p); if (r < 0) return r; @@ -668,11 +668,11 @@ static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { } void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { - _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; DnsResourceKey *key = NULL; bool tentative = false; - int r, fd; + int r; assert(s); assert(p); @@ -694,7 +694,7 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { r = dns_packet_extract(p); if (r < 0) { - log_debug_errno(r, "Failed to extract resources from incoming packet: %m"); + log_debug_errno(r, "Failed to extract resource records from incoming packet: %m"); return; } @@ -724,9 +724,21 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { return; } - if (stream) + if (stream) { r = dns_stream_write_packet(stream, reply); - else { + if (r < 0) { + log_debug_errno(r, "Failed to enqueue reply packet: %m"); + return; + } + + /* Let's take an extra reference on this stream, so that it stays around after returning. The reference + * will be dangling until the stream is disconnected, and the default completion handler of the stream + * will then unref the stream and destroy it */ + if (DNS_STREAM_QUEUED(stream)) + dns_stream_ref(stream); + } else { + int fd; + if (!ratelimit_test(&s->ratelimit)) return; @@ -748,12 +760,11 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { * verified uniqueness for all records. Also see RFC * 4795, Section 2.7 */ - r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply); - } - - if (r < 0) { - log_debug_errno(r, "Failed to send reply packet: %m"); - return; + r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, NULL, reply); + if (r < 0) { + log_debug_errno(r, "Failed to send reply packet: %m"); + return; + } } } diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index bcbfa69aff..7226111c07 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -21,6 +21,7 @@ #include "alloc-util.h" #include "resolved-dns-server.h" +#include "resolved-dns-stub.h" #include "resolved-resolv-conf.h" #include "siphash24.h" #include "string-table.h" @@ -750,6 +751,19 @@ void manager_next_dns_server(Manager *m) { manager_set_dns_server(m, m->dns_servers); } +bool dns_server_address_valid(int family, const union in_addr_union *sa) { + + /* Refuses the 0 IP addresses as well as 127.0.0.53 (which is our own DNS stub) */ + + if (in_addr_is_null(family, sa)) + return false; + + if (family == AF_INET && sa->in.s_addr == htobe32(INADDR_DNS_STUB)) + return false; + + return true; +} + static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { [DNS_SERVER_SYSTEM] = "system", [DNS_SERVER_FALLBACK] = "fallback", diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 463c5724a7..7d07fa3e29 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -141,6 +141,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); DnsServer *manager_get_dns_server(Manager *m); void manager_next_dns_server(Manager *m); +bool dns_server_address_valid(int family, const union in_addr_union *sa); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index a1040aeff4..dd0e0b90e3 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -56,8 +56,8 @@ static int dns_stream_complete(DnsStream *s, int error) { if (s->complete) s->complete(s, error); - else - dns_stream_free(s); + else /* the default action if no completion function is set is to close the stream */ + dns_stream_unref(s); return 0; } @@ -323,10 +323,16 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use return 0; } -DnsStream *dns_stream_free(DnsStream *s) { +DnsStream *dns_stream_unref(DnsStream *s) { if (!s) return NULL; + assert(s->n_ref > 0); + s->n_ref--; + + if (s->n_ref > 0) + return NULL; + dns_stream_stop(s); if (s->manager) { @@ -339,13 +345,23 @@ DnsStream *dns_stream_free(DnsStream *s) { free(s); - return 0; + return NULL; } -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_unref); + +DnsStream *dns_stream_ref(DnsStream *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + s->n_ref++; + + return s; +} int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - _cleanup_(dns_stream_freep) DnsStream *s = NULL; + _cleanup_(dns_stream_unrefp) DnsStream *s = NULL; int r; assert(m); @@ -358,6 +374,7 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { if (!s) return -ENOMEM; + s->n_ref = 1; s->fd = -1; s->protocol = protocol; diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index 5ccc842249..e6569678fa 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -26,8 +26,16 @@ typedef struct DnsStream DnsStream; #include "resolved-dns-packet.h" #include "resolved-dns-transaction.h" +/* Streams are used by three subsystems: + * + * 1. The normal transaction logic when doing a DNS or LLMNR lookup via TCP + * 2. The LLMNR logic when accepting a TCP-based lookup + * 3. The DNS stub logic when accepting a TCP-based lookup + */ + struct DnsStream { Manager *manager; + int n_ref; DnsProtocol protocol; @@ -50,12 +58,23 @@ struct DnsStream { int (*on_packet)(DnsStream *s); int (*complete)(DnsStream *s, int error); - DnsTransaction *transaction; + DnsTransaction *transaction; /* when used by the transaction logic */ + DnsQuery *query; /* when used by the DNS stub logic */ LIST_FIELDS(DnsStream, streams); }; int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); -DnsStream *dns_stream_free(DnsStream *s); +DnsStream *dns_stream_unref(DnsStream *s); +DnsStream *dns_stream_ref(DnsStream *s); int dns_stream_write_packet(DnsStream *s, DnsPacket *p); + +static inline bool DNS_STREAM_QUEUED(DnsStream *s) { + assert(s); + + if (s->fd < 0) /* already stopped? */ + return false; + + return !!s->write_packet; +} diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c new file mode 100644 index 0000000000..d263cedcd9 --- /dev/null +++ b/src/resolve/resolved-dns-stub.c @@ -0,0 +1,572 @@ +/*** + 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 "resolved-dns-stub.h" +#include "socket-util.h" + +/* The MTU of the loopback device is 64K on Linux, advertise that as maximum datagram size, but subtract the Ethernet, + * IP and UDP header sizes */ +#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U) + +static int dns_stub_make_reply_packet( + uint16_t id, + int rcode, + DnsQuestion *q, + DnsAnswer *answer, + bool add_opt, /* add an OPT RR to this packet */ + bool edns0_do, /* set the EDNS0 DNSSEC OK bit */ + bool ad, /* set the DNSSEC authenticated data bit */ + DnsPacket **ret) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsResourceRecord *rr; + unsigned c = 0; + int r; + + /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence + * roundtrips aren't expensive. */ + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return r; + + /* If the client didn't do EDNS, clamp the rcode to 4 bit */ + if (!add_opt && rcode > 0xF) + rcode = DNS_RCODE_SERVFAIL; + + DNS_PACKET_HEADER(p)->id = id; + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1 /* qr */, + 0 /* opcode */, + 0 /* aa */, + 0 /* tc */, + 1 /* rd */, + 1 /* ra */, + ad /* ad */, + 0 /* cd */, + rcode)); + + r = dns_packet_append_question(p, q); + if (r < 0) + return r; + DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q)); + + DNS_ANSWER_FOREACH(rr, answer) { + r = dns_question_matches_rr(q, rr, NULL); + if (r < 0) + return r; + if (r > 0) + goto add; + + r = dns_question_matches_cname_or_dname(q, rr, NULL); + if (r < 0) + return r; + if (r > 0) + goto add; + + continue; + add: + r = dns_packet_append_rr(p, rr, NULL, NULL); + if (r < 0) + return r; + + c++; + } + DNS_PACKET_HEADER(p)->ancount = htobe16(c); + + if (add_opt) { + r = dns_packet_append_opt(p, ADVERTISE_DATAGRAM_SIZE_MAX, edns0_do, rcode, NULL); + if (r < 0) + return r; + } + + *ret = p; + p = NULL; + + return 0; +} + +static void dns_stub_detach_stream(DnsStream *s) { + assert(s); + + s->complete = NULL; + s->on_packet = NULL; + s->query = NULL; +} + +static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *reply) { + int r; + + assert(m); + assert(p); + assert(reply); + + if (s) + r = dns_stream_write_packet(s, reply); + else { + int fd; + + /* Truncate the message to the right size */ + if (reply->size > DNS_PACKET_PAYLOAD_SIZE_MAX(p)) { + dns_packet_truncate(reply, DNS_PACKET_UNICAST_SIZE_MAX); + DNS_PACKET_HEADER(reply)->flags = htobe16(be16toh(DNS_PACKET_HEADER(reply)->flags) | DNS_PACKET_FLAG_TC); + } + + fd = manager_dns_stub_udp_fd(m); + if (fd < 0) + return log_debug_errno(fd, "Failed to get reply socket: %m"); + + /* Note that it is essential here that we explicitly choose the source IP address for this packet. This + * is because otherwise the kernel will choose it automatically based on the routing table and will + * thus pick 127.0.0.1 rather than 127.0.0.53. */ + + r = manager_send(m, fd, LOOPBACK_IFINDEX, p->family, &p->sender, p->sender_port, &p->destination, reply); + } + if (r < 0) + return log_debug_errno(r, "Failed to send reply packet: %m"); + + return 0; +} + +static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode) { + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + int r; + + assert(m); + assert(p); + + r = dns_stub_make_reply_packet(DNS_PACKET_ID(p), rcode, p->question, NULL, !!p->opt, DNS_PACKET_DO(p), false, &reply); + if (r < 0) + return log_debug_errno(r, "Failed to build failure packet: %m"); + + return dns_stub_send(m, s, p, reply); +} + +static void dns_stub_query_complete(DnsQuery *q) { + int r; + + assert(q); + assert(q->request_dns_packet); + + switch (q->state) { + + case DNS_TRANSACTION_SUCCESS: { + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + + r = dns_stub_make_reply_packet( + DNS_PACKET_ID(q->request_dns_packet), + q->answer_rcode, + q->question_idna, + q->answer, + !!q->request_dns_packet->opt, + DNS_PACKET_DO(q->request_dns_packet), + DNS_PACKET_DO(q->request_dns_packet) && q->answer_authenticated, + &reply); + if (r < 0) { + log_debug_errno(r, "Failed to build reply packet: %m"); + break; + } + + (void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, reply); + break; + } + + case DNS_TRANSACTION_RCODE_FAILURE: + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode); + break; + + case DNS_TRANSACTION_NOT_FOUND: + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN); + break; + + case DNS_TRANSACTION_TIMEOUT: + case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: + /* Propagate a timeout as a no packet, i.e. that the client also gets a timeout */ + break; + + case DNS_TRANSACTION_NO_SERVERS: + case DNS_TRANSACTION_INVALID_REPLY: + case DNS_TRANSACTION_ERRNO: + case DNS_TRANSACTION_ABORTED: + case DNS_TRANSACTION_DNSSEC_FAILED: + case DNS_TRANSACTION_NO_TRUST_ANCHOR: + case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: + case DNS_TRANSACTION_NETWORK_DOWN: + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL); + break; + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + default: + assert_not_reached("Impossible state"); + } + + /* If there's a packet to write set, let's leave the stream around */ + if (q->request_dns_stream && DNS_STREAM_QUEUED(q->request_dns_stream)) { + + /* Detach the stream from our query (make it an orphan), but do not drop the reference to it. The + * default completion action of the stream will drop the reference. */ + + dns_stub_detach_stream(q->request_dns_stream); + q->request_dns_stream = NULL; + } + + dns_query_free(q); +} + +static int dns_stub_stream_complete(DnsStream *s, int error) { + assert(s); + + log_debug_errno(error, "DNS TCP connection terminated, destroying query: %m"); + + assert(s->query); + dns_query_free(s->query); + + return 0; +} + +static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) { + DnsQuery *q = NULL; + int r; + + assert(m); + assert(p); + assert(p->protocol == DNS_PROTOCOL_DNS); + + /* Takes ownership of the *s stream object */ + + if (in_addr_is_localhost(p->family, &p->sender) <= 0 || + in_addr_is_localhost(p->family, &p->destination) <= 0) { + log_error("Got packet on unexpected IP range, refusing."); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + goto fail; + } + + r = dns_packet_extract(p); + if (r < 0) { + log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m"); + dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR); + goto fail; + } + + if (!DNS_PACKET_VERSION_SUPPORTED(p)) { + log_debug("Got EDNS OPT field with unsupported version number."); + dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS); + goto fail; + } + + if (dns_type_is_obsolete(p->question->keys[0]->type)) { + log_debug("Got message with obsolete key type, refusing."); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + goto fail; + } + + if (dns_type_is_zone_transer(p->question->keys[0]->type)) { + log_debug("Got request for zone transfer, refusing."); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + goto fail; + } + + if (!DNS_PACKET_RD(p)) { + /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */ + log_debug("Got request with recursion disabled, refusing."); + dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED); + goto fail; + } + + if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { + log_debug("Got request with DNSSEC CD bit set, refusing."); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + goto fail; + } + + r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_NO_CNAME); + if (r < 0) { + log_error_errno(r, "Failed to generate query object: %m"); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + goto fail; + } + + /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */ + q->clamp_ttl = true; + + q->request_dns_packet = dns_packet_ref(p); + q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */ + q->complete = dns_stub_query_complete; + + if (s) { + s->on_packet = NULL; + s->complete = dns_stub_stream_complete; + s->query = q; + } + + r = dns_query_go(q); + if (r < 0) { + log_error_errno(r, "Failed to start query: %m"); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + goto fail; + } + + log_info("Processing query..."); + return; + +fail: + if (s && DNS_STREAM_QUEUED(s)) + dns_stub_detach_stream(s); + + dns_query_free(q); +} + +static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + Manager *m = userdata; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p); + if (r <= 0) + return r; + + if (dns_packet_validate_query(p) > 0) { + log_debug("Got DNS stub UDP query packet for id %u", DNS_PACKET_ID(p)); + + dns_stub_process_query(m, NULL, p); + } else + log_debug("Invalid DNS stub UDP packet, ignoring."); + + return 0; +} + +int manager_dns_stub_udp_fd(Manager *m) { + static const int one = 1; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(53), + .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), + }; + + int r; + + if (m->dns_stub_udp_fd >= 0) + return m->dns_stub_udp_fd; + + m->dns_stub_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->dns_stub_udp_fd < 0) + return -errno; + + r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Make sure no traffic from outside the local host can leak to onto this socket */ + r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->dns_stub_udp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, m->dns_stub_udp_fd, EPOLLIN, on_dns_stub_packet, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp"); + + return m->dns_stub_udp_fd; + +fail: + m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd); + return r; +} + +static int on_dns_stub_stream_packet(DnsStream *s) { + assert(s); + assert(s->read_packet); + + if (dns_packet_validate_query(s->read_packet) > 0) { + log_debug("Got DNS stub TCP query packet for id %u", DNS_PACKET_ID(s->read_packet)); + + dns_stub_process_query(s->manager, s, s->read_packet); + } else + log_debug("Invalid DNS stub TCP packet, ignoring."); + + /* Drop the reference to the stream. Either a query was created and added its own reference to the stream now, + * or that didn't happen in which case we want to free the stream */ + dns_stream_unref(s); + + return 0; +} + +static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + DnsStream *stream; + Manager *m = userdata; + int cfd, r; + + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (cfd < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + r = dns_stream_new(m, &stream, DNS_PROTOCOL_DNS, cfd); + if (r < 0) { + safe_close(cfd); + return r; + } + + stream->on_packet = on_dns_stub_stream_packet; + + /* We let the reference to the stream dangling here, it will either be dropped by the default "complete" action + * of the stream, or by our packet callback, or when the manager is shut down. */ + + return 0; +} + +int manager_dns_stub_tcp_fd(Manager *m) { + static const int one = 1; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), + .in.sin_port = htobe16(53), + }; + + int r; + + if (m->dns_stub_tcp_fd >= 0) + return m->dns_stub_tcp_fd; + + m->dns_stub_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->dns_stub_tcp_fd < 0) + return -errno; + + r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Make sure no traffic from outside the local host can leak to onto this socket */ + r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->dns_stub_tcp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->dns_stub_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, m->dns_stub_tcp_fd, EPOLLIN, on_dns_stub_stream, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp"); + + return m->dns_stub_tcp_fd; + +fail: + m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd); + return r; +} + +int manager_dns_stub_start(Manager *m) { + int r; + + assert(m); + + r = manager_dns_stub_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_dns_stub_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + return 0; + +eaddrinuse: + log_warning("Another process is already listening on 127.0.0.53:53. Turning off local DNS stub support."); + manager_dns_stub_stop(m); + + return 0; +} + +void manager_dns_stub_stop(Manager *m) { + assert(m); + + m->dns_stub_udp_event_source = sd_event_source_unref(m->dns_stub_udp_event_source); + m->dns_stub_tcp_event_source = sd_event_source_unref(m->dns_stub_tcp_event_source); + + m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd); + m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd); +} diff --git a/src/resolve/resolved-dns-stub.h b/src/resolve/resolved-dns-stub.h new file mode 100644 index 0000000000..fce4d25ede --- /dev/null +++ b/src/resolve/resolved-dns-stub.h @@ -0,0 +1,31 @@ +#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" + +/* 127.0.0.53 in native endian */ +#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U) + +int manager_dns_stub_udp_fd(Manager *m); +int manager_dns_stub_tcp_fd(Manager *m); + +void manager_dns_stub_stop(Manager *m); +int manager_dns_stub_start(Manager *m); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 2d1767be0a..09f60d3e76 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -60,7 +60,14 @@ static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { static void dns_transaction_close_connection(DnsTransaction *t) { assert(t); - t->stream = dns_stream_free(t->stream); + if (t->stream) { + /* Let's detach the stream from our transaction, in case something else keeps a reference to it. */ + t->stream->complete = NULL; + t->stream->on_packet = NULL; + t->stream->transaction = NULL; + t->stream = dns_stream_unref(t->stream); + } + t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); t->dns_udp_fd = safe_close(t->dns_udp_fd); } @@ -444,7 +451,7 @@ static int on_stream_complete(DnsStream *s, int error) { t = s->transaction; p = dns_packet_ref(s->read_packet); - t->stream = dns_stream_free(t->stream); + dns_transaction_close_connection(t); if (ERRNO_IS_DISCONNECT(error)) { usec_t usec; @@ -556,7 +563,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { r = dns_stream_write_packet(t->stream, t->sent); if (r < 0) { - t->stream = dns_stream_free(t->stream); + t->stream = dns_stream_unref(t->stream); return r; } diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index acce8682de..364812250f 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -230,6 +230,9 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_ if (sz != FAMILY_ADDRESS_SIZE(family)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); + if (!dns_server_address_valid(family, d)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNS server address"); + r = sd_bus_message_exit_container(message); if (r < 0) return r; diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index 8b1d71a3eb..3516af58ee 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -91,18 +91,19 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u DnsScope *scope; int r; + assert(s); + assert(fd >= 0); + assert(m); + r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); if (r <= 0) return r; scope = manager_find_scope(m, p); - if (!scope) { + if (!scope) log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p)); + else if (dns_packet_validate_reply(p) > 0) { + log_debug("Got LLMNR UDP reply packet for id %u", DNS_PACKET_ID(p)); dns_scope_check_conflicts(scope, p); @@ -111,7 +112,7 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u dns_transaction_process_reply(t, p); } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p)); + log_debug("Got LLMNR UDP query packet for id %u", DNS_PACKET_ID(p)); dns_scope_process_query(scope, NULL, p); } else @@ -283,25 +284,19 @@ static int on_llmnr_stream_packet(DnsStream *s) { DnsScope *scope; assert(s); + assert(s->read_packet); scope = manager_find_scope(s->manager, s->read_packet); - if (!scope) { + if (!scope) log_warning("Got LLMNR TCP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_query(s->read_packet) > 0) { - log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); + else if (dns_packet_validate_query(s->read_packet) > 0) { + log_debug("Got LLMNR TCP query packet for id %u", DNS_PACKET_ID(s->read_packet)); dns_scope_process_query(scope, s, s->read_packet); - - /* If no reply packet was set, we free the stream */ - if (s->write_packet) - return 0; } else - log_debug("Invalid LLMNR TCP packet."); + log_debug("Invalid LLMNR TCP packet, ignoring."); - dns_stream_free(s); + dns_stream_unref(s); return 0; } diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index e8811fa1d8..30036049da 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -36,6 +36,7 @@ #include "random-util.h" #include "resolved-bus.h" #include "resolved-conf.h" +#include "resolved-dns-stub.h" #include "resolved-etc-hosts.h" #include "resolved-llmnr.h" #include "resolved-manager.h" @@ -493,6 +494,7 @@ int manager_new(Manager **ret) { m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; + m->dns_stub_udp_fd = m->dns_stub_tcp_fd = -1; m->hostname_fd = -1; m->llmnr_support = RESOLVE_SUPPORT_YES; @@ -555,6 +557,10 @@ int manager_start(Manager *m) { assert(m); + r = manager_dns_stub_start(m); + if (r < 0) + return r; + r = manager_llmnr_start(m); if (r < 0) return r; @@ -584,6 +590,11 @@ Manager *manager_free(Manager *m) { dns_scope_free(m->unicast_scope); + /* At this point only orphaned streams should remain. All others should have been freed already by their + * owners */ + while (m->dns_streams) + dns_stream_unref(m->dns_streams); + hashmap_free(m->links); hashmap_free(m->dns_transactions); @@ -595,6 +606,7 @@ Manager *manager_free(Manager *m) { manager_llmnr_stop(m); manager_mdns_stop(m); + manager_dns_stub_stop(m); sd_bus_slot_unref(m->prepare_for_sleep_slot); sd_event_source_unref(m->bus_retry_event_source); @@ -809,7 +821,14 @@ int manager_write(Manager *m, int fd, DnsPacket *p) { return 0; } -static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { +static int manager_ipv4_send( + Manager *m, + int fd, + int ifindex, + const struct in_addr *destination, + uint16_t port, + const struct in_addr *source, + DnsPacket *p) { union sockaddr_union sa = { .in.sin_family = AF_INET, }; @@ -822,14 +841,14 @@ static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_ad assert(m); assert(fd >= 0); - assert(addr); + assert(destination); assert(port > 0); assert(p); iov.iov_base = DNS_PACKET_DATA(p); iov.iov_len = p->size; - sa.in.sin_addr = *addr; + sa.in.sin_addr = *destination; sa.in.sin_port = htobe16(port), mh.msg_iov = &iov; @@ -853,12 +872,23 @@ static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_ad pi = (struct in_pktinfo*) CMSG_DATA(cmsg); pi->ipi_ifindex = ifindex; + + if (source) + pi->ipi_spec_dst = *source; } return sendmsg_loop(fd, &mh, 0); } -static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) { +static int manager_ipv6_send( + Manager *m, + int fd, + int ifindex, + const struct in6_addr *destination, + uint16_t port, + const struct in6_addr *source, + DnsPacket *p) { + union sockaddr_union sa = { .in6.sin6_family = AF_INET6, }; @@ -871,14 +901,14 @@ static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_a assert(m); assert(fd >= 0); - assert(addr); + assert(destination); assert(port > 0); assert(p); iov.iov_base = DNS_PACKET_DATA(p); iov.iov_len = p->size; - sa.in6.sin6_addr = *addr; + sa.in6.sin6_addr = *destination; sa.in6.sin6_port = htobe16(port), sa.in6.sin6_scope_id = ifindex; @@ -903,24 +933,36 @@ static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_a pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); pi->ipi6_ifindex = ifindex; + + if (source) + pi->ipi6_addr = *source; } return sendmsg_loop(fd, &mh, 0); } -int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) { +int manager_send( + Manager *m, + int fd, + int ifindex, + int family, + const union in_addr_union *destination, + uint16_t port, + const union in_addr_union *source, + DnsPacket *p) { + assert(m); assert(fd >= 0); - assert(addr); + assert(destination); assert(port > 0); assert(p); log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); if (family == AF_INET) - return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p); + return manager_ipv4_send(m, fd, ifindex, &destination->in, port, &source->in, p); if (family == AF_INET6) - return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p); + return manager_ipv6_send(m, fd, ifindex, &destination->in6, port, &source->in6, p); return -EAFNOSUPPORT; } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 0821904e84..114fec7927 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -128,6 +128,13 @@ struct Manager { Set* etc_hosts_by_address; Hashmap* etc_hosts_by_name; usec_t etc_hosts_last, etc_hosts_mtime; + + /* Local DNS stub on 127.0.0.53:53 */ + int dns_stub_udp_fd; + int dns_stub_tcp_fd; + + sd_event_source *dns_stub_udp_event_source; + sd_event_source *dns_stub_tcp_event_source; }; /* Manager */ @@ -140,7 +147,7 @@ int manager_start(Manager *m); uint32_t manager_find_mtu(Manager *m); int manager_write(Manager *m, int fd, DnsPacket *p); -int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p); +int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p); int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 4eb5bba660..31b25ca50f 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -194,10 +194,13 @@ static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *doma Iterator i; fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" - "# Third party programs must not access this file directly, but\n" - "# only through the symlink at /etc/resolv.conf. To manage\n" - "# resolv.conf(5) in a different way, replace the symlink by a\n" - "# static file or a different symlink.\n\n", f); + "# This is a dynamic resolv.conf file for connecting local clients directly to\n" + "# all known DNS servers.\n#\n" + "# Third party programs must not access this file directly, but only through the\n" + "# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,\n" + "# replace this symlink by a static file or a different symlink.\n#\n" + "# See systemd-resolved.service(8) for details about the supported modes of\n" + "# operation for /etc/resolv.conf.\n\n", f); if (ordered_set_isempty(dns)) fputs("# No DNS servers known.\n", f); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index 3a47b82d8a..deb75f9ae5 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -67,7 +67,11 @@ int main(int argc, char *argv[]) { goto finish; } - r = drop_privileges(uid, gid, 0); + /* Drop privileges, but keep three caps. Note that we drop those too, later on (see below) */ + r = drop_privileges(uid, gid, + (UINT64_C(1) << CAP_NET_RAW)| /* needed for SO_BINDTODEVICE */ + (UINT64_C(1) << CAP_NET_BIND_SERVICE)| /* needed to bind on port 53 */ + (UINT64_C(1) << CAP_SETPCAP) /* needed in order to drop the caps later */); if (r < 0) goto finish; @@ -88,6 +92,13 @@ int main(int argc, char *argv[]) { /* Write finish default resolv.conf to avoid a dangling symlink */ (void) manager_write_resolv_conf(m); + /* Let's drop the remaining caps now */ + r = capability_bounding_set_drop(0, true); + if (r < 0) { + log_error_errno(r, "Failed to drop remaining caps: %m"); + goto finish; + } + sd_notify(false, "READY=1\n" "STATUS=Processing requests..."); diff --git a/tmpfiles.d/etc.conf.m4 b/tmpfiles.d/etc.conf.m4 index ef7b9b9541..064eae94f1 100644 --- a/tmpfiles.d/etc.conf.m4 +++ b/tmpfiles.d/etc.conf.m4 @@ -14,7 +14,7 @@ m4_ifdef(`HAVE_SMACK_RUN_LABEL', t /etc/mtab - - - - security.SMACK64=_ )m4_dnl m4_ifdef(`ENABLE_RESOLVED', -L! /etc/resolv.conf - - - - ../run/systemd/resolve/resolv.conf +L! /etc/resolv.conf - - - - ../usr/lib/systemd/resolv.conf )m4_dnl C /etc/nsswitch.conf - - - - m4_ifdef(`HAVE_PAM', diff --git a/units/systemd-resolved.service.m4.in b/units/systemd-resolved.service.m4.in index a9cc3988ed..15ab56a066 100644 --- a/units/systemd-resolved.service.m4.in +++ b/units/systemd-resolved.service.m4.in @@ -23,7 +23,7 @@ Type=notify Restart=always RestartSec=0 ExecStart=@rootlibexecdir@/systemd-resolved -CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER +CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_NET_RAW CAP_NET_BIND_SERVICE ProtectSystem=full ProtectHome=yes WatchdogSec=3min -- cgit v1.2.3