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