]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-dhcp-common.c
network: configure DHCP clients after MAC address is assigned
[thirdparty/systemd.git] / src / network / networkd-dhcp-common.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <netinet/in.h>
4 #include <linux/if_arp.h>
5
6 #include "bus-error.h"
7 #include "dhcp-identifier.h"
8 #include "dhcp-internal.h"
9 #include "dhcp6-internal.h"
10 #include "escape.h"
11 #include "hexdecoct.h"
12 #include "in-addr-prefix-util.h"
13 #include "networkd-dhcp-common.h"
14 #include "networkd-link.h"
15 #include "networkd-manager.h"
16 #include "networkd-network.h"
17 #include "parse-util.h"
18 #include "socket-util.h"
19 #include "string-table.h"
20 #include "strv.h"
21 #include "vrf.h"
22
23 static uint32_t link_get_vrf_table(Link *link) {
24 assert(link);
25 assert(link->network);
26
27 return link->network->vrf ? VRF(link->network->vrf)->table : RT_TABLE_MAIN;
28 }
29
30 uint32_t link_get_dhcp4_route_table(Link *link) {
31 assert(link);
32 assert(link->network);
33
34 /* When the interface is part of an VRF use the VRFs routing table, unless
35 * another table is explicitly specified. */
36
37 if (link->network->dhcp_route_table_set)
38 return link->network->dhcp_route_table;
39 return link_get_vrf_table(link);
40 }
41
42 uint32_t link_get_ipv6_accept_ra_route_table(Link *link) {
43 assert(link);
44 assert(link->network);
45
46 if (link->network->ipv6_accept_ra_route_table_set)
47 return link->network->ipv6_accept_ra_route_table;
48 return link_get_vrf_table(link);
49 }
50
51 bool link_dhcp_enabled(Link *link, int family) {
52 assert(link);
53 assert(IN_SET(family, AF_INET, AF_INET6));
54
55 if (family == AF_INET6 && !socket_ipv6_is_supported())
56 return false;
57
58 if (link->flags & IFF_LOOPBACK)
59 return false;
60
61 if (link->iftype == ARPHRD_CAN)
62 return false;
63
64 if (!IN_SET(link->hw_addr.length, ETH_ALEN, INFINIBAND_ALEN) &&
65 !streq_ptr(link->kind, "wwan"))
66 /* Currently, only interfaces whose MAC address length is ETH_ALEN or INFINIBAND_ALEN
67 * are supported. Note, wwan interfaces may be assigned MAC address slightly later.
68 * Hence, let's wait for a while.*/
69 return false;
70
71 if (!link->network)
72 return false;
73
74 return link->network->dhcp & (family == AF_INET ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6);
75 }
76
77 void network_adjust_dhcp(Network *network) {
78 assert(network);
79 assert(network->dhcp >= 0);
80
81 if (network->dhcp == ADDRESS_FAMILY_NO)
82 return;
83
84 /* Bonding slave does not support addressing. */
85 if (network->bond) {
86 log_warning("%s: Cannot enable DHCP= when Bond= is specified, disabling DHCP=.",
87 network->filename);
88 network->dhcp = ADDRESS_FAMILY_NO;
89 return;
90 }
91
92 if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) &&
93 FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV6)) {
94 log_warning("%s: DHCPv6 client is enabled but IPv6 link local addressing is disabled. "
95 "Disabling DHCPv6 client.", network->filename);
96 SET_FLAG(network->dhcp, ADDRESS_FAMILY_IPV6, false);
97 }
98
99 network_adjust_dhcp4(network);
100 }
101
102 static bool duid_needs_product_uuid(const DUID *duid) {
103 assert(duid);
104
105 return duid->type == DUID_TYPE_UUID && duid->raw_data_len == 0;
106 }
107
108 static const struct DUID fallback_duid = { .type = DUID_TYPE_EN };
109
110 const DUID *link_get_duid(Link *link, int family) {
111 const DUID *duid;
112
113 assert(link);
114 assert(IN_SET(family, AF_INET, AF_INET6));
115
116 if (link->network) {
117 duid = family == AF_INET ? &link->network->dhcp_duid : &link->network->dhcp6_duid;
118 if (duid->type != _DUID_TYPE_INVALID) {
119 if (duid_needs_product_uuid(duid))
120 return &link->manager->duid_product_uuid;
121 else
122 return duid;
123 }
124 }
125
126 duid = family == AF_INET ? &link->manager->dhcp_duid : &link->manager->dhcp6_duid;
127 if (link->hw_addr.length == 0 && IN_SET(duid->type, DUID_TYPE_LLT, DUID_TYPE_LL))
128 /* Fallback to DUID that works without MAC address.
129 * This is useful for tunnel devices without MAC address. */
130 return &fallback_duid;
131
132 return duid;
133 }
134
135 static int get_product_uuid_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
136 Manager *manager = userdata;
137 const sd_bus_error *e;
138 const void *a;
139 size_t sz;
140 int r;
141
142 assert(m);
143 assert(manager);
144
145 /* To avoid calling GetProductUUID() bus method so frequently, set the flag below
146 * even if the method fails. */
147 manager->has_product_uuid = true;
148
149 e = sd_bus_message_get_error(m);
150 if (e) {
151 r = sd_bus_error_get_errno(e);
152 log_warning_errno(r, "Could not get product UUID. Falling back to use machine-app-specific ID as DUID-UUID: %s",
153 bus_error_message(e, r));
154 return 0;
155 }
156
157 r = sd_bus_message_read_array(m, 'y', &a, &sz);
158 if (r < 0) {
159 log_warning_errno(r, "Failed to get product UUID. Falling back to use machine-app-specific ID as DUID-UUID: %m");
160 return 0;
161 }
162
163 if (sz != sizeof(sd_id128_t)) {
164 log_warning("Invalid product UUID. Falling back to use machine-app-specific ID as DUID-UUID.");
165 return 0;
166 }
167
168 log_debug("Successfully obtained product UUID");
169
170 memcpy(&manager->duid_product_uuid.raw_data, a, sz);
171 manager->duid_product_uuid.raw_data_len = sz;
172
173 return 0;
174 }
175
176 int manager_request_product_uuid(Manager *m) {
177 static bool bus_method_is_called = false;
178 int r;
179
180 assert(m);
181
182 if (bus_method_is_called)
183 return 0;
184
185 if (sd_bus_is_ready(m->bus) <= 0 && !m->product_uuid_requested) {
186 log_debug("Not connected to system bus, requesting product UUID later.");
187 m->product_uuid_requested = true;
188 return 0;
189 }
190
191 m->product_uuid_requested = false;
192
193 r = sd_bus_call_method_async(
194 m->bus,
195 NULL,
196 "org.freedesktop.hostname1",
197 "/org/freedesktop/hostname1",
198 "org.freedesktop.hostname1",
199 "GetProductUUID",
200 get_product_uuid_handler,
201 m,
202 "b",
203 false);
204 if (r < 0)
205 return log_warning_errno(r, "Failed to get product UUID: %m");
206
207 log_debug("Requesting product UUID.");
208
209 bus_method_is_called = true;
210
211 return 0;
212 }
213
214 int dhcp_configure_duid(Link *link, const DUID *duid) {
215 Manager *m;
216 int r;
217
218 assert(link);
219 assert(link->manager);
220 assert(duid);
221
222 m = link->manager;
223
224 if (!duid_needs_product_uuid(duid))
225 return 1;
226
227 if (m->has_product_uuid)
228 return 1;
229
230 r = manager_request_product_uuid(m);
231 if (r < 0) {
232 log_link_warning_errno(link, r,
233 "Failed to get product UUID. Falling back to use machine-app-specific ID as DUID-UUID: %m");
234
235 m->has_product_uuid = true; /* Do not request UUID again on failure. */
236 return 1;
237 }
238
239 return 0;
240 }
241
242 bool address_is_filtered(int family, const union in_addr_union *address, uint8_t prefixlen, Set *allow_list, Set *deny_list) {
243 struct in_addr_prefix *p;
244
245 assert(IN_SET(family, AF_INET, AF_INET6));
246 assert(address);
247
248 if (allow_list) {
249 SET_FOREACH(p, allow_list)
250 if (p->family == family &&
251 p->prefixlen <= prefixlen &&
252 in_addr_prefix_covers(family, &p->address, p->prefixlen, address) > 0)
253 return false;
254
255 return true;
256 }
257
258 SET_FOREACH(p, deny_list)
259 if (p->family == family &&
260 in_addr_prefix_intersect(family, &p->address, p->prefixlen, address, prefixlen) > 0)
261 return true;
262
263 return false;
264 }
265
266 int config_parse_dhcp(
267 const char* unit,
268 const char *filename,
269 unsigned line,
270 const char *section,
271 unsigned section_line,
272 const char *lvalue,
273 int ltype,
274 const char *rvalue,
275 void *data,
276 void *userdata) {
277
278 AddressFamily *dhcp = data, s;
279
280 assert(filename);
281 assert(lvalue);
282 assert(rvalue);
283 assert(data);
284
285 /* Note that this is mostly like
286 * config_parse_address_family(), except that it
287 * understands some old names for the enum values */
288
289 s = address_family_from_string(rvalue);
290 if (s < 0) {
291
292 /* Previously, we had a slightly different enum here,
293 * support its values for compatibility. */
294
295 s = dhcp_deprecated_address_family_from_string(rvalue);
296 if (s < 0) {
297 log_syntax(unit, LOG_WARNING, filename, line, s,
298 "Failed to parse DHCP option, ignoring: %s", rvalue);
299 return 0;
300 }
301
302 log_syntax(unit, LOG_WARNING, filename, line, 0,
303 "DHCP=%s is deprecated, please use DHCP=%s instead.",
304 rvalue, address_family_to_string(s));
305 }
306
307 *dhcp = s;
308 return 0;
309 }
310
311 int config_parse_dhcp_or_ra_route_metric(
312 const char* unit,
313 const char *filename,
314 unsigned line,
315 const char *section,
316 unsigned section_line,
317 const char *lvalue,
318 int ltype,
319 const char *rvalue,
320 void *data,
321 void *userdata) {
322
323 Network *network = userdata;
324 uint32_t metric;
325 int r;
326
327 assert(filename);
328 assert(lvalue);
329 assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
330 assert(rvalue);
331 assert(data);
332
333 r = safe_atou32(rvalue, &metric);
334 if (r < 0) {
335 log_syntax(unit, LOG_WARNING, filename, line, r,
336 "Failed to parse RouteMetric=%s, ignoring assignment: %m", rvalue);
337 return 0;
338 }
339
340 switch(ltype) {
341 case AF_INET:
342 network->dhcp_route_metric = metric;
343 network->dhcp_route_metric_set = true;
344 break;
345 case AF_INET6:
346 network->ipv6_accept_ra_route_metric = metric;
347 network->ipv6_accept_ra_route_metric_set = true;
348 break;
349 case AF_UNSPEC:
350 /* For backward compatibility. */
351 if (!network->dhcp_route_metric_set)
352 network->dhcp_route_metric = metric;
353 if (!network->ipv6_accept_ra_route_metric_set)
354 network->ipv6_accept_ra_route_metric = metric;
355 break;
356 default:
357 assert_not_reached();
358 }
359
360 return 0;
361 }
362
363 int config_parse_dhcp_use_dns(
364 const char* unit,
365 const char *filename,
366 unsigned line,
367 const char *section,
368 unsigned section_line,
369 const char *lvalue,
370 int ltype,
371 const char *rvalue,
372 void *data,
373 void *userdata) {
374
375 Network *network = userdata;
376 int r;
377
378 assert(filename);
379 assert(lvalue);
380 assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
381 assert(rvalue);
382 assert(data);
383
384 r = parse_boolean(rvalue);
385 if (r < 0) {
386 log_syntax(unit, LOG_WARNING, filename, line, r,
387 "Failed to parse UseDNS=%s, ignoring assignment: %m", rvalue);
388 return 0;
389 }
390
391 switch(ltype) {
392 case AF_INET:
393 network->dhcp_use_dns = r;
394 network->dhcp_use_dns_set = true;
395 break;
396 case AF_INET6:
397 network->dhcp6_use_dns = r;
398 network->dhcp6_use_dns_set = true;
399 break;
400 case AF_UNSPEC:
401 /* For backward compatibility. */
402 if (!network->dhcp_use_dns_set)
403 network->dhcp_use_dns = r;
404 if (!network->dhcp6_use_dns_set)
405 network->dhcp6_use_dns = r;
406 break;
407 default:
408 assert_not_reached();
409 }
410
411 return 0;
412 }
413
414 int config_parse_dhcp_use_domains(
415 const char* unit,
416 const char *filename,
417 unsigned line,
418 const char *section,
419 unsigned section_line,
420 const char *lvalue,
421 int ltype,
422 const char *rvalue,
423 void *data,
424 void *userdata) {
425
426 Network *network = userdata;
427 DHCPUseDomains d;
428
429 assert(filename);
430 assert(lvalue);
431 assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
432 assert(rvalue);
433 assert(data);
434
435 d = dhcp_use_domains_from_string(rvalue);
436 if (d < 0) {
437 log_syntax(unit, LOG_WARNING, filename, line, d,
438 "Failed to parse %s=%s, ignoring assignment: %m", lvalue, rvalue);
439 return 0;
440 }
441
442 switch(ltype) {
443 case AF_INET:
444 network->dhcp_use_domains = d;
445 network->dhcp_use_domains_set = true;
446 break;
447 case AF_INET6:
448 network->dhcp6_use_domains = d;
449 network->dhcp6_use_domains_set = true;
450 break;
451 case AF_UNSPEC:
452 /* For backward compatibility. */
453 if (!network->dhcp_use_domains_set)
454 network->dhcp_use_domains = d;
455 if (!network->dhcp6_use_domains_set)
456 network->dhcp6_use_domains = d;
457 break;
458 default:
459 assert_not_reached();
460 }
461
462 return 0;
463 }
464
465 int config_parse_dhcp_use_ntp(
466 const char* unit,
467 const char *filename,
468 unsigned line,
469 const char *section,
470 unsigned section_line,
471 const char *lvalue,
472 int ltype,
473 const char *rvalue,
474 void *data,
475 void *userdata) {
476
477 Network *network = userdata;
478 int r;
479
480 assert(filename);
481 assert(lvalue);
482 assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
483 assert(rvalue);
484 assert(data);
485
486 r = parse_boolean(rvalue);
487 if (r < 0) {
488 log_syntax(unit, LOG_WARNING, filename, line, r,
489 "Failed to parse UseNTP=%s, ignoring assignment: %m", rvalue);
490 return 0;
491 }
492
493 switch(ltype) {
494 case AF_INET:
495 network->dhcp_use_ntp = r;
496 network->dhcp_use_ntp_set = true;
497 break;
498 case AF_INET6:
499 network->dhcp6_use_ntp = r;
500 network->dhcp6_use_ntp_set = true;
501 break;
502 case AF_UNSPEC:
503 /* For backward compatibility. */
504 if (!network->dhcp_use_ntp_set)
505 network->dhcp_use_ntp = r;
506 if (!network->dhcp6_use_ntp_set)
507 network->dhcp6_use_ntp = r;
508 break;
509 default:
510 assert_not_reached();
511 }
512
513 return 0;
514 }
515
516 int config_parse_dhcp_or_ra_route_table(
517 const char *unit,
518 const char *filename,
519 unsigned line,
520 const char *section,
521 unsigned section_line,
522 const char *lvalue,
523 int ltype,
524 const char *rvalue,
525 void *data,
526 void *userdata) {
527
528 Network *network = userdata;
529 uint32_t rt;
530 int r;
531
532 assert(filename);
533 assert(lvalue);
534 assert(IN_SET(ltype, AF_INET, AF_INET6));
535 assert(rvalue);
536 assert(userdata);
537
538 r = safe_atou32(rvalue, &rt);
539 if (r < 0) {
540 log_syntax(unit, LOG_WARNING, filename, line, r,
541 "Failed to parse RouteTable=%s, ignoring assignment: %m", rvalue);
542 return 0;
543 }
544
545 switch(ltype) {
546 case AF_INET:
547 network->dhcp_route_table = rt;
548 network->dhcp_route_table_set = true;
549 break;
550 case AF_INET6:
551 network->ipv6_accept_ra_route_table = rt;
552 network->ipv6_accept_ra_route_table_set = true;
553 break;
554 default:
555 assert_not_reached();
556 }
557
558 return 0;
559 }
560
561 int config_parse_iaid(
562 const char *unit,
563 const char *filename,
564 unsigned line,
565 const char *section,
566 unsigned section_line,
567 const char *lvalue,
568 int ltype,
569 const char *rvalue,
570 void *data,
571 void *userdata) {
572
573 Network *network = userdata;
574 uint32_t iaid;
575 int r;
576
577 assert(filename);
578 assert(lvalue);
579 assert(rvalue);
580 assert(network);
581 assert(IN_SET(ltype, AF_INET, AF_INET6));
582
583 r = safe_atou32(rvalue, &iaid);
584 if (r < 0) {
585 log_syntax(unit, LOG_WARNING, filename, line, r,
586 "Unable to read IAID, ignoring assignment: %s", rvalue);
587 return 0;
588 }
589
590 if (ltype == AF_INET) {
591 network->dhcp_iaid = iaid;
592 network->dhcp_iaid_set = true;
593 if (!network->dhcp6_iaid_set_explicitly) {
594 /* Backward compatibility. Previously, IAID is shared by DHCPv4 and DHCPv6.
595 * If DHCPv6 IAID is not specified explicitly, then use DHCPv4 IAID for DHCPv6. */
596 network->dhcp6_iaid = iaid;
597 network->dhcp6_iaid_set = true;
598 }
599 } else {
600 assert(ltype == AF_INET6);
601 network->dhcp6_iaid = iaid;
602 network->dhcp6_iaid_set = true;
603 network->dhcp6_iaid_set_explicitly = true;
604 }
605
606 return 0;
607 }
608
609 int config_parse_dhcp_user_or_vendor_class(
610 const char *unit,
611 const char *filename,
612 unsigned line,
613 const char *section,
614 unsigned section_line,
615 const char *lvalue,
616 int ltype,
617 const char *rvalue,
618 void *data,
619 void *userdata) {
620
621 char ***l = data;
622 int r;
623
624 assert(l);
625 assert(lvalue);
626 assert(rvalue);
627 assert(IN_SET(ltype, AF_INET, AF_INET6));
628
629 if (isempty(rvalue)) {
630 *l = strv_free(*l);
631 return 0;
632 }
633
634 for (const char *p = rvalue;;) {
635 _cleanup_free_ char *w = NULL;
636 size_t len;
637
638 r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
639 if (r == -ENOMEM)
640 return log_oom();
641 if (r < 0) {
642 log_syntax(unit, LOG_WARNING, filename, line, r,
643 "Failed to split user classes option, ignoring: %s", rvalue);
644 return 0;
645 }
646 if (r == 0)
647 return 0;
648
649 len = strlen(w);
650 if (ltype == AF_INET) {
651 if (len > UINT8_MAX || len == 0) {
652 log_syntax(unit, LOG_WARNING, filename, line, 0,
653 "%s length is not in the range 1…255, ignoring.", w);
654 continue;
655 }
656 } else {
657 if (len > UINT16_MAX || len == 0) {
658 log_syntax(unit, LOG_WARNING, filename, line, 0,
659 "%s length is not in the range 1…65535, ignoring.", w);
660 continue;
661 }
662 }
663
664 r = strv_consume(l, TAKE_PTR(w));
665 if (r < 0)
666 return log_oom();
667 }
668 }
669
670 int config_parse_dhcp_send_option(
671 const char *unit,
672 const char *filename,
673 unsigned line,
674 const char *section,
675 unsigned section_line,
676 const char *lvalue,
677 int ltype,
678 const char *rvalue,
679 void *data,
680 void *userdata) {
681
682 _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL;
683 _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL;
684 _unused_ _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *old4 = NULL;
685 _unused_ _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *old6 = NULL;
686 uint32_t uint32_data, enterprise_identifier = 0;
687 _cleanup_free_ char *word = NULL, *q = NULL;
688 OrderedHashmap **options = data;
689 uint16_t u16, uint16_data;
690 union in_addr_union addr;
691 DHCPOptionDataType type;
692 uint8_t u8, uint8_data;
693 const void *udata;
694 const char *p;
695 ssize_t sz;
696 int r;
697
698 assert(filename);
699 assert(lvalue);
700 assert(rvalue);
701 assert(data);
702
703 if (isempty(rvalue)) {
704 *options = ordered_hashmap_free(*options);
705 return 0;
706 }
707
708 p = rvalue;
709 if (ltype == AF_INET6 && streq(lvalue, "SendVendorOption")) {
710 r = extract_first_word(&p, &word, ":", 0);
711 if (r == -ENOMEM)
712 return log_oom();
713 if (r <= 0 || isempty(p)) {
714 log_syntax(unit, LOG_WARNING, filename, line, r,
715 "Invalid DHCP option, ignoring assignment: %s", rvalue);
716 return 0;
717 }
718
719 r = safe_atou32(word, &enterprise_identifier);
720 if (r < 0) {
721 log_syntax(unit, LOG_WARNING, filename, line, r,
722 "Failed to parse DHCPv6 enterprise identifier data, ignoring assignment: %s", p);
723 return 0;
724 }
725 word = mfree(word);
726 }
727
728 r = extract_first_word(&p, &word, ":", 0);
729 if (r == -ENOMEM)
730 return log_oom();
731 if (r <= 0 || isempty(p)) {
732 log_syntax(unit, LOG_WARNING, filename, line, r,
733 "Invalid DHCP option, ignoring assignment: %s", rvalue);
734 return 0;
735 }
736
737 if (ltype == AF_INET6) {
738 r = safe_atou16(word, &u16);
739 if (r < 0) {
740 log_syntax(unit, LOG_WARNING, filename, line, r,
741 "Invalid DHCP option, ignoring assignment: %s", rvalue);
742 return 0;
743 }
744 if (u16 < 1 || u16 >= UINT16_MAX) {
745 log_syntax(unit, LOG_WARNING, filename, line, 0,
746 "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue);
747 return 0;
748 }
749 } else {
750 r = safe_atou8(word, &u8);
751 if (r < 0) {
752 log_syntax(unit, LOG_WARNING, filename, line, r,
753 "Invalid DHCP option, ignoring assignment: %s", rvalue);
754 return 0;
755 }
756 if (u8 < 1 || u8 >= UINT8_MAX) {
757 log_syntax(unit, LOG_WARNING, filename, line, 0,
758 "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue);
759 return 0;
760 }
761 }
762
763 word = mfree(word);
764 r = extract_first_word(&p, &word, ":", 0);
765 if (r == -ENOMEM)
766 return log_oom();
767 if (r <= 0 || isempty(p)) {
768 log_syntax(unit, LOG_WARNING, filename, line, r,
769 "Invalid DHCP option, ignoring assignment: %s", rvalue);
770 return 0;
771 }
772
773 type = dhcp_option_data_type_from_string(word);
774 if (type < 0) {
775 log_syntax(unit, LOG_WARNING, filename, line, type,
776 "Invalid DHCP option data type, ignoring assignment: %s", p);
777 return 0;
778 }
779
780 switch(type) {
781 case DHCP_OPTION_DATA_UINT8:{
782 r = safe_atou8(p, &uint8_data);
783 if (r < 0) {
784 log_syntax(unit, LOG_WARNING, filename, line, r,
785 "Failed to parse DHCP uint8 data, ignoring assignment: %s", p);
786 return 0;
787 }
788
789 udata = &uint8_data;
790 sz = sizeof(uint8_t);
791 break;
792 }
793 case DHCP_OPTION_DATA_UINT16:{
794 uint16_t k;
795
796 r = safe_atou16(p, &k);
797 if (r < 0) {
798 log_syntax(unit, LOG_WARNING, filename, line, r,
799 "Failed to parse DHCP uint16 data, ignoring assignment: %s", p);
800 return 0;
801 }
802
803 uint16_data = htobe16(k);
804 udata = &uint16_data;
805 sz = sizeof(uint16_t);
806 break;
807 }
808 case DHCP_OPTION_DATA_UINT32: {
809 uint32_t k;
810
811 r = safe_atou32(p, &k);
812 if (r < 0) {
813 log_syntax(unit, LOG_WARNING, filename, line, r,
814 "Failed to parse DHCP uint32 data, ignoring assignment: %s", p);
815 return 0;
816 }
817
818 uint32_data = htobe32(k);
819 udata = &uint32_data;
820 sz = sizeof(uint32_t);
821
822 break;
823 }
824 case DHCP_OPTION_DATA_IPV4ADDRESS: {
825 r = in_addr_from_string(AF_INET, p, &addr);
826 if (r < 0) {
827 log_syntax(unit, LOG_WARNING, filename, line, r,
828 "Failed to parse DHCP ipv4address data, ignoring assignment: %s", p);
829 return 0;
830 }
831
832 udata = &addr.in;
833 sz = sizeof(addr.in.s_addr);
834 break;
835 }
836 case DHCP_OPTION_DATA_IPV6ADDRESS: {
837 r = in_addr_from_string(AF_INET6, p, &addr);
838 if (r < 0) {
839 log_syntax(unit, LOG_WARNING, filename, line, r,
840 "Failed to parse DHCP ipv6address data, ignoring assignment: %s", p);
841 return 0;
842 }
843
844 udata = &addr.in6;
845 sz = sizeof(addr.in6.s6_addr);
846 break;
847 }
848 case DHCP_OPTION_DATA_STRING:
849 sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &q);
850 if (sz < 0)
851 log_syntax(unit, LOG_WARNING, filename, line, sz,
852 "Failed to decode DHCP option data, ignoring assignment: %s", p);
853
854 udata = q;
855 break;
856 default:
857 return -EINVAL;
858 }
859
860 if (ltype == AF_INET6) {
861 r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6);
862 if (r < 0) {
863 log_syntax(unit, LOG_WARNING, filename, line, r,
864 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
865 return 0;
866 }
867
868 r = ordered_hashmap_ensure_allocated(options, &dhcp6_option_hash_ops);
869 if (r < 0)
870 return log_oom();
871
872 /* Overwrite existing option */
873 old6 = ordered_hashmap_get(*options, UINT_TO_PTR(u16));
874 r = ordered_hashmap_replace(*options, UINT_TO_PTR(u16), opt6);
875 if (r < 0) {
876 log_syntax(unit, LOG_WARNING, filename, line, r,
877 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
878 return 0;
879 }
880 TAKE_PTR(opt6);
881 } else {
882 r = sd_dhcp_option_new(u8, udata, sz, &opt4);
883 if (r < 0) {
884 log_syntax(unit, LOG_WARNING, filename, line, r,
885 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
886 return 0;
887 }
888
889 r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops);
890 if (r < 0)
891 return log_oom();
892
893 /* Overwrite existing option */
894 old4 = ordered_hashmap_get(*options, UINT_TO_PTR(u8));
895 r = ordered_hashmap_replace(*options, UINT_TO_PTR(u8), opt4);
896 if (r < 0) {
897 log_syntax(unit, LOG_WARNING, filename, line, r,
898 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
899 return 0;
900 }
901 TAKE_PTR(opt4);
902 }
903 return 0;
904 }
905
906 int config_parse_dhcp_request_options(
907 const char *unit,
908 const char *filename,
909 unsigned line,
910 const char *section,
911 unsigned section_line,
912 const char *lvalue,
913 int ltype,
914 const char *rvalue,
915 void *data,
916 void *userdata) {
917
918 Network *network = userdata;
919 int r;
920
921 assert(filename);
922 assert(lvalue);
923 assert(rvalue);
924 assert(data);
925
926 if (isempty(rvalue)) {
927 if (ltype == AF_INET)
928 network->dhcp_request_options = set_free(network->dhcp_request_options);
929 else
930 network->dhcp6_request_options = set_free(network->dhcp6_request_options);
931
932 return 0;
933 }
934
935 for (const char *p = rvalue;;) {
936 _cleanup_free_ char *n = NULL;
937 uint32_t i;
938
939 r = extract_first_word(&p, &n, NULL, 0);
940 if (r == -ENOMEM)
941 return log_oom();
942 if (r < 0) {
943 log_syntax(unit, LOG_WARNING, filename, line, r,
944 "Failed to parse DHCP request option, ignoring assignment: %s",
945 rvalue);
946 return 0;
947 }
948 if (r == 0)
949 return 0;
950
951 r = safe_atou32(n, &i);
952 if (r < 0) {
953 log_syntax(unit, LOG_WARNING, filename, line, r,
954 "DHCP request option is invalid, ignoring assignment: %s", n);
955 continue;
956 }
957
958 if (i < 1 || i >= UINT8_MAX) {
959 log_syntax(unit, LOG_WARNING, filename, line, 0,
960 "DHCP request option is invalid, valid range is 1-254, ignoring assignment: %s", n);
961 continue;
962 }
963
964 r = set_ensure_put(ltype == AF_INET ? &network->dhcp_request_options : &network->dhcp6_request_options,
965 NULL, UINT32_TO_PTR(i));
966 if (r < 0)
967 log_syntax(unit, LOG_WARNING, filename, line, r,
968 "Failed to store DHCP request option '%s', ignoring assignment: %m", n);
969 }
970 }
971
972 static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = {
973 [DHCP_USE_DOMAINS_NO] = "no",
974 [DHCP_USE_DOMAINS_ROUTE] = "route",
975 [DHCP_USE_DOMAINS_YES] = "yes",
976 };
977
978 DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES);
979
980 static const char * const dhcp_option_data_type_table[_DHCP_OPTION_DATA_MAX] = {
981 [DHCP_OPTION_DATA_UINT8] = "uint8",
982 [DHCP_OPTION_DATA_UINT16] = "uint16",
983 [DHCP_OPTION_DATA_UINT32] = "uint32",
984 [DHCP_OPTION_DATA_STRING] = "string",
985 [DHCP_OPTION_DATA_IPV4ADDRESS] = "ipv4address",
986 [DHCP_OPTION_DATA_IPV6ADDRESS] = "ipv6address",
987 };
988
989 DEFINE_STRING_TABLE_LOOKUP(dhcp_option_data_type, DHCPOptionDataType);
990
991 static const char* const duid_type_table[_DUID_TYPE_MAX] = {
992 [DUID_TYPE_LLT] = "link-layer-time",
993 [DUID_TYPE_EN] = "vendor",
994 [DUID_TYPE_LL] = "link-layer",
995 [DUID_TYPE_UUID] = "uuid",
996 };
997 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(duid_type, DUIDType);
998
999 int config_parse_duid_type(
1000 const char *unit,
1001 const char *filename,
1002 unsigned line,
1003 const char *section,
1004 unsigned section_line,
1005 const char *lvalue,
1006 int ltype,
1007 const char *rvalue,
1008 void *data,
1009 void *userdata) {
1010
1011 _cleanup_free_ char *type_string = NULL;
1012 const char *p = rvalue;
1013 bool force = ltype;
1014 DUID *duid = data;
1015 DUIDType type;
1016 int r;
1017
1018 assert(filename);
1019 assert(lvalue);
1020 assert(rvalue);
1021 assert(duid);
1022
1023 if (!force && duid->set)
1024 return 0;
1025
1026 r = extract_first_word(&p, &type_string, ":", 0);
1027 if (r == -ENOMEM)
1028 return log_oom();
1029 if (r < 0) {
1030 log_syntax(unit, LOG_WARNING, filename, line, r,
1031 "Invalid syntax, ignoring: %s", rvalue);
1032 return 0;
1033 }
1034 if (r == 0) {
1035 log_syntax(unit, LOG_WARNING, filename, line, 0,
1036 "Failed to extract DUID type from '%s', ignoring.", rvalue);
1037 return 0;
1038 }
1039
1040 type = duid_type_from_string(type_string);
1041 if (type < 0) {
1042 log_syntax(unit, LOG_WARNING, filename, line, type,
1043 "Failed to parse DUID type '%s', ignoring.", type_string);
1044 return 0;
1045 }
1046
1047 if (!isempty(p)) {
1048 usec_t u;
1049
1050 if (type != DUID_TYPE_LLT) {
1051 log_syntax(unit, LOG_WARNING, filename, line, r,
1052 "Invalid syntax, ignoring: %s", rvalue);
1053 return 0;
1054 }
1055
1056 r = parse_timestamp(p, &u);
1057 if (r < 0) {
1058 log_syntax(unit, LOG_WARNING, filename, line, r,
1059 "Failed to parse timestamp, ignoring: %s", p);
1060 return 0;
1061 }
1062
1063 duid->llt_time = u;
1064 }
1065
1066 duid->type = type;
1067 duid->set = force;
1068
1069 return 0;
1070 }
1071
1072 int config_parse_manager_duid_type(
1073 const char *unit,
1074 const char *filename,
1075 unsigned line,
1076 const char *section,
1077 unsigned section_line,
1078 const char *lvalue,
1079 int ltype,
1080 const char *rvalue,
1081 void *data,
1082 void *userdata) {
1083
1084 Manager *manager = userdata;
1085 int r;
1086
1087 assert(manager);
1088
1089 /* For backward compatibility. Setting both DHCPv4 and DHCPv6 DUID if they are not specified explicitly. */
1090
1091 r = config_parse_duid_type(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp_duid, manager);
1092 if (r < 0)
1093 return r;
1094
1095 return config_parse_duid_type(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp6_duid, manager);
1096 }
1097
1098 int config_parse_network_duid_type(
1099 const char *unit,
1100 const char *filename,
1101 unsigned line,
1102 const char *section,
1103 unsigned section_line,
1104 const char *lvalue,
1105 int ltype,
1106 const char *rvalue,
1107 void *data,
1108 void *userdata) {
1109
1110 Network *network = userdata;
1111 int r;
1112
1113 assert(network);
1114
1115 r = config_parse_duid_type(unit, filename, line, section, section_line, lvalue, true, rvalue, &network->dhcp_duid, network);
1116 if (r < 0)
1117 return r;
1118
1119 /* For backward compatibility, also set DHCPv6 DUID if not specified explicitly. */
1120 return config_parse_duid_type(unit, filename, line, section, section_line, lvalue, false, rvalue, &network->dhcp6_duid, network);
1121 }
1122
1123 int config_parse_duid_rawdata(
1124 const char *unit,
1125 const char *filename,
1126 unsigned line,
1127 const char *section,
1128 unsigned section_line,
1129 const char *lvalue,
1130 int ltype,
1131 const char *rvalue,
1132 void *data,
1133 void *userdata) {
1134
1135 uint8_t raw_data[MAX_DUID_LEN];
1136 unsigned count = 0;
1137 bool force = ltype;
1138 DUID *duid = data;
1139
1140 assert(filename);
1141 assert(lvalue);
1142 assert(rvalue);
1143 assert(duid);
1144
1145 if (!force && duid->set)
1146 return 0;
1147
1148 /* RawData contains DUID in format "NN:NN:NN..." */
1149 for (const char *p = rvalue;;) {
1150 int n1, n2, len, r;
1151 uint32_t byte;
1152 _cleanup_free_ char *cbyte = NULL;
1153
1154 r = extract_first_word(&p, &cbyte, ":", 0);
1155 if (r == -ENOMEM)
1156 return log_oom();
1157 if (r < 0) {
1158 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to read DUID, ignoring assignment: %s.", rvalue);
1159 return 0;
1160 }
1161 if (r == 0)
1162 break;
1163
1164 if (count >= MAX_DUID_LEN) {
1165 log_syntax(unit, LOG_WARNING, filename, line, 0, "Max DUID length exceeded, ignoring assignment: %s.", rvalue);
1166 return 0;
1167 }
1168
1169 len = strlen(cbyte);
1170 if (!IN_SET(len, 1, 2)) {
1171 log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid length - DUID byte: %s, ignoring assignment: %s.", cbyte, rvalue);
1172 return 0;
1173 }
1174 n1 = unhexchar(cbyte[0]);
1175 if (len == 2)
1176 n2 = unhexchar(cbyte[1]);
1177 else
1178 n2 = 0;
1179
1180 if (n1 < 0 || n2 < 0) {
1181 log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid DUID byte: %s. Ignoring assignment: %s.", cbyte, rvalue);
1182 return 0;
1183 }
1184
1185 byte = ((uint8_t) n1 << (4 * (len-1))) | (uint8_t) n2;
1186 raw_data[count++] = byte;
1187 }
1188
1189 assert_cc(sizeof(raw_data) == sizeof(duid->raw_data));
1190 memcpy(duid->raw_data, raw_data, count);
1191 duid->raw_data_len = count;
1192 duid->set = force;
1193
1194 return 0;
1195 }
1196
1197 int config_parse_manager_duid_rawdata(
1198 const char *unit,
1199 const char *filename,
1200 unsigned line,
1201 const char *section,
1202 unsigned section_line,
1203 const char *lvalue,
1204 int ltype,
1205 const char *rvalue,
1206 void *data,
1207 void *userdata) {
1208
1209 Manager *manager = userdata;
1210 int r;
1211
1212 assert(manager);
1213
1214 /* For backward compatibility. Setting both DHCPv4 and DHCPv6 DUID if they are not specified explicitly. */
1215
1216 r = config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp_duid, manager);
1217 if (r < 0)
1218 return r;
1219
1220 return config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp6_duid, manager);
1221 }
1222
1223 int config_parse_network_duid_rawdata(
1224 const char *unit,
1225 const char *filename,
1226 unsigned line,
1227 const char *section,
1228 unsigned section_line,
1229 const char *lvalue,
1230 int ltype,
1231 const char *rvalue,
1232 void *data,
1233 void *userdata) {
1234
1235 Network *network = userdata;
1236 int r;
1237
1238 assert(network);
1239
1240 r = config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, true, rvalue, &network->dhcp_duid, network);
1241 if (r < 0)
1242 return r;
1243
1244 /* For backward compatibility, also set DHCPv6 DUID if not specified explicitly. */
1245 return config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, false, rvalue, &network->dhcp6_duid, network);
1246 }
1247
1248 int config_parse_uplink(
1249 const char *unit,
1250 const char *filename,
1251 unsigned line,
1252 const char *section,
1253 unsigned section_line,
1254 const char *lvalue,
1255 int ltype,
1256 const char *rvalue,
1257 void *data,
1258 void *userdata) {
1259
1260 Network *network = userdata;
1261 bool accept_none = true;
1262 int *index, r;
1263 char **name;
1264
1265 assert(filename);
1266 assert(section);
1267 assert(lvalue);
1268 assert(rvalue);
1269 assert(userdata);
1270
1271 if (streq(section, "DHCPServer")) {
1272 index = &network->dhcp_server_uplink_index;
1273 name = &network->dhcp_server_uplink_name;
1274 } else if (streq(section, "IPv6SendRA")) {
1275 index = &network->router_uplink_index;
1276 name = &network->router_uplink_name;
1277 } else if (STR_IN_SET(section, "DHCPv6PrefixDelegation", "DHCPPrefixDelegation")) {
1278 index = &network->dhcp_pd_uplink_index;
1279 name = &network->dhcp_pd_uplink_name;
1280 accept_none = false;
1281 } else
1282 assert_not_reached();
1283
1284 if (isempty(rvalue) || streq(rvalue, ":auto")) {
1285 *index = UPLINK_INDEX_AUTO;
1286 *name = mfree(*name);
1287 return 0;
1288 }
1289
1290 if (accept_none && streq(rvalue, ":none")) {
1291 *index = UPLINK_INDEX_NONE;
1292 *name = mfree(*name);
1293 return 0;
1294 }
1295
1296 if (!accept_none && streq(rvalue, ":self")) {
1297 *index = UPLINK_INDEX_SELF;
1298 *name = mfree(*name);
1299 return 0;
1300 }
1301
1302 r = parse_ifindex(rvalue);
1303 if (r > 0) {
1304 *index = r;
1305 *name = mfree(*name);
1306 return 0;
1307 }
1308
1309 if (!ifname_valid_full(rvalue, IFNAME_VALID_ALTERNATIVE)) {
1310 log_syntax(unit, LOG_WARNING, filename, line, 0,
1311 "Invalid interface name in %s=, ignoring assignment: %s", lvalue, rvalue);
1312 return 0;
1313 }
1314
1315 /* The interface name will be resolved later. */
1316 r = free_and_strdup_warn(name, rvalue);
1317 if (r < 0)
1318 return r;
1319
1320 /* Note, if uplink_name is set, then uplink_index will be ignored. So, the below does not mean
1321 * an uplink interface will be selected automatically. */
1322 *index = UPLINK_INDEX_AUTO;
1323 return 0;
1324 }