diff options
Diffstat (limited to 'src/network/networkd-radv.c')
-rw-r--r-- | src/network/networkd-radv.c | 1619 |
1 files changed, 1619 insertions, 0 deletions
diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c new file mode 100644 index 0000000..fc36a00 --- /dev/null +++ b/src/network/networkd-radv.c @@ -0,0 +1,1619 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2017 Intel Corporation. All rights reserved. +***/ + +#include <netinet/icmp6.h> +#include <arpa/inet.h> + +#include "dns-domain.h" +#include "networkd-address-generation.h" +#include "networkd-address.h" +#include "networkd-dhcp-prefix-delegation.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-queue.h" +#include "networkd-radv.h" +#include "networkd-route-util.h" +#include "parse-util.h" +#include "radv-internal.h" +#include "string-util.h" +#include "string-table.h" +#include "strv.h" + +void network_adjust_radv(Network *network) { + assert(network); + + /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */ + + if (network->dhcp_pd < 0) + /* For backward compatibility. */ + network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); + + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) + log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. " + "Disabling IPv6PrefixDelegation=.", network->filename); + + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE; + } + + if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) { + network->n_router_dns = 0; + network->router_dns = mfree(network->router_dns); + network->router_search_domains = ordered_set_free(network->router_search_domains); + } + + if (!FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_STATIC)) { + network->prefixes_by_section = hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); + network->route_prefixes_by_section = hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); + network->pref64_prefixes_by_section = hashmap_free_with_destructor(network->pref64_prefixes_by_section, pref64_prefix_free); + } +} + +bool link_radv_enabled(Link *link) { + assert(link); + + if (!link_may_have_ipv6ll(link, /* check_multicast = */ true)) + return false; + + if (link->hw_addr.length != ETH_ALEN) + return false; + + return link->network->router_prefix_delegation; +} + +Prefix *prefix_free(Prefix *prefix) { + if (!prefix) + return NULL; + + if (prefix->network) { + assert(prefix->section); + hashmap_remove(prefix->network->prefixes_by_section, prefix->section); + } + + config_section_free(prefix->section); + set_free(prefix->tokens); + + return mfree(prefix); +} + +DEFINE_SECTION_CLEANUP_FUNCTIONS(Prefix, prefix_free); + +static int prefix_new_static(Network *network, const char *filename, unsigned section_line, Prefix **ret) { + _cleanup_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(prefix_freep) Prefix *prefix = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + prefix = hashmap_get(network->prefixes_by_section, n); + if (prefix) { + *ret = TAKE_PTR(prefix); + return 0; + } + + prefix = new(Prefix, 1); + if (!prefix) + return -ENOMEM; + + *prefix = (Prefix) { + .network = network, + .section = TAKE_PTR(n), + + .preferred_lifetime = RADV_DEFAULT_PREFERRED_LIFETIME_USEC, + .valid_lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + .onlink = true, + .address_auto_configuration = true, + }; + + r = hashmap_ensure_put(&network->prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); + if (r < 0) + return r; + + *ret = TAKE_PTR(prefix); + return 0; +} + +RoutePrefix *route_prefix_free(RoutePrefix *prefix) { + if (!prefix) + return NULL; + + if (prefix->network) { + assert(prefix->section); + hashmap_remove(prefix->network->route_prefixes_by_section, prefix->section); + } + + config_section_free(prefix->section); + + return mfree(prefix); +} + +DEFINE_SECTION_CLEANUP_FUNCTIONS(RoutePrefix, route_prefix_free); + +static int route_prefix_new_static(Network *network, const char *filename, unsigned section_line, RoutePrefix **ret) { + _cleanup_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(route_prefix_freep) RoutePrefix *prefix = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + prefix = hashmap_get(network->route_prefixes_by_section, n); + if (prefix) { + *ret = TAKE_PTR(prefix); + return 0; + } + + prefix = new(RoutePrefix, 1); + if (!prefix) + return -ENOMEM; + + *prefix = (RoutePrefix) { + .network = network, + .section = TAKE_PTR(n), + + .lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + }; + + r = hashmap_ensure_put(&network->route_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); + if (r < 0) + return r; + + *ret = TAKE_PTR(prefix); + return 0; +} + +pref64Prefix *pref64_prefix_free(pref64Prefix *prefix) { + if (!prefix) + return NULL; + + if (prefix->network) { + assert(prefix->section); + hashmap_remove(prefix->network->pref64_prefixes_by_section, prefix->section); + } + + config_section_free(prefix->section); + + return mfree(prefix); +} + +DEFINE_SECTION_CLEANUP_FUNCTIONS(pref64Prefix, pref64_prefix_free); + +static int pref64_prefix_new_static(Network *network, const char *filename, unsigned section_line, pref64Prefix **ret) { + _cleanup_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(pref64_prefix_freep) pref64Prefix *prefix = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + prefix = hashmap_get(network->pref64_prefixes_by_section, n); + if (prefix) { + *ret = TAKE_PTR(prefix); + return 0; + } + + prefix = new(pref64Prefix, 1); + if (!prefix) + return -ENOMEM; + + *prefix = (pref64Prefix) { + .network = network, + .section = TAKE_PTR(n), + + .lifetime = RADV_PREF64_DEFAULT_LIFETIME_USEC, + }; + + r = hashmap_ensure_put(&network->pref64_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); + if (r < 0) + return r; + + *ret = TAKE_PTR(prefix); + return 0; +} + +int link_request_radv_addresses(Link *link) { + Prefix *p; + int r; + + assert(link); + + if (!link_radv_enabled(link)) + return 0; + + HASHMAP_FOREACH(p, link->network->prefixes_by_section) { + _cleanup_set_free_ Set *addresses = NULL; + struct in6_addr *a; + + if (!p->assign) + continue; + + /* radv_generate_addresses() below requires the prefix length <= 64. */ + if (p->prefixlen > 64) + continue; + + r = radv_generate_addresses(link, p->tokens, &p->prefix, p->prefixlen, &addresses); + if (r < 0) + return r; + + SET_FOREACH(a, addresses) { + _cleanup_(address_freep) Address *address = NULL; + + r = address_new(&address); + if (r < 0) + return -ENOMEM; + + address->source = NETWORK_CONFIG_SOURCE_STATIC; + address->family = AF_INET6; + address->in_addr.in6 = *a; + address->prefixlen = p->prefixlen; + address->route_metric = p->route_metric; + + r = link_request_static_address(link, address); + if (r < 0) + return r; + } + } + + return 0; +} + +static int radv_set_prefix(Link *link, Prefix *prefix) { + _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; + int r; + + assert(link); + assert(link->radv); + assert(prefix); + + r = sd_radv_prefix_new(&p); + if (r < 0) + return r; + + r = sd_radv_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen); + if (r < 0) + return r; + + r = sd_radv_prefix_set_preferred_lifetime(p, prefix->preferred_lifetime, USEC_INFINITY); + if (r < 0) + return r; + + r = sd_radv_prefix_set_valid_lifetime(p, prefix->valid_lifetime, USEC_INFINITY); + if (r < 0) + return r; + + r = sd_radv_prefix_set_onlink(p, prefix->onlink); + if (r < 0) + return r; + + r = sd_radv_prefix_set_address_autoconfiguration(p, prefix->address_auto_configuration); + if (r < 0) + return r; + + return sd_radv_add_prefix(link->radv, p); +} + +static int radv_set_route_prefix(Link *link, RoutePrefix *prefix) { + _cleanup_(sd_radv_route_prefix_unrefp) sd_radv_route_prefix *p = NULL; + int r; + + assert(link); + assert(link->radv); + assert(prefix); + + r = sd_radv_route_prefix_new(&p); + if (r < 0) + return r; + + r = sd_radv_route_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen); + if (r < 0) + return r; + + r = sd_radv_route_prefix_set_lifetime(p, prefix->lifetime, USEC_INFINITY); + if (r < 0) + return r; + + return sd_radv_add_route_prefix(link->radv, p); +} + +static int radv_set_pref64_prefix(Link *link, pref64Prefix *prefix) { + _cleanup_(sd_radv_pref64_prefix_unrefp) sd_radv_pref64_prefix *p = NULL; + int r; + + assert(link); + assert(link->radv); + assert(prefix); + + r = sd_radv_pref64_prefix_new(&p); + if (r < 0) + return r; + + r = sd_radv_pref64_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen, prefix->lifetime); + if (r < 0) + return r; + + return sd_radv_add_pref64_prefix(link->radv, p); +} + +static int network_get_ipv6_dns(Network *network, struct in6_addr **ret_addresses, size_t *ret_size) { + _cleanup_free_ struct in6_addr *addresses = NULL; + size_t n_addresses = 0; + + assert(network); + assert(ret_addresses); + assert(ret_size); + + for (size_t i = 0; i < network->n_dns; i++) { + union in_addr_union *addr; + + if (network->dns[i]->family != AF_INET6) + continue; + + addr = &network->dns[i]->address; + + if (in_addr_is_null(AF_INET6, addr) || + in_addr_is_link_local(AF_INET6, addr) || + in_addr_is_localhost(AF_INET6, addr)) + continue; + + if (!GREEDY_REALLOC(addresses, n_addresses + 1)) + return -ENOMEM; + + addresses[n_addresses++] = addr->in6; + } + + *ret_addresses = TAKE_PTR(addresses); + *ret_size = n_addresses; + + return n_addresses; +} + +static int radv_set_dns(Link *link, Link *uplink) { + _cleanup_free_ struct in6_addr *dns = NULL; + size_t n_dns; + int r; + + if (!link->network->router_emit_dns) + return 0; + + if (link->network->router_dns) { + struct in6_addr *p; + + dns = new(struct in6_addr, link->network->n_router_dns); + if (!dns) + return -ENOMEM; + + p = dns; + for (size_t i = 0; i < link->network->n_router_dns; i++) + if (in6_addr_is_null(&link->network->router_dns[i])) { + if (in6_addr_is_set(&link->ipv6ll_address)) + *(p++) = link->ipv6ll_address; + } else + *(p++) = link->network->router_dns[i]; + + n_dns = p - dns; + + goto set_dns; + } + + r = network_get_ipv6_dns(link->network, &dns, &n_dns); + if (r > 0) + goto set_dns; + + if (uplink) { + assert(uplink->network); + + r = network_get_ipv6_dns(uplink->network, &dns, &n_dns); + if (r > 0) + goto set_dns; + } + + return 0; + +set_dns: + return sd_radv_set_rdnss(link->radv, + link->network->router_dns_lifetime_usec, + dns, n_dns); +} + +static int radv_set_domains(Link *link, Link *uplink) { + _cleanup_free_ char **s = NULL; /* just free() because the strings are owned by the set */ + OrderedSet *search_domains; + + if (!link->network->router_emit_domains) + return 0; + + search_domains = link->network->router_search_domains; + + if (search_domains) + goto set_domains; + + search_domains = link->network->search_domains; + if (search_domains) + goto set_domains; + + if (uplink) { + assert(uplink->network); + + search_domains = uplink->network->search_domains; + if (search_domains) + goto set_domains; + } + + return 0; + +set_domains: + s = ordered_set_get_strv(search_domains); + if (!s) + return log_oom(); + + return sd_radv_set_dnssl(link->radv, + link->network->router_dns_lifetime_usec, + s); + +} + +static int radv_find_uplink(Link *link, Link **ret) { + int r; + + assert(link); + + if (link->network->router_uplink_name) + return link_get_by_name(link->manager, link->network->router_uplink_name, ret); + + if (link->network->router_uplink_index > 0) + return link_get_by_index(link->manager, link->network->router_uplink_index, ret); + + if (link->network->router_uplink_index == UPLINK_INDEX_AUTO) { + if (link_dhcp_pd_is_enabled(link)) + r = dhcp_pd_find_uplink(link, ret); /* When DHCP-PD is enabled, use its uplink. */ + else + r = manager_find_uplink(link->manager, AF_INET6, link, ret); + if (r < 0) + /* It is not necessary to propagate error in automatic selection. */ + *ret = NULL; + return 0; + } + + *ret = NULL; + return 0; +} + +static int radv_configure(Link *link) { + Link *uplink = NULL; + RoutePrefix *q; + pref64Prefix *n; + Prefix *p; + int r; + + assert(link); + assert(link->network); + + if (link->radv) + return -EBUSY; + + r = sd_radv_new(&link->radv); + if (r < 0) + return r; + + r = sd_radv_attach_event(link->radv, link->manager->event, 0); + if (r < 0) + return r; + + if (link->hw_addr.length == ETH_ALEN) { + r = sd_radv_set_mac(link->radv, &link->hw_addr.ether); + if (r < 0) + return r; + } + + r = sd_radv_set_ifindex(link->radv, link->ifindex); + if (r < 0) + return r; + + r = sd_radv_set_managed_information(link->radv, link->network->router_managed); + if (r < 0) + return r; + + r = sd_radv_set_other_information(link->radv, link->network->router_other_information); + if (r < 0) + return r; + + r = sd_radv_set_router_lifetime(link->radv, link->network->router_lifetime_usec); + if (r < 0) + return r; + + r = sd_radv_set_hop_limit(link->radv, link->network->router_hop_limit); + if (r < 0) + return r; + + if (link->network->router_lifetime_usec > 0) { + r = sd_radv_set_preference(link->radv, link->network->router_preference); + if (r < 0) + return r; + } + + if (link->network->router_retransmit_usec > 0) { + r = sd_radv_set_retransmit(link->radv, link->network->router_retransmit_usec); + if (r < 0) + return r; + } + + HASHMAP_FOREACH(p, link->network->prefixes_by_section) { + r = radv_set_prefix(link, p); + if (r < 0 && r != -EEXIST) + return r; + } + + HASHMAP_FOREACH(q, link->network->route_prefixes_by_section) { + r = radv_set_route_prefix(link, q); + if (r < 0 && r != -EEXIST) + return r; + } + + HASHMAP_FOREACH(n, link->network->pref64_prefixes_by_section) { + r = radv_set_pref64_prefix(link, n); + if (r < 0 && r != -EEXIST) + return r; + } + + (void) radv_find_uplink(link, &uplink); + + r = radv_set_dns(link, uplink); + if (r < 0) + return log_link_debug_errno(link, r, "Could not set RA DNS: %m"); + + r = radv_set_domains(link, uplink); + if (r < 0) + return log_link_debug_errno(link, r, "Could not set RA Domains: %m"); + + r = sd_radv_set_home_agent_information(link->radv, link->network->router_home_agent_information); + if (r < 0) + return r; + + r = sd_radv_set_home_agent_preference(link->radv, link->network->router_home_agent_preference); + if (r < 0) + return r; + + r = sd_radv_set_home_agent_lifetime(link->radv, link->network->home_agent_lifetime_usec); + if (r < 0) + return r; + + return 0; +} + +int radv_update_mac(Link *link) { + bool restart; + int r; + + assert(link); + + if (!link->radv) + return 0; + + if (link->hw_addr.length != ETH_ALEN) + return 0; + + restart = sd_radv_is_running(link->radv); + + r = sd_radv_stop(link->radv); + if (r < 0) + return r; + + r = sd_radv_set_mac(link->radv, &link->hw_addr.ether); + if (r < 0) + return r; + + if (restart) { + r = sd_radv_start(link->radv); + if (r < 0) + return r; + } + + return 0; +} + +static int radv_is_ready_to_configure(Link *link) { + bool needs_uplink = false; + int r; + + assert(link); + assert(link->network); + + if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false)) + return false; + + if (in6_addr_is_null(&link->ipv6ll_address)) + return false; + + if (link->hw_addr.length != ETH_ALEN || hw_addr_is_null(&link->hw_addr)) + return false; + + if (link->network->router_emit_dns && !link->network->router_dns) { + _cleanup_free_ struct in6_addr *dns = NULL; + size_t n_dns; + + r = network_get_ipv6_dns(link->network, &dns, &n_dns); + if (r < 0) + return r; + + needs_uplink = r == 0; + } + + if (link->network->router_emit_domains && + !link->network->router_search_domains && + !link->network->search_domains) + needs_uplink = true; + + if (needs_uplink) { + Link *uplink = NULL; + + if (radv_find_uplink(link, &uplink) < 0) + return false; + + if (uplink && !uplink->network) + return false; + } + + return true; +} + +static int radv_process_request(Request *req, Link *link, void *userdata) { + int r; + + assert(link); + + r = radv_is_ready_to_configure(link); + if (r <= 0) + return r; + + r = radv_configure(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure IPv6 Router Advertisement engine: %m"); + + if (link_has_carrier(link)) { + r = radv_start(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to start IPv6 Router Advertisement engine: %m"); + } + + log_link_debug(link, "IPv6 Router Advertisement engine is configured%s.", + link_has_carrier(link) ? " and started" : ""); + return 1; +} + +int link_request_radv(Link *link) { + int r; + + assert(link); + + if (!link_radv_enabled(link)) + return 0; + + if (link->radv) + return 0; + + r = link_queue_request(link, REQUEST_TYPE_RADV, radv_process_request, NULL); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to request configuring of the IPv6 Router Advertisement engine: %m"); + + log_link_debug(link, "Requested configuring of the IPv6 Router Advertisement engine."); + return 0; +} + +int radv_start(Link *link) { + int r; + + assert(link); + assert(link->network); + + if (!link->radv) + return 0; + + if (!link_has_carrier(link)) + return 0; + + if (in6_addr_is_null(&link->ipv6ll_address)) + return 0; + + if (sd_radv_is_running(link->radv)) + return 0; + + if (link->network->dhcp_pd_announce) { + r = dhcp_request_prefix_delegation(link); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m"); + } + + log_link_debug(link, "Starting IPv6 Router Advertisements"); + return sd_radv_start(link->radv); +} + +int radv_add_prefix( + Link *link, + const struct in6_addr *prefix, + uint8_t prefix_len, + usec_t lifetime_preferred_usec, + usec_t lifetime_valid_usec) { + + _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; + int r; + + assert(link); + + if (!link->radv) + return 0; + + r = sd_radv_prefix_new(&p); + if (r < 0) + return r; + + r = sd_radv_prefix_set_prefix(p, prefix, prefix_len); + if (r < 0) + return r; + + r = sd_radv_prefix_set_preferred_lifetime(p, RADV_DEFAULT_PREFERRED_LIFETIME_USEC, lifetime_preferred_usec); + if (r < 0) + return r; + + r = sd_radv_prefix_set_valid_lifetime(p, RADV_DEFAULT_VALID_LIFETIME_USEC, lifetime_valid_usec); + if (r < 0) + return r; + + r = sd_radv_add_prefix(link->radv, p); + if (r < 0 && r != -EEXIST) + return r; + + return 0; +} + +static int prefix_section_verify(Prefix *p) { + assert(p); + + if (section_is_invalid(p->section)) + return -EINVAL; + + if (in6_addr_is_null(&p->prefix)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: [IPv6Prefix] section without Prefix= field configured, " + "or specified prefix is the null address. " + "Ignoring [IPv6Prefix] section from line %u.", + p->section->filename, p->section->line); + + if (p->prefixlen < 3 || p->prefixlen > 128) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid prefix length %u is specified in [IPv6Prefix] section. " + "Valid range is 3…128. Ignoring [IPv6Prefix] section from line %u.", + p->section->filename, p->prefixlen, p->section->line); + + if (p->prefixlen > 64) { + log_info("%s:%u: Unusual prefix length %u (> 64) is specified in [IPv6Prefix] section from line %s%s.", + p->section->filename, p->section->line, + p->prefixlen, + p->assign ? ", refusing to assign an address in " : "", + p->assign ? IN6_ADDR_PREFIX_TO_STRING(&p->prefix, p->prefixlen) : ""); + + p->assign = false; + } + + if (p->valid_lifetime == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: The valid lifetime of prefix cannot be zero. " + "Ignoring [IPv6Prefix] section from line %u.", + p->section->filename, p->section->line); + + if (p->preferred_lifetime > p->valid_lifetime) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: The preferred lifetime %s is longer than the valid lifetime %s. " + "Ignoring [IPv6Prefix] section from line %u.", + p->section->filename, + FORMAT_TIMESPAN(p->preferred_lifetime, USEC_PER_SEC), + FORMAT_TIMESPAN(p->valid_lifetime, USEC_PER_SEC), + p->section->line); + + return 0; +} + +void network_drop_invalid_prefixes(Network *network) { + Prefix *p; + + assert(network); + + HASHMAP_FOREACH(p, network->prefixes_by_section) + if (prefix_section_verify(p) < 0) + prefix_free(p); +} + +static int route_prefix_section_verify(RoutePrefix *p) { + if (section_is_invalid(p->section)) + return -EINVAL; + + if (p->prefixlen > 128) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid prefix length %u is specified in [IPv6RoutePrefix] section. " + "Valid range is 0…128. Ignoring [IPv6RoutePrefix] section from line %u.", + p->section->filename, p->prefixlen, p->section->line); + + if (p->lifetime == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: The lifetime of route cannot be zero. " + "Ignoring [IPv6RoutePrefix] section from line %u.", + p->section->filename, p->section->line); + + return 0; +} + +void network_drop_invalid_route_prefixes(Network *network) { + RoutePrefix *p; + + assert(network); + + HASHMAP_FOREACH(p, network->route_prefixes_by_section) + if (route_prefix_section_verify(p) < 0) + route_prefix_free(p); +} + +void network_drop_invalid_pref64_prefixes(Network *network) { + pref64Prefix *p; + + assert(network); + + HASHMAP_FOREACH(p, network->pref64_prefixes_by_section) + if (section_is_invalid(p->section)) + pref64_prefix_free(p); +} + +int config_parse_prefix( + 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) { + + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + union in_addr_union a; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + (void) in6_addr_mask(&a.in6, p->prefixlen); + p->prefix = a.in6; + + TAKE_PTR(p); + return 0; +} + +int config_parse_prefix_boolean( + 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) { + + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "OnLink")) + p->onlink = r; + else if (streq(lvalue, "AddressAutoconfiguration")) + p->address_auto_configuration = r; + else if (streq(lvalue, "Assign")) + p->assign = r; + else + assert_not_reached(); + + TAKE_PTR(p); + return 0; +} + +int config_parse_prefix_lifetime( + 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) { + + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + usec_t usec; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Lifetime is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + if (usec != USEC_INFINITY && DIV_ROUND_UP(usec, USEC_PER_SEC) >= UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Lifetime is too long, ignoring assignment: %s", rvalue); + return 0; + } + + if (streq(lvalue, "PreferredLifetimeSec")) + p->preferred_lifetime = usec; + else if (streq(lvalue, "ValidLifetimeSec")) + p->valid_lifetime = usec; + else + assert_not_reached(); + + TAKE_PTR(p); + return 0; +} + +int config_parse_prefix_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) { + + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = safe_atou32(rvalue, &p->route_metric); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + TAKE_PTR(p); + return 0; +} + +int config_parse_prefix_token( + 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) { + + _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = config_parse_address_generation_type(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, &p->tokens, userdata); + if (r < 0) + return r; + + TAKE_PTR(p); + return 0; +} + +int config_parse_route_prefix( + 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) { + + _cleanup_(route_prefix_free_or_set_invalidp) RoutePrefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + union in_addr_union a; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = route_prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Route prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + (void) in6_addr_mask(&a.in6, p->prefixlen); + p->prefix = a.in6; + + TAKE_PTR(p); + return 0; +} + +int config_parse_route_prefix_lifetime( + 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) { + + _cleanup_(route_prefix_free_or_set_invalidp) RoutePrefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + usec_t usec; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = route_prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Route lifetime is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + if (usec != USEC_INFINITY && DIV_ROUND_UP(usec, USEC_PER_SEC) >= UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Lifetime is too long, ignoring assignment: %s", rvalue); + return 0; + } + + p->lifetime = usec; + + TAKE_PTR(p); + return 0; +} + +int config_parse_pref64_prefix( + 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) { + + _cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + union in_addr_union a; + uint8_t prefixlen; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = pref64_prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "PREF64 prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + if (!IN_SET(prefixlen, 96, 64, 56, 48, 40, 32)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "PREF64 prefixlen is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + (void) in6_addr_mask(&a.in6,prefixlen); + p->prefix = a.in6; + p->prefixlen = prefixlen; + + TAKE_PTR(p); + return 0; +} + +int config_parse_pref64_prefix_lifetime( + 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) { + + _cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + usec_t usec; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + r = pref64_prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "PREF64 lifetime is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + if (usec == USEC_INFINITY || DIV_ROUND_UP(usec, 8 * USEC_PER_SEC) >= UINT64_C(1) << 13) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "PREF64 lifetime is too long, ignoring assignment: %s", rvalue); + return 0; + } + + p->lifetime = usec; + + TAKE_PTR(p); + return 0; +} + +int config_parse_radv_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) { + + Network *n = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->n_router_dns = 0; + n->router_dns = mfree(n->router_dns); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + union in_addr_union a; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract word, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + if (streq(w, "_link_local")) + a = IN_ADDR_NULL; + else { + r = in_addr_from_string(AF_INET6, w, &a); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse DNS server address, ignoring: %s", w); + continue; + } + + if (in_addr_is_null(AF_INET6, &a)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "DNS server address is null, ignoring: %s", w); + continue; + } + } + + struct in6_addr *m; + m = reallocarray(n->router_dns, n->n_router_dns + 1, sizeof(struct in6_addr)); + if (!m) + return log_oom(); + + m[n->n_router_dns++] = a.in6; + n->router_dns = m; + } +} + +int config_parse_radv_search_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) { + + Network *n = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->router_search_domains = ordered_set_free(n->router_search_domains); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL, *idna = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract word, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_apply_idna(w, &idna); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to apply IDNA to domain name '%s', ignoring: %m", w); + continue; + } else if (r == 0) + /* transfer ownership to simplify subsequent operations */ + idna = TAKE_PTR(w); + + r = ordered_set_ensure_allocated(&n->router_search_domains, &string_hash_ops_free); + if (r < 0) + return log_oom(); + + r = ordered_set_consume(n->router_search_domains, TAKE_PTR(idna)); + if (r < 0) + return log_oom(); + } +} + +static const char * const radv_prefix_delegation_table[_RADV_PREFIX_DELEGATION_MAX] = { + [RADV_PREFIX_DELEGATION_NONE] = "no", + [RADV_PREFIX_DELEGATION_STATIC] = "static", + [RADV_PREFIX_DELEGATION_DHCP6] = "dhcpv6", + [RADV_PREFIX_DELEGATION_BOTH] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN( + radv_prefix_delegation, + RADVPrefixDelegation, + RADV_PREFIX_DELEGATION_BOTH); + +int config_parse_router_prefix_delegation( + 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) { + + RADVPrefixDelegation val, *ra = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (streq(lvalue, "IPv6SendRA")) { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s= setting, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + /* When IPv6SendRA= is enabled, only static prefixes are sent by default, and users + * need to explicitly enable DHCPv6PrefixDelegation=. */ + *ra = r ? RADV_PREFIX_DELEGATION_STATIC : RADV_PREFIX_DELEGATION_NONE; + return 0; + } + + /* For backward compatibility */ + val = radv_prefix_delegation_from_string(rvalue); + if (val < 0) { + log_syntax(unit, LOG_WARNING, filename, line, val, + "Invalid %s= setting, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *ra = val; + return 0; +} + +int config_parse_router_lifetime( + 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) { + + usec_t usec, *lifetime = ASSERT_PTR(data); + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *lifetime = RADV_DEFAULT_ROUTER_LIFETIME_USEC; + return 0; + } + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse router lifetime, ignoring assignment: %s", rvalue); + return 0; + } + if (usec > 0) { + if (usec < RADV_MIN_ROUTER_LIFETIME_USEC) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Router lifetime %s is too short, using %s.", + FORMAT_TIMESPAN(usec, USEC_PER_SEC), + FORMAT_TIMESPAN(RADV_MIN_ROUTER_LIFETIME_USEC, USEC_PER_SEC)); + usec = RADV_MIN_ROUTER_LIFETIME_USEC; + } else if (usec > RADV_MAX_ROUTER_LIFETIME_USEC) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Router lifetime %s is too large, using %s.", + FORMAT_TIMESPAN(usec, USEC_PER_SEC), + FORMAT_TIMESPAN(RADV_MAX_ROUTER_LIFETIME_USEC, USEC_PER_SEC)); + usec = RADV_MAX_ROUTER_LIFETIME_USEC; + } + } + + *lifetime = usec; + return 0; +} + +int config_parse_router_retransmit( + 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) { + + usec_t usec, *router_retransmit_usec = ASSERT_PTR(data); + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *router_retransmit_usec = 0; + return 0; + } + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (usec != USEC_INFINITY && + usec > RADV_MAX_RETRANSMIT_USEC) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue); + return 0; + } + + *router_retransmit_usec = usec; + return 0; +} + +int config_parse_router_preference( + 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; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "high")) + network->router_preference = SD_NDISC_PREFERENCE_HIGH; + else if (STR_IN_SET(rvalue, "medium", "normal", "default")) + network->router_preference = SD_NDISC_PREFERENCE_MEDIUM; + else if (streq(rvalue, "low")) + network->router_preference = SD_NDISC_PREFERENCE_LOW; + else + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid router preference, ignoring assignment: %s", rvalue); + + return 0; +} + +int config_parse_router_home_agent_lifetime( + 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) { + + usec_t usec, *home_agent_lifetime_usec = ASSERT_PTR(data); + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *home_agent_lifetime_usec = 0; + return 0; + } + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (!timestamp_is_set(usec) || + usec > RADV_HOME_AGENT_MAX_LIFETIME_USEC) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue); + return 0; + } + + *home_agent_lifetime_usec = usec; + return 0; +} |