]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-dhcp-common.c
2b442aa4c18bda44ae5bfdd2bab92248e912b457
[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 int config_parse_dhcp_use_dns(
534 const char* unit,
535 const char *filename,
536 unsigned line,
537 const char *section,
538 unsigned section_line,
539 const char *lvalue,
540 int ltype,
541 const char *rvalue,
542 void *data,
543 void *userdata) {
544
545 Network *network = userdata;
546 int r;
547
548 assert(filename);
549 assert(lvalue);
550 assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
551 assert(rvalue);
552 assert(data);
553
554 r = parse_boolean(rvalue);
555 if (r < 0) {
556 log_syntax(unit, LOG_WARNING, filename, line, r,
557 "Failed to parse UseDNS=%s, ignoring assignment: %m", rvalue);
558 return 0;
559 }
560
561 switch (ltype) {
562 case AF_INET:
563 network->dhcp_use_dns = r;
564 network->dhcp_use_dns_set = true;
565 break;
566 case AF_INET6:
567 network->dhcp6_use_dns = r;
568 network->dhcp6_use_dns_set = true;
569 break;
570 case AF_UNSPEC:
571 /* For backward compatibility. */
572 if (!network->dhcp_use_dns_set)
573 network->dhcp_use_dns = r;
574 if (!network->dhcp6_use_dns_set)
575 network->dhcp6_use_dns = r;
576 break;
577 default:
578 assert_not_reached();
579 }
580
581 return 0;
582 }
583
584 int config_parse_dhcp_use_domains(
585 const char* unit,
586 const char *filename,
587 unsigned line,
588 const char *section,
589 unsigned section_line,
590 const char *lvalue,
591 int ltype,
592 const char *rvalue,
593 void *data,
594 void *userdata) {
595
596 Network *network = userdata;
597 DHCPUseDomains d;
598
599 assert(filename);
600 assert(lvalue);
601 assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
602 assert(rvalue);
603 assert(data);
604
605 d = dhcp_use_domains_from_string(rvalue);
606 if (d < 0) {
607 log_syntax(unit, LOG_WARNING, filename, line, d,
608 "Failed to parse %s=%s, ignoring assignment: %m", lvalue, rvalue);
609 return 0;
610 }
611
612 switch (ltype) {
613 case AF_INET:
614 network->dhcp_use_domains = d;
615 network->dhcp_use_domains_set = true;
616 break;
617 case AF_INET6:
618 network->dhcp6_use_domains = d;
619 network->dhcp6_use_domains_set = true;
620 break;
621 case AF_UNSPEC:
622 /* For backward compatibility. */
623 if (!network->dhcp_use_domains_set)
624 network->dhcp_use_domains = d;
625 if (!network->dhcp6_use_domains_set)
626 network->dhcp6_use_domains = d;
627 break;
628 default:
629 assert_not_reached();
630 }
631
632 return 0;
633 }
634
635 int config_parse_dhcp_use_ntp(
636 const char* unit,
637 const char *filename,
638 unsigned line,
639 const char *section,
640 unsigned section_line,
641 const char *lvalue,
642 int ltype,
643 const char *rvalue,
644 void *data,
645 void *userdata) {
646
647 Network *network = userdata;
648 int r;
649
650 assert(filename);
651 assert(lvalue);
652 assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6));
653 assert(rvalue);
654 assert(data);
655
656 r = parse_boolean(rvalue);
657 if (r < 0) {
658 log_syntax(unit, LOG_WARNING, filename, line, r,
659 "Failed to parse UseNTP=%s, ignoring assignment: %m", rvalue);
660 return 0;
661 }
662
663 switch (ltype) {
664 case AF_INET:
665 network->dhcp_use_ntp = r;
666 network->dhcp_use_ntp_set = true;
667 break;
668 case AF_INET6:
669 network->dhcp6_use_ntp = r;
670 network->dhcp6_use_ntp_set = true;
671 break;
672 case AF_UNSPEC:
673 /* For backward compatibility. */
674 if (!network->dhcp_use_ntp_set)
675 network->dhcp_use_ntp = r;
676 if (!network->dhcp6_use_ntp_set)
677 network->dhcp6_use_ntp = r;
678 break;
679 default:
680 assert_not_reached();
681 }
682
683 return 0;
684 }
685
686 int config_parse_dhcp_or_ra_route_table(
687 const char *unit,
688 const char *filename,
689 unsigned line,
690 const char *section,
691 unsigned section_line,
692 const char *lvalue,
693 int ltype,
694 const char *rvalue,
695 void *data,
696 void *userdata) {
697
698 Network *network = ASSERT_PTR(userdata);
699 uint32_t rt;
700 int r;
701
702 assert(filename);
703 assert(lvalue);
704 assert(IN_SET(ltype, AF_INET, AF_INET6));
705 assert(rvalue);
706
707 r = manager_get_route_table_from_string(network->manager, rvalue, &rt);
708 if (r < 0) {
709 log_syntax(unit, LOG_WARNING, filename, line, r,
710 "Failed to parse RouteTable=%s, ignoring assignment: %m", rvalue);
711 return 0;
712 }
713
714 switch (ltype) {
715 case AF_INET:
716 network->dhcp_route_table = rt;
717 network->dhcp_route_table_set = true;
718 break;
719 case AF_INET6:
720 network->ndisc_route_table = rt;
721 network->ndisc_route_table_set = true;
722 break;
723 default:
724 assert_not_reached();
725 }
726
727 return 0;
728 }
729
730 int config_parse_iaid(
731 const char *unit,
732 const char *filename,
733 unsigned line,
734 const char *section,
735 unsigned section_line,
736 const char *lvalue,
737 int ltype,
738 const char *rvalue,
739 void *data,
740 void *userdata) {
741
742 Network *network = ASSERT_PTR(userdata);
743 uint32_t iaid;
744 int r;
745
746 assert(filename);
747 assert(lvalue);
748 assert(rvalue);
749 assert(IN_SET(ltype, AF_INET, AF_INET6));
750
751 r = safe_atou32(rvalue, &iaid);
752 if (r < 0) {
753 log_syntax(unit, LOG_WARNING, filename, line, r,
754 "Unable to read IAID, ignoring assignment: %s", rvalue);
755 return 0;
756 }
757
758 if (ltype == AF_INET) {
759 network->dhcp_iaid = iaid;
760 network->dhcp_iaid_set = true;
761 if (!network->dhcp6_iaid_set_explicitly) {
762 /* Backward compatibility. Previously, IAID is shared by DHCPv4 and DHCPv6.
763 * If DHCPv6 IAID is not specified explicitly, then use DHCPv4 IAID for DHCPv6. */
764 network->dhcp6_iaid = iaid;
765 network->dhcp6_iaid_set = true;
766 }
767 } else {
768 assert(ltype == AF_INET6);
769 network->dhcp6_iaid = iaid;
770 network->dhcp6_iaid_set = true;
771 network->dhcp6_iaid_set_explicitly = true;
772 }
773
774 return 0;
775 }
776
777 int config_parse_dhcp_user_or_vendor_class(
778 const char *unit,
779 const char *filename,
780 unsigned line,
781 const char *section,
782 unsigned section_line,
783 const char *lvalue,
784 int ltype,
785 const char *rvalue,
786 void *data,
787 void *userdata) {
788
789 char ***l = ASSERT_PTR(data);
790 int r;
791
792 assert(lvalue);
793 assert(rvalue);
794 assert(IN_SET(ltype, AF_INET, AF_INET6));
795
796 if (isempty(rvalue)) {
797 *l = strv_free(*l);
798 return 0;
799 }
800
801 for (const char *p = rvalue;;) {
802 _cleanup_free_ char *w = NULL;
803 size_t len;
804
805 r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
806 if (r == -ENOMEM)
807 return log_oom();
808 if (r < 0) {
809 log_syntax(unit, LOG_WARNING, filename, line, r,
810 "Failed to split user classes option, ignoring: %s", rvalue);
811 return 0;
812 }
813 if (r == 0)
814 return 0;
815
816 len = strlen(w);
817 if (ltype == AF_INET) {
818 if (len > UINT8_MAX || len == 0) {
819 log_syntax(unit, LOG_WARNING, filename, line, 0,
820 "%s length is not in the range 1…255, ignoring.", w);
821 continue;
822 }
823 } else {
824 if (len > UINT16_MAX || len == 0) {
825 log_syntax(unit, LOG_WARNING, filename, line, 0,
826 "%s length is not in the range 1…65535, ignoring.", w);
827 continue;
828 }
829 }
830
831 r = strv_consume(l, TAKE_PTR(w));
832 if (r < 0)
833 return log_oom();
834 }
835 }
836
837 int config_parse_dhcp_send_option(
838 const char *unit,
839 const char *filename,
840 unsigned line,
841 const char *section,
842 unsigned section_line,
843 const char *lvalue,
844 int ltype,
845 const char *rvalue,
846 void *data,
847 void *userdata) {
848
849 _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL;
850 _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL;
851 _unused_ _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *old4 = NULL;
852 _unused_ _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *old6 = NULL;
853 uint32_t uint32_data, enterprise_identifier = 0;
854 _cleanup_free_ char *word = NULL, *q = NULL;
855 OrderedHashmap **options = ASSERT_PTR(data);
856 uint16_t u16, uint16_data;
857 union in_addr_union addr;
858 DHCPOptionDataType type;
859 uint8_t u8, uint8_data;
860 const void *udata;
861 const char *p;
862 ssize_t sz;
863 int r;
864
865 assert(filename);
866 assert(lvalue);
867 assert(rvalue);
868
869 if (isempty(rvalue)) {
870 *options = ordered_hashmap_free(*options);
871 return 0;
872 }
873
874 p = rvalue;
875 if (ltype == AF_INET6 && streq(lvalue, "SendVendorOption")) {
876 r = extract_first_word(&p, &word, ":", 0);
877 if (r == -ENOMEM)
878 return log_oom();
879 if (r <= 0 || isempty(p)) {
880 log_syntax(unit, LOG_WARNING, filename, line, r,
881 "Invalid DHCP option, ignoring assignment: %s", rvalue);
882 return 0;
883 }
884
885 r = safe_atou32(word, &enterprise_identifier);
886 if (r < 0) {
887 log_syntax(unit, LOG_WARNING, filename, line, r,
888 "Failed to parse DHCPv6 enterprise identifier data, ignoring assignment: %s", p);
889 return 0;
890 }
891 word = mfree(word);
892 }
893
894 r = extract_first_word(&p, &word, ":", 0);
895 if (r == -ENOMEM)
896 return log_oom();
897 if (r <= 0 || isempty(p)) {
898 log_syntax(unit, LOG_WARNING, filename, line, r,
899 "Invalid DHCP option, ignoring assignment: %s", rvalue);
900 return 0;
901 }
902
903 if (ltype == AF_INET6) {
904 r = safe_atou16(word, &u16);
905 if (r < 0) {
906 log_syntax(unit, LOG_WARNING, filename, line, r,
907 "Invalid DHCP option, ignoring assignment: %s", rvalue);
908 return 0;
909 }
910 if (u16 < 1 || u16 >= UINT16_MAX) {
911 log_syntax(unit, LOG_WARNING, filename, line, 0,
912 "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue);
913 return 0;
914 }
915 } else {
916 r = safe_atou8(word, &u8);
917 if (r < 0) {
918 log_syntax(unit, LOG_WARNING, filename, line, r,
919 "Invalid DHCP option, ignoring assignment: %s", rvalue);
920 return 0;
921 }
922 if (u8 < 1 || u8 >= UINT8_MAX) {
923 log_syntax(unit, LOG_WARNING, filename, line, 0,
924 "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue);
925 return 0;
926 }
927 }
928
929 word = mfree(word);
930 r = extract_first_word(&p, &word, ":", 0);
931 if (r == -ENOMEM)
932 return log_oom();
933 if (r <= 0 || isempty(p)) {
934 log_syntax(unit, LOG_WARNING, filename, line, r,
935 "Invalid DHCP option, ignoring assignment: %s", rvalue);
936 return 0;
937 }
938
939 type = dhcp_option_data_type_from_string(word);
940 if (type < 0) {
941 log_syntax(unit, LOG_WARNING, filename, line, type,
942 "Invalid DHCP option data type, ignoring assignment: %s", p);
943 return 0;
944 }
945
946 switch (type) {
947 case DHCP_OPTION_DATA_UINT8:{
948 r = safe_atou8(p, &uint8_data);
949 if (r < 0) {
950 log_syntax(unit, LOG_WARNING, filename, line, r,
951 "Failed to parse DHCP uint8 data, ignoring assignment: %s", p);
952 return 0;
953 }
954
955 udata = &uint8_data;
956 sz = sizeof(uint8_t);
957 break;
958 }
959 case DHCP_OPTION_DATA_UINT16:{
960 uint16_t k;
961
962 r = safe_atou16(p, &k);
963 if (r < 0) {
964 log_syntax(unit, LOG_WARNING, filename, line, r,
965 "Failed to parse DHCP uint16 data, ignoring assignment: %s", p);
966 return 0;
967 }
968
969 uint16_data = htobe16(k);
970 udata = &uint16_data;
971 sz = sizeof(uint16_t);
972 break;
973 }
974 case DHCP_OPTION_DATA_UINT32: {
975 uint32_t k;
976
977 r = safe_atou32(p, &k);
978 if (r < 0) {
979 log_syntax(unit, LOG_WARNING, filename, line, r,
980 "Failed to parse DHCP uint32 data, ignoring assignment: %s", p);
981 return 0;
982 }
983
984 uint32_data = htobe32(k);
985 udata = &uint32_data;
986 sz = sizeof(uint32_t);
987
988 break;
989 }
990 case DHCP_OPTION_DATA_IPV4ADDRESS: {
991 r = in_addr_from_string(AF_INET, p, &addr);
992 if (r < 0) {
993 log_syntax(unit, LOG_WARNING, filename, line, r,
994 "Failed to parse DHCP ipv4address data, ignoring assignment: %s", p);
995 return 0;
996 }
997
998 udata = &addr.in;
999 sz = sizeof(addr.in.s_addr);
1000 break;
1001 }
1002 case DHCP_OPTION_DATA_IPV6ADDRESS: {
1003 r = in_addr_from_string(AF_INET6, p, &addr);
1004 if (r < 0) {
1005 log_syntax(unit, LOG_WARNING, filename, line, r,
1006 "Failed to parse DHCP ipv6address data, ignoring assignment: %s", p);
1007 return 0;
1008 }
1009
1010 udata = &addr.in6;
1011 sz = sizeof(addr.in6.s6_addr);
1012 break;
1013 }
1014 case DHCP_OPTION_DATA_STRING:
1015 sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &q);
1016 if (sz < 0) {
1017 log_syntax(unit, LOG_WARNING, filename, line, sz,
1018 "Failed to decode DHCP option data, ignoring assignment: %s", p);
1019 return 0;
1020 }
1021
1022 udata = q;
1023 break;
1024 default:
1025 return -EINVAL;
1026 }
1027
1028 if (ltype == AF_INET6) {
1029 r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6);
1030 if (r < 0) {
1031 log_syntax(unit, LOG_WARNING, filename, line, r,
1032 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
1033 return 0;
1034 }
1035
1036 r = ordered_hashmap_ensure_allocated(options, &dhcp6_option_hash_ops);
1037 if (r < 0)
1038 return log_oom();
1039
1040 /* Overwrite existing option */
1041 old6 = ordered_hashmap_get(*options, UINT_TO_PTR(u16));
1042 r = ordered_hashmap_replace(*options, UINT_TO_PTR(u16), opt6);
1043 if (r < 0) {
1044 log_syntax(unit, LOG_WARNING, filename, line, r,
1045 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
1046 return 0;
1047 }
1048 TAKE_PTR(opt6);
1049 } else {
1050 r = sd_dhcp_option_new(u8, udata, sz, &opt4);
1051 if (r < 0) {
1052 log_syntax(unit, LOG_WARNING, filename, line, r,
1053 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
1054 return 0;
1055 }
1056
1057 r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops);
1058 if (r < 0)
1059 return log_oom();
1060
1061 /* Overwrite existing option */
1062 old4 = ordered_hashmap_get(*options, UINT_TO_PTR(u8));
1063 r = ordered_hashmap_replace(*options, UINT_TO_PTR(u8), opt4);
1064 if (r < 0) {
1065 log_syntax(unit, LOG_WARNING, filename, line, r,
1066 "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
1067 return 0;
1068 }
1069 TAKE_PTR(opt4);
1070 }
1071 return 0;
1072 }
1073
1074 int config_parse_dhcp_request_options(
1075 const char *unit,
1076 const char *filename,
1077 unsigned line,
1078 const char *section,
1079 unsigned section_line,
1080 const char *lvalue,
1081 int ltype,
1082 const char *rvalue,
1083 void *data,
1084 void *userdata) {
1085
1086 Network *network = userdata;
1087 int r;
1088
1089 assert(filename);
1090 assert(lvalue);
1091 assert(rvalue);
1092 assert(data);
1093
1094 if (isempty(rvalue)) {
1095 if (ltype == AF_INET)
1096 network->dhcp_request_options = set_free(network->dhcp_request_options);
1097 else
1098 network->dhcp6_request_options = set_free(network->dhcp6_request_options);
1099
1100 return 0;
1101 }
1102
1103 for (const char *p = rvalue;;) {
1104 _cleanup_free_ char *n = NULL;
1105 uint32_t i;
1106
1107 r = extract_first_word(&p, &n, NULL, 0);
1108 if (r == -ENOMEM)
1109 return log_oom();
1110 if (r < 0) {
1111 log_syntax(unit, LOG_WARNING, filename, line, r,
1112 "Failed to parse DHCP request option, ignoring assignment: %s",
1113 rvalue);
1114 return 0;
1115 }
1116 if (r == 0)
1117 return 0;
1118
1119 r = safe_atou32(n, &i);
1120 if (r < 0) {
1121 log_syntax(unit, LOG_WARNING, filename, line, r,
1122 "DHCP request option is invalid, ignoring assignment: %s", n);
1123 continue;
1124 }
1125
1126 if (i < 1 || i >= UINT8_MAX) {
1127 log_syntax(unit, LOG_WARNING, filename, line, 0,
1128 "DHCP request option is invalid, valid range is 1-254, ignoring assignment: %s", n);
1129 continue;
1130 }
1131
1132 r = set_ensure_put(ltype == AF_INET ? &network->dhcp_request_options : &network->dhcp6_request_options,
1133 NULL, UINT32_TO_PTR(i));
1134 if (r < 0)
1135 log_syntax(unit, LOG_WARNING, filename, line, r,
1136 "Failed to store DHCP request option '%s', ignoring assignment: %m", n);
1137 }
1138 }
1139
1140 static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = {
1141 [DHCP_USE_DOMAINS_NO] = "no",
1142 [DHCP_USE_DOMAINS_ROUTE] = "route",
1143 [DHCP_USE_DOMAINS_YES] = "yes",
1144 };
1145
1146 DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES);
1147
1148 static const char * const dhcp_option_data_type_table[_DHCP_OPTION_DATA_MAX] = {
1149 [DHCP_OPTION_DATA_UINT8] = "uint8",
1150 [DHCP_OPTION_DATA_UINT16] = "uint16",
1151 [DHCP_OPTION_DATA_UINT32] = "uint32",
1152 [DHCP_OPTION_DATA_STRING] = "string",
1153 [DHCP_OPTION_DATA_IPV4ADDRESS] = "ipv4address",
1154 [DHCP_OPTION_DATA_IPV6ADDRESS] = "ipv6address",
1155 };
1156
1157 DEFINE_STRING_TABLE_LOOKUP(dhcp_option_data_type, DHCPOptionDataType);
1158
1159 static const char* const duid_type_table[_DUID_TYPE_MAX] = {
1160 [DUID_TYPE_LLT] = "link-layer-time",
1161 [DUID_TYPE_EN] = "vendor",
1162 [DUID_TYPE_LL] = "link-layer",
1163 [DUID_TYPE_UUID] = "uuid",
1164 };
1165 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(duid_type, DUIDType);
1166
1167 int config_parse_duid_type(
1168 const char *unit,
1169 const char *filename,
1170 unsigned line,
1171 const char *section,
1172 unsigned section_line,
1173 const char *lvalue,
1174 int ltype,
1175 const char *rvalue,
1176 void *data,
1177 void *userdata) {
1178
1179 _cleanup_free_ char *type_string = NULL;
1180 const char *p = ASSERT_PTR(rvalue);
1181 bool force = ltype;
1182 DUID *duid = ASSERT_PTR(data);
1183 DUIDType type;
1184 int r;
1185
1186 assert(filename);
1187 assert(lvalue);
1188
1189 if (!force && duid->set)
1190 return 0;
1191
1192 r = extract_first_word(&p, &type_string, ":", 0);
1193 if (r == -ENOMEM)
1194 return log_oom();
1195 if (r < 0) {
1196 log_syntax(unit, LOG_WARNING, filename, line, r,
1197 "Invalid syntax, ignoring: %s", rvalue);
1198 return 0;
1199 }
1200 if (r == 0) {
1201 log_syntax(unit, LOG_WARNING, filename, line, 0,
1202 "Failed to extract DUID type from '%s', ignoring.", rvalue);
1203 return 0;
1204 }
1205
1206 type = duid_type_from_string(type_string);
1207 if (type < 0) {
1208 uint16_t t;
1209
1210 r = safe_atou16(type_string, &t);
1211 if (r < 0) {
1212 log_syntax(unit, LOG_WARNING, filename, line, r,
1213 "Failed to parse DUID type '%s', ignoring.", type_string);
1214 return 0;
1215 }
1216
1217 type = t;
1218 assert(type == t); /* Check if type can store uint16_t. */
1219 }
1220
1221 if (!isempty(p)) {
1222 usec_t u;
1223
1224 if (type != DUID_TYPE_LLT) {
1225 log_syntax(unit, LOG_WARNING, filename, line, r,
1226 "Invalid syntax, ignoring: %s", rvalue);
1227 return 0;
1228 }
1229
1230 r = parse_timestamp(p, &u);
1231 if (r < 0) {
1232 log_syntax(unit, LOG_WARNING, filename, line, r,
1233 "Failed to parse timestamp, ignoring: %s", p);
1234 return 0;
1235 }
1236
1237 duid->llt_time = u;
1238 }
1239
1240 duid->type = type;
1241 duid->set = force;
1242
1243 return 0;
1244 }
1245
1246 int config_parse_manager_duid_type(
1247 const char *unit,
1248 const char *filename,
1249 unsigned line,
1250 const char *section,
1251 unsigned section_line,
1252 const char *lvalue,
1253 int ltype,
1254 const char *rvalue,
1255 void *data,
1256 void *userdata) {
1257
1258 Manager *manager = ASSERT_PTR(userdata);
1259 int r;
1260
1261 /* For backward compatibility. Setting both DHCPv4 and DHCPv6 DUID if they are not specified explicitly. */
1262
1263 r = config_parse_duid_type(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp_duid, manager);
1264 if (r < 0)
1265 return r;
1266
1267 return config_parse_duid_type(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp6_duid, manager);
1268 }
1269
1270 int config_parse_network_duid_type(
1271 const char *unit,
1272 const char *filename,
1273 unsigned line,
1274 const char *section,
1275 unsigned section_line,
1276 const char *lvalue,
1277 int ltype,
1278 const char *rvalue,
1279 void *data,
1280 void *userdata) {
1281
1282 Network *network = ASSERT_PTR(userdata);
1283 int r;
1284
1285 r = config_parse_duid_type(unit, filename, line, section, section_line, lvalue, true, rvalue, &network->dhcp_duid, network);
1286 if (r < 0)
1287 return r;
1288
1289 /* For backward compatibility, also set DHCPv6 DUID if not specified explicitly. */
1290 return config_parse_duid_type(unit, filename, line, section, section_line, lvalue, false, rvalue, &network->dhcp6_duid, network);
1291 }
1292
1293 int config_parse_duid_rawdata(
1294 const char *unit,
1295 const char *filename,
1296 unsigned line,
1297 const char *section,
1298 unsigned section_line,
1299 const char *lvalue,
1300 int ltype,
1301 const char *rvalue,
1302 void *data,
1303 void *userdata) {
1304
1305 uint8_t raw_data[MAX_DUID_DATA_LEN];
1306 unsigned count = 0;
1307 bool force = ltype;
1308 DUID *duid = ASSERT_PTR(data);
1309
1310 assert(filename);
1311 assert(lvalue);
1312 assert(rvalue);
1313
1314 if (!force && duid->set)
1315 return 0;
1316
1317 /* RawData contains DUID in format "NN:NN:NN..." */
1318 for (const char *p = rvalue;;) {
1319 int n1, n2, len, r;
1320 uint32_t byte;
1321 _cleanup_free_ char *cbyte = NULL;
1322
1323 r = extract_first_word(&p, &cbyte, ":", 0);
1324 if (r == -ENOMEM)
1325 return log_oom();
1326 if (r < 0) {
1327 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to read DUID, ignoring assignment: %s.", rvalue);
1328 return 0;
1329 }
1330 if (r == 0)
1331 break;
1332
1333 if (count >= MAX_DUID_DATA_LEN) {
1334 log_syntax(unit, LOG_WARNING, filename, line, 0, "Max DUID length exceeded, ignoring assignment: %s.", rvalue);
1335 return 0;
1336 }
1337
1338 len = strlen(cbyte);
1339 if (!IN_SET(len, 1, 2)) {
1340 log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid length - DUID byte: %s, ignoring assignment: %s.", cbyte, rvalue);
1341 return 0;
1342 }
1343 n1 = unhexchar(cbyte[0]);
1344 if (len == 2)
1345 n2 = unhexchar(cbyte[1]);
1346 else
1347 n2 = 0;
1348
1349 if (n1 < 0 || n2 < 0) {
1350 log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid DUID byte: %s. Ignoring assignment: %s.", cbyte, rvalue);
1351 return 0;
1352 }
1353
1354 byte = ((uint8_t) n1 << (4 * (len-1))) | (uint8_t) n2;
1355 raw_data[count++] = byte;
1356 }
1357
1358 assert_cc(sizeof(raw_data) == sizeof(duid->raw_data));
1359 memcpy(duid->raw_data, raw_data, count);
1360 duid->raw_data_len = count;
1361 duid->set = force;
1362
1363 return 0;
1364 }
1365
1366 int config_parse_manager_duid_rawdata(
1367 const char *unit,
1368 const char *filename,
1369 unsigned line,
1370 const char *section,
1371 unsigned section_line,
1372 const char *lvalue,
1373 int ltype,
1374 const char *rvalue,
1375 void *data,
1376 void *userdata) {
1377
1378 Manager *manager = ASSERT_PTR(userdata);
1379 int r;
1380
1381 /* For backward compatibility. Setting both DHCPv4 and DHCPv6 DUID if they are not specified explicitly. */
1382
1383 r = config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp_duid, manager);
1384 if (r < 0)
1385 return r;
1386
1387 return config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, false, rvalue, &manager->dhcp6_duid, manager);
1388 }
1389
1390 int config_parse_network_duid_rawdata(
1391 const char *unit,
1392 const char *filename,
1393 unsigned line,
1394 const char *section,
1395 unsigned section_line,
1396 const char *lvalue,
1397 int ltype,
1398 const char *rvalue,
1399 void *data,
1400 void *userdata) {
1401
1402 Network *network = ASSERT_PTR(userdata);
1403 int r;
1404
1405 r = config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, true, rvalue, &network->dhcp_duid, network);
1406 if (r < 0)
1407 return r;
1408
1409 /* For backward compatibility, also set DHCPv6 DUID if not specified explicitly. */
1410 return config_parse_duid_rawdata(unit, filename, line, section, section_line, lvalue, false, rvalue, &network->dhcp6_duid, network);
1411 }
1412
1413 int config_parse_uplink(
1414 const char *unit,
1415 const char *filename,
1416 unsigned line,
1417 const char *section,
1418 unsigned section_line,
1419 const char *lvalue,
1420 int ltype,
1421 const char *rvalue,
1422 void *data,
1423 void *userdata) {
1424
1425 Network *network = ASSERT_PTR(userdata);
1426 bool accept_none = true;
1427 int *index, r;
1428 char **name;
1429
1430 assert(filename);
1431 assert(section);
1432 assert(lvalue);
1433 assert(rvalue);
1434
1435 if (streq(section, "DHCPServer")) {
1436 index = &network->dhcp_server_uplink_index;
1437 name = &network->dhcp_server_uplink_name;
1438 } else if (streq(section, "IPv6SendRA")) {
1439 index = &network->router_uplink_index;
1440 name = &network->router_uplink_name;
1441 } else if (STR_IN_SET(section, "DHCPv6PrefixDelegation", "DHCPPrefixDelegation")) {
1442 index = &network->dhcp_pd_uplink_index;
1443 name = &network->dhcp_pd_uplink_name;
1444 accept_none = false;
1445 } else
1446 assert_not_reached();
1447
1448 if (isempty(rvalue) || streq(rvalue, ":auto")) {
1449 *index = UPLINK_INDEX_AUTO;
1450 *name = mfree(*name);
1451 return 0;
1452 }
1453
1454 if (accept_none && streq(rvalue, ":none")) {
1455 *index = UPLINK_INDEX_NONE;
1456 *name = mfree(*name);
1457 return 0;
1458 }
1459
1460 if (!accept_none && streq(rvalue, ":self")) {
1461 *index = UPLINK_INDEX_SELF;
1462 *name = mfree(*name);
1463 return 0;
1464 }
1465
1466 r = parse_ifindex(rvalue);
1467 if (r > 0) {
1468 *index = r;
1469 *name = mfree(*name);
1470 return 0;
1471 }
1472
1473 if (!ifname_valid_full(rvalue, IFNAME_VALID_ALTERNATIVE)) {
1474 log_syntax(unit, LOG_WARNING, filename, line, 0,
1475 "Invalid interface name in %s=, ignoring assignment: %s", lvalue, rvalue);
1476 return 0;
1477 }
1478
1479 /* The interface name will be resolved later. */
1480 r = free_and_strdup_warn(name, rvalue);
1481 if (r < 0)
1482 return r;
1483
1484 /* Note, if uplink_name is set, then uplink_index will be ignored. So, the below does not mean
1485 * an uplink interface will be selected automatically. */
1486 *index = UPLINK_INDEX_AUTO;
1487 return 0;
1488 }