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