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