dhcpv6-ia: split statefile handling to separate file
authorDavid Härdeman <[email protected]>
Fri, 7 Nov 2025 15:43:26 +0000 (16:43 +0100)
committerÁlvaro Fernández Rojas <[email protected]>
Tue, 11 Nov 2025 07:28:22 +0000 (08:28 +0100)
dhcpv6-ia.c is already quite long, which makes it harder to work with.
In addition, the whole statefile machinery isn't specific to the dhcpv6
server, but shared by the dhcpv4 and dhcpv6 servers, so split it out to
its own file.

This is in preparation for some more hacking on the statefile handling.

The copyright headers on statefiles.[ch] are the result of some git
archeology, and I've taken the liberty of putting them in REUSE format,
using only the initial copyright year for each contributor (one of the
recommendations from the REUSE project).

Signed-off-by: David Härdeman <[email protected]>
Link: https://github.com/openwrt/odhcpd/pull/302
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
CMakeLists.txt
src/dhcpv4.c
src/dhcpv6-ia.c
src/dhcpv6-ia.h [new file with mode: 0644]
src/odhcpd.h
src/statefiles.c [new file with mode: 0644]
src/statefiles.h [new file with mode: 0644]
src/ubus.c

index 1bb5a8e68f9eff8839a5e820c4a5728e618e0266..34d6ea4a2cc767c40605a4cf346d3c007e7fa60c 100644 (file)
@@ -13,6 +13,7 @@ target_sources(${PROJECT_NAME} PRIVATE
        src/dhcpv6-pxe.c
        src/ndp.c
        src/netlink.c
+       src/statefiles.c
        src/router.c
 )
 
index 73bb66ce654498b7c2b7bd26d3e7e4f64c700276..96e18a19d0e21b837f97ba784c807cec3125642c 100644 (file)
@@ -35,6 +35,7 @@
 #include "odhcpd.h"
 #include "dhcpv4.h"
 #include "dhcpv6.h"
+#include "statefiles.h"
 
 #define MAX_PREFIX_LEN 28
 
index fdddbecdf7df97dc92662669e824df16cbf3655a..2a1c079bc296c834e94dd1c3fe0f0357b3607ef6 100644 (file)
 #include "odhcpd.h"
 #include "dhcpv6.h"
 #include "dhcpv4.h"
+#include "dhcpv6-ia.h"
+#include "statefiles.h"
 
 #include <time.h>
-#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
-#include <poll.h>
-#include <alloca.h>
 #include <resolv.h>
-#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <libgen.h>
 #include <stdbool.h>
 #include <arpa/inet.h>
 
 #include <libubox/md5.h>
 
-#define ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs) \
-    ((iface)->dhcpv6_assignall || (i) == (m) || \
-     (addrs)[(i)].prefix > 64)
-
 static void dhcpv6_netevent_cb(unsigned long event, struct netevent_handler_info *info);
 static void apply_lease(struct dhcpv6_lease *a, bool add);
 static void set_border_assignment_size(struct interface *iface, struct dhcpv6_lease *b);
@@ -49,7 +42,6 @@ static void valid_until_cb(struct uloop_timeout *event);
 static struct netevent_handler dhcpv6_netevent_handler = { .cb = dhcpv6_netevent_cb, };
 static struct uloop_timeout valid_until_timeout = {.cb = valid_until_cb};
 static uint32_t serial = 0;
-static uint8_t statemd5[16];
 
 static struct dhcpv6_lease *
 dhcpv6_alloc_lease(size_t extra_len)
@@ -139,18 +131,7 @@ static void dhcpv6_netevent_cb(unsigned long event, struct netevent_handler_info
        }
 }
 
-
-static inline bool valid_prefix_length(const struct dhcpv6_lease *a, const uint8_t prefix_length)
-{
-       return a->length > prefix_length;
-}
-
-static inline bool valid_addr(const struct odhcpd_ipaddr *addr, time_t now)
-{
-       return (addr->prefix <= 96 && addr->valid_lt > (uint32_t)now && addr->preferred_lt > (uint32_t)now);
-}
-
-static size_t get_preferred_addr(const struct odhcpd_ipaddr *addrs, const size_t addrlen)
+size_t get_preferred_addr(const struct odhcpd_ipaddr *addrs, const size_t addrlen)
 {
        size_t i, m;
 
@@ -286,7 +267,7 @@ static void in6_copy_iid(struct in6_addr *dest, uint64_t iid, unsigned n)
        }
 }
 
-static struct in6_addr in6_from_prefix_and_iid(const struct odhcpd_ipaddr *prefix, uint64_t iid)
+struct in6_addr in6_from_prefix_and_iid(const struct odhcpd_ipaddr *prefix, uint64_t iid)
 {
        struct in6_addr addr;
        uint8_t iid_len = min(128 - prefix->prefix, 64);
@@ -297,374 +278,6 @@ static struct in6_addr in6_from_prefix_and_iid(const struct odhcpd_ipaddr *prefi
        return addr;
 }
 
-void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcpv6_lease *c,
-                         time_t now, dhcpv6_binding_cb_handler_t func, void *arg)
-{
-       struct odhcpd_ipaddr *addrs = iface->addr6;
-       size_t m = get_preferred_addr(addrs, iface->addr6_len);
-
-       for (size_t i = 0; i < iface->addr6_len; ++i) {
-               struct in6_addr addr;
-               uint32_t preferred_lt, valid_lt;
-               int prefix = c->length;
-
-               if (!valid_addr(&addrs[i], now))
-                       continue;
-
-               /* Filter Out Prefixes */
-               if (ADDR_MATCH_PIO_FILTER(&addrs[i], iface)) {
-                       char addrbuf[INET6_ADDRSTRLEN];
-                       info("Address %s filtered out on %s",
-                            inet_ntop(AF_INET6, &addrs[i].addr.in6, addrbuf, sizeof(addrbuf)),
-                            iface->name);
-                       continue;
-               }
-
-               if (c->flags & OAF_DHCPV6_NA) {
-                       if (!ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs))
-                               continue;
-
-                       addr = in6_from_prefix_and_iid(&addrs[i], c->assigned_host_id);
-               } else {
-                       if (!valid_prefix_length(c, addrs[i].prefix))
-                               continue;
-
-                       addr = addrs[i].addr.in6;
-                       addr.s6_addr32[1] |= htonl(c->assigned_subnet_id);
-                       addr.s6_addr32[2] = addr.s6_addr32[3] = 0;
-               }
-
-               preferred_lt = addrs[i].preferred_lt;
-               if (preferred_lt > (uint32_t)c->preferred_until)
-                       preferred_lt = c->preferred_until;
-
-               if (preferred_lt > (uint32_t)c->valid_until)
-                       preferred_lt = c->valid_until;
-
-               if (preferred_lt != UINT32_MAX)
-                       preferred_lt -= now;
-
-               valid_lt = addrs[i].valid_lt;
-               if (valid_lt > (uint32_t)c->valid_until)
-                       valid_lt = c->valid_until;
-
-               if (valid_lt != UINT32_MAX)
-                       valid_lt -= now;
-
-               func(&addr, prefix, preferred_lt, valid_lt, arg);
-       }
-}
-
-struct write_ctxt {
-       FILE *fp;
-       md5_ctx_t md5;
-       struct dhcpv6_lease *c;
-       struct interface *iface;
-       char *buf;
-       int buf_len;
-       int buf_idx;
-};
-
-static void dhcpv6_write_ia_addrhosts(struct in6_addr *addr, int prefix, _unused uint32_t pref_lt,
-                               _unused uint32_t valid_lt, 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_lt,
-                               _unused uint32_t valid_lt, void *arg)
-{
-       struct write_ctxt *ctxt = (struct write_ctxt *)arg;
-       char ipbuf[INET6_ADDRSTRLEN];
-
-       inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf) - 1);
-
-       if ((ctxt->c->flags & OAF_DHCPV6_NA) && ctxt->c->hostname &&
-           !(ctxt->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", ctxt->c->hostname, b);
-
-               fprintf(ctxt->fp, "\t%s\n", ctxt->c->hostname);
-               md5_hash(ipbuf, strlen(ipbuf), &ctxt->md5);
-               md5_hash(ctxt->c->hostname, strlen(ctxt->c->hostname), &ctxt->md5);
-       }
-
-       ctxt->buf_idx += snprintf(ctxt->buf + ctxt->buf_idx,ctxt->buf_len - ctxt->buf_idx,
-                                       "%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))
-                                       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 dhcpv4_lease *c;
-
-                       list_for_each_entry(c, &ctxt.iface->dhcpv4_leases, head) {
-                               if (!(c->flags & OAF_BOUND))
-                                       continue;
-
-                               char ipbuf[INET_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;
-
-       md5_begin(&ctxt.md5);
-
-       if (config.dhcp_statefile) {
-               unsigned statefile_strlen = strlen(config.dhcp_statefile) + 1;
-               unsigned tmp_statefile_strlen = statefile_strlen + 1; /* space for . */
-               char *tmp_statefile = alloca(tmp_statefile_strlen);
-
-               char *dir_statefile;
-               char *base_statefile;
-               char *pdir_statefile;
-               char *pbase_statefile;
-
-               time_t now = odhcpd_time(), wall_time = time(NULL);
-               int fd, ret;
-               char leasebuf[512];
-
-               dir_statefile = strndup(config.dhcp_statefile, statefile_strlen);
-               base_statefile = strndup(config.dhcp_statefile, statefile_strlen);
-
-               pdir_statefile = dirname(dir_statefile);
-               pbase_statefile = basename(base_statefile);
-
-               snprintf(tmp_statefile, tmp_statefile_strlen, "%s/.%s", pdir_statefile, pbase_statefile);
-
-               free(dir_statefile);
-               free(base_statefile);
-
-               fd = open(tmp_statefile, 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;
-               }
-
-               ctxt.buf = leasebuf;
-               ctxt.buf_len = sizeof(leasebuf);
-
-               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))
-                                               continue;
-
-                                       char duidbuf[DUID_HEXSTRLEN];
-
-                                       odhcpd_hexlify(duidbuf, ctxt.c->clid_data, ctxt.c->clid_len);
-
-                                       /* iface DUID iaid hostname lifetime assigned_host_id length [addrs...] */
-                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s %x %s%s %"PRId64" ",
-                                                               ctxt.iface->ifname, duidbuf, ntohl(ctxt.c->iaid),
-                                                               (ctxt.c->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
-                                                               (ctxt.c->hostname ? ctxt.c->hostname : "-"),
-                                                               (ctxt.c->valid_until > now ?
-                                                                       (int64_t)(ctxt.c->valid_until - now + wall_time) :
-                                                                       (INFINITE_VALID(ctxt.c->valid_until) ? -1 : 0)));
-
-                                       if (ctxt.c->flags & OAF_DHCPV6_NA)
-                                               ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx, ctxt.buf_len - ctxt.buf_idx,
-                                                                        "%" PRIx64" %u ", ctxt.c->assigned_host_id, (unsigned)ctxt.c->length);
-                                       else
-                                               ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx, ctxt.buf_len - ctxt.buf_idx,
-                                                                        "%" PRIx32" %u ", ctxt.c->assigned_subnet_id, (unsigned)ctxt.c->length);
-
-                                       if (INFINITE_VALID(ctxt.c->valid_until) || ctxt.c->valid_until > now)
-                                               dhcpv6_ia_enum_addrs(ctxt.iface, ctxt.c, now,
-                                                                       dhcpv6_write_ia_addr, &ctxt);
-
-                                       ctxt.buf[ctxt.buf_idx - 1] = '\n';
-                                       fwrite(ctxt.buf, 1, ctxt.buf_idx, ctxt.fp);
-                               }
-                       }
-
-                       if (ctxt.iface->dhcpv4 == MODE_SERVER) {
-                               struct dhcpv4_lease *c;
-
-                               list_for_each_entry(c, &ctxt.iface->dhcpv4_leases, head) {
-                                       if (!(c->flags & OAF_BOUND))
-                                               continue;
-
-                                       char ipbuf[INET6_ADDRSTRLEN];
-                                       char duidbuf[16];
-                                       odhcpd_hexlify(duidbuf, c->hwaddr, sizeof(c->hwaddr));
-
-                                       /* iface DUID iaid hostname lifetime assigned length [addrs...] */
-                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s ipv4 %s%s %"PRId64" %x 32 ",
-                                                               ctxt.iface->ifname, duidbuf,
-                                                               (c->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
-                                                               (c->hostname ? c->hostname : "-"),
-                                                               (c->valid_until > now ?
-                                                                       (int64_t)(c->valid_until - now + wall_time) :
-                                                                       (INFINITE_VALID(c->valid_until) ? -1 : 0)),
-                                                               ntohl(c->addr));
-
-                                       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);
-                                               md5_hash(ipbuf, strlen(ipbuf), &ctxt.md5);
-                                               md5_hash(c->hostname, strlen(c->hostname), &ctxt.md5);
-                                       }
-
-                                       ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx,
-                                                                       ctxt.buf_len - ctxt.buf_idx,
-                                                                       "%s/32 ", ipbuf);
-                                       ctxt.buf[ctxt.buf_idx - 1] = '\n';
-                                       fwrite(ctxt.buf, 1, ctxt.buf_idx, ctxt.fp);
-                               }
-                       }
-               }
-
-               fclose(ctxt.fp);
-
-               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));
-
-                       if (config.dhcp_hostsfile)
-                               dhcpv6_ia_write_hostsfile(now);
-
-                       if (config.dhcp_cb) {
-                               char *argv[2] = {config.dhcp_cb, NULL};
-                               if (!vfork()) {
-                                       execv(argv[0], argv);
-                                       _exit(128);
-                               }
-                       }
-               }
-       }
-}
-
 static void __apply_lease(struct dhcpv6_lease *a,
                struct odhcpd_ipaddr *addrs, ssize_t addr_len, bool add)
 {
diff --git a/src/dhcpv6-ia.h b/src/dhcpv6-ia.h
new file mode 100644 (file)
index 0000000..e061804
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2013 Steven Barth <[email protected]>
+ * Copyright (C) 2016 Hans Dedecker <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _DHCPV6_IA_H_
+#define _DHCPV6_IA_H_
+
+#define ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs) \
+    ((iface)->dhcpv6_assignall || (i) == (m) || \
+     (addrs)[(i)].prefix > 64)
+
+size_t get_preferred_addr(const struct odhcpd_ipaddr *addrs, const size_t addrlen);
+
+struct in6_addr in6_from_prefix_and_iid(const struct odhcpd_ipaddr *prefix, uint64_t iid);
+
+static inline bool valid_prefix_length(const struct dhcpv6_lease *a, const uint8_t prefix_length)
+{
+       return a->length > prefix_length;
+}
+
+static inline bool valid_addr(const struct odhcpd_ipaddr *addr, time_t now)
+{
+       return (addr->prefix <= 96 && addr->valid_lt > (uint32_t)now && addr->preferred_lt > (uint32_t)now);
+}
+
+#endif /* _DHCPV6_IA_H_ */
index e8cb6ddf30b69e2f141c5e020aca96c54ebd7ba0..d13d0782f5feef68e1cfc121e29c8b601646beef 100644 (file)
@@ -584,9 +584,6 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
 int dhcpv6_ia_init(void);
 int dhcpv6_ia_setup_interface(struct interface *iface, bool enable);
 void dhcpv6_free_lease(struct dhcpv6_lease *lease);
-void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcpv6_lease *lease,
-                         time_t now, dhcpv6_binding_cb_handler_t func, void *arg);
-void dhcpv6_ia_write_statefile(void);
 
 int netlink_add_netevent_handler(struct netevent_handler *hdlr);
 ssize_t netlink_get_interface_addrs(const int ifindex, bool v6,
diff --git a/src/statefiles.c b/src/statefiles.c
new file mode 100644 (file)
index 0000000..0dedbca
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ * SPDX-FileCopyrightText: 2013 Steven Barth <[email protected]>
+ * SPDX-FileCopyrightText: 2013 Hans Dedecker <[email protected]>
+ * SPDX-FileCopyrightText: 2022 Kevin Darbyshire-Bryant <[email protected]>
+ * SPDX-FileCopyrightText: 2024 Paul Donald <[email protected]>
+ * SPDX-FileCopyrightText: 2024 David Härdeman <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL2.0-only
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <arpa/inet.h>
+#include <resolv.h>
+
+#include <libubox/md5.h>
+
+#include "odhcpd.h"
+#include "dhcpv6-ia.h"
+#include "statefiles.h"
+
+static uint8_t statemd5[16];
+
+void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcpv6_lease *c,
+                         time_t now, dhcpv6_binding_cb_handler_t func, void *arg)
+{
+       struct odhcpd_ipaddr *addrs = iface->addr6;
+       size_t m = get_preferred_addr(addrs, iface->addr6_len);
+
+       for (size_t i = 0; i < iface->addr6_len; ++i) {
+               struct in6_addr addr;
+               uint32_t preferred_lt, valid_lt;
+               int prefix = c->length;
+
+               if (!valid_addr(&addrs[i], now))
+                       continue;
+
+               /* Filter Out Prefixes */
+               if (ADDR_MATCH_PIO_FILTER(&addrs[i], iface)) {
+                       char addrbuf[INET6_ADDRSTRLEN];
+                       info("Address %s filtered out on %s",
+                            inet_ntop(AF_INET6, &addrs[i].addr.in6, addrbuf, sizeof(addrbuf)),
+                            iface->name);
+                       continue;
+               }
+
+               if (c->flags & OAF_DHCPV6_NA) {
+                       if (!ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs))
+                               continue;
+
+                       addr = in6_from_prefix_and_iid(&addrs[i], c->assigned_host_id);
+               } else {
+                       if (!valid_prefix_length(c, addrs[i].prefix))
+                               continue;
+
+                       addr = addrs[i].addr.in6;
+                       addr.s6_addr32[1] |= htonl(c->assigned_subnet_id);
+                       addr.s6_addr32[2] = addr.s6_addr32[3] = 0;
+               }
+
+               preferred_lt = addrs[i].preferred_lt;
+               if (preferred_lt > (uint32_t)c->preferred_until)
+                       preferred_lt = c->preferred_until;
+
+               if (preferred_lt > (uint32_t)c->valid_until)
+                       preferred_lt = c->valid_until;
+
+               if (preferred_lt != UINT32_MAX)
+                       preferred_lt -= now;
+
+               valid_lt = addrs[i].valid_lt;
+               if (valid_lt > (uint32_t)c->valid_until)
+                       valid_lt = c->valid_until;
+
+               if (valid_lt != UINT32_MAX)
+                       valid_lt -= now;
+
+               func(&addr, prefix, preferred_lt, valid_lt, arg);
+       }
+}
+
+struct write_ctxt {
+       FILE *fp;
+       md5_ctx_t md5;
+       struct dhcpv6_lease *c;
+       struct interface *iface;
+       char *buf;
+       int buf_len;
+       int buf_idx;
+};
+
+static void dhcpv6_write_ia_addrhosts(struct in6_addr *addr, int prefix, _unused uint32_t pref_lt,
+                               _unused uint32_t valid_lt, 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_lt,
+                               _unused uint32_t valid_lt, void *arg)
+{
+       struct write_ctxt *ctxt = (struct write_ctxt *)arg;
+       char ipbuf[INET6_ADDRSTRLEN];
+
+       inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf) - 1);
+
+       if ((ctxt->c->flags & OAF_DHCPV6_NA) && ctxt->c->hostname &&
+           !(ctxt->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", ctxt->c->hostname, b);
+
+               fprintf(ctxt->fp, "\t%s\n", ctxt->c->hostname);
+               md5_hash(ipbuf, strlen(ipbuf), &ctxt->md5);
+               md5_hash(ctxt->c->hostname, strlen(ctxt->c->hostname), &ctxt->md5);
+       }
+
+       ctxt->buf_idx += snprintf(ctxt->buf + ctxt->buf_idx,ctxt->buf_len - ctxt->buf_idx,
+                                       "%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))
+                                       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 dhcpv4_lease *c;
+
+                       list_for_each_entry(c, &ctxt.iface->dhcpv4_leases, head) {
+                               if (!(c->flags & OAF_BOUND))
+                                       continue;
+
+                               char ipbuf[INET_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;
+
+       md5_begin(&ctxt.md5);
+
+       if (config.dhcp_statefile) {
+               unsigned statefile_strlen = strlen(config.dhcp_statefile) + 1;
+               unsigned tmp_statefile_strlen = statefile_strlen + 1; /* space for . */
+               char *tmp_statefile = alloca(tmp_statefile_strlen);
+
+               char *dir_statefile;
+               char *base_statefile;
+               char *pdir_statefile;
+               char *pbase_statefile;
+
+               time_t now = odhcpd_time(), wall_time = time(NULL);
+               int fd, ret;
+               char leasebuf[512];
+
+               dir_statefile = strndup(config.dhcp_statefile, statefile_strlen);
+               base_statefile = strndup(config.dhcp_statefile, statefile_strlen);
+
+               pdir_statefile = dirname(dir_statefile);
+               pbase_statefile = basename(base_statefile);
+
+               snprintf(tmp_statefile, tmp_statefile_strlen, "%s/.%s", pdir_statefile, pbase_statefile);
+
+               free(dir_statefile);
+               free(base_statefile);
+
+               fd = open(tmp_statefile, 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;
+               }
+
+               ctxt.buf = leasebuf;
+               ctxt.buf_len = sizeof(leasebuf);
+
+               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))
+                                               continue;
+
+                                       char duidbuf[DUID_HEXSTRLEN];
+
+                                       odhcpd_hexlify(duidbuf, ctxt.c->clid_data, ctxt.c->clid_len);
+
+                                       /* iface DUID iaid hostname lifetime assigned_host_id length [addrs...] */
+                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s %x %s%s %"PRId64" ",
+                                                               ctxt.iface->ifname, duidbuf, ntohl(ctxt.c->iaid),
+                                                               (ctxt.c->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
+                                                               (ctxt.c->hostname ? ctxt.c->hostname : "-"),
+                                                               (ctxt.c->valid_until > now ?
+                                                                       (int64_t)(ctxt.c->valid_until - now + wall_time) :
+                                                                       (INFINITE_VALID(ctxt.c->valid_until) ? -1 : 0)));
+
+                                       if (ctxt.c->flags & OAF_DHCPV6_NA)
+                                               ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx, ctxt.buf_len - ctxt.buf_idx,
+                                                                        "%" PRIx64" %u ", ctxt.c->assigned_host_id, (unsigned)ctxt.c->length);
+                                       else
+                                               ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx, ctxt.buf_len - ctxt.buf_idx,
+                                                                        "%" PRIx32" %u ", ctxt.c->assigned_subnet_id, (unsigned)ctxt.c->length);
+
+                                       if (INFINITE_VALID(ctxt.c->valid_until) || ctxt.c->valid_until > now)
+                                               dhcpv6_ia_enum_addrs(ctxt.iface, ctxt.c, now,
+                                                                       dhcpv6_write_ia_addr, &ctxt);
+
+                                       ctxt.buf[ctxt.buf_idx - 1] = '\n';
+                                       fwrite(ctxt.buf, 1, ctxt.buf_idx, ctxt.fp);
+                               }
+                       }
+
+                       if (ctxt.iface->dhcpv4 == MODE_SERVER) {
+                               struct dhcpv4_lease *c;
+
+                               list_for_each_entry(c, &ctxt.iface->dhcpv4_leases, head) {
+                                       if (!(c->flags & OAF_BOUND))
+                                               continue;
+
+                                       char ipbuf[INET6_ADDRSTRLEN];
+                                       char duidbuf[16];
+                                       odhcpd_hexlify(duidbuf, c->hwaddr, sizeof(c->hwaddr));
+
+                                       /* iface DUID iaid hostname lifetime assigned length [addrs...] */
+                                       ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s ipv4 %s%s %"PRId64" %x 32 ",
+                                                               ctxt.iface->ifname, duidbuf,
+                                                               (c->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
+                                                               (c->hostname ? c->hostname : "-"),
+                                                               (c->valid_until > now ?
+                                                                       (int64_t)(c->valid_until - now + wall_time) :
+                                                                       (INFINITE_VALID(c->valid_until) ? -1 : 0)),
+                                                               ntohl(c->addr));
+
+                                       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);
+                                               md5_hash(ipbuf, strlen(ipbuf), &ctxt.md5);
+                                               md5_hash(c->hostname, strlen(c->hostname), &ctxt.md5);
+                                       }
+
+                                       ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx,
+                                                                       ctxt.buf_len - ctxt.buf_idx,
+                                                                       "%s/32 ", ipbuf);
+                                       ctxt.buf[ctxt.buf_idx - 1] = '\n';
+                                       fwrite(ctxt.buf, 1, ctxt.buf_idx, ctxt.fp);
+                               }
+                       }
+               }
+
+               fclose(ctxt.fp);
+
+               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));
+
+                       if (config.dhcp_hostsfile)
+                               dhcpv6_ia_write_hostsfile(now);
+
+                       if (config.dhcp_cb) {
+                               char *argv[2] = {config.dhcp_cb, NULL};
+                               if (!vfork()) {
+                                       execv(argv[0], argv);
+                                       _exit(128);
+                               }
+                       }
+               }
+       }
+}
+
diff --git a/src/statefiles.h b/src/statefiles.h
new file mode 100644 (file)
index 0000000..6a06ec2
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: 2024 David Härdeman <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL2.0-only
+ */
+
+#ifndef _STATEFILES_H_
+#define _STATEFILES_H_
+
+void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcpv6_lease *lease,
+                         time_t now, dhcpv6_binding_cb_handler_t func, void *arg);
+
+void dhcpv6_ia_write_statefile(void);
+
+#endif /* _STATEFILES_H_ */
index 4b31e1acd72ce0c2cac08f809bdec5a539734c21..bb1a0612f459ed29927fcef38be5c0ea5b901353 100644 (file)
@@ -9,6 +9,7 @@
 #include "odhcpd.h"
 #include "dhcpv6.h"
 #include "dhcpv4.h"
+#include "statefiles.h"
 
 static struct ubus_context *ubus = NULL;
 static struct ubus_subscriber netifd;