add hostsfile output in addition to statefile
authorKevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
Sat, 21 Oct 2023 18:50:25 +0000 (19:50 +0100)
committerKevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
Mon, 23 Oct 2023 15:27:10 +0000 (16:27 +0100)
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>
README
src/config.c
src/dhcpv6-ia.c
src/odhcpd.h

diff --git a/README b/README
index 8f0e6a4336e204ce3c9e6cf8d8e5e08c6ee5b632..243ae2477936c562ac2da969aca735c3bc595f1e 100644 (file)
--- 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)
 
 
index e63181400193f38970c0a5dab6e0620565bda9e2..5ba4d9ecd4dcbfc9e63d1a6ed534c520bfd201ef 100644 (file)
@@ -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));
index 41c9f30fbcc9cb6c7e9cba895626a035569cd4d3..1fbed44e0555d496a638ab6a4db95139605050cd 100644 (file)
@@ -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);
                }
        }
 }
index 08b4920a56e2049de47cae2cb53629e94da1eaf8..02b6ac0f3ed26b16b91cc4a09ff3578d89f9a40d 100644 (file)
@@ -165,6 +165,7 @@ struct config {
        bool main_dhcpv4;
        char *dhcp_cb;
        char *dhcp_statefile;
+       char *dhcp_hostsfile;
        int log_level;
 };