network: Implement DHCP Option 119 (Domain Search List) (#5932)

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.
This commit is contained in:
Daniel Wang 2017-05-13 07:19:32 -07:00 committed by Zbigniew Jędrzejewski-Szmek
parent 6e4177315f
commit b85bc551c3
9 changed files with 264 additions and 7 deletions

View 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 \

View 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);

View 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,

View File

@ -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();
}

View 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);

View 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)

View 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,

View 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);

View 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',