summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>2023-10-21 19:50:25 +0100
committerKevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>2023-10-23 16:27:10 +0100
commit4bbc6e74248feeb756bd03dc500fb4f446ccfc49 (patch)
tree05ac6873b732d897f760e69f09c30250fce6bd37
parentc9e619f015fdcc520c850f8a16e5046fea9660da (diff)
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 <ldir@darbyshire-bryant.me.uk>
-rw-r--r--README1
-rw-r--r--src/config.c9
-rw-r--r--src/dhcpv6-ia.c120
-rw-r--r--src/odhcpd.h1
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;
};