mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
network: support the RFC 4833 timezone option in DHCPv6
For Legacy IP, DHCPv4 has supported UseTimezone= since v226, requesting the tzdb timezone name (option 101) and passing it to timedated. The equivalent exists for DHCPv6 (option 42; OPTION_NEW_TZDB_TIMEZONE), but support for that was missing. Parse option 42 into the lease, validated with timezone_is_valid() as on the DHCPv4 side, expose it as sd_dhcp6_lease_get_timezone(), and add UseTimezone= to the [DHCPv6] section. The timezone is applied on both the address-acquired and information-request paths; the latter matters because a SLAAC network with the O flag set only ever does stateless DHCPv6. The test message uses "UTC" rather than a real tzdb name because timezone_is_valid() wants to open the tzfile, and UTC is the one name that is accepted even when no tzdata is installed.
This commit is contained in:
1 parent
b43258ee75
commit
16a7a07e9d
11 files changed
+194
-9
No files matched your search
@@ -3322,6 +3322,16 @@ MultiPathRoute=2001:db8::1@eth0</programlisting>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>UseTimezone=</varname></term>
|
||||
<listitem>
|
||||
<para>When true, the timezone received from the DHCPv6 server will be set as timezone
|
||||
of the local system. Defaults to false.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>UseDelegatedPrefix=</varname></term>
|
||||
<listitem>
|
||||
|
||||
@@ -48,6 +48,7 @@ struct sd_dhcp6_lease {
|
||||
char **sip_domains;
|
||||
char *fqdn;
|
||||
char *captive_portal;
|
||||
char *timezone;
|
||||
struct sd_dhcp6_option **sorted_vendor_options;
|
||||
Set *vendor_options;
|
||||
};
|
||||
@@ -69,6 +70,7 @@ int dhcp6_lease_add_sip_addrs(sd_dhcp6_lease *lease, const uint8_t *optval, size
|
||||
int dhcp6_lease_add_sip_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
|
||||
int dhcp6_lease_set_fqdn(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
|
||||
int dhcp6_lease_set_captive_portal(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
|
||||
int dhcp6_lease_set_timezone(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen);
|
||||
|
||||
int dhcp6_lease_new(sd_dhcp6_lease **ret);
|
||||
int dhcp6_lease_new_from_message(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "sort-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
#include "unaligned.h"
|
||||
|
||||
#define IRT_DEFAULT (1 * USEC_PER_DAY)
|
||||
@@ -740,6 +741,34 @@ int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhcp6_lease_set_timezone(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
|
||||
_cleanup_free_ char *tz = NULL;
|
||||
int r;
|
||||
|
||||
assert(lease);
|
||||
assert(optval || optlen == 0);
|
||||
|
||||
r = dhcp6_option_parse_string(optval, optlen, &tz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!tz || !timezone_is_valid(tz, LOG_DEBUG))
|
||||
return -EINVAL;
|
||||
|
||||
return free_and_replace(lease->timezone, tz);
|
||||
}
|
||||
|
||||
int sd_dhcp6_lease_get_timezone(sd_dhcp6_lease *lease, const char **ret) {
|
||||
assert_return(lease, -EINVAL);
|
||||
assert_return(ret, -EINVAL);
|
||||
|
||||
if (!lease->timezone)
|
||||
return -ENODATA;
|
||||
|
||||
*ret = lease->timezone;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret) {
|
||||
int r;
|
||||
|
||||
@@ -1006,6 +1035,13 @@ static int dhcp6_lease_parse_message(
|
||||
|
||||
break;
|
||||
|
||||
case SD_DHCP6_OPTION_TZDB_TIMEZONE:
|
||||
r = dhcp6_lease_set_timezone(lease, optval, optlen);
|
||||
if (r < 0)
|
||||
log_dhcp6_client_errno(client, r, "Failed to parse timezone option, ignoring: %m");
|
||||
|
||||
break;
|
||||
|
||||
case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
|
||||
if (optlen != 4)
|
||||
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
|
||||
@@ -1079,6 +1115,7 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
|
||||
dns_resolver_free_array(lease->dnr, lease->n_dnr);
|
||||
free(lease->fqdn);
|
||||
free(lease->captive_portal);
|
||||
free(lease->timezone);
|
||||
strv_free(lease->domains);
|
||||
free(lease->ntp);
|
||||
strv_free(lease->ntp_fqdn);
|
||||
|
||||
@@ -403,6 +403,25 @@ TEST(option_status) {
|
||||
ASSERT_NULL(ia);
|
||||
}
|
||||
|
||||
TEST(lease_timezone) {
|
||||
_cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
|
||||
const char *str;
|
||||
|
||||
ASSERT_OK(dhcp6_lease_new(&lease));
|
||||
|
||||
/* "UTC" in the main test messages bypasses the tzfile check in
|
||||
* verify_timezone(); exercise the validation with invalid inputs. */
|
||||
ASSERT_ERROR(dhcp6_lease_set_timezone(lease, (const uint8_t *) "", 0), EINVAL);
|
||||
ASSERT_ERROR(dhcp6_lease_set_timezone(lease, (const uint8_t *) "UTC\0", 4), EINVAL);
|
||||
ASSERT_ERROR(dhcp6_lease_set_timezone(lease, (const uint8_t *) "/etc/passwd", 11), EINVAL);
|
||||
ASSERT_ERROR(dhcp6_lease_set_timezone(lease, (const uint8_t *) "../passwd", 9), EINVAL);
|
||||
ASSERT_ERROR(sd_dhcp6_lease_get_timezone(lease, &str), ENODATA);
|
||||
|
||||
ASSERT_OK(dhcp6_lease_set_timezone(lease, (const uint8_t *) "UTC", 3));
|
||||
ASSERT_OK(sd_dhcp6_lease_get_timezone(lease, &str));
|
||||
ASSERT_STREQ(str, "UTC");
|
||||
}
|
||||
|
||||
TEST(client_parse_message_issue_22099) {
|
||||
static const uint8_t msg[] = {
|
||||
/* Message type */
|
||||
@@ -782,6 +801,9 @@ static const uint8_t msg_reply[] = {
|
||||
/* Client FQDN */
|
||||
0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x13,
|
||||
0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
|
||||
/* Time Zone (TZ Database name) */
|
||||
0x00, SD_DHCP6_OPTION_TZDB_TIMEZONE, 0x00, 0x03,
|
||||
'U', 'T', 'C',
|
||||
/* Vendor specific options */
|
||||
0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09,
|
||||
0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES,
|
||||
@@ -871,6 +893,9 @@ static const uint8_t msg_advertise[] = {
|
||||
/* Client FQDN */
|
||||
0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x13,
|
||||
0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
|
||||
/* Time Zone (TZ Database name) */
|
||||
0x00, SD_DHCP6_OPTION_TZDB_TIMEZONE, 0x00, 0x03,
|
||||
'U', 'T', 'C',
|
||||
/* Vendor specific options */
|
||||
0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09,
|
||||
0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES,
|
||||
@@ -970,6 +995,9 @@ static void test_lease_common(sd_dhcp6_client *client) {
|
||||
ASSERT_OK(sd_dhcp6_lease_get_fqdn(lease, &str));
|
||||
ASSERT_STREQ(str, "client.lab.intra");
|
||||
|
||||
ASSERT_OK(sd_dhcp6_lease_get_timezone(lease, &str));
|
||||
ASSERT_STREQ(str, "UTC");
|
||||
|
||||
ASSERT_OK_EQ(sd_dhcp6_lease_get_dns(lease, &addrs), 2);
|
||||
ASSERT_TRUE(in6_addr_equal(&addrs[0], &dns1));
|
||||
ASSERT_TRUE(in6_addr_equal(&addrs[1], &dns2));
|
||||
|
||||
@@ -311,6 +311,29 @@ static int dhcp6_request_hostname(Link *link) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dhcp6_request_timezone(Link *link) {
|
||||
const char *tz;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(link->network);
|
||||
|
||||
if (!link->network->dhcp6_use_timezone)
|
||||
return 0;
|
||||
|
||||
r = sd_dhcp6_lease_get_timezone(link->dhcp6_lease, &tz);
|
||||
if (r == -ENODATA)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_set_timezone(link->manager, tz);
|
||||
if (r < 0)
|
||||
log_link_warning_errno(link, r, "Failed to set timezone to '%s', ignoring: %m", tz);
|
||||
|
||||
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;
|
||||
@@ -329,6 +352,10 @@ static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dhcp6_request_timezone(link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dhcp6_address_acquired(link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -370,6 +397,10 @@ static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link)
|
||||
|
||||
unref_and_replace_new_ref(link->dhcp6_lease, lease, sd_dhcp6_lease_ref, sd_dhcp6_lease_unref);
|
||||
|
||||
r = dhcp6_request_timezone(link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
link_dirty(link);
|
||||
return 0;
|
||||
}
|
||||
@@ -690,6 +721,12 @@ static int dhcp6_configure(Link *link) {
|
||||
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request captive portal: %m");
|
||||
}
|
||||
|
||||
if (link->network->dhcp6_use_timezone) {
|
||||
r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_TZDB_TIMEZONE);
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request timezone: %m");
|
||||
}
|
||||
|
||||
if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
|
||||
r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER);
|
||||
if (r < 0)
|
||||
|
||||
@@ -90,6 +90,8 @@ static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_b
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_send_timezone(Manager *m);
|
||||
|
||||
static int on_connected(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
@@ -99,7 +101,7 @@ static int on_connected(sd_bus_message *message, void *userdata, sd_bus_error *r
|
||||
if (m->dynamic_hostname)
|
||||
(void) manager_set_hostname(m, m->dynamic_hostname);
|
||||
if (m->dynamic_timezone)
|
||||
(void) manager_set_timezone(m, m->dynamic_timezone);
|
||||
(void) manager_send_timezone(m);
|
||||
if (m->product_uuid_requested)
|
||||
(void) manager_request_product_uuid(m);
|
||||
|
||||
@@ -1302,6 +1304,7 @@ int manager_set_hostname(Manager *m, const char *hostname) {
|
||||
}
|
||||
|
||||
static int set_timezone_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
|
||||
Manager *manager = ASSERT_PTR(userdata);
|
||||
const sd_bus_error *e;
|
||||
int r;
|
||||
|
||||
@@ -1311,21 +1314,19 @@ static int set_timezone_handler(sd_bus_message *m, void *userdata, sd_bus_error
|
||||
if (e) {
|
||||
r = sd_bus_error_get_errno(e);
|
||||
log_warning_errno(r, "Could not set timezone: %s", bus_error_message(e, r));
|
||||
|
||||
/* Forget the cached zone, so that the next lease retries. */
|
||||
manager->dynamic_timezone = mfree(manager->dynamic_timezone);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int manager_set_timezone(Manager *m, const char *tz) {
|
||||
static int manager_send_timezone(Manager *m) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(tz);
|
||||
|
||||
log_debug("Setting system timezone: '%s'", tz);
|
||||
r = free_and_strdup_warn(&m->dynamic_timezone, tz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
assert(m->dynamic_timezone);
|
||||
|
||||
if (sd_bus_is_ready(m->bus) <= 0) {
|
||||
log_debug("Not connected to system bus, setting system timezone later.");
|
||||
@@ -1340,7 +1341,7 @@ int manager_set_timezone(Manager *m, const char *tz) {
|
||||
set_timezone_handler,
|
||||
m,
|
||||
"sb",
|
||||
tz,
|
||||
m->dynamic_timezone,
|
||||
false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not set timezone: %m");
|
||||
@@ -1348,6 +1349,32 @@ int manager_set_timezone(Manager *m, const char *tz) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_set_timezone(Manager *m, const char *tz) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(tz);
|
||||
|
||||
/* DHCP leases are renewed periodically; do not reset the timezone (and
|
||||
* override a possible manual change) unless it actually changed. */
|
||||
if (streq_ptr(m->dynamic_timezone, tz))
|
||||
return 0;
|
||||
|
||||
log_debug("Setting system timezone: '%s'", tz);
|
||||
r = free_and_strdup_warn(&m->dynamic_timezone, tz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = manager_send_timezone(m);
|
||||
if (r < 0) {
|
||||
/* Forget the cached zone, so that the next lease retries. */
|
||||
m->dynamic_timezone = mfree(m->dynamic_timezone);
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_reload(Manager *m, sd_bus_message *message, sd_varlink *varlink, bool reconfigure_links) {
|
||||
Link *link;
|
||||
int r;
|
||||
|
||||
@@ -318,6 +318,7 @@ DHCPv6.UseDomains, config_parse_use_domains,
|
||||
DHCPv6.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp6_use_ntp)
|
||||
DHCPv6.UseSIP, config_parse_bool, 0, offsetof(Network, dhcp6_use_sip)
|
||||
DHCPv6.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp6_use_captive_portal)
|
||||
DHCPv6.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp6_use_timezone)
|
||||
DHCPv6.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp6_mudurl)
|
||||
DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0
|
||||
DHCPv6.SendHostnameUpdates, config_parse_bool, 0, offsetof(Network, dhcp6_send_hostname_updates)
|
||||
|
||||
@@ -188,6 +188,7 @@ typedef struct Network {
|
||||
int dhcp6_use_ntp;
|
||||
bool dhcp6_use_sip;
|
||||
bool dhcp6_use_captive_portal;
|
||||
bool dhcp6_use_timezone;
|
||||
bool dhcp6_use_rapid_commit;
|
||||
UseDomains dhcp6_use_domains;
|
||||
uint32_t dhcp6_iaid;
|
||||
|
||||
@@ -81,6 +81,7 @@ int sd_dhcp6_lease_get_sip_addrs(sd_dhcp6_lease *lease, const struct in6_addr **
|
||||
int sd_dhcp6_lease_get_sip_domains(sd_dhcp6_lease *lease, char ***ret);
|
||||
int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret);
|
||||
int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret);
|
||||
int sd_dhcp6_lease_get_timezone(sd_dhcp6_lease *lease, const char **ret);
|
||||
int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret);
|
||||
|
||||
_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Match]
|
||||
Name=veth99
|
||||
|
||||
[Network]
|
||||
DHCP=ipv6
|
||||
|
||||
[DHCPv6]
|
||||
UseTimezone=yes
|
||||
@@ -8832,6 +8832,38 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
def tearDown(self):
|
||||
tear_down_common()
|
||||
|
||||
def test_dhcp_client_ipv6_use_timezone(self):
|
||||
copy_network_unit(
|
||||
'25-veth.netdev',
|
||||
'25-dhcp-server-veth-peer.network',
|
||||
'25-dhcp-client-ipv6-timezone.network',
|
||||
)
|
||||
|
||||
# Make sure the zone is not already the one the server will emit,
|
||||
# e.g. left over from test_emit_router_timezone.
|
||||
timedatectl('set-timezone', 'UTC')
|
||||
|
||||
start_networkd()
|
||||
self.wait_online('veth-peer:carrier')
|
||||
|
||||
start_dnsmasq(
|
||||
'--dhcp-option=option6:42,Europe/Berlin',
|
||||
ra_mode='ra-stateless',
|
||||
)
|
||||
self.wait_online('veth99:routable', 'veth-peer:routable')
|
||||
|
||||
# The DHCPv6 REPLY for INFORMATION-REQUEST may be received after the
|
||||
# link entered the configured state, and the timezone is applied
|
||||
# through an asynchronous call to timedated. Wait for it.
|
||||
tz = None
|
||||
for _ in range(100):
|
||||
r = run(*timedatectl_cmd, 'show', '--value', '--property', 'Timezone', env=env)
|
||||
tz = r.stdout.rstrip()
|
||||
if tz == 'Europe/Berlin':
|
||||
break
|
||||
time.sleep(0.2)
|
||||
self.assertEqual(tz, 'Europe/Berlin')
|
||||
|
||||
def test_dhcp_client_ipv6_only(self):
|
||||
copy_network_unit(
|
||||
'25-veth.netdev',
|
||||
|
||||
Reference in new issue
Block a user