From 86a66e9b95048b1a3a4e297ba2884afcedd1585e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 8 Oct 2023 13:06:25 +0900 Subject: [PATCH 1/5] network: also save NTP servers and friends obtained by other protocols Previously, only servers that statically configursd or obtained by DHCPv4 protocol are saved in the manager state file. NTP servers obtained by DHCPv6 could not be used by timesyncd. Fixes #29148. --- src/network/networkd-state-file.c | 347 +++++++++++++++++++----------- 1 file changed, 225 insertions(+), 122 deletions(-) diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index 85c9d21082..03146c62c7 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) From 34290c6aa9e90d57a63f3c8f3cf7f4892f524e48 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 8 Oct 2023 13:28:32 +0900 Subject: [PATCH 2/5] test-network: test for NTP servers by DHCPv6 protocol For issue #29148. --- .../conf/25-dhcp-client-ipv6-only.network | 4 ++ test/test-network/systemd-networkd-tests.py | 38 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) 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 eb5bd027ff..017f76f4d5 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 9edbeb4ab1..007a6425dd 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) @@ -5099,7 +5103,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): start_networkd() self.wait_online(['veth-peer:carrier']) - start_dnsmasq() + start_dnsmasq('--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]') self.wait_online(['veth99:routable', 'veth-peer:routable']) # checking address @@ -5118,6 +5123,20 @@ 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) @@ -5131,7 +5150,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,6 +5167,20 @@ 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) From 814d8f962fe132aaaf01fc8376b7832a03aa0cf2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 8 Oct 2023 16:18:26 +0900 Subject: [PATCH 3/5] network/dhcp6: shorten dhcp6_handler() Note, currently dhcp6_lease_information_acquired() do nothing, so this does not change any behavior. --- src/network/networkd-dhcp6.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 5c5dff9413..5fb1b6bdad 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -366,7 +366,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 +378,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) { From 9709f9edc58ac9f977e4bbef8b5595cee928bc9f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 8 Oct 2023 16:20:15 +0900 Subject: [PATCH 4/5] network/dhcp6: keep lease when running in information request mode Fixes #28566. --- src/network/networkd-dhcp6.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 5fb1b6bdad..6044411a75 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) { From 2d7ca6b45d0b03ef9f3632e152eddfd2ff68bc3e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 8 Oct 2023 16:30:48 +0900 Subject: [PATCH 5/5] test-network: add test for DHCPv6 information requesting mode For issue #28566. --- test/test-network/systemd-networkd-tests.py | 46 +++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 007a6425dd..1496a615fe 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -571,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}', @@ -583,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', @@ -5103,8 +5108,41 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): start_networkd() self.wait_online(['veth-peer:carrier']) + + # 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 @@ -5140,6 +5178,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): 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) @@ -5184,6 +5223,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): 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)