From: David Härdeman Date: Fri, 7 Nov 2025 15:43:26 +0000 (+0100) Subject: dhcpv6-ia: split statefile handling to separate file X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=7136fbe390a567b08324c9d9978d32ad1ea596a7;p=project%2Fodhcpd.git dhcpv6-ia: split statefile handling to separate file 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 Link: https://github.com/openwrt/odhcpd/pull/302 Signed-off-by: Álvaro Fernández Rojas --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bb5a8e..34d6ea4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/src/dhcpv4.c b/src/dhcpv4.c index 73bb66c..96e18a1 100644 --- a/src/dhcpv4.c +++ b/src/dhcpv4.c @@ -35,6 +35,7 @@ #include "odhcpd.h" #include "dhcpv4.h" #include "dhcpv6.h" +#include "statefiles.h" #define MAX_PREFIX_LEN 28 diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index fdddbec..2a1c079 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -16,28 +16,21 @@ #include "odhcpd.h" #include "dhcpv6.h" #include "dhcpv4.h" +#include "dhcpv6-ia.h" +#include "statefiles.h" #include -#include #include #include -#include -#include #include -#include #include #include #include -#include #include #include #include -#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 index 0000000..e061804 --- /dev/null +++ b/src/dhcpv6-ia.h @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2013 Steven Barth + * Copyright (C) 2016 Hans Dedecker + * + * 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_ */ diff --git a/src/odhcpd.h b/src/odhcpd.h index e8cb6dd..d13d078 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -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 index 0000000..0dedbca --- /dev/null +++ b/src/statefiles.c @@ -0,0 +1,400 @@ +/* + * SPDX-FileCopyrightText: 2013 Steven Barth + * SPDX-FileCopyrightText: 2013 Hans Dedecker + * SPDX-FileCopyrightText: 2022 Kevin Darbyshire-Bryant + * SPDX-FileCopyrightText: 2024 Paul Donald + * SPDX-FileCopyrightText: 2024 David Härdeman + * + * SPDX-License-Identifier: GPL2.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 index 0000000..6a06ec2 --- /dev/null +++ b/src/statefiles.h @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 David Härdeman + * + * 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_ */ diff --git a/src/ubus.c b/src/ubus.c index 4b31e1a..bb1a061 100644 --- a/src/ubus.c +++ b/src/ubus.c @@ -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;