diff --git a/Makefile.am b/Makefile.am index 771efa4f8e..b62166cd16 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 82cae2300a..7847ce0709 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -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); diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 7fed55c5fc..5906151360 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -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 index 0000000000..0f881809ab --- /dev/null +++ b/src/libsystemd-network/test-sd-dhcp-lease.c @@ -0,0 +1,90 @@ +#include + +#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(); +} diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 6ed8380942..37bebcf191 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -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); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index ea1c320809..5f10b4f993 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -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) diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index ffe7f836de..f731fdcbd4 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -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, diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index 2f565ca825..7ab99cccd1 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -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); diff --git a/src/test/meson.build b/src/test/meson.build index 4ae1210fe1..6748de69ef 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -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',