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