Skip to content

Commit b85bc55

Browse files
wonderflykeszybz
authored andcommitted
network: Implement DHCP Option 119 (Domain Search List) (systemd#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 systemd#2710.
1 parent 6e41773 commit b85bc55

File tree

9 files changed

+264
-7
lines changed

9 files changed

+264
-7
lines changed

Makefile.am

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3690,6 +3690,14 @@ test_dhcp_option_LDADD = \
36903690
libsystemd-network.la \
36913691
libsystemd-shared.la
36923692

3693+
test_sd_dhcp_lease_SOURCES = \
3694+
src/libsystemd-network/dhcp-lease-internal.h \
3695+
src/libsystemd-network/test-sd-dhcp-lease.c
3696+
3697+
test_sd_dhcp_lease_LDADD = \
3698+
libsystemd-network.la \
3699+
libsystemd-shared.la
3700+
36933701
test_dhcp_client_SOURCES = \
36943702
src/systemd/sd-dhcp-client.h \
36953703
src/libsystemd-network/dhcp-protocol.h \
@@ -3768,6 +3776,7 @@ tests += \
37683776
test-dhcp-option \
37693777
test-dhcp-client \
37703778
test-dhcp-server \
3779+
test-sd-dhcp-lease \
37713780
test-ipv4ll \
37723781
test-ndisc-rs \
37733782
test-dhcp6-client \

src/libsystemd-network/dhcp-lease-internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ struct sd_dhcp_lease {
7575
uint16_t mtu; /* 0 if unset */
7676

7777
char *domainname;
78+
char **search_domains;
7879
char *hostname;
7980
char *root_path;
8081

@@ -92,6 +93,7 @@ struct sd_dhcp_lease {
9293
int dhcp_lease_new(sd_dhcp_lease **ret);
9394

9495
int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata);
96+
int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains);
9597
int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len);
9698

9799
int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);

src/libsystemd-network/sd-dhcp-lease.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,21 @@ int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) {
231231
return (int) lease->static_route_size;
232232
}
233233

234+
int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) {
235+
unsigned r;
236+
237+
assert_return(lease, -EINVAL);
238+
assert_return(domains, -EINVAL);
239+
240+
r = strv_length(lease->search_domains);
241+
if (r > 0) {
242+
*domains = lease->search_domains;
243+
return (int) r;
244+
}
245+
246+
return -ENODATA;
247+
}
248+
234249
int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) {
235250
assert_return(lease, -EINVAL);
236251
assert_return(data, -EINVAL);
@@ -282,6 +297,7 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
282297
free(lease->static_route);
283298
free(lease->client_id);
284299
free(lease->vendor_specific);
300+
strv_free(lease->search_domains);
285301
return mfree(lease);
286302
}
287303

@@ -605,6 +621,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
605621

606622
break;
607623

624+
case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST:
625+
r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains);
626+
if (r < 0)
627+
log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m");
628+
break;
629+
608630
case SD_DHCP_OPTION_HOST_NAME:
609631
r = lease_parse_domain(option, len, &lease->hostname);
610632
if (r < 0) {
@@ -696,6 +718,96 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
696718
return 0;
697719
}
698720

721+
/* Parses compressed domain names. */
722+
int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) {
723+
_cleanup_strv_free_ char **names = NULL;
724+
size_t pos = 0, cnt = 0;
725+
int r;
726+
727+
assert(domains);
728+
assert_return(option && len > 0, -ENODATA);
729+
730+
while (pos < len) {
731+
_cleanup_free_ char *name = NULL;
732+
size_t n = 0, allocated = 0;
733+
size_t jump_barrier = pos, next_chunk = 0;
734+
bool first = true;
735+
736+
for (;;) {
737+
uint8_t c;
738+
c = option[pos++];
739+
740+
if (c == 0) {
741+
/* End of name */
742+
break;
743+
} else if (c <= 63) {
744+
const char *label;
745+
746+
/* Literal label */
747+
label = (const char*) (option + pos);
748+
pos += c;
749+
if (pos >= len)
750+
return -EBADMSG;
751+
752+
if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
753+
return -ENOMEM;
754+
755+
if (first)
756+
first = false;
757+
else
758+
name[n++] = '.';
759+
760+
r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
761+
if (r < 0)
762+
return r;
763+
764+
n += r;
765+
} else if ((c & 0xc0) == 0xc0) {
766+
/* Pointer */
767+
768+
uint8_t d;
769+
uint16_t ptr;
770+
771+
if (pos >= len)
772+
return -EBADMSG;
773+
774+
d = option[pos++];
775+
ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d;
776+
777+
/* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
778+
if (ptr >= jump_barrier)
779+
return -EBADMSG;
780+
jump_barrier = ptr;
781+
782+
/* Save current location so we don't end up re-parsing what's parsed so far. */
783+
if (next_chunk == 0)
784+
next_chunk = pos;
785+
786+
pos = ptr;
787+
} else
788+
return -EBADMSG;
789+
}
790+
791+
if (!GREEDY_REALLOC(name, allocated, n + 1))
792+
return -ENOMEM;
793+
name[n] = 0;
794+
795+
r = strv_extend(&names, name);
796+
if (r < 0)
797+
return r;
798+
799+
cnt++;
800+
801+
if (next_chunk != 0)
802+
pos = next_chunk;
803+
}
804+
805+
*domains = names;
806+
names = NULL;
807+
808+
return cnt;
809+
}
810+
699811
int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) {
700812
struct sd_dhcp_raw_option *cur, *option;
701813

@@ -751,6 +863,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
751863
const char *string;
752864
uint16_t mtu;
753865
_cleanup_free_ sd_dhcp_route **routes = NULL;
866+
char **search_domains = NULL;
754867
uint32_t t1, t2, lifetime;
755868
int r;
756869

@@ -824,6 +937,13 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
824937
if (r >= 0)
825938
fprintf(f, "DOMAINNAME=%s\n", string);
826939

940+
r = sd_dhcp_lease_get_search_domains(lease, &search_domains);
941+
if (r > 0) {
942+
fputs("DOMAIN_SEARCH_LIST=", f);
943+
fputstrv(f, search_domains, NULL, NULL);
944+
fputs("\n", f);
945+
}
946+
827947
r = sd_dhcp_lease_get_hostname(lease, &string);
828948
if (r >= 0)
829949
fprintf(f, "HOSTNAME=%s\n", string);
@@ -905,6 +1025,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
9051025
*ntp = NULL,
9061026
*mtu = NULL,
9071027
*routes = NULL,
1028+
*domains = NULL,
9081029
*client_id_hex = NULL,
9091030
*vendor_specific_hex = NULL,
9101031
*lifetime = NULL,
@@ -933,6 +1054,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
9331054
"MTU", &mtu,
9341055
"DOMAINNAME", &lease->domainname,
9351056
"HOSTNAME", &lease->hostname,
1057+
"DOMAIN_SEARCH_LIST", &domains,
9361058
"ROOT_PATH", &lease->root_path,
9371059
"ROUTES", &routes,
9381060
"CLIENTID", &client_id_hex,
@@ -1038,6 +1160,18 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
10381160
log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu);
10391161
}
10401162

1163+
if (domains) {
1164+
_cleanup_strv_free_ char **a = NULL;
1165+
a = strv_split(domains, " ");
1166+
if (!a)
1167+
return -ENOMEM;
1168+
1169+
if (!strv_isempty(a)) {
1170+
lease->search_domains = a;
1171+
a = NULL;
1172+
}
1173+
}
1174+
10411175
if (routes) {
10421176
r = deserialize_dhcp_routes(
10431177
&lease->static_route,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include <errno.h>
2+
3+
#include "dhcp-lease-internal.h"
4+
#include "macro.h"
5+
#include "string-util.h"
6+
#include "strv.h"
7+
8+
/* According to RFC1035 section 4.1.4, a domain name in a message can be either:
9+
* - a sequence of labels ending in a zero octet
10+
* - a pointer
11+
* - a sequence of labels ending with a pointer
12+
*/
13+
static void test_dhcp_lease_parse_search_domains_basic(void) {
14+
int r;
15+
_cleanup_strv_free_ char **domains = NULL;
16+
static const uint8_t optionbuf[] = {
17+
0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
18+
0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00,
19+
};
20+
21+
r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
22+
assert_se(r == 2);
23+
assert_se(streq(domains[0], "FOO.BAR"));
24+
assert_se(streq(domains[1], "ABCD.EFG"));
25+
}
26+
27+
static void test_dhcp_lease_parse_search_domains_ptr(void) {
28+
int r;
29+
_cleanup_strv_free_ char **domains = NULL;
30+
static const uint8_t optionbuf[] = {
31+
0x03, 'F', 'O', 'O', 0x00, 0xC0, 0x00,
32+
};
33+
34+
r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
35+
assert_se(r == 2);
36+
assert_se(streq(domains[0], "FOO"));
37+
assert_se(streq(domains[1], "FOO"));
38+
}
39+
40+
static void test_dhcp_lease_parse_search_domains_labels_and_ptr(void) {
41+
int r;
42+
_cleanup_strv_free_ char **domains = NULL;
43+
static const uint8_t optionbuf[] = {
44+
0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
45+
0x03, 'A', 'B', 'C', 0xC0, 0x04,
46+
};
47+
48+
r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains);
49+
assert_se(r == 2);
50+
assert_se(streq(domains[0], "FOO.BAR"));
51+
assert_se(streq(domains[1], "ABC.BAR"));
52+
}
53+
54+
/* Tests for exceptions. */
55+
56+
static void test_dhcp_lease_parse_search_domains_no_data(void) {
57+
_cleanup_strv_free_ char **domains = NULL;
58+
static const uint8_t optionbuf[3] = {0, 0, 0};
59+
60+
assert_se(dhcp_lease_parse_search_domains(NULL, 0, &domains) == -ENODATA);
61+
assert_se(dhcp_lease_parse_search_domains(optionbuf, 0, &domains) == -ENODATA);
62+
}
63+
64+
static void test_dhcp_lease_parse_search_domains_loops(void) {
65+
_cleanup_strv_free_ char **domains = NULL;
66+
static const uint8_t optionbuf[] = {
67+
0x03, 'F', 'O', 'O', 0x00, 0x03, 'B', 'A', 'R', 0xC0, 0x06,
68+
};
69+
70+
assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains) == -EBADMSG);
71+
}
72+
73+
static void test_dhcp_lease_parse_search_domains_wrong_len(void) {
74+
_cleanup_strv_free_ char **domains = NULL;
75+
static const uint8_t optionbuf[] = {
76+
0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00,
77+
0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00,
78+
};
79+
80+
assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf) - 5, &domains) == -EBADMSG);
81+
}
82+
83+
int main(int argc, char *argv[]) {
84+
test_dhcp_lease_parse_search_domains_basic();
85+
test_dhcp_lease_parse_search_domains_ptr();
86+
test_dhcp_lease_parse_search_domains_labels_and_ptr();
87+
test_dhcp_lease_parse_search_domains_no_data();
88+
test_dhcp_lease_parse_search_domains_loops();
89+
test_dhcp_lease_parse_search_domains_wrong_len();
90+
}

src/network/networkd-link.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3266,6 +3266,7 @@ int link_save(Link *link) {
32663266
sd_dhcp6_lease *dhcp6_lease = NULL;
32673267
const char *dhcp_domainname = NULL;
32683268
char **dhcp6_domains = NULL;
3269+
char **dhcp_domains = NULL;
32693270
unsigned j;
32703271

32713272
if (link->dhcp6_client) {
@@ -3375,20 +3376,25 @@ int link_save(Link *link) {
33753376
fputc('\n', f);
33763377

33773378
if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
3378-
if (link->dhcp_lease)
3379+
if (link->dhcp_lease) {
33793380
(void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname);
3381+
(void) sd_dhcp_lease_get_search_domains(link->dhcp_lease, &dhcp_domains);
3382+
}
33803383
if (dhcp6_lease)
33813384
(void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains);
33823385
}
33833386

33843387
fputs("DOMAINS=", f);
3388+
space = false;
33853389
fputstrv(f, link->network->search_domains, NULL, &space);
33863390

33873391
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
33883392
NDiscDNSSL *dd;
33893393

33903394
if (dhcp_domainname)
33913395
fputs_with_space(f, dhcp_domainname, NULL, &space);
3396+
if (dhcp_domains)
3397+
fputstrv(f, dhcp_domains, NULL, &space);
33923398
if (dhcp6_domains)
33933399
fputstrv(f, dhcp6_domains, NULL, &space);
33943400

@@ -3399,13 +3405,16 @@ int link_save(Link *link) {
33993405
fputc('\n', f);
34003406

34013407
fputs("ROUTE_DOMAINS=", f);
3402-
fputstrv(f, link->network->route_domains, NULL, NULL);
3408+
space = false;
3409+
fputstrv(f, link->network->route_domains, NULL, &space);
34033410

34043411
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
34053412
NDiscDNSSL *dd;
34063413

34073414
if (dhcp_domainname)
34083415
fputs_with_space(f, dhcp_domainname, NULL, &space);
3416+
if (dhcp_domains)
3417+
fputstrv(f, dhcp_domains, NULL, &space);
34093418
if (dhcp6_domains)
34103419
fputstrv(f, dhcp6_domains, NULL, &space);
34113420

0 commit comments

Comments
 (0)