/* SPDX-License-Identifier: LGPL-2.1-or-later */ /*** Copyright © 2014 Intel Corporation. All rights reserved. ***/ #include "sd-dhcp6-client.h" #include "hashmap.h" #include "hostname-setup.h" #include "hostname-util.h" #include "networkd-address.h" #include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp6.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-queue.h" #include "networkd-route.h" #include "string-table.h" #include "string-util.h" bool link_dhcp6_with_address_enabled(Link *link) { if (!link_dhcp6_enabled(link)) return false; return link->network->dhcp6_use_address; } static DHCP6ClientStartMode link_get_dhcp6_client_start_mode(Link *link) { assert(link); if (!link->network) return DHCP6_CLIENT_START_MODE_NO; /* When WithoutRA= is explicitly specified, then honor it. */ if (link->network->dhcp6_client_start_mode >= 0) return link->network->dhcp6_client_start_mode; /* When this interface itself is an uplink interface, then start dhcp6 client in solicit mode. */ if (dhcp_pd_is_uplink(link, link, /* accept_auto = */ false)) return DHCP6_CLIENT_START_MODE_SOLICIT; /* Otherwise, start dhcp6 client when RA is received. */ return DHCP6_CLIENT_START_MODE_NO; } static int dhcp6_remove(Link *link, bool only_marked) { Address *address; Route *route; int k, r = 0; assert(link); if (!only_marked) link->dhcp6_configured = false; SET_FOREACH(route, link->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP6) continue; if (only_marked && !route_is_marked(route)) continue; k = route_remove(route); if (k < 0) r = k; route_cancel_request(route, link); } SET_FOREACH(address, link->addresses) { if (address->source != NETWORK_CONFIG_SOURCE_DHCP6) continue; if (only_marked && !address_is_marked(address)) continue; k = address_remove(address); if (k < 0) r = k; address_cancel_request(address); } return r; } static int dhcp6_address_ready_callback(Address *address) { Address *a; assert(address); assert(address->link); SET_FOREACH(a, address->link->addresses) if (a->source == NETWORK_CONFIG_SOURCE_DHCP6) a->callback = NULL; return dhcp6_check_ready(address->link); } int dhcp6_check_ready(Link *link) { bool has_ready = false; Address *address; int r; assert(link); if (link->dhcp6_messages > 0) { log_link_debug(link, "%s(): DHCPv6 addresses and routes are not set.", __func__); return 0; } SET_FOREACH(address, link->addresses) { if (address->source != NETWORK_CONFIG_SOURCE_DHCP6) continue; if (address_is_ready(address)) { has_ready = true; break; } } if (!has_ready) { SET_FOREACH(address, link->addresses) if (address->source == NETWORK_CONFIG_SOURCE_DHCP6) address->callback = dhcp6_address_ready_callback; log_link_debug(link, "%s(): no DHCPv6 address is ready.", __func__); return 0; } link->dhcp6_configured = true; log_link_debug(link, "DHCPv6 addresses and routes set."); r = dhcp6_remove(link, /* only_marked = */ true); if (r < 0) return r; link_check_ready(link); return 0; } static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) { int r; assert(link); r = address_configure_handler_internal(rtnl, m, link, "Could not set DHCPv6 address"); if (r <= 0) return r; r = dhcp6_check_ready(link); if (r < 0) link_enter_failed(link); return 1; } static int verify_dhcp6_address(Link *link, const Address *address) { _cleanup_free_ char *buffer = NULL; bool by_ndisc = false; Address *existing; int log_level; assert(link); assert(address); assert(address->family == AF_INET6); (void) in6_addr_to_string(&address->in_addr.in6, &buffer); if (address_get(link, address, &existing) < 0 && link_get_address(link, AF_INET6, &address->in_addr, 0, &existing) < 0) { /* New address. */ log_level = LOG_INFO; goto simple_log; } else log_level = LOG_DEBUG; if (address->prefixlen == existing->prefixlen) /* Currently, only conflict in prefix length is reported. */ goto simple_log; if (existing->source == NETWORK_CONFIG_SOURCE_NDISC) by_ndisc = true; log_link_warning(link, "Ignoring DHCPv6 address %s/%u (valid %s, preferred %s) which conflicts with %s/%u%s.", strna(buffer), address->prefixlen, FORMAT_LIFETIME(address->lifetime_valid_usec), FORMAT_LIFETIME(address->lifetime_preferred_usec), strna(buffer), existing->prefixlen, by_ndisc ? " assigned by NDisc" : ""); if (by_ndisc) log_link_warning(link, "Hint: use IPv6Token= setting to change the address generated by NDisc or set UseAutonomousPrefix=no."); return -EEXIST; simple_log: log_link_full(link, log_level, "DHCPv6 address %s/%u (valid %s, preferred %s)", strna(buffer), address->prefixlen, FORMAT_LIFETIME(address->lifetime_valid_usec), FORMAT_LIFETIME(address->lifetime_preferred_usec)); return 0; } static int dhcp6_request_address( Link *link, const struct in6_addr *server_address, const struct in6_addr *ip6_addr, usec_t lifetime_preferred_usec, usec_t lifetime_valid_usec) { _cleanup_(address_freep) Address *addr = NULL; Address *existing; int r; r = address_new(&addr); if (r < 0) return log_oom(); addr->source = NETWORK_CONFIG_SOURCE_DHCP6; addr->provider.in6 = *server_address; addr->family = AF_INET6; addr->in_addr.in6 = *ip6_addr; addr->flags = IFA_F_NOPREFIXROUTE; addr->prefixlen = 128; addr->lifetime_preferred_usec = lifetime_preferred_usec; addr->lifetime_valid_usec = lifetime_valid_usec; if (verify_dhcp6_address(link, addr) < 0) return 0; if (address_get(link, addr, &existing) < 0) link->dhcp6_configured = false; else address_unmark(existing); r = link_request_address(link, TAKE_PTR(addr), true, &link->dhcp6_messages, dhcp6_address_handler, NULL); if (r < 0) { _cleanup_free_ char *buffer = NULL; (void) in6_addr_to_string(ip6_addr, &buffer); return log_link_error_errno(link, r, "Failed to request DHCPv6 address %s/128: %m", strna(buffer)); } return 0; } static int dhcp6_address_acquired(Link *link) { struct in6_addr server_address; usec_t timestamp_usec; int r; assert(link); assert(link->network); assert(link->dhcp6_lease); if (!link->network->dhcp6_use_address) return 0; r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &server_address); if (r < 0) return log_link_warning_errno(link, r, "Failed to get server address of DHCPv6 lease: %m"); r = sd_dhcp6_lease_get_timestamp(link->dhcp6_lease, CLOCK_BOOTTIME, ×tamp_usec); if (r < 0) return log_link_warning_errno(link, r, "Failed to get timestamp of DHCPv6 lease: %m"); for (sd_dhcp6_lease_reset_address_iter(link->dhcp6_lease);;) { uint32_t lifetime_preferred_sec, lifetime_valid_sec; struct in6_addr ip6_addr; r = sd_dhcp6_lease_get_address(link->dhcp6_lease, &ip6_addr, &lifetime_preferred_sec, &lifetime_valid_sec); if (r < 0) break; r = dhcp6_request_address(link, &server_address, &ip6_addr, usec_add(lifetime_preferred_sec * USEC_PER_SEC, timestamp_usec), usec_add(lifetime_valid_sec * USEC_PER_SEC, timestamp_usec)); if (r < 0) return r; } if (link->network->dhcp6_use_hostname) { const char *dhcpname = NULL; _cleanup_free_ char *hostname = NULL; (void) sd_dhcp6_lease_get_fqdn(link->dhcp6_lease, &dhcpname); if (dhcpname) { r = shorten_overlong(dhcpname, &hostname); if (r < 0) log_link_warning_errno(link, r, "Unable to shorten overlong DHCP hostname '%s', ignoring: %m", dhcpname); if (r == 1) log_link_notice(link, "Overlong DHCP hostname received, shortened from '%s' to '%s'", dhcpname, hostname); } if (hostname) { r = manager_set_hostname(link->manager, hostname); if (r < 0) log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname); } } return 0; } static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) { _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease_old = NULL; sd_dhcp6_lease *lease; int r; link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP6, NULL); link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP6, NULL); r = sd_dhcp6_client_get_lease(client, &lease); if (r < 0) return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); lease_old = TAKE_PTR(link->dhcp6_lease); link->dhcp6_lease = sd_dhcp6_lease_ref(lease); r = dhcp6_address_acquired(link); if (r < 0) return r; if (dhcp6_lease_has_pd_prefix(lease)) { r = dhcp6_pd_prefix_acquired(link); if (r < 0) return r; } else if (dhcp6_lease_has_pd_prefix(lease_old)) /* When we had PD prefixes but not now, we need to remove them. */ dhcp_pd_prefix_lost(link); if (link->dhcp6_messages == 0) { link->dhcp6_configured = true; r = dhcp6_remove(link, /* only_marked = */ true); if (r < 0) return r; } else log_link_debug(link, "Setting DHCPv6 addresses and routes"); if (!link->dhcp6_configured) link_set_state(link, LINK_STATE_CONFIGURING); link_check_ready(link); return 0; } static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) { return 0; } static int dhcp6_lease_lost(Link *link) { int r; assert(link); assert(link->manager); log_link_info(link, "DHCPv6 lease lost"); if (dhcp6_lease_has_pd_prefix(link->dhcp6_lease)) dhcp_pd_prefix_lost(link); link->dhcp6_lease = sd_dhcp6_lease_unref(link->dhcp6_lease); r = dhcp6_remove(link, /* only_marked = */ false); if (r < 0) return r; return 0; } static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { Link *link = userdata; int r; assert(link); assert(link->network); if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) return; switch (event) { case SD_DHCP6_CLIENT_EVENT_STOP: case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE: case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX: r = dhcp6_lease_lost(link); if (r < 0) link_enter_failed(link); break; case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE: r = dhcp6_lease_ip_acquired(client, link); if (r < 0) { link_enter_failed(link); return; } _fallthrough_; case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: r = dhcp6_lease_information_acquired(client, link); if (r < 0) link_enter_failed(link); break; default: if (event < 0) log_link_warning_errno(link, event, "DHCPv6 error: %m"); else log_link_warning(link, "DHCPv6 unknown event: %d", event); return; } } int dhcp6_start_on_ra(Link *link, bool information_request) { int r; assert(link); assert(link->dhcp6_client); assert(link->network); assert(in6_addr_is_link_local(&link->ipv6ll_address)); if (link_get_dhcp6_client_start_mode(link) != DHCP6_CLIENT_START_MODE_NO) /* When WithoutRA= is specified, then the DHCPv6 client should be already running in * the requested mode. Hence, ignore the requests by RA. */ return 0; r = sd_dhcp6_client_is_running(link->dhcp6_client); if (r < 0) return r; if (r > 0) { int inf_req; r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req); if (r < 0) return r; if (inf_req == information_request) /* The client is already running in the requested mode. */ return 0; if (!inf_req) { log_link_debug(link, "The DHCPv6 client is already running in the managed mode, " "refusing to start the client in the information requesting mode."); return 0; } log_link_debug(link, "The DHCPv6 client is running in the information requesting mode. " "Restarting the client in the managed mode."); r = sd_dhcp6_client_stop(link->dhcp6_client); if (r < 0) return r; } else { r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address); if (r < 0) return r; } r = sd_dhcp6_client_set_information_request(link->dhcp6_client, information_request); if (r < 0) return r; r = sd_dhcp6_client_start(link->dhcp6_client); if (r < 0) return r; return 0; } int dhcp6_start(Link *link) { DHCP6ClientStartMode start_mode; int r; assert(link); assert(link->network); if (!link->dhcp6_client) return 0; if (!link_dhcp6_enabled(link)) return 0; if (!link_has_carrier(link)) return 0; if (sd_dhcp6_client_is_running(link->dhcp6_client) > 0) return 0; if (!in6_addr_is_link_local(&link->ipv6ll_address)) { log_link_debug(link, "IPv6 link-local address is not set, delaying to start DHCPv6 client."); return 0; } r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address); if (r < 0) return r; start_mode = link_get_dhcp6_client_start_mode(link); if (start_mode == DHCP6_CLIENT_START_MODE_NO) return 0; r = sd_dhcp6_client_set_information_request(link->dhcp6_client, start_mode == DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST); if (r < 0) return r; r = sd_dhcp6_client_start(link->dhcp6_client); if (r < 0) return r; return 1; } static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) { _cleanup_free_ char *hostname = NULL; const char *hn; int r; assert(link); if (!link->network->dhcp_send_hostname) hn = NULL; else if (link->network->dhcp_hostname) hn = link->network->dhcp_hostname; else { r = gethostname_strict(&hostname); if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */ return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to get hostname: %m"); hn = hostname; } r = sd_dhcp6_client_set_fqdn(client, hn); if (r == -EINVAL && hostname) /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */ log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m"); else if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set hostname: %m"); return 0; } static int dhcp6_set_identifier(Link *link, sd_dhcp6_client *client) { const DUID *duid; int r; assert(link); assert(link->network); assert(client); r = sd_dhcp6_client_set_mac(client, link->hw_addr.bytes, link->hw_addr.length, link->iftype); if (r < 0) return r; if (link->network->dhcp6_iaid_set) { r = sd_dhcp6_client_set_iaid(client, link->network->dhcp6_iaid); if (r < 0) return r; } duid = link_get_dhcp6_duid(link); if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0) r = sd_dhcp6_client_set_duid_llt(client, duid->llt_time); else r = sd_dhcp6_client_set_duid(client, duid->type, duid->raw_data_len > 0 ? duid->raw_data : NULL, duid->raw_data_len); if (r < 0) return r; return 0; } static int dhcp6_configure(Link *link) { _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; sd_dhcp6_option *vendor_option; sd_dhcp6_option *send_option; void *request_options; int r; assert(link); assert(link->network); if (link->dhcp6_client) return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBUSY), "DHCPv6 client is already configured."); r = sd_dhcp6_client_new(&client); if (r == -ENOMEM) return log_oom_debug(); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to create DHCPv6 client: %m"); r = sd_dhcp6_client_attach_event(client, link->manager->event, 0); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to attach event: %m"); r = dhcp6_set_identifier(link, client); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set identifier: %m"); ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp6_client_send_options) { r = sd_dhcp6_client_add_option(client, send_option); if (r == -EEXIST) continue; if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set option: %m"); } r = dhcp6_set_hostname(client, link); if (r < 0) return r; r = sd_dhcp6_client_set_ifindex(client, link->ifindex); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set ifindex: %m"); if (link->network->dhcp6_mudurl) { r = sd_dhcp6_client_set_request_mud_url(client, link->network->dhcp6_mudurl); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set MUD URL: %m"); } if (link->network->dhcp6_use_dns) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request DNS servers: %m"); } if (link->network->dhcp6_use_domains > 0) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request domains: %m"); } if (link->network->dhcp6_use_ntp) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request NTP servers: %m"); /* If the server does not provide NTP servers, then we fallback to use SNTP servers. */ r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request SNTP servers: %m"); } SET_FOREACH(request_options, link->network->dhcp6_request_options) { uint32_t option = PTR_TO_UINT32(request_options); r = sd_dhcp6_client_set_request_option(client, option); if (r == -EEXIST) { log_link_debug(link, "DHCPv6 CLIENT: Failed to set request flag for '%u' already exists, ignoring.", option); continue; } if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set request flag for '%u': %m", option); } if (link->network->dhcp6_user_class) { r = sd_dhcp6_client_set_request_user_class(client, link->network->dhcp6_user_class); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set user class: %m"); } if (link->network->dhcp6_vendor_class) { r = sd_dhcp6_client_set_request_vendor_class(client, link->network->dhcp6_vendor_class); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set vendor class: %m"); } ORDERED_HASHMAP_FOREACH(vendor_option, link->network->dhcp6_client_send_vendor_options) { r = sd_dhcp6_client_add_vendor_option(client, vendor_option); if (r == -EEXIST) continue; if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set vendor option: %m"); } r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set callback: %m"); r = sd_dhcp6_client_set_prefix_delegation(client, link->network->dhcp6_use_pd_prefix); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to %s requesting prefixes to be delegated: %m", enable_disable(link->network->dhcp6_use_pd_prefix)); /* Even if UseAddress=no, we need to request IA_NA, as the dhcp6 client may be started in solicit mode. */ r = sd_dhcp6_client_set_address_request(client, link->network->dhcp6_use_pd_prefix ? link->network->dhcp6_use_address : true); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to %s requesting address: %m", enable_disable(link->network->dhcp6_use_address)); if (link->network->dhcp6_pd_prefix_length > 0) { r = sd_dhcp6_client_set_prefix_delegation_hint(client, link->network->dhcp6_pd_prefix_length, &link->network->dhcp6_pd_prefix_hint); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set prefix delegation hint: %m"); } link->dhcp6_client = TAKE_PTR(client); return 0; } int dhcp6_update_mac(Link *link) { bool restart; int r; assert(link); if (!link->dhcp6_client) return 0; restart = sd_dhcp6_client_is_running(link->dhcp6_client) > 0; if (restart) { r = sd_dhcp6_client_stop(link->dhcp6_client); if (r < 0) return r; } r = dhcp6_set_identifier(link, link->dhcp6_client); if (r < 0) return r; if (restart) { r = sd_dhcp6_client_start(link->dhcp6_client); if (r < 0) return log_link_warning_errno(link, r, "Could not restart DHCPv6 client: %m"); } return 0; } static int dhcp6_process_request(Request *req, Link *link, void *userdata) { int r; assert(link); if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) return 0; if (!IN_SET(link->hw_addr.length, ETH_ALEN, INFINIBAND_ALEN) || hw_addr_is_null(&link->hw_addr)) /* No MAC address is assigned to the hardware, or non-supported MAC address length. */ return 0; r = dhcp_configure_duid(link, link_get_dhcp6_duid(link)); if (r <= 0) return r; r = dhcp6_configure(link); if (r < 0) return log_link_warning_errno(link, r, "Failed to configure DHCPv6 client: %m"); r = ndisc_start(link); if (r < 0) return log_link_warning_errno(link, r, "Failed to start IPv6 Router Discovery: %m"); r = dhcp6_start(link); if (r < 0) return log_link_warning_errno(link, r, "Failed to start DHCPv6 client: %m"); log_link_debug(link, "DHCPv6 client is configured%s.", r > 0 ? ", acquiring DHCPv6 lease" : ""); return 1; } int link_request_dhcp6_client(Link *link) { int r; assert(link); if (!link_dhcp6_enabled(link) && !link_ipv6_accept_ra_enabled(link)) return 0; if (link->dhcp6_client) return 0; r = link_queue_request(link, REQUEST_TYPE_DHCP6_CLIENT, dhcp6_process_request, NULL); if (r < 0) return log_link_warning_errno(link, r, "Failed to request configuring of the DHCPv6 client: %m"); log_link_debug(link, "Requested configuring of the DHCPv6 client."); return 0; } int link_serialize_dhcp6_client(Link *link, FILE *f) { _cleanup_free_ char *duid = NULL; uint32_t iaid; int r; assert(link); if (!link->dhcp6_client) return 0; r = sd_dhcp6_client_get_iaid(link->dhcp6_client, &iaid); if (r >= 0) fprintf(f, "DHCP6_CLIENT_IAID=0x%x\n", iaid); r = sd_dhcp6_client_duid_as_string(link->dhcp6_client, &duid); if (r >= 0) fprintf(f, "DHCP6_CLIENT_DUID=%s\n", duid); return 0; } int config_parse_dhcp6_pd_prefix_hint( 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) { Network *network = userdata; union in_addr_union u; unsigned char prefixlen; int r; assert(filename); assert(lvalue); assert(rvalue); assert(userdata); r = in_addr_prefix_from_string(rvalue, AF_INET6, &u, &prefixlen); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s=%s, ignoring assignment.", lvalue, rvalue); return 0; } if (prefixlen < 1 || prefixlen > 128) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid prefix length in %s=%s, ignoring assignment.", lvalue, rvalue); return 0; } network->dhcp6_pd_prefix_hint = u.in6; network->dhcp6_pd_prefix_length = prefixlen; return 0; } DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp6_client_start_mode, dhcp6_client_start_mode, DHCP6ClientStartMode, "Failed to parse WithoutRA= setting"); static const char* const dhcp6_client_start_mode_table[_DHCP6_CLIENT_START_MODE_MAX] = { [DHCP6_CLIENT_START_MODE_NO] = "no", [DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST] = "information-request", [DHCP6_CLIENT_START_MODE_SOLICIT] = "solicit", }; DEFINE_STRING_TABLE_LOOKUP(dhcp6_client_start_mode, DHCP6ClientStartMode);