diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 5c5dff9413d..6044411a75f 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -341,7 +341,17 @@ static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) { } static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) { - return 0; + sd_dhcp6_lease *lease; + int r; + + assert(client); + assert(link); + + r = sd_dhcp6_client_get_lease(client, &lease); + if (r < 0) + return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); + + return unref_and_replace_full(link->dhcp6_lease, lease, sd_dhcp6_lease_ref, sd_dhcp6_lease_unref); } static int dhcp6_lease_lost(Link *link) { @@ -366,7 +376,7 @@ static int dhcp6_lease_lost(Link *link) { static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { Link *link = ASSERT_PTR(userdata); - int r; + int r = 0; assert(link->network); @@ -378,31 +388,24 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { 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; - } + break; - _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"); + log_link_warning_errno(link, event, "DHCPv6 error, ignoring: %m"); else log_link_warning(link, "DHCPv6 unknown event: %d", event); - return; } + if (r < 0) + link_enter_failed(link); } int dhcp6_start_on_ra(Link *link, bool information_request) { diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index 85c9d210820..03146c62c7f 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -21,64 +21,28 @@ #include "strv.h" #include "tmpfile-util.h" -static int ordered_set_put_dns_server(OrderedSet **s, int ifindex, struct in_addr_full *dns) { - const char *p; - int r; - - assert(s); - assert(dns); - - if (dns->ifindex != 0 && dns->ifindex != ifindex) - return 0; - - p = in_addr_full_to_string(dns); - if (!p) - return 0; - - r = ordered_set_put_strdup(s, p); - if (r == -EEXIST) - return 0; - - return r; -} - static int ordered_set_put_dns_servers(OrderedSet **s, int ifindex, struct in_addr_full **dns, unsigned n) { - int r, c = 0; + int r; assert(s); assert(dns || n == 0); - for (unsigned i = 0; i < n; i++) { - r = ordered_set_put_dns_server(s, ifindex, dns[i]); + FOREACH_ARRAY(a, dns, n) { + const char *p; + + if ((*a)->ifindex != 0 && (*a)->ifindex != ifindex) + return 0; + + p = in_addr_full_to_string(*a); + if (!p) + return 0; + + r = ordered_set_put_strdup(s, p); if (r < 0) return r; - - c += r; } - return c; -} - -static int ordered_set_put_in4_addr(OrderedSet **s, const struct in_addr *address) { - _cleanup_free_ char *p = NULL; - int r; - - assert(s); - assert(address); - - r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p); - if (r < 0) - return r; - - r = ordered_set_ensure_allocated(s, &string_hash_ops_free); - if (r < 0) - return r; - - r = ordered_set_consume(*s, TAKE_PTR(p)); - if (r == -EEXIST) - return 0; - - return r; + return 0; } static int ordered_set_put_in4_addrv( @@ -87,22 +51,220 @@ static int ordered_set_put_in4_addrv( size_t n, bool (*predicate)(const struct in_addr *addr)) { - int r, c = 0; + int r; assert(s); assert(n == 0 || addresses); - for (size_t i = 0; i < n; i++) { - if (predicate && !predicate(&addresses[i])) + FOREACH_ARRAY(a, addresses, n) { + if (predicate && !predicate(a)) continue; - r = ordered_set_put_in4_addr(s, addresses+i); + + r = ordered_set_put_strdup(s, IN4_ADDR_TO_STRING(a)); if (r < 0) return r; - - c += r; } - return c; + return 0; +} + +static int ordered_set_put_in6_addrv( + OrderedSet **s, + const struct in6_addr *addresses, + size_t n) { + + int r; + + assert(s); + assert(n == 0 || addresses); + + FOREACH_ARRAY(a, addresses, n) { + r = ordered_set_put_strdup(s, IN6_ADDR_TO_STRING(a)); + if (r < 0) + return r; + } + + return 0; +} + +static int link_put_dns(Link *link, OrderedSet **s) { + int r; + + assert(link); + assert(link->network); + assert(s); + + if (link->n_dns != UINT_MAX) + return ordered_set_put_dns_servers(s, link->ifindex, link->dns, link->n_dns); + + r = ordered_set_put_dns_servers(s, link->ifindex, link->network->dns, link->network->n_dns); + if (r < 0) + return r; + + if (link->dhcp_lease && link->network->dhcp_use_dns) { + const struct in_addr *addresses; + + r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in4_addrv(s, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } + } + + if (link->dhcp6_lease && link->network->dhcp6_use_dns) { + const struct in6_addr *addresses; + + r = sd_dhcp6_lease_get_dns(link->dhcp6_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in6_addrv(s, addresses, r); + if (r < 0) + return r; + } + } + + if (link->network->ipv6_accept_ra_use_dns) { + NDiscRDNSS *a; + + SET_FOREACH(a, link->ndisc_rdnss) { + r = ordered_set_put_in6_addrv(s, &a->router, 1); + if (r < 0) + return r; + } + } + + return 0; +} + +static int link_put_ntp(Link *link, OrderedSet **s) { + int r; + + assert(link); + assert(link->network); + assert(s); + + if (link->ntp) + return ordered_set_put_strdupv(s, link->ntp); + + r = ordered_set_put_strdupv(s, link->network->ntp); + if (r < 0) + return r; + + if (link->dhcp_lease && link->network->dhcp_use_ntp) { + const struct in_addr *addresses; + + r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in4_addrv(s, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } + } + + if (link->dhcp6_lease && link->network->dhcp6_use_ntp) { + const struct in6_addr *addresses; + char **fqdn; + + r = sd_dhcp6_lease_get_ntp_addrs(link->dhcp6_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in6_addrv(s, addresses, r); + if (r < 0) + return r; + } + + r = sd_dhcp6_lease_get_ntp_fqdn(link->dhcp6_lease, &fqdn); + if (r >= 0) { + r = ordered_set_put_strdupv(s, fqdn); + if (r < 0) + return r; + } + } + + return 0; +} + +static int link_put_sip(Link *link, OrderedSet **s) { + int r; + + assert(link); + assert(link->network); + assert(s); + + if (link->dhcp_lease && link->network->dhcp_use_ntp) { + const struct in_addr *addresses; + + r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in4_addrv(s, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } + } + + return 0; +} + +static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { + OrderedSet *link_domains, *network_domains; + DHCPUseDomains use_domains; + int r; + + assert(link); + assert(link->network); + assert(s); + + link_domains = is_route ? link->route_domains : link->search_domains; + network_domains = is_route ? link->network->route_domains : link->network->search_domains; + use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES; + + if (link_domains) + return ordered_set_put_string_set(s, link_domains); + + r = ordered_set_put_string_set(s, network_domains); + if (r < 0) + return r; + + if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { + const char *domainname; + char **domains; + + r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); + if (r >= 0) { + r = ordered_set_put_strdup(s, domainname); + if (r < 0) + return r; + } + + r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains); + if (r >= 0) { + r = ordered_set_put_strdupv(s, domains); + if (r < 0) + return r; + } + } + + if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { + char **domains; + + r = sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains); + if (r >= 0) { + r = ordered_set_put_strdupv(s, domains); + if (r < 0) + return r; + } + } + + if (link->network->ipv6_accept_ra_use_domains == use_domains) { + NDiscDNSSL *a; + + SET_FOREACH(a, link->ndisc_dnssl) { + r = ordered_set_put_strdup(s, NDISC_DNSSL_DOMAIN(a)); + if (r < 0) + return r; + } + } + + return 0; } int manager_save(Manager *m) { @@ -126,8 +288,6 @@ int manager_save(Manager *m) { return 0; /* Do not update state file when running in test mode. */ HASHMAP_FOREACH(link, m->links_by_index) { - const struct in_addr *addresses; - if (link->flags & IFF_LOOPBACK) continue; @@ -147,82 +307,25 @@ int manager_save(Manager *m) { links_online++; } - /* First add the static configured entries */ - if (link->n_dns != UINT_MAX) - r = ordered_set_put_dns_servers(&dns, link->ifindex, link->dns, link->n_dns); - else - r = ordered_set_put_dns_servers(&dns, link->ifindex, link->network->dns, link->network->n_dns); + r = link_put_dns(link, &dns); if (r < 0) return r; - r = ordered_set_put_strdupv(&ntp, link->ntp ?: link->network->ntp); + r = link_put_ntp(link, &ntp); if (r < 0) return r; - r = ordered_set_put_string_set(&search_domains, link->search_domains ?: link->network->search_domains); + r = link_put_sip(link, &sip); if (r < 0) return r; - r = ordered_set_put_string_set(&route_domains, link->route_domains ?: link->network->route_domains); + r = link_put_domains(link, /* is_route = */ false, &search_domains); if (r < 0) return r; - if (!link->dhcp_lease) - continue; - - /* Secondly, add the entries acquired via DHCP */ - if (link->network->dhcp_use_dns) { - r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); - if (r > 0) { - r = ordered_set_put_in4_addrv(&dns, addresses, r, in4_addr_is_non_local); - if (r < 0) - return r; - } else if (r < 0 && r != -ENODATA) - return r; - } - - if (link->network->dhcp_use_ntp) { - r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); - if (r > 0) { - r = ordered_set_put_in4_addrv(&ntp, addresses, r, in4_addr_is_non_local); - if (r < 0) - return r; - } else if (r < 0 && r != -ENODATA) - return r; - } - - if (link->network->dhcp_use_sip) { - r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses); - if (r > 0) { - r = ordered_set_put_in4_addrv(&sip, addresses, r, in4_addr_is_non_local); - if (r < 0) - return r; - } else if (r < 0 && r != -ENODATA) - return r; - } - - if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { - OrderedSet **target_domains; - const char *domainname; - char **domains = NULL; - - target_domains = link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES ? &search_domains : &route_domains; - r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); - if (r >= 0) { - r = ordered_set_put_strdup(target_domains, domainname); - if (r < 0) - return r; - } else if (r != -ENODATA) - return r; - - r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains); - if (r >= 0) { - r = ordered_set_put_strdupv(target_domains, domains); - if (r < 0) - return r; - } else if (r != -ENODATA) - return r; - } + r = link_put_domains(link, /* is_route = */ true, &route_domains); + if (r < 0) + return r; } if (carrier_state >= LINK_CARRIER_STATE_ENSLAVED) diff --git a/test/test-network/conf/25-dhcp-client-ipv6-only.network b/test/test-network/conf/25-dhcp-client-ipv6-only.network index eb5bd027ff1..017f76f4d58 100644 --- a/test/test-network/conf/25-dhcp-client-ipv6-only.network +++ b/test/test-network/conf/25-dhcp-client-ipv6-only.network @@ -9,3 +9,7 @@ IPv6Token=::1a:2b:3c:4d [Route] Gateway=_ipv6ra Destination=2001:1234:5:9fff:ff:ff:ff:ff/128 + +[IPv6AcceptRA] +# To check DNS and NTP servers are really obtained by DHCPv6 +UseDNS=no diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 9edbeb4ab1f..1496a615fe4 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -534,6 +534,10 @@ def read_link_attr(*args): with open(os.path.join('/sys/class/net', *args), encoding='utf-8') as f: return f.readline().strip() +def read_manager_state_file(): + with open('/run/systemd/netif/state', encoding='utf-8') as f: + return f.read() + def read_link_state_file(link): ifindex = read_link_attr(link, 'ifindex') path = os.path.join('/run/systemd/netif/links', ifindex) @@ -567,7 +571,12 @@ def stop_by_pid_file(pid_file): print(f"Unexpected exception when waiting for {pid} to die: {e.errno}") rm_f(pid_file) -def start_dnsmasq(*additional_options, interface='veth-peer', lease_time='2m', ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): +def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): + if ra_mode: + ra_mode = f',{ra_mode}' + else: + ra_mode = '' + command = ( 'dnsmasq', f'--log-facility={dnsmasq_log_file}', @@ -579,8 +588,8 @@ def start_dnsmasq(*additional_options, interface='veth-peer', lease_time='2m', i f'--interface={interface}', f'--dhcp-leasefile={dnsmasq_lease_file}', '--enable-ra', - f'--dhcp-range={ipv6_range},{lease_time}', - f'--dhcp-range={ipv4_range},{lease_time}', + f'--dhcp-range={ipv6_range}{ra_mode},2m', + f'--dhcp-range={ipv4_range},2m', '--dhcp-option=option:mtu,1492', f'--dhcp-option=option:router,{ipv4_router}', '--port=0', @@ -5099,7 +5108,41 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): start_networkd() self.wait_online(['veth-peer:carrier']) - start_dnsmasq() + + # information request mode + start_dnsmasq('--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ra_mode='ra-stateless') + self.wait_online(['veth99:routable', 'veth-peer:routable']) + + # Check link state file + print('## link state file') + output = read_link_state_file('veth99') + print(output) + self.assertIn('DNS=2600::ee', output) + self.assertIn('NTP=2600::ff', output) + + # Check manager state file + print('## manager state file') + output = read_manager_state_file() + print(output) + self.assertRegex(output, 'DNS=.*2600::ee') + self.assertRegex(output, 'NTP=.*2600::ff') + + print('## dnsmasq log') + output = read_dnsmasq_log_file() + print(output) + self.assertIn('DHCPINFORMATION-REQUEST(veth-peer)', output) + self.assertNotIn('DHCPSOLICIT(veth-peer)', output) + self.assertNotIn('DHCPADVERTISE(veth-peer)', output) + self.assertNotIn('DHCPREQUEST(veth-peer)', output) + self.assertNotIn('DHCPREPLY(veth-peer)', output) + + # solicit mode + stop_dnsmasq() + start_dnsmasq('--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]') + networkctl_reconfigure('veth99') self.wait_online(['veth99:routable', 'veth-peer:routable']) # checking address @@ -5118,9 +5161,24 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, 'token :: dev veth99') + # Check link state file + print('## link state file') + output = read_link_state_file('veth99') + print(output) + self.assertIn('DNS=2600::ee', output) + self.assertIn('NTP=2600::ff', output) + + # Check manager state file + print('## manager state file') + output = read_manager_state_file() + print(output) + self.assertRegex(output, 'DNS=.*2600::ee') + self.assertRegex(output, 'NTP=.*2600::ff') + print('## dnsmasq log') output = read_dnsmasq_log_file() print(output) + self.assertNotIn('DHCPINFORMATION-REQUEST(veth-peer)', output) self.assertIn('DHCPSOLICIT(veth-peer)', output) self.assertNotIn('DHCPADVERTISE(veth-peer)', output) self.assertNotIn('DHCPREQUEST(veth-peer)', output) @@ -5131,7 +5189,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): f.write('\n[DHCPv6]\nRapidCommit=no\n') stop_dnsmasq() - start_dnsmasq() + start_dnsmasq('--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]') networkctl_reload() self.wait_online(['veth99:routable', 'veth-peer:routable']) @@ -5147,9 +5206,24 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, 'via fe80::1034:56ff:fe78:9abd') + # Check link state file + print('## link state file') + output = read_link_state_file('veth99') + print(output) + self.assertIn('DNS=2600::ee', output) + self.assertIn('NTP=2600::ff', output) + + # Check manager state file + print('## manager state file') + output = read_manager_state_file() + print(output) + self.assertRegex(output, 'DNS=.*2600::ee') + self.assertRegex(output, 'NTP=.*2600::ff') + print('## dnsmasq log') output = read_dnsmasq_log_file() print(output) + self.assertNotIn('DHCPINFORMATION-REQUEST(veth-peer)', output) self.assertIn('DHCPSOLICIT(veth-peer)', output) self.assertIn('DHCPADVERTISE(veth-peer)', output) self.assertIn('DHCPREQUEST(veth-peer)', output)