From 4bbc6e74248feeb756bd03dc500fb4f446ccfc49 Mon Sep 17 00:00:00 2001 From: Kevin Darbyshire-Bryant Date: Sat, 21 Oct 2023 19:50:25 +0100 Subject: [PATCH] add hostsfile output in addition to statefile a92c0a7 made the temporary state/leasefile hidden so that an atomic change was made and dnsmasq only saw the new file on rename. A misguided optimisation was made to only rename the temporary file if something had changed. Unfortunately only address and hostnames were considered in the change, lease durations were not. As a result it was possible for LUCI which consumes the state/leasefile to report DHCPv6 leases had expired when they had not. Revert the optimisation so that the file rename occurs irrespective of content change, this keeps LUCI reporting of state/lease expiry correct. This leaves us back with hosts file/dnsmasq update problem. Solve this by writing out a separate hosts file. Update this file using the original IP/Hostname change logic that prompts calling the 'lease' script. odhcpd config now supports a string 'hostsfile' which defines the path and name of the hosts file in an identical manner to 'leasefile'. A state 'leasefile' must be defined IF a 'hostsfile' is also required. eg. leasefile '/tmp/odhcpdstate' hostsfile '/tmp/hosts/odhcpdhosts' Signed-off-by: Kevin Darbyshire-Bryant --- README | 1 + src/config.c | 9 +++- src/dhcpv6-ia.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++-- src/odhcpd.h | 1 + 4 files changed, 127 insertions(+), 4 deletions(-) diff --git a/README b/README index 8f0e6a4..243ae24 100644 --- a/README +++ b/README @@ -64,6 +64,7 @@ maindhcp bool 0 Use odhcpd as the main DHCPv4 service leasefile string DHCP/v6 lease/hostfile leasetrigger string Lease trigger script +hostsfile string DHCP/v6 hostfile loglevel integer 6 Syslog level priority (0-7) diff --git a/src/config.c b/src/config.c index e631814..5ba4d9e 100644 --- a/src/config.c +++ b/src/config.c @@ -29,7 +29,7 @@ static void lease_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_tree leases = VLIST_TREE_INIT(leases, lease_cmp, lease_update, true, false); AVL_TREE(interfaces, avl_strcmp, false, NULL); struct config config = {.legacy = false, .main_dhcpv4 = false, - .dhcp_cb = NULL, .dhcp_statefile = NULL, + .dhcp_cb = NULL, .dhcp_statefile = NULL, .dhcp_hostsfile = NULL, .log_level = LOG_WARNING}; #define START_DEFAULT 100 @@ -180,6 +180,7 @@ enum { ODHCPD_ATTR_LEASEFILE, ODHCPD_ATTR_LEASETRIGGER, ODHCPD_ATTR_LOGLEVEL, + ODHCPD_ATTR_HOSTSFILE, ODHCPD_ATTR_MAX }; @@ -189,6 +190,7 @@ static const struct blobmsg_policy odhcpd_attrs[ODHCPD_ATTR_MAX] = { [ODHCPD_ATTR_LEASEFILE] = { .name = "leasefile", .type = BLOBMSG_TYPE_STRING }, [ODHCPD_ATTR_LEASETRIGGER] = { .name = "leasetrigger", .type = BLOBMSG_TYPE_STRING }, [ODHCPD_ATTR_LOGLEVEL] = { .name = "loglevel", .type = BLOBMSG_TYPE_INT32 }, + [ODHCPD_ATTR_HOSTSFILE] = { .name = "hostsfile", .type = BLOBMSG_TYPE_STRING }, }; const struct uci_blob_param_list odhcpd_attr_list = { @@ -326,6 +328,11 @@ static void set_config(struct uci_section *s) config.dhcp_statefile = strdup(blobmsg_get_string(c)); } + if ((c = tb[ODHCPD_ATTR_HOSTSFILE])) { + free(config.dhcp_hostsfile); + config.dhcp_hostsfile = strdup(blobmsg_get_string(c)); + } + if ((c = tb[ODHCPD_ATTR_LEASETRIGGER])) { free(config.dhcp_cb); config.dhcp_cb = strdup(blobmsg_get_string(c)); diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index 41c9f30..1fbed44 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -288,6 +288,26 @@ struct write_ctxt { int buf_idx; }; +static void dhcpv6_write_ia_addrhosts(struct in6_addr *addr, int prefix, _unused uint32_t pref, + _unused uint32_t valid, void *arg) +{ + struct write_ctxt *ctxt = (struct write_ctxt *)arg; + char ipbuf[INET6_ADDRSTRLEN]; + + if ((ctxt->c->flags & OAF_DHCPV6_NA) && ctxt->c->hostname && + !(ctxt->c->flags & OAF_BROKEN_HOSTNAME)) { + inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf) - 1); + fputs(ipbuf, ctxt->fp); + + char b[256]; + if (dn_expand(ctxt->iface->search, ctxt->iface->search + ctxt->iface->search_len, + ctxt->iface->search, b, sizeof(b)) > 0) + fprintf(ctxt->fp, "\t%s.%s", ctxt->c->hostname, b); + + fprintf(ctxt->fp, "\t%s\n", ctxt->c->hostname); + } +} + static void dhcpv6_write_ia_addr(struct in6_addr *addr, int prefix, _unused uint32_t pref, _unused uint32_t valid, void *arg) { @@ -314,6 +334,98 @@ static void dhcpv6_write_ia_addr(struct in6_addr *addr, int prefix, _unused uint "%s/%d ", ipbuf, prefix); } +static void dhcpv6_ia_write_hostsfile(time_t now) +{ + struct write_ctxt ctxt; + + unsigned hostsfile_strlen = strlen(config.dhcp_hostsfile) + 1; + unsigned tmp_hostsfile_strlen = hostsfile_strlen + 1; /* space for . */ + char *tmp_hostsfile = alloca(tmp_hostsfile_strlen); + + char *dir_hostsfile; + char *base_hostsfile; + char *pdir_hostsfile; + char *pbase_hostsfile; + + int fd, ret; + + dir_hostsfile = strndup(config.dhcp_hostsfile, hostsfile_strlen); + base_hostsfile = strndup(config.dhcp_hostsfile, hostsfile_strlen); + + pdir_hostsfile = dirname(dir_hostsfile); + pbase_hostsfile = basename(base_hostsfile); + + snprintf(tmp_hostsfile, tmp_hostsfile_strlen, "%s/.%s", pdir_hostsfile, pbase_hostsfile); + + free(dir_hostsfile); + free(base_hostsfile); + + fd = open(tmp_hostsfile, O_CREAT | O_WRONLY | O_CLOEXEC, 0644); + if (fd < 0) + return; + + ret = lockf(fd, F_LOCK, 0); + if (ret < 0) { + close(fd); + return; + } + + if (ftruncate(fd, 0) < 0) {} + + ctxt.fp = fdopen(fd, "w"); + if (!ctxt.fp) { + close(fd); + return; + } + + avl_for_each_element(&interfaces, ctxt.iface, avl) { + if (ctxt.iface->dhcpv6 != MODE_SERVER && + ctxt.iface->dhcpv4 != MODE_SERVER) + continue; + + if (ctxt.iface->dhcpv6 == MODE_SERVER) { + list_for_each_entry(ctxt.c, &ctxt.iface->ia_assignments, head) { + if (!(ctxt.c->flags & OAF_BOUND) || ctxt.c->managed_size < 0) + continue; + + if (INFINITE_VALID(ctxt.c->valid_until) || ctxt.c->valid_until > now) + dhcpv6_ia_enum_addrs(ctxt.iface, ctxt.c, now, + dhcpv6_write_ia_addrhosts, &ctxt); + } + } + + if (ctxt.iface->dhcpv4 == MODE_SERVER) { + struct dhcp_assignment *c; + + list_for_each_entry(c, &ctxt.iface->dhcpv4_assignments, head) { + if (!(c->flags & OAF_BOUND)) + continue; + + char ipbuf[INET6_ADDRSTRLEN]; + struct in_addr addr = {.s_addr = c->addr}; + inet_ntop(AF_INET, &addr, ipbuf, sizeof(ipbuf) - 1); + + if (c->hostname && !(c->flags & OAF_BROKEN_HOSTNAME)) { + fputs(ipbuf, ctxt.fp); + + char b[256]; + + if (dn_expand(ctxt.iface->search, + ctxt.iface->search + ctxt.iface->search_len, + ctxt.iface->search, b, sizeof(b)) > 0) + fprintf(ctxt.fp, "\t%s.%s", c->hostname, b); + + fprintf(ctxt.fp, "\t%s\n", c->hostname); + } + } + } + } + + fclose(ctxt.fp); + + rename(tmp_hostsfile, config.dhcp_hostsfile); +} + void dhcpv6_ia_write_statefile(void) { struct write_ctxt ctxt; @@ -457,9 +569,13 @@ void dhcpv6_ia_write_statefile(void) uint8_t newmd5[16]; md5_end(newmd5, &ctxt.md5); + rename(tmp_statefile, config.dhcp_statefile); + if (memcmp(newmd5, statemd5, sizeof(newmd5))) { memcpy(statemd5, newmd5, sizeof(statemd5)); - rename(tmp_statefile, config.dhcp_statefile); + + if (config.dhcp_hostsfile) + dhcpv6_ia_write_hostsfile(now); if (config.dhcp_cb) { char *argv[2] = {config.dhcp_cb, NULL}; @@ -468,8 +584,6 @@ void dhcpv6_ia_write_statefile(void) _exit(128); } } - } else { - unlink(tmp_statefile); } } } diff --git a/src/odhcpd.h b/src/odhcpd.h index 08b4920..02b6ac0 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -165,6 +165,7 @@ struct config { bool main_dhcpv4; char *dhcp_cb; char *dhcp_statefile; + char *dhcp_hostsfile; int log_level; }; -- 2.30.2