]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/resolve/resolved-manager.c
resolved: make sure order of dns servers is stable
[thirdparty/systemd.git] / src / resolve / resolved-manager.c
index 13852192c4d10638d1f9ca898c3e8637d72f5ff4..c3c61a0893c40511905ddcb83d509295be31cb93 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
  ***/
 
-#include <resolv.h>
-#include <sys/ioctl.h>
-#include <poll.h>
 #include <netinet/in.h>
+#include <poll.h>
+#include <sys/ioctl.h>
 
-#include "netlink-util.h"
-#include "network-internal.h"
-#include "socket-util.h"
 #include "af-list.h"
-#include "utf8.h"
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "fd-util.h"
 #include "fileio-label.h"
+#include "hostname-util.h"
+#include "io-util.h"
+#include "netlink-util.h"
+#include "network-internal.h"
 #include "ordered-set.h"
+#include "parse-util.h"
 #include "random-util.h"
-#include "hostname-util.h"
-
-#include "dns-domain.h"
-#include "resolved-conf.h"
 #include "resolved-bus.h"
-#include "resolved-manager.h"
+#include "resolved-conf.h"
 #include "resolved-llmnr.h"
+#include "resolved-manager.h"
+#include "resolved-resolv-conf.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "utf8.h"
 
 #define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC)
 
@@ -471,10 +476,7 @@ int manager_new(Manager **ret) {
 
         m->llmnr_support = SUPPORT_YES;
         m->read_resolv_conf = true;
-
-        r = manager_parse_dns_server(m, DNS_SERVER_FALLBACK, DNS_SERVERS);
-        if (r < 0)
-                return r;
+        m->need_builtin_fallbacks = true;
 
         r = sd_event_default(&m->event);
         if (r < 0)
@@ -531,15 +533,15 @@ Manager *manager_free(Manager *m) {
         if (!m)
                 return NULL;
 
+        manager_flush_dns_servers(m, DNS_SERVER_SYSTEM);
+        manager_flush_dns_servers(m, DNS_SERVER_FALLBACK);
+
         while ((l = hashmap_first(m->links)))
                link_free(l);
 
         while (m->dns_queries)
                 dns_query_free(m->dns_queries);
 
-        manager_flush_dns_servers(m, DNS_SERVER_SYSTEM);
-        manager_flush_dns_servers(m, DNS_SERVER_FALLBACK);
-
         dns_scope_free(m->unicast_scope);
 
         hashmap_free(m->links);
@@ -548,6 +550,9 @@ Manager *manager_free(Manager *m) {
         sd_event_source_unref(m->network_event_source);
         sd_network_monitor_unref(m->network_monitor);
 
+        sd_netlink_unref(m->rtnl);
+        sd_event_source_unref(m->rtnl_event_source);
+
         manager_llmnr_stop(m);
 
         sd_bus_slot_unref(m->prepare_for_sleep_slot);
@@ -571,295 +576,6 @@ Manager *manager_free(Manager *m) {
         return NULL;
 }
 
-int manager_read_resolv_conf(Manager *m) {
-        _cleanup_fclose_ FILE *f = NULL;
-        struct stat st, own;
-        char line[LINE_MAX];
-        DnsServer *s, *nx;
-        usec_t t;
-        int r;
-
-        assert(m);
-
-        /* Reads the system /etc/resolv.conf, if it exists and is not
-         * symlinked to our own resolv.conf instance */
-
-        if (!m->read_resolv_conf)
-                return 0;
-
-        r = stat("/etc/resolv.conf", &st);
-        if (r < 0) {
-                if (errno != ENOENT)
-                        log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m");
-                r = -errno;
-                goto clear;
-        }
-
-        /* Have we already seen the file? */
-        t = timespec_load(&st.st_mtim);
-        if (t == m->resolv_conf_mtime)
-                return 0;
-
-        m->resolv_conf_mtime = t;
-
-        /* Is it symlinked to our own file? */
-        if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 &&
-            st.st_dev == own.st_dev &&
-            st.st_ino == own.st_ino) {
-                r = 0;
-                goto clear;
-        }
-
-        f = fopen("/etc/resolv.conf", "re");
-        if (!f) {
-                if (errno != ENOENT)
-                        log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m");
-                r = -errno;
-                goto clear;
-        }
-
-        if (fstat(fileno(f), &st) < 0) {
-                log_error_errno(errno, "Failed to stat open file: %m");
-                r = -errno;
-                goto clear;
-        }
-
-        LIST_FOREACH(servers, s, m->dns_servers)
-                s->marked = true;
-
-        FOREACH_LINE(line, f, r = -errno; goto clear) {
-                union in_addr_union address;
-                int family;
-                char *l;
-                const char *a;
-
-                truncate_nl(line);
-
-                l = strstrip(line);
-                if (*l == '#' || *l == ';')
-                        continue;
-
-                a = first_word(l, "nameserver");
-                if (!a)
-                        continue;
-
-                r = in_addr_from_string_auto(a, &family, &address);
-                if (r < 0) {
-                        log_warning("Failed to parse name server %s.", a);
-                        continue;
-                }
-
-                LIST_FOREACH(servers, s, m->dns_servers)
-                        if (s->family == family && in_addr_equal(family, &s->address, &address) > 0)
-                                break;
-
-                if (s)
-                        s->marked = false;
-                else {
-                        r = dns_server_new(m, NULL, DNS_SERVER_SYSTEM, NULL, family, &address);
-                        if (r < 0)
-                                goto clear;
-                }
-        }
-
-        LIST_FOREACH_SAFE(servers, s, nx, m->dns_servers)
-                if (s->marked) {
-                        LIST_REMOVE(servers, m->dns_servers, s);
-                        dns_server_unref(s);
-                }
-
-        /* Whenever /etc/resolv.conf changes, start using the first
-         * DNS server of it. This is useful to deal with broken
-         * network managing implementations (like NetworkManager),
-         * that when connecting to a VPN place both the VPN DNS
-         * servers and the local ones in /etc/resolv.conf. Without
-         * resetting the DNS server to use back to the first entry we
-         * will continue to use the local one thus being unable to
-         * resolve VPN domains. */
-        manager_set_dns_server(m, m->dns_servers);
-
-        return 0;
-
-clear:
-        while (m->dns_servers) {
-                s = m->dns_servers;
-
-                LIST_REMOVE(servers, m->dns_servers, s);
-                dns_server_unref(s);
-        }
-
-        return r;
-}
-
-static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
-        _cleanup_free_ char *t  = NULL;
-        int r;
-
-        assert(s);
-        assert(f);
-        assert(count);
-
-        r = in_addr_to_string(s->family, &s->address, &t);
-        if (r < 0) {
-                log_warning_errno(r, "Invalid DNS address. Ignoring: %m");
-                return;
-        }
-
-        if (*count == MAXNS)
-                fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
-
-        fprintf(f, "nameserver %s\n", t);
-        (*count) ++;
-}
-
-static void write_resolv_conf_search(
-                const char *domain, FILE *f,
-                unsigned *count,
-                unsigned *length) {
-
-        assert(domain);
-        assert(f);
-        assert(length);
-
-        if (*count >= MAXDNSRCH ||
-            *length + strlen(domain) > 256) {
-                if (*count == MAXDNSRCH)
-                        fputs(" # Too many search domains configured, remaining ones ignored.", f);
-                if (*length <= 256)
-                        fputs(" # Total length of all search domains is too long, remaining ones ignored.", f);
-
-                return;
-        }
-
-        fprintf(f, " %s", domain);
-
-        (*length) += strlen(domain);
-        (*count) ++;
-}
-
-static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) {
-        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);
-
-        if (ordered_set_isempty(dns))
-                fputs("# No DNS servers known.\n", f);
-        else {
-                DnsServer *s;
-                unsigned count = 0;
-
-                ORDERED_SET_FOREACH(s, dns, i)
-                        write_resolv_conf_server(s, f, &count);
-        }
-
-        if (!ordered_set_isempty(domains)) {
-                unsigned length = 0, count = 0;
-                char *domain;
-
-                fputs("search", f);
-                ORDERED_SET_FOREACH(domain, domains, i)
-                        write_resolv_conf_search(domain, f, &count, &length);
-                fputs("\n", f);
-        }
-
-        return fflush_and_check(f);
-}
-
-int manager_write_resolv_conf(Manager *m) {
-        static const char path[] = "/run/systemd/resolve/resolv.conf";
-        _cleanup_free_ char *temp_path = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL;
-        DnsServer *s;
-        Iterator i;
-        Link *l;
-        int r;
-
-        assert(m);
-
-        /* Read the system /etc/resolv.conf first */
-        manager_read_resolv_conf(m);
-
-        /* Add the full list to a set, to filter out duplicates */
-        dns = ordered_set_new(&dns_server_hash_ops);
-        if (!dns)
-                return -ENOMEM;
-
-        domains = ordered_set_new(&dns_name_hash_ops);
-        if (!domains)
-                return -ENOMEM;
-
-        /* First add the system-wide servers */
-        LIST_FOREACH(servers, s, m->dns_servers) {
-                r = ordered_set_put(dns, s);
-                if (r == -EEXIST)
-                        continue;
-                if (r < 0)
-                        return r;
-        }
-
-        /* Then, add the per-link servers and domains */
-        HASHMAP_FOREACH(l, m->links, i) {
-                char **domain;
-
-                LIST_FOREACH(servers, s, l->dns_servers) {
-                        r = ordered_set_put(dns, s);
-                        if (r == -EEXIST)
-                                continue;
-                        if (r < 0)
-                                return r;
-                }
-
-                if (!l->unicast_scope)
-                        continue;
-
-                STRV_FOREACH(domain, l->unicast_scope->domains) {
-                        r = ordered_set_put(domains, *domain);
-                        if (r == -EEXIST)
-                                continue;
-                        if (r < 0)
-                                return r;
-                }
-        }
-
-        /* If we found nothing, add the fallback servers */
-        if (ordered_set_isempty(dns)) {
-                LIST_FOREACH(servers, s, m->fallback_dns_servers) {
-                        r = ordered_set_put(dns, s);
-                        if (r == -EEXIST)
-                                continue;
-                        if (r < 0)
-                                return r;
-                }
-        }
-
-        r = fopen_temporary_label(path, path, &f, &temp_path);
-        if (r < 0)
-                return r;
-
-        fchmod(fileno(f), 0644);
-
-        r = write_resolv_conf_contents(f, dns, domains);
-        if (r < 0)
-                goto fail;
-
-        if (rename(temp_path, path) < 0) {
-                r = -errno;
-                goto fail;
-        }
-
-        return 0;
-
-fail:
-        (void) unlink(path);
-        (void) unlink(temp_path);
-        return r;
-}
-
 int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
         _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
         union {
@@ -1167,97 +883,6 @@ int manager_send(Manager *m, int fd, int ifindex, int family, const union in_add
         return -EAFNOSUPPORT;
 }
 
-DnsServer* manager_find_dns_server(Manager *m, int family, const union in_addr_union *in_addr) {
-        DnsServer *s;
-
-        assert(m);
-        assert(in_addr);
-
-        LIST_FOREACH(servers, s, m->dns_servers)
-                if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0)
-                        return s;
-
-        LIST_FOREACH(servers, s, m->fallback_dns_servers)
-                if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0)
-                        return s;
-
-        return NULL;
-}
-
-DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) {
-        assert(m);
-
-        if (m->current_dns_server == s)
-                return s;
-
-        if (s) {
-                _cleanup_free_ char *ip = NULL;
-
-                in_addr_to_string(s->family, &s->address, &ip);
-                log_info("Switching to system DNS server %s.", strna(ip));
-        }
-
-        m->current_dns_server = s;
-
-        if (m->unicast_scope)
-                dns_cache_flush(&m->unicast_scope->cache);
-
-        return s;
-}
-
-DnsServer *manager_get_dns_server(Manager *m) {
-        Link *l;
-        assert(m);
-
-        /* Try to read updates resolv.conf */
-        manager_read_resolv_conf(m);
-
-        if (!m->current_dns_server)
-                manager_set_dns_server(m, m->dns_servers);
-
-        if (!m->current_dns_server) {
-                bool found = false;
-                Iterator i;
-
-                /* No DNS servers configured, let's see if there are
-                 * any on any links. If not, we use the fallback
-                 * servers */
-
-                HASHMAP_FOREACH(l, m->links, i)
-                        if (l->dns_servers) {
-                                found = true;
-                                break;
-                        }
-
-                if (!found)
-                        manager_set_dns_server(m, m->fallback_dns_servers);
-        }
-
-        return m->current_dns_server;
-}
-
-void manager_next_dns_server(Manager *m) {
-        assert(m);
-
-        /* If there's currently no DNS server set, then the next
-         * manager_get_dns_server() will find one */
-        if (!m->current_dns_server)
-                return;
-
-        /* Change to the next one */
-        if (m->current_dns_server->servers_next) {
-                manager_set_dns_server(m, m->current_dns_server->servers_next);
-                return;
-        }
-
-        /* If there was no next one, then start from the beginning of
-         * the list */
-        if (m->current_dns_server->type == DNS_SERVER_FALLBACK)
-                manager_set_dns_server(m, m->fallback_dns_servers);
-        else
-                manager_set_dns_server(m, m->dns_servers);
-}
-
 uint32_t manager_find_mtu(Manager *m) {
         uint32_t mtu = 0;
         Link *l;
@@ -1411,28 +1036,6 @@ void manager_verify_all(Manager *m) {
                 dns_zone_verify_all(&s->zone);
 }
 
-void manager_flush_dns_servers(Manager *m, DnsServerType t) {
-        DnsServer *s;
-
-        assert(m);
-
-        if (t == DNS_SERVER_SYSTEM)
-                while (m->dns_servers) {
-                        s = m->dns_servers;
-
-                        LIST_REMOVE(servers, m->dns_servers, s);
-                        dns_server_unref(s);
-                }
-
-        if (t == DNS_SERVER_FALLBACK)
-                while (m->fallback_dns_servers) {
-                        s = m->fallback_dns_servers;
-
-                        LIST_REMOVE(servers, m->fallback_dns_servers, s);
-                        dns_server_unref(s);
-                }
-}
-
 int manager_is_own_hostname(Manager *m, const char *name) {
         int r;