From: Paul Donald Date: Wed, 22 Oct 2025 00:17:06 +0000 (+0200) Subject: dhcpv6: RFC4833 timezones X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=7956f4271b4e;p=project%2Fodhcpd.git dhcpv6: RFC4833 timezones This implements RFC4833 - supplying timezone information to clients that request them. Both forms are possible, when timezone is configured in the uci system settings (the luci GUI saves both forms to the config). e.g. ``` config system option zonename 'America/Puerto Rico' option timezone 'AST4' ``` There is also an odhcpd flag to disable their use, set in uci dhcp. ``` config odhcpd 'odhcpd' option enable_tzdb '0' ``` Once enabled, the options, when requested, are sent: NEW_POSIX_TIMEZONE 41 // 'AST4' NEW_TZDB_TIMEZONE 42 // 'America/Puerto_Rico' Wireshark disassemble of options sent to client: ``` ... Time Zone Database Option: Time Zone Database (42) Length: 19 TZ-database: America/Puerto_Rico Time Zone Option: Time Zone (41) Length: 4 Time-zone: AST4 ``` Signed-off-by: Paul Donald Link: https://github.com/openwrt/odhcpd/pull/284 Signed-off-by: Álvaro Fernández Rojas --- diff --git a/README.md b/README.md index cbea25f..65787d7 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ and may also receive information from ubus | hostsfile | string| | DHCP/v6 hostfile | | loglevel |integer| 6 | Syslog level priority (0-7) | | piofolder |string | | Folder to store IPv6 prefix information (to detect stale prefixes, see RFC9096, §3.5) | +| enable_tz |bool | 1 | Toggle whether RFC4833 timezone information is sent to clients, if set in system | ### Sections of type dhcp (configure DHCP / DHCPv6 / RA / NDP service) @@ -138,6 +139,13 @@ and may also receive information from ubus | arch |integer| no | the arch code. `07` is EFI. If not present, this boot6 will be the default. | +### System variables for Timezone options (uci system.system) +| Option | Type |Required|Description | +| :------------ | :---- | :---- | :---------- | +| timezone |string | no | e.g. `EST5EDT4,M3.2.0/02:00,M11.1.0/02:00` | +| zonename |string| no | e.g. `Europe/Zurich` | + + ## ubus Interface odhcpd currently exposes the following methods under the `dhcp` object path: diff --git a/src/config.c b/src/config.c index ddc4537..6a33b7c 100644 --- a/src/config.c +++ b/src/config.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ struct vlist_tree leases = VLIST_TREE_INIT(leases, lease_cmp, lease_update, true AVL_TREE(interfaces, avl_strcmp, false, NULL); struct config config = { .legacy = false, + .enable_tz = true, .main_dhcpv4 = false, .dhcp_cb = NULL, .dhcp_statefile = NULL, @@ -45,6 +47,14 @@ struct config config = { .log_syslog = true, }; +struct sys_conf sys_conf = { + .uci_cfgfile = "system", + .posix_tz = NULL, // "timezone" + .posix_tz_len = 0, + .tzdb_tz = NULL, // "zonename" + .tzdb_tz_len = 0, +}; + #define START_DEFAULT 100 #define LIMIT_DEFAULT 150 @@ -215,6 +225,7 @@ enum { ODHCPD_ATTR_LOGLEVEL, ODHCPD_ATTR_HOSTSFILE, ODHCPD_ATTR_PIOFOLDER, + ODHCPD_ATTR_ENABLE_TZ, ODHCPD_ATTR_MAX }; @@ -226,6 +237,7 @@ static const struct blobmsg_policy odhcpd_attrs[ODHCPD_ATTR_MAX] = { [ODHCPD_ATTR_LOGLEVEL] = { .name = "loglevel", .type = BLOBMSG_TYPE_INT32 }, [ODHCPD_ATTR_HOSTSFILE] = { .name = "hostsfile", .type = BLOBMSG_TYPE_STRING }, [ODHCPD_ATTR_PIOFOLDER] = { .name = "piofolder", .type = BLOBMSG_TYPE_STRING }, + [ODHCPD_ATTR_ENABLE_TZ] = { .name = "enable_tz", .type = BLOBMSG_TYPE_BOOL }, }; const struct uci_blob_param_list odhcpd_attr_list = { @@ -233,6 +245,22 @@ const struct uci_blob_param_list odhcpd_attr_list = { .params = odhcpd_attrs, }; +enum { + SYSTEM_ATTR_TIMEZONE, + SYSTEM_ATTR_ZONENAME, + SYSTEM_ATTR_MAX +}; + +static const struct blobmsg_policy system_attrs[SYSTEM_ATTR_MAX] = { + [SYSTEM_ATTR_TIMEZONE] = { .name = "timezone", .type = BLOBMSG_TYPE_STRING }, + [SYSTEM_ATTR_ZONENAME] = { .name = "zonename", .type = BLOBMSG_TYPE_STRING }, +}; + +const struct uci_blob_param_list system_attr_list = { + .n_params = SYSTEM_ATTR_MAX, + .params = system_attrs, +}; + static const struct { const char *name; uint8_t flag; } ra_flags[] = { { .name = "managed-config", .flag = ND_RA_FLAG_MANAGED }, { .name = "other-config", .flag = ND_RA_FLAG_OTHER }, @@ -432,6 +460,54 @@ static void set_config(struct uci_section *s) notice("Log level set to %d\n", config.log_level); } } + + if ((c = tb[ODHCPD_ATTR_ENABLE_TZ])) + config.enable_tz = blobmsg_get_bool(c); + +} + +static void sanitize_tz_string(const char *src, uint8_t **dst, size_t *dst_len) +{ + /* replace any spaces with '_' in tz strings. luci, where these strings + are normally set, (had a bug that) replaced underscores for spaces in the + names. */ + + if (!dst || !dst_len) + return; + + free(*dst); + *dst = NULL; + *dst_len = 0; + + if (!src || !*src) + return; + + char *copy = strdup(src); + if (!copy) + return; + + for (char *p = copy; *p; p++) { + if (isspace((unsigned char)*p)) + *p = '_'; + } + + *dst = (uint8_t *)copy; + *dst_len = strlen(copy); +} + +static void set_timezone_info_from_uci(struct uci_section *s) +{ + struct blob_attr *tb[SYSTEM_ATTR_MAX], *c; + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, &system_attr_list); + blobmsg_parse(system_attrs, SYSTEM_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head)); + + if ((c = tb[SYSTEM_ATTR_TIMEZONE])) + sanitize_tz_string(blobmsg_get_string(c), &sys_conf.posix_tz, &sys_conf.posix_tz_len); + + if ((c = tb[SYSTEM_ATTR_ZONENAME])) + sanitize_tz_string(blobmsg_get_string(c), &sys_conf.tzdb_tz, &sys_conf.tzdb_tz_len); } static uint32_t parse_leasetime(struct blob_attr *c) { @@ -2193,6 +2269,18 @@ void odhcpd_reload(void) ipv6_pxe_dump(); } + struct uci_package *system = NULL; + if (!uci_load(uci, sys_conf.uci_cfgfile, &system) && config.enable_tz == true) { + struct uci_element *e; + + /* 1. System settings */ + uci_foreach_element(&system->sections, e) { + struct uci_section *s = uci_to_section(e); + if (!strcmp(s->type, "system")) + set_timezone_info_from_uci(s); + } + } + if (config.dhcp_statefile) { char *path = strdup(config.dhcp_statefile); @@ -2287,6 +2375,7 @@ void odhcpd_reload(void) } uci_unload(uci, dhcp); + uci_unload(uci, system); uci_free_context(uci); } diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 8ce1bff..000370f 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -185,6 +185,10 @@ enum { IOV_DHCPV4O6_SERVER, IOV_DNR, IOV_BOOTFILE_URL, + IOV_POSIX_TZ, + IOV_POSIX_TZ_STR, + IOV_TZDB_TZ, + IOV_TZDB_TZ_STR, IOV_TOTAL }; @@ -430,6 +434,29 @@ static void handle_client_request(void *addr, void *data, size_t len, uint16_t len; } dhcpv6_sntp; + /* RFC 4833 - Timezones */ + uint8_t *posix_ptr = sys_conf.posix_tz; + uint16_t posix_len = sys_conf.posix_tz_len; + /* RFC 4833 - OPTION_NEW_POSIX_TIMEZONE (41) + * e.g. EST5EDT4,M3.2.0/02:00,M11.1.0/02:00 + * Variable-length opaque tz_string blob. + */ + struct { + uint16_t type; + uint16_t len; + } posix_tz; + + uint8_t *tzdb_ptr = sys_conf.tzdb_tz; + uint16_t tzdb_len = sys_conf.tzdb_tz_len; + /* RFC 4833 - OPTION_NEW_TZDB_TIMEZONE (42) + * e.g. Europe/Zurich + * Variable-length opaque tz_name blob. + */ + struct { + uint16_t type; + uint16_t len; + } tzdb_tz; + /* NTP */ uint8_t *ntp_ptr = iface->dhcpv6_ntp; uint16_t ntp_len = iface->dhcpv6_ntp_len; @@ -482,6 +509,16 @@ static void handle_client_request(void *addr, void *data, size_t len, ntp.len = htons(ntp_len); break; + case DHCPV6_OPT_NEW_POSIX_TIMEZONE: + posix_tz.type = htons(DHCPV6_OPT_NEW_POSIX_TIMEZONE); + posix_tz.len = htons(posix_len); + break; + + case DHCPV6_OPT_NEW_TZDB_TIMEZONE: + tzdb_tz.type = htons(DHCPV6_OPT_NEW_TZDB_TIMEZONE); + tzdb_tz.len = htons(tzdb_len); + break; + case DHCPV6_OPT_DNR: for (size_t i = 0; i < iface->dnr_cnt; i++) { struct dnr_options *dnr = &iface->dnr[i]; @@ -596,6 +633,10 @@ static void handle_client_request(void *addr, void *data, size_t len, [IOV_NTP_ADDR] = {ntp_ptr, (ntp_cnt) ? ntp_len : 0}, [IOV_SNTP] = {&dhcpv6_sntp, (sntp_cnt) ? sizeof(dhcpv6_sntp) : 0}, [IOV_SNTP_ADDR] = {sntp_addr_ptr, sntp_cnt * sizeof(*sntp_addr_ptr)}, + [IOV_POSIX_TZ] = {&posix_tz, (posix_len) ? sizeof(posix_tz) : 0}, + [IOV_POSIX_TZ_STR] = {posix_ptr, (posix_len) ? posix_len : 0 }, + [IOV_TZDB_TZ] = {&tzdb_tz, (tzdb_len) ? sizeof(tzdb_tz) : 0}, + [IOV_TZDB_TZ_STR] = {tzdb_ptr, (tzdb_len) ? tzdb_len : 0 }, [IOV_DNR] = {dnrs, dnrs_len}, [IOV_RELAY_MSG] = {NULL, 0}, [IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0}, @@ -751,6 +792,8 @@ static void handle_client_request(void *addr, void *data, size_t len, iov[IOV_CERID].iov_len + iov[IOV_DHCPV6_RAW].iov_len + iov[IOV_NTP].iov_len + iov[IOV_NTP_ADDR].iov_len + iov[IOV_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len + + iov[IOV_POSIX_TZ].iov_len + iov[IOV_POSIX_TZ_STR].iov_len + + iov[IOV_TZDB_TZ].iov_len + iov[IOV_TZDB_TZ_STR].iov_len + iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].iov_len - (4 + opts_end - opts)); diff --git a/src/dhcpv6.h b/src/dhcpv6.h index 4220fe2..5c3986c 100644 --- a/src/dhcpv6.h +++ b/src/dhcpv6.h @@ -60,6 +60,10 @@ #define DHCPV6_OPT_SNTP_SERVERS 31 #define DHCPV6_OPT_INFO_REFRESH 32 #define DHCPV6_OPT_FQDN 39 +/* RFC 4833 */ +#define DHCPV6_OPT_NEW_POSIX_TIMEZONE 41 +#define DHCPV6_OPT_NEW_TZDB_TIMEZONE 42 + #define DHCPV6_OPT_NTP_SERVERS 56 #define DHCPV6_OPT_BOOTFILE_URL 59 #define DHCPV6_OPT_BOOTFILE_PARAM 60 diff --git a/src/odhcpd.h b/src/odhcpd.h index 02babc5..693da99 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -73,6 +73,7 @@ struct interface; struct nl_sock; extern struct vlist_tree leases; extern struct config config; +extern struct sys_conf sys_conf; void __iflog(int lvl, const char *fmt, ...); #define debug(fmt, ...) __iflog(LOG_DEBUG, fmt __VA_OPT__(, ) __VA_ARGS__) @@ -184,6 +185,7 @@ enum odhcpd_assignment_flags { struct config { bool legacy; + bool enable_tz; bool main_dhcpv4; char *dhcp_cb; char *dhcp_statefile; @@ -198,6 +200,14 @@ struct config { bool log_syslog; }; +struct sys_conf { + uint8_t *posix_tz; + size_t posix_tz_len; + char *uci_cfgfile; + uint8_t *tzdb_tz; + size_t tzdb_tz_len; +}; + /* 2-byte type + 128-byte DUID, RFC8415, §11.1 */ #define DUID_MAX_LEN 130 #define DUID_HEXSTRLEN (DUID_MAX_LEN * 2 + 1)