]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: Implement DHCP Option 119 (Domain Search List) (#5932)
authorDaniel Wang <wonderfly@users.noreply.github.com>
Sat, 13 May 2017 14:19:32 +0000 (07:19 -0700)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sat, 13 May 2017 14:19:32 +0000 (10:19 -0400)
This adds a modified version of dhcp6_option_parse_domainname() that is
able to parse compressed domain names, borrowing the idea from
dns_packet_read_name(). It also adds pieces in networkd-link and
networkd-manager to properly save/load the added option field.

Resolves #2710.

Makefile.am
src/libsystemd-network/dhcp-lease-internal.h
src/libsystemd-network/sd-dhcp-lease.c
src/libsystemd-network/test-sd-dhcp-lease.c [new file with mode: 0644]
src/network/networkd-link.c
src/network/networkd-manager.c
src/systemd/sd-dhcp-client.h
src/systemd/sd-dhcp-lease.h
src/test/meson.build

index 771efa4f8e7757b0398f41d908b291ef3c667d76..b62166cd16c6b1407fa84a06dfd65dae279e47cc 100644 (file)
@@ -3690,6 +3690,14 @@ test_dhcp_option_LDADD = \
        libsystemd-network.la \
        libsystemd-shared.la
 
+test_sd_dhcp_lease_SOURCES = \
+      src/libsystemd-network/dhcp-lease-internal.h \
+      src/libsystemd-network/test-sd-dhcp-lease.c
+
+test_sd_dhcp_lease_LDADD = \
+      libsystemd-network.la \
+      libsystemd-shared.la
+
 test_dhcp_client_SOURCES = \
        src/systemd/sd-dhcp-client.h \
        src/libsystemd-network/dhcp-protocol.h \
@@ -3768,6 +3776,7 @@ tests += \
        test-dhcp-option \
        test-dhcp-client \
        test-dhcp-server \
+       test-sd-dhcp-lease \
        test-ipv4ll \
        test-ndisc-rs \
        test-dhcp6-client \
index 82cae2300ac72ba72cf438bb471a95cd853a9124..7847ce07095b4b2439c074f3efe08ed76aad8a63 100644 (file)
@@ -75,6 +75,7 @@ struct sd_dhcp_lease {
         uint16_t mtu; /* 0 if unset */
 
         char *domainname;
+        char **search_domains;
         char *hostname;
         char *root_path;
 
@@ -92,6 +93,7 @@ struct sd_dhcp_lease {
 int dhcp_lease_new(sd_dhcp_lease **ret);
 
 int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata);
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains);
 int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len);
 
 int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);
index 7fed55c5fc88eec6fdbb7e5d08ec7f73a73ac734..5906151360604e99747f1b55b68a53507e84de4b 100644 (file)
@@ -231,6 +231,21 @@ int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) {
         return (int) lease->static_route_size;
 }
 
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) {
+        unsigned r;
+
+        assert_return(lease, -EINVAL);
+        assert_return(domains, -EINVAL);
+
+        r = strv_length(lease->search_domains);
+        if (r > 0) {
+                *domains = lease->search_domains;
+                return (int) r;
+        }
+
+        return -ENODATA;
+}
+
 int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) {
         assert_return(lease, -EINVAL);
         assert_return(data, -EINVAL);
@@ -282,6 +297,7 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
         free(lease->static_route);
         free(lease->client_id);
         free(lease->vendor_specific);
+        strv_free(lease->search_domains);
         return mfree(lease);
 }
 
@@ -605,6 +621,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
 
                 break;
 
+        case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST:
+                r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m");
+                break;
+
         case SD_DHCP_OPTION_HOST_NAME:
                 r = lease_parse_domain(option, len, &lease->hostname);
                 if (r < 0) {
@@ -696,6 +718,96 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
         return 0;
 }
 
+/* Parses compressed domain names. */
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) {
+        _cleanup_strv_free_ char **names = NULL;
+        size_t pos = 0, cnt = 0;
+        int r;
+
+        assert(domains);
+        assert_return(option && len > 0, -ENODATA);
+
+        while (pos < len) {
+                _cleanup_free_ char *name = NULL;
+                size_t n = 0, allocated = 0;
+                size_t jump_barrier = pos, next_chunk = 0;
+                bool first = true;
+
+                for (;;) {
+                        uint8_t c;
+                        c = option[pos++];
+
+                        if (c == 0) {
+                                /* End of name */
+                                break;
+                        } else if (c <= 63) {
+                                const char *label;
+
+                                /* Literal label */
+                                label = (const char*) (option + pos);
+                                pos += c;
+                                if (pos >= len)
+                                        return -EBADMSG;
+
+                                if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                                        return -ENOMEM;
+
+                                if (first)
+                                        first = false;
+                                else
+                                        name[n++] = '.';
+
+                                r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
+                                if (r < 0)
+                                        return r;
+
+                                n += r;
+                        } else if ((c & 0xc0) == 0xc0) {
+                                /* Pointer */
+
+                                uint8_t d;
+                                uint16_t ptr;
+
+                                if (pos >= len)
+                                        return -EBADMSG;
+
+                                d = option[pos++];
+                                ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d;
+
+                                /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
+                                if (ptr >= jump_barrier)
+                                        return -EBADMSG;
+                                jump_barrier = ptr;
+
+                                /* Save current location so we don't end up re-parsing what's parsed so far. */
+                                if (next_chunk == 0)
+                                        next_chunk = pos;
+
+                                pos = ptr;
+                        } else
+                                return -EBADMSG;
+                }
+
+                if (!GREEDY_REALLOC(name, allocated, n + 1))
+                        return -ENOMEM;
+                name[n] = 0;
+
+                r = strv_extend(&names, name);
+                if (r < 0)
+                        return r;
+
+                cnt++;
+
+                if (next_chunk != 0)
+                      pos = next_chunk;
+        }
+
+        *domains = names;
+        names = NULL;
+
+        return cnt;
+}
+
 int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) {
         struct sd_dhcp_raw_option *cur, *option;
 
@@ -751,6 +863,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         const char *string;
         uint16_t mtu;
         _cleanup_free_ sd_dhcp_route **routes = NULL;
+        char **search_domains = NULL;
         uint32_t t1, t2, lifetime;
         int r;
 
@@ -824,6 +937,13 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         if (r >= 0)
                 fprintf(f, "DOMAINNAME=%s\n", string);
 
+        r = sd_dhcp_lease_get_search_domains(lease, &search_domains);
+        if (r > 0) {
+                fputs("DOMAIN_SEARCH_LIST=", f);
+                fputstrv(f, search_domains, NULL, NULL);
+                fputs("\n", f);
+        }
+
         r = sd_dhcp_lease_get_hostname(lease, &string);
         if (r >= 0)
                 fprintf(f, "HOSTNAME=%s\n", string);
@@ -905,6 +1025,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                 *ntp = NULL,
                 *mtu = NULL,
                 *routes = NULL,
+                *domains = NULL,
                 *client_id_hex = NULL,
                 *vendor_specific_hex = NULL,
                 *lifetime = NULL,
@@ -933,6 +1054,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                            "MTU", &mtu,
                            "DOMAINNAME", &lease->domainname,
                            "HOSTNAME", &lease->hostname,
+                           "DOMAIN_SEARCH_LIST", &domains,
                            "ROOT_PATH", &lease->root_path,
                            "ROUTES", &routes,
                            "CLIENTID", &client_id_hex,
@@ -1038,6 +1160,18 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                         log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu);
         }
 
+        if (domains) {
+                _cleanup_strv_free_ char **a = NULL;
+                a = strv_split(domains, " ");
+                if (!a)
+                        return -ENOMEM;
+
+                if (!strv_isempty(a)) {
+                        lease->search_domains = a;
+                        a = NULL;
+                }
+        }
+
         if (routes) {
                 r = deserialize_dhcp_routes(
                                 &lease->static_route,
diff --git a/src/libsystemd-network/test-sd-dhcp-lease.c b/src/libsystemd-network/test-sd-dhcp-lease.c
new file mode 100644 (file)
index 0000000..0f88180
--- /dev/null
@@ -0,0 +1,90 @@
+#include <errno.h>
+
+#include "dhcp-lease-internal.h"
+#include "macro.h"
+#include "string-util.h"
+#include "strv.h"
+
+/* According to RFC1035 section 4.1.4, a domain name in a message can be either:
+ *      - a sequence of labels ending in a zero octet
+ *      - a pointer
+ *      - a sequence of labels ending with a pointer
+ */
+static void test_dhcp_lease_parse_search_domains_basic(void) {
+        int r;
+        _cleanup_strv_free_ char **domains = NULL;
+        static const uint8_t optionbuf[] = {
+                0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
+                0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00,
+        };
+
+        r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
+        assert_se(r == 2);
+        assert_se(streq(domains[0], "FOO.BAR"));
+        assert_se(streq(domains[1], "ABCD.EFG"));
+}
+
+static void test_dhcp_lease_parse_search_domains_ptr(void) {
+        int r;
+        _cleanup_strv_free_ char **domains = NULL;
+        static const uint8_t optionbuf[] = {
+                0x03, 'F', 'O', 'O', 0x00, 0xC0, 0x00,
+        };
+
+        r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
+        assert_se(r == 2);
+        assert_se(streq(domains[0], "FOO"));
+        assert_se(streq(domains[1], "FOO"));
+}
+
+static void test_dhcp_lease_parse_search_domains_labels_and_ptr(void) {
+        int r;
+        _cleanup_strv_free_ char **domains = NULL;
+        static const uint8_t optionbuf[] = {
+                0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
+                0x03, 'A', 'B', 'C', 0xC0, 0x04,
+        };
+
+        r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
+        assert_se(r == 2);
+        assert_se(streq(domains[0], "FOO.BAR"));
+        assert_se(streq(domains[1], "ABC.BAR"));
+}
+
+/* Tests for exceptions. */
+
+static void test_dhcp_lease_parse_search_domains_no_data(void) {
+        _cleanup_strv_free_ char **domains = NULL;
+        static const uint8_t optionbuf[3] = {0, 0, 0};
+
+        assert_se(dhcp_lease_parse_search_domains(NULL, 0, &domains) == -ENODATA);
+        assert_se(dhcp_lease_parse_search_domains(optionbuf, 0, &domains) == -ENODATA);
+}
+
+static void test_dhcp_lease_parse_search_domains_loops(void) {
+        _cleanup_strv_free_ char **domains = NULL;
+        static const uint8_t optionbuf[] = {
+                0x03, 'F', 'O', 'O', 0x00, 0x03, 'B', 'A', 'R', 0xC0, 0x06,
+        };
+
+        assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains) == -EBADMSG);
+}
+
+static void test_dhcp_lease_parse_search_domains_wrong_len(void) {
+        _cleanup_strv_free_ char **domains = NULL;
+        static const uint8_t optionbuf[] = {
+                0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
+                0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00,
+        };
+
+        assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf) - 5, &domains) == -EBADMSG);
+}
+
+int main(int argc, char *argv[]) {
+        test_dhcp_lease_parse_search_domains_basic();
+        test_dhcp_lease_parse_search_domains_ptr();
+        test_dhcp_lease_parse_search_domains_labels_and_ptr();
+        test_dhcp_lease_parse_search_domains_no_data();
+        test_dhcp_lease_parse_search_domains_loops();
+        test_dhcp_lease_parse_search_domains_wrong_len();
+}
index 6ed838094246fcfb94d3b33c82ebedc6e2f2bf1e..37bebcf19161ceef8c0709d61284ae6db44bde89 100644 (file)
@@ -3266,6 +3266,7 @@ int link_save(Link *link) {
                 sd_dhcp6_lease *dhcp6_lease = NULL;
                 const char *dhcp_domainname = NULL;
                 char **dhcp6_domains = NULL;
+                char **dhcp_domains = NULL;
                 unsigned j;
 
                 if (link->dhcp6_client) {
@@ -3375,13 +3376,16 @@ int link_save(Link *link) {
                 fputc('\n', f);
 
                 if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
-                        if (link->dhcp_lease)
+                        if (link->dhcp_lease) {
                                 (void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname);
+                                (void) sd_dhcp_lease_get_search_domains(link->dhcp_lease, &dhcp_domains);
+                        }
                         if (dhcp6_lease)
                                 (void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains);
                 }
 
                 fputs("DOMAINS=", f);
+                space = false;
                 fputstrv(f, link->network->search_domains, NULL, &space);
 
                 if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
@@ -3389,6 +3393,8 @@ int link_save(Link *link) {
 
                         if (dhcp_domainname)
                                 fputs_with_space(f, dhcp_domainname, NULL, &space);
+                        if (dhcp_domains)
+                                fputstrv(f, dhcp_domains, NULL, &space);
                         if (dhcp6_domains)
                                 fputstrv(f, dhcp6_domains, NULL, &space);
 
@@ -3399,13 +3405,16 @@ int link_save(Link *link) {
                 fputc('\n', f);
 
                 fputs("ROUTE_DOMAINS=", f);
-                fputstrv(f, link->network->route_domains, NULL, NULL);
+                space = false;
+                fputstrv(f, link->network->route_domains, NULL, &space);
 
                 if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
                         NDiscDNSSL *dd;
 
                         if (dhcp_domainname)
                                 fputs_with_space(f, dhcp_domainname, NULL, &space);
+                        if (dhcp_domains)
+                                fputstrv(f, dhcp_domains, NULL, &space);
                         if (dhcp6_domains)
                                 fputstrv(f, dhcp6_domains, NULL, &space);
 
index ea1c320809f9f23a8fb8f08037a406a66cf5e108..5f10b4f993af8c1cb235c3e1dbd9a821fd1ec8c2 100644 (file)
@@ -961,15 +961,20 @@ static int manager_save(Manager *m) {
 
                 if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
                         const char *domainname;
+                        char **domains = NULL;
 
+                        OrderedSet *target_domains = (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) ? search_domains : route_domains;
                         r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
                         if (r >= 0) {
+                                r = ordered_set_put_strdup(target_domains, domainname);
+                                if (r < 0)
+                                        return r;
+                        } else if (r != -ENODATA)
+                                return r;
 
-                                if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES)
-                                        r = ordered_set_put_strdup(search_domains, domainname);
-                                else
-                                        r = ordered_set_put_strdup(route_domains, domainname);
-
+                        r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains);
+                        if (r >= 0) {
+                                r = ordered_set_put_strdupv(target_domains, domains);
                                 if (r < 0)
                                         return r;
                         } else if (r != -ENODATA)
index ffe7f836de0c87d0459c11b1e4013361738002c5..f731fdcbd48cbc79da4029cc5502e861368697bc 100644 (file)
@@ -76,6 +76,7 @@ enum {
         SD_DHCP_OPTION_FQDN                        = 81,
         SD_DHCP_OPTION_NEW_POSIX_TIMEZONE          = 100,
         SD_DHCP_OPTION_NEW_TZDB_TIMEZONE           = 101,
+        SD_DHCP_OPTION_DOMAIN_SEARCH_LIST          = 119,
         SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE      = 121,
         SD_DHCP_OPTION_PRIVATE_BASE                = 224,
         SD_DHCP_OPTION_PRIVATE_LAST                = 254,
index 2f565ca825a30ba718ed2572bcc99732526b4d47..7ab99cccd16a803ee0d29cc9f9ce9868f20665ca 100644 (file)
@@ -49,6 +49,7 @@ int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu);
 int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname);
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains);
 int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname);
 int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path);
 int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes);
index 4ae1210fe14e24b6376a2665f638835a71785e61..6748de69ef12506ff24ea9518bf7b7b31f6345d2 100644 (file)
@@ -800,6 +800,12 @@ tests += [
           libsystemd_network],
          []],
 
+        [['src/libsystemd-network/test-sd-dhcp-lease.c',
+          'src/libsystemd-network/dhcp-lease-internal.h'],
+         [libshared,
+          libsystemd_network],
+         []],
+
         [['src/libsystemd-network/test-dhcp-client.c',
           'src/libsystemd-network/dhcp-protocol.h',
           'src/libsystemd-network/dhcp-internal.h',