]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: support IPv6 DNS servers on the local link
authorLennart Poettering <lennart@poettering.net>
Fri, 3 Jun 2016 19:29:14 +0000 (21:29 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 6 Jun 2016 17:17:38 +0000 (19:17 +0200)
Make sure we can parse DNS server addresses that use the "zone id" syntax for
local link addresses, i.e. "fe80::c256:27ff:febb:12f%wlp3s0", when reading
/etc/resolv.conf.

Also make sure we spit this out correctly again when writing /etc/resolv.conf
and via the bus.

Fixes: #3359
12 files changed:
src/basic/in-addr-util.c
src/basic/in-addr-util.h
src/resolve/resolved-bus.c
src/resolve/resolved-conf.c
src/resolve/resolved-dns-scope.c
src/resolve/resolved-dns-server.c
src/resolve/resolved-dns-server.h
src/resolve/resolved-link-bus.c
src/resolve/resolved-link.c
src/resolve/resolved-manager.c
src/resolve/resolved-resolv-conf.c
src/test/test-socket-util.c

index 245107ebb8458c02c652e6e78d8b609f44a25086..e5a9daeab8f4e8629279a995f7b521a78ed0ea94 100644 (file)
 #include <arpa/inet.h>
 #include <endian.h>
 #include <errno.h>
+#include <net/if.h>
 #include <stdint.h>
 #include <stdlib.h>
 
 #include "alloc-util.h"
 #include "in-addr-util.h"
 #include "macro.h"
+#include "parse-util.h"
 #include "util.h"
 
 int in_addr_is_null(int family, const union in_addr_union *u) {
@@ -224,6 +226,48 @@ int in_addr_to_string(int family, const union in_addr_union *u, char **ret) {
         return 0;
 }
 
+int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret) {
+        size_t l;
+        char *x;
+        int r;
+
+        assert(u);
+        assert(ret);
+
+        /* Much like in_addr_to_string(), but optionally appends the zone interface index to the address, to properly
+         * handle IPv6 link-local addresses. */
+
+        if (family != AF_INET6)
+                goto fallback;
+        if (ifindex <= 0)
+                goto fallback;
+
+        r = in_addr_is_link_local(family, u);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                goto fallback;
+
+        l = INET6_ADDRSTRLEN + 1 + DECIMAL_STR_MAX(ifindex) + 1;
+        x = new(char, l);
+        if (!x)
+                return -ENOMEM;
+
+        errno = 0;
+        if (!inet_ntop(family, u, x, l)) {
+                free(x);
+                return errno > 0 ? -errno : -EINVAL;
+        }
+
+        sprintf(strchr(x, 0), "%%%i", ifindex);
+        *ret = x;
+
+        return 0;
+
+fallback:
+        return in_addr_to_string(family, u, ret);
+}
+
 int in_addr_from_string(int family, const char *s, union in_addr_union *ret) {
 
         assert(s);
@@ -261,6 +305,47 @@ int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *re
         return -EINVAL;
 }
 
+int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex) {
+        const char *suffix;
+        int r, ifi = 0;
+
+        assert(s);
+        assert(family);
+        assert(ret);
+
+        /* Similar to in_addr_from_string_auto() but also parses an optionally appended IPv6 zone suffix ("scope id")
+         * if one is found. */
+
+        suffix = strchr(s, '%');
+        if (suffix) {
+
+                if (ifindex) {
+                        /* If we shall return the interface index, try to parse it */
+                        r = parse_ifindex(suffix + 1, &ifi);
+                        if (r < 0) {
+                                unsigned u;
+
+                                u = if_nametoindex(suffix + 1);
+                                if (u <= 0)
+                                        return -errno;
+
+                                ifi = (int) u;
+                        }
+                }
+
+                s = strndupa(s, suffix - s);
+        }
+
+        r = in_addr_from_string_auto(s, family, ret);
+        if (r < 0)
+                return r;
+
+        if (ifindex)
+                *ifindex = ifi;
+
+        return r;
+}
+
 unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) {
         assert(addr);
 
index 17798ce8169fc8cd35f3b170a5fa9fc148c98ab3..7fdf3a15fc6f4db56dc920ee9001c398b95ba780 100644 (file)
@@ -43,8 +43,10 @@ int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_
 int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
 int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
 int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
+int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret);
 int in_addr_from_string(int family, const char *s, union in_addr_union *ret);
 int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret);
+int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex);
 unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr);
 struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
 int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
index 33f7c615576c94c0bd7b0675cd89f878bd826870..6d4e5746f76d13381102cf333fd0af2c14f2fbb6 100644 (file)
@@ -1221,7 +1221,7 @@ int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex
                 return r;
 
         if (with_ifindex) {
-                r = sd_bus_message_append(reply, "i", s->link ? s->link->ifindex : 0);
+                r = sd_bus_message_append(reply, "i", dns_server_ifindex(s));
                 if (r < 0)
                         return r;
         }
index 990dc03b60b1a2a6f813f487e1083984863824a3..fecf7ecccf4e55f79d4bdee25d12d1ff0933137a 100644 (file)
 
 int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) {
         union in_addr_union address;
-        int family, r;
+        int family, r, ifindex = 0;
         DnsServer *s;
 
         assert(m);
         assert(word);
 
-        r = in_addr_from_string_auto(word, &family, &address);
+        r = in_addr_ifindex_from_string_auto(word, &family, &address, &ifindex);
         if (r < 0)
                 return r;
 
         /* Filter out duplicates */
-        s = dns_server_find(manager_get_first_dns_server(m, type), family, &address);
+        s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, ifindex);
         if (s) {
                 /*
                  * Drop the marker. This is used to find the servers
@@ -50,7 +50,7 @@ int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char
                 return 0;
         }
 
-        return dns_server_new(m, NULL, type, NULL, family, &address);
+        return dns_server_new(m, NULL, type, NULL, family, &address, ifindex);
 }
 
 int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) {
@@ -70,7 +70,7 @@ int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, con
 
                 r = manager_add_dns_server_by_string(m, type, word);
                 if (r < 0)
-                        log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word);
+                        log_warning_errno(r, "Failed to add DNS server address '%s', ignoring: %m", word);
         }
 
         return 0;
@@ -125,7 +125,7 @@ int manager_parse_search_domains_and_warn(Manager *m, const char *string) {
 
                 r = manager_add_search_domain_by_string(m, word);
                 if (r < 0)
-                        log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word);
+                        log_warning_errno(r, "Failed to add search domain '%s', ignoring: %m", word);
         }
 
         return 0;
index 66e4585c18794f30f8b26fcc75d330e7c0800b74..6a69d7b7c2ebde485e0d5c25ca4d3978b79312ae 100644 (file)
@@ -307,7 +307,7 @@ static int dns_scope_socket(
         union sockaddr_union sa = {};
         socklen_t salen;
         static const int one = 1;
-        int ret, r;
+        int ret, r, ifindex;
 
         assert(s);
 
@@ -315,6 +315,8 @@ static int dns_scope_socket(
                 assert(family == AF_UNSPEC);
                 assert(!address);
 
+                ifindex = dns_server_ifindex(server);
+
                 sa.sa.sa_family = server->family;
                 if (server->family == AF_INET) {
                         sa.in.sin_port = htobe16(port);
@@ -323,7 +325,7 @@ static int dns_scope_socket(
                 } else if (server->family == AF_INET6) {
                         sa.in6.sin6_port = htobe16(port);
                         sa.in6.sin6_addr = server->address.in6;
-                        sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
+                        sa.in6.sin6_scope_id = ifindex;
                         salen = sizeof(sa.in6);
                 } else
                         return -EAFNOSUPPORT;
@@ -332,6 +334,7 @@ static int dns_scope_socket(
                 assert(address);
 
                 sa.sa.sa_family = family;
+                ifindex = s->link ? s->link->ifindex : 0;
 
                 if (family == AF_INET) {
                         sa.in.sin_port = htobe16(port);
@@ -340,7 +343,7 @@ static int dns_scope_socket(
                 } else if (family == AF_INET6) {
                         sa.in6.sin6_port = htobe16(port);
                         sa.in6.sin6_addr = address->in6;
-                        sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
+                        sa.in6.sin6_scope_id = ifindex;
                         salen = sizeof(sa.in6);
                 } else
                         return -EAFNOSUPPORT;
@@ -357,14 +360,14 @@ static int dns_scope_socket(
         }
 
         if (s->link) {
-                uint32_t ifindex = htobe32(s->link->ifindex);
+                be32_t ifindex_be = htobe32(ifindex);
 
                 if (sa.sa.sa_family == AF_INET) {
-                        r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex));
+                        r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_be, sizeof(ifindex_be));
                         if (r < 0)
                                 return -errno;
                 } else if (sa.sa.sa_family == AF_INET6) {
-                        r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex));
+                        r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex_be, sizeof(ifindex_be));
                         if (r < 0)
                                 return -errno;
                 }
index 3095c042db40e1061c8477bf05d41575b8a84a0f..5acfcb42392e3e35f7b656647bae5501300c4cc7 100644 (file)
@@ -43,7 +43,8 @@ int dns_server_new(
                 DnsServerType type,
                 Link *l,
                 int family,
-                const union in_addr_union *in_addr) {
+                const union in_addr_union *in_addr,
+                int ifindex) {
 
         DnsServer *s;
 
@@ -75,6 +76,7 @@ int dns_server_new(
         s->type = type;
         s->family = family;
         s->address = *in_addr;
+        s->ifindex = ifindex;
         s->resend_timeout = DNS_TIMEOUT_MIN_USEC;
 
         switch (type) {
@@ -518,11 +520,24 @@ int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeature
         return dns_packet_append_opt(packet, packet_size, edns_do, NULL);
 }
 
+int dns_server_ifindex(const DnsServer *s) {
+        assert(s);
+
+        /* The link ifindex always takes precedence */
+        if (s->link)
+                return s->link->ifindex;
+
+        if (s->ifindex > 0)
+                return s->ifindex;
+
+        return 0;
+}
+
 const char *dns_server_string(DnsServer *server) {
         assert(server);
 
         if (!server->server_string)
-                (void) in_addr_to_string(server->family, &server->address, &server->server_string);
+                (void) in_addr_ifindex_to_string(server->family, &server->address, dns_server_ifindex(server), &server->server_string);
 
         return strna(server->server_string);
 }
@@ -571,17 +586,28 @@ static void dns_server_hash_func(const void *p, struct siphash *state) {
 
         siphash24_compress(&s->family, sizeof(s->family), state);
         siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state);
+        siphash24_compress(&s->ifindex, sizeof(s->ifindex), state);
 }
 
 static int dns_server_compare_func(const void *a, const void *b) {
         const DnsServer *x = a, *y = b;
+        int r;
 
         if (x->family < y->family)
                 return -1;
         if (x->family > y->family)
                 return 1;
 
-        return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
+        r = memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
+        if (r != 0)
+                return r;
+
+        if (x->ifindex < y->ifindex)
+                return -1;
+        if (x->ifindex > y->ifindex)
+                return 1;
+
+        return 0;
 }
 
 const struct hash_ops dns_server_hash_ops = {
@@ -623,11 +649,11 @@ void dns_server_mark_all(DnsServer *first) {
         dns_server_mark_all(first->servers_next);
 }
 
-DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) {
+DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex) {
         DnsServer *s;
 
         LIST_FOREACH(servers, s, first)
-                if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0)
+                if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0 && s->ifindex == ifindex)
                         return s;
 
         return NULL;
index 9f4a69c37abcbd0b9ba259e9d0122e62da13c932..463c5724a78a31d27979207283e064f7f3aa6bac 100644 (file)
@@ -62,6 +62,7 @@ struct DnsServer {
 
         int family;
         union in_addr_union address;
+        int ifindex; /* for IPv6 link-local DNS servers */
 
         char *server_string;
 
@@ -101,7 +102,8 @@ int dns_server_new(
                 DnsServerType type,
                 Link *link,
                 int family,
-                const union in_addr_union *address);
+                const union in_addr_union *address,
+                int ifindex);
 
 DnsServer* dns_server_ref(DnsServer *s);
 DnsServer* dns_server_unref(DnsServer *s);
@@ -121,12 +123,13 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
 int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level);
 
 const char *dns_server_string(DnsServer *server);
+int dns_server_ifindex(const DnsServer *s);
 
 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);
+DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex);
 
 void dns_server_unlink_all(DnsServer *first);
 void dns_server_unlink_marked(DnsServer *first);
index 7f21891819752672ceaf869580f80547c5fb749a..2d5cd4a20d456814bbe0ba9f8102bba2d0beb543 100644 (file)
@@ -218,11 +218,11 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_
         for (i = 0; i < n; i++) {
                 DnsServer *s;
 
-                s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address);
+                s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address, 0);
                 if (s)
                         dns_server_move_back_and_unmark(s);
                 else {
-                        r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address);
+                        r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address, 0);
                         if (r < 0)
                                 goto clear;
                 }
index b0dc65036d9e77e1e32081f66e59bb60b6771b09..b189c219201d38611877ccfdecebfed7b5d4d2b8 100644 (file)
@@ -216,11 +216,11 @@ static int link_update_dns_servers(Link *l) {
                 if (r < 0)
                         goto clear;
 
-                s = dns_server_find(l->dns_servers, family, &a);
+                s = dns_server_find(l->dns_servers, family, &a, 0);
                 if (s)
                         dns_server_move_back_and_unmark(s);
                 else {
-                        r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a);
+                        r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, 0);
                         if (r < 0)
                                 goto clear;
                 }
index 9600bde1e952c308843f49f94f1bac7dedd16dff..1be0fd289be2d282fe602ce7d027375697376efb 100644 (file)
@@ -903,7 +903,7 @@ int manager_send(Manager *m, int fd, int ifindex, int family, const union in_add
 
         if (family == AF_INET)
                 return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p);
-        else if (family == AF_INET6)
+        if (family == AF_INET6)
                 return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p);
 
         return -EAFNOSUPPORT;
index fa89de4c219f01b18f0f5e6f0cc15e8e814c3a6d..df738e31ef87df45181a3e6a38a20475229ff8bf 100644 (file)
@@ -92,7 +92,7 @@ int manager_read_resolv_conf(Manager *m) {
 
                 a = first_word(l, "nameserver");
                 if (a) {
-                        r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a);
+                        r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, a);
                         if (r < 0)
                                 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
 
@@ -149,9 +149,7 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
         assert(f);
         assert(count);
 
-        (void) dns_server_string(s);
-
-        if (!s->server_string) {
+        if (!dns_server_string(s)) {
                 log_warning("Our of memory, or invalid DNS address. Ignoring server.");
                 return;
         }
@@ -160,7 +158,7 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
                 fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
         (*count)++;
 
-        fprintf(f, "nameserver %s\n", s->server_string);
+        fprintf(f, "nameserver %s\n", dns_server_string(s));
 }
 
 static void write_resolv_conf_search(
index b480fdaa9cceb4b38bbb7fc18b6687fab1440a79..1a439bd0d47c7ec9ae4ee85548aa00311f3ddce5 100644 (file)
@@ -286,6 +286,55 @@ static void test_in_addr_to_string(void) {
         test_in_addr_to_string_one(AF_INET6, "fe80::");
 }
 
+static void test_in_addr_ifindex_to_string_one(int f, const char *a, int ifindex, const char *b) {
+        _cleanup_free_ char *r = NULL;
+        union in_addr_union ua, uuaa;
+        int ff, ifindex2;
+
+        assert_se(in_addr_from_string(f, a, &ua) >= 0);
+        assert_se(in_addr_ifindex_to_string(f, &ua, ifindex, &r) >= 0);
+        printf("test_in_addr_ifindex_to_string_one: %s == %s\n", b, r);
+        assert_se(streq(b, r));
+
+        assert_se(in_addr_ifindex_from_string_auto(b, &ff, &uuaa, &ifindex2) >= 0);
+        assert_se(ff == f);
+        assert_se(in_addr_equal(f, &ua, &uuaa));
+        assert_se(ifindex2 == ifindex || ifindex2 == 0);
+}
+
+static void test_in_addr_ifindex_to_string(void) {
+        test_in_addr_ifindex_to_string_one(AF_INET, "192.168.0.1", 7, "192.168.0.1");
+        test_in_addr_ifindex_to_string_one(AF_INET, "10.11.12.13", 9, "10.11.12.13");
+        test_in_addr_ifindex_to_string_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 10, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        test_in_addr_ifindex_to_string_one(AF_INET6, "::1", 11, "::1");
+        test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 12, "fe80::%12");
+        test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 0, "fe80::");
+        test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::14", 12, "fe80::14%12");
+        test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::15", -7, "fe80::15");
+        test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::16", LOOPBACK_IFINDEX, "fe80::16%1");
+}
+
+static void test_in_addr_ifindex_from_string_auto(void) {
+        int family, ifindex;
+        union in_addr_union ua;
+
+        /* Most in_addr_ifindex_from_string_auto() invocations have already been tested above, but let's test some more */
+
+        assert_se(in_addr_ifindex_from_string_auto("fe80::17", &family, &ua, &ifindex) >= 0);
+        assert_se(family == AF_INET6);
+        assert_se(ifindex == 0);
+
+        assert_se(in_addr_ifindex_from_string_auto("fe80::18%19", &family, &ua, &ifindex) >= 0);
+        assert_se(family == AF_INET6);
+        assert_se(ifindex == 19);
+
+        assert_se(in_addr_ifindex_from_string_auto("fe80::18%lo", &family, &ua, &ifindex) >= 0);
+        assert_se(family == AF_INET6);
+        assert_se(ifindex == LOOPBACK_IFINDEX);
+
+        assert_se(in_addr_ifindex_from_string_auto("fe80::19%thisinterfacecantexist", &family, &ua, &ifindex) == -ENODEV);
+}
+
 static void *connect_thread(void *arg) {
         union sockaddr_union *sa = arg;
         _cleanup_close_ int fd = -1;
@@ -398,6 +447,8 @@ int main(int argc, char *argv[]) {
         test_in_addr_prefix_intersect();
         test_in_addr_prefix_next();
         test_in_addr_to_string();
+        test_in_addr_ifindex_to_string();
+        test_in_addr_ifindex_from_string_auto();
 
         test_nameinfo_pretty();