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