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