| 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)
| 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:
#include <net/if.h>
#include <string.h>
#include <sys/stat.h>
+#include <ctype.h>
#include <uci.h>
#include <uci_blob.h>
AVL_TREE(interfaces, avl_strcmp, false, NULL);
struct config config = {
.legacy = false,
+ .enable_tz = true,
.main_dhcpv4 = false,
.dhcp_cb = NULL,
.dhcp_statefile = NULL,
.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
ODHCPD_ATTR_LOGLEVEL,
ODHCPD_ATTR_HOSTSFILE,
ODHCPD_ATTR_PIOFOLDER,
+ ODHCPD_ATTR_ENABLE_TZ,
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 = {
.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 },
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) {
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);
}
uci_unload(uci, dhcp);
+ uci_unload(uci, system);
uci_free_context(uci);
}
IOV_DHCPV4O6_SERVER,
IOV_DNR,
IOV_BOOTFILE_URL,
+ IOV_POSIX_TZ,
+ IOV_POSIX_TZ_STR,
+ IOV_TZDB_TZ,
+ IOV_TZDB_TZ_STR,
IOV_TOTAL
};
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;
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];
[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},
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));
#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
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__)
struct config {
bool legacy;
+ bool enable_tz;
bool main_dhcpv4;
char *dhcp_cb;
char *dhcp_statefile;
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)