From 12d2e981b844484f7c85b2c274d63b48f6882471 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 15:20:06 +0100 Subject: [PATCH 01/16] workflows: limit to read only --- .github/workflows/build.yml | 2 ++ .github/workflows/format.yml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1245a309..33f3166f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,6 @@ name: Build +permissions: + contents: read on: push: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 7203d8b6..fc7ff6b2 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,8 +1,12 @@ name: Clang Format Checker +permissions: + contents: read + on: push: pull_request: branches: [master] + jobs: clang-format-checking: runs-on: ubuntu-latest -- 2.47.3 From b746a5bd82834c5fa225f21535b1b41c5b0c53dd Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 15:47:22 +0100 Subject: [PATCH 02/16] eloop: Fix compile warning where UTIME_MAX is a calculation And does not have correct parenthesis --- src/eloop.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/eloop.c b/src/eloop.c index 4ebe9f58..912742a5 100644 --- a/src/eloop.c +++ b/src/eloop.c @@ -434,9 +434,9 @@ eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp, unsigned long long tsecs, usecs, secs; long nsecs; - tsecs = (unsigned long long)tsp->tv_sec & UTIME_MAX; - usecs = (unsigned long long)usp->tv_sec & UTIME_MAX; - secs = (tsecs - usecs) & UTIME_MAX; + tsecs = (unsigned long long)tsp->tv_sec & (UTIME_MAX); + usecs = (unsigned long long)usp->tv_sec & (UTIME_MAX); + secs = (tsecs - usecs) & (UTIME_MAX); if (secs > TIME_MAX) /* Monotonic clock went backward */ secs = 0; -- 2.47.3 From 2752331fddab0781627d23e2a9a1addf40c30b2d Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 16:24:10 +0100 Subject: [PATCH 03/16] vsio: Allow zero length options The spec allows it and this is an oversight. --- src/if-options.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/if-options.c b/src/if-options.c index 9d644827..0984db91 100644 --- a/src/if-options.c +++ b/src/if-options.c @@ -940,19 +940,18 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, if (fp != NULL) fp = strskipwhite(fp); - if (fp != NULL) - p = strchr(fp, ','); - else - p = NULL; - if (p == NULL || p[1] == '\0') { + if (fp == NULL) { logerrx("invalid vendor format: %s", arg); return -1; } /* Strip and preserve the comma */ - *p = '\0'; + p = strchr(fp, ','); + if (p != NULL) + *p = '\0'; i = (int)strtoi(fp, NULL, 0, 1, (intmax_t)opt_max, &e); - *p = ','; + if (p != NULL) + *p = ','; if (e) { logerrx("vendor option should be between" " 1 and %zu inclusive", @@ -960,9 +959,12 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, return -1; } - fp = p + 1; + if (p != NULL) + fp = p + 1; + else + fp = NULL; - if (fp) { + if (fp != NULL && *fp != '\0') { if (inet_pton(AF_INET, fp, &addr) == 1) { s = sizeof(addr.s_addr); dl = (size_t)s; @@ -988,12 +990,16 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, return -1; } dl = (size_t)s; - np = malloc(dl); - if (np == NULL) { - logerr(__func__); - return -1; + if (dl == 0) + np = NULL; + else { + np = malloc(dl); + if (np == NULL) { + logerr(__func__); + return -1; + } + parse_string(np, dl, fp); } - parse_string(np, dl, fp); } } else { dl = 0; @@ -1023,7 +1029,7 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, sl++, vsio_so++) opt_max -= opt_header + vsio_so->len; if (opt_header + dl > opt_max) { - logerrx("vsio is too big: %s", fp); + logerrx("vsio is too big: %s", arg); free(np); return -1; } -- 2.47.3 From d28c97179f3beabc06577957ce7e1d6644b51fb7 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 18:06:35 +0100 Subject: [PATCH 04/16] DHCP6: Fix configuring the suffix to delegated prefixes (#665) Depending on the delegated prefix length and the user configured suffix length, some parts of the suffix would be lost. Reported by NVIDIA Project Vanessa --- src/ipv6.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipv6.c b/src/ipv6.c index 8670e30f..ee25c40a 100644 --- a/src/ipv6.c +++ b/src/ipv6.c @@ -590,7 +590,7 @@ ipv6_userprefix(const struct in6_addr *prefix, // prefix from router if (prefix_len >= 64) user_high = 0; else - user_high = user_number >> (result_len - prefix_len); + user_high = user_number >> (result_len - 64); user_low = user_number << (128 - result_len); } else if (result_len == 64) { user_high = user_number; -- 2.47.3 From a52bc9f685dca009188d1540089977c21865aeca Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 19:25:48 +0100 Subject: [PATCH 05/16] IPv6: Fix numerous issues extending temporary address times Fix a potential integer underflow if pltime is less than aquisition time. Fix potential time truncation to uint32_t. Reported by NVIDIA Project Vanessa --- src/ipv6.c | 52 +++++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/ipv6.c b/src/ipv6.c index ee25c40a..20caa5e6 100644 --- a/src/ipv6.c +++ b/src/ipv6.c @@ -1985,13 +1985,14 @@ ipv6_settemptime(struct ipv6_addr *ia, int flags) TAILQ_FOREACH_REVERSE(ap, &state->addrs, ipv6_addrhead, next) { if (ap->flags & IPV6_AF_TEMPORARY && ap->prefix_pltime && IN6_ARE_ADDR_EQUAL(&ia->prefix, &ap->prefix)) { - unsigned int max, ext; + unsigned long long elapsed; + uint32_t limit, rmtime; if (flags == 0) { - if (ap->prefix_pltime - - (uint32_t)(ia->acquired.tv_sec - - ap->acquired.tv_sec) < - REGEN_ADVANCE) + elapsed = eloop_timespec_diff(&ia->acquired, + &ap->acquired, NULL); + if (ap->prefix_pltime <= elapsed || + ap->prefix_pltime - elapsed < REGEN_ADVANCE) continue; return ap; @@ -2001,6 +2002,9 @@ ipv6_settemptime(struct ipv6_addr *ia, int flags) ap->flags |= IPV6_AF_NEW | IPV6_AF_AUTOCONF; ap->flags &= ~IPV6_AF_STALE; + elapsed = eloop_timespec_diff(&ia->acquired, + &ap->created, NULL); + /* RFC4941 Section 3.4 * Deprecated prefix, deprecate the temporary address */ if (ia->prefix_pltime == 0) { @@ -2014,26 +2018,28 @@ ipv6_settemptime(struct ipv6_addr *ia, int flags) /* RFC4941 Section 3.3.2 * Extend temporary times, but ensure that they * never last beyond the system limit. */ - ext = (unsigned int)ia->acquired.tv_sec + - ia->prefix_pltime; - max = (unsigned int)(ap->created.tv_sec + - TEMP_PREFERRED_LIFETIME - state->desync_factor); - if (ext < max) - ap->prefix_pltime = ia->prefix_pltime; - else - ap->prefix_pltime = (uint32_t)(max - - ia->acquired.tv_sec); + limit = TEMP_PREFERRED_LIFETIME - state->desync_factor; + if (elapsed >= limit) + ap->prefix_pltime = 0; + else { + rmtime = (uint32_t)(limit - elapsed); + if (ia->prefix_pltime < rmtime) + ap->prefix_pltime = ia->prefix_pltime; + else + ap->prefix_pltime = rmtime; + } valid: - ext = (unsigned int)ia->acquired.tv_sec + - ia->prefix_vltime; - max = (unsigned int)(ap->created.tv_sec + - TEMP_VALID_LIFETIME); - if (ext < max) - ap->prefix_vltime = ia->prefix_vltime; - else - ap->prefix_vltime = (uint32_t)(max - - ia->acquired.tv_sec); + limit = TEMP_VALID_LIFETIME; + if (elapsed >= limit) + ap->prefix_vltime = 0; + else { + rmtime = (uint32_t)(limit - elapsed); + if (ia->prefix_vltime < rmtime) + ap->prefix_vltime = ia->prefix_vltime; + else + ap->prefix_vltime = rmtime; + } /* Just extend the latest matching prefix */ ap->acquired = ia->acquired; -- 2.47.3 From 7891a22dc316e1e3337c459ce8af7073582bdbc4 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 20:56:18 +0100 Subject: [PATCH 06/16] capsicum: Avoid some overflow issues in privsep sysctl Limit olen to SIZE_MAX - sizeof(olen). Ensure our buffer holds is big enough for olen AND newlen AND is zeroed out as the following sysctl call might return less data. Reported by NVIDIA Project Vanessa --- src/privsep-bsd.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/privsep-bsd.c b/src/privsep-bsd.c index 4cca33a4..8386a401 100644 --- a/src/privsep-bsd.c +++ b/src/privsep-bsd.c @@ -234,9 +234,13 @@ ps_root_dosysctl(unsigned long flags, void *data, size_t len, void **rdata, errno = EINVAL; return -1; } + if (oldlen > SIZE_MAX - sizeof(oldlen)) { + errno = EINVAL; + return -1; + } memcpy(&newlen, p, sizeof(newlen)); p += sizeof(newlen); - if (p + newlen > e) { + if (newlen > (size_t)(e - p)) { errno = EINVAL; return -1; } @@ -244,7 +248,10 @@ ps_root_dosysctl(unsigned long flags, void *data, size_t len, void **rdata, if (flags & PS_SYSCTL_OLEN) { *rlen = sizeof(oldlen) + oldlen; - *rdata = malloc(*rlen); + /* The sysctl call below might return less data + * and thus we return data we might not want to + * so ensure our block zeroed out. */ + *rdata = calloc(1, *rlen); if (*rdata == NULL) return -1; oldlenp = (size_t *)*rdata; @@ -393,7 +400,7 @@ ps_root_sysctl(struct dhcpcd_ctx *ctx, const int *name, unsigned int namelen, unsigned long flags = 0; size_t olen = (oldp && oldlenp) ? *oldlenp : 0, nolen; size_t buflen = sizeof(namelen) + (sizeof(*name) * namelen) + - sizeof(oldlenp) + sizeof(newlen) + newlen; + sizeof(oldlenp) + sizeof(newlen) + MAX(newlen, olen); if (ps_bufalloc(ctx, buflen) == -1) return -1; -- 2.47.3 From 7240d8322e8017308907321d7e24e714cedb371f Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 21:14:11 +0100 Subject: [PATCH 07/16] script: Fix buffer over and under flows in script_buftoenv Enforce we have a buffer and it is terminated. Rework code so that we no longer assert. Reported by NVIDIA Project Vanessa --- src/script.c | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/script.c b/src/script.c index 1edf0c22..ca273e2b 100644 --- a/src/script.c +++ b/src/script.c @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -165,20 +164,30 @@ script_buftoenv(struct dhcpcd_ctx *ctx, char *buf, size_t len) char **env, **envp, *bufp, *endp; size_t nenv; - /* Count the terminated env strings. - * Assert that the terminations are correct. */ + /* We should have something to process */ + if (buf == NULL || len == 0) { + errno = EINVAL; + return NULL; + } + + /* Ensure the buffer ends with NUL */ + if (buf[len - 1] != '\0') { + errno = EINVAL; + return NULL; + } + + /* Count the NUL-terminated env strings */ nenv = 0; endp = buf + len; for (bufp = buf; bufp < endp; bufp++) { if (*bufp == '\0') { -#ifndef NDEBUG - if (bufp + 1 < endp) - assert(*(bufp + 1) != '\0'); -#endif + /* Skip consecutive NULs safely without crashing */ + while (bufp + 1 < endp && *(bufp + 1) == '\0') { + bufp++; + } nenv++; } } - assert(*(bufp - 1) == '\0'); if (nenv == 0) return NULL; @@ -192,11 +201,23 @@ script_buftoenv(struct dhcpcd_ctx *ctx, char *buf, size_t len) bufp = buf; envp = ctx->script_env; - *envp++ = bufp++; - endp--; /* Avoid setting the last \0 to an invalid pointer */ - for (; bufp < endp; bufp++) { - if (*bufp == '\0') - *envp++ = bufp + 1; + + /* If first char is NUL, skip leading empty strings if any, + * or handle them gracefully */ + while (bufp < endp && *bufp == '\0') + bufp++; + if (bufp < endp) + *envp++ = bufp; + + /* Walk and capture pointers to each environment variable */ + for (; bufp < endp - 1; bufp++) { + if (*bufp == '\0') { + /* Skip consecutive NULs */ + while (bufp < endp - 1 && *(bufp + 1) == '\0') + bufp++; + if (bufp + 1 < endp) + *envp++ = bufp + 1; + } } *envp = NULL; -- 2.47.3 From 78ea09ed1633a583dbcde6e7bab9df4639ec8a34 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 22 Jun 2026 23:41:53 +0100 Subject: [PATCH 08/16] control: Avoid hangup in the recvdata path Instead return an error and bubble it up where it can be hangup / freed more cleanly. Reported-by: CuB3y0nd --- src/control.c | 47 ++++++++++++++++++++++++------------------- src/control.h | 2 +- src/privsep-control.c | 7 ++++++- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/control.c b/src/control.c index 24d7480c..ad006c20 100644 --- a/src/control.c +++ b/src/control.c @@ -113,10 +113,8 @@ control_handle_read(struct fd_list *fd) bytes = read(fd->fd, buffer, sizeof(buffer) - 1); if (bytes == -1) logerr(__func__); - if (bytes == -1 || bytes == 0) { - control_hangup(fd); - return -1; - } + if (bytes == -1 || bytes == 0) + return (int)bytes; #ifdef PRIVSEP if (IN_PRIVSEP(fd->ctx)) { @@ -132,15 +130,13 @@ control_handle_read(struct fd_list *fd) if (err == 1 && ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) { logerr(__func__); - control_free(fd); return -1; } - return 0; + return 1; } #endif - control_recvdata(fd, buffer, (size_t)bytes); - return 0; + return control_recvdata(fd, buffer, (size_t)bytes); } static int @@ -203,23 +199,31 @@ static void control_handle_data(void *arg, unsigned short events) { struct fd_list *fd = arg; + int err; if (!(events & (ELE_READ | ELE_WRITE | ELE_HANGUP))) logerrx("%s: unexpected event 0x%04x", __func__, events); if (events & ELE_WRITE && !(events & ELE_HANGUP)) { - if (control_handle_write(fd) == -1) - return; + err = control_handle_write(fd); + if (err == -1) + goto hangup; } if (events & ELE_READ) { - if (control_handle_read(fd) == -1) - return; + err = control_handle_read(fd); + if (err == -1 || err == 0) + goto hangup; } if (events & ELE_HANGUP) - control_hangup(fd); + goto hangup; + + return; + +hangup: + control_hangup(fd); } -void +int control_recvdata(struct fd_list *fd, char *data, size_t len) { char *p = data, *e; @@ -241,12 +245,13 @@ control_recvdata(struct fd_list *fd, char *data, size_t len) if (e == NULL) { errno = EINVAL; logerrx("%s: no terminator", __func__); - return; + return -1; } - if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) { + if ((size_t)argc + 1 >= + sizeof(argvp) / sizeof(argvp[0])) { errno = ENOBUFS; logerrx("%s: no arg buffer", __func__); - return; + return -1; } *ap++ = p; argc++; @@ -266,12 +271,12 @@ control_recvdata(struct fd_list *fd, char *data, size_t len) *ap = NULL; if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) { logerr(__func__); - if (errno != EINTR && errno != EAGAIN) { - control_free(fd); - return; - } + if (errno != EINTR && errno != EAGAIN) + return -1; } } + + return 1; } struct fd_list * diff --git a/src/control.h b/src/control.h index dd696d20..9e966f77 100644 --- a/src/control.h +++ b/src/control.h @@ -77,5 +77,5 @@ struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int); void control_free(struct fd_list *); void control_delete(struct fd_list *); int control_queue(struct fd_list *, void *, size_t); -void control_recvdata(struct fd_list *fd, char *, size_t); +int control_recvdata(struct fd_list *fd, char *, size_t); #endif diff --git a/src/privsep-control.c b/src/privsep-control.c index 10768a32..3737d07d 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -109,6 +109,7 @@ ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) struct iovec *iov = msg->msg_iov; struct fd_list *fd; unsigned int fd_flags = FD_SENDLEN; + int err; switch (psm->ps_flags) { case PS_CTL_PRIV: @@ -132,7 +133,11 @@ ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) if (fd == NULL) return -1; ctx->ps_control_client = fd; - control_recvdata(fd, iov->iov_base, iov->iov_len); + err = control_recvdata(fd, iov->iov_base, iov->iov_len); + if (err == -1 || err == 0) { + control_free(fd); + ctx->ps_control_client = NULL; + } break; case PS_CTL_EOF: ctx->ps_control_client = NULL; -- 2.47.3 From 708b4a56bae080a5b18c2e0c4c6fbe103131a2b0 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 23 Jun 2026 00:34:58 +0100 Subject: [PATCH 09/16] IPv6ND: Free routeinfo when it expires (#670) Reported-by: CuB3y0nd --- src/ipv6nd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipv6nd.c b/src/ipv6nd.c index f2c8c207..2d45b8e9 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -1814,6 +1814,7 @@ ipv6nd_expirera(void *arg) logwarnx("%s: expired route %s", rap->iface->name, rinfo->sprefix); TAILQ_REMOVE(&rap->rinfos, rinfo, next); + free(rinfo); } } -- 2.47.3 From 2f00c7bfc408b6582d331932dfa47829c4819029 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 23 Jun 2026 02:06:55 +0100 Subject: [PATCH 10/16] DHCPv6: Prefix exclude option can be 17 octets (#671) Well that's a simple off by one error Reported-by: CuB3y0nd --- src/dhcp6.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dhcp6.c b/src/dhcp6.c index 4a15319c..460040bd 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -1060,7 +1060,7 @@ dhcp6_makemessage(struct interface *ifp) /* RFC6603 Section 4.2 */ if (ap->prefix_exclude_len) { - uint8_t exb[16], *ep, u8; + uint8_t exb[17], *ep, u8; const uint8_t *pp; n = (size_t)((ap->prefix_exclude_len - -- 2.47.3 From 5733d3c59a5651f64357ac11c98b4f39895c8d25 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 23 Jun 2026 02:17:10 +0100 Subject: [PATCH 11/16] DHCPv6: When deprecating addresses, restart on prefix deletions As that might invalidate the next address to iterate on. Reported-by: CuB3y0nd --- src/dhcp6.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/dhcp6.c b/src/dhcp6.c index 460040bd..02464eb3 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -2561,12 +2561,13 @@ dhcp6_findia(struct interface *ifp, struct dhcp6_message *m, size_t l, } #ifndef SMALL -static void +static bool dhcp6_deprecatedele(struct ipv6_addr *ia) { struct ipv6_addr *da, *dan, *dda; struct timespec now; struct dhcp6_state *state; + bool freed = false; timespecclear(&now); TAILQ_FOREACH_SAFE(da, &ia->pd_pfxs, pd_next, dan) { @@ -2595,11 +2596,14 @@ dhcp6_deprecatedele(struct ipv6_addr *ia) if (IN6_ARE_ADDR_EQUAL(&dda->addr, &da->addr)) break; } - if (dda != NULL) { + if (dda != ia && dda != NULL) { TAILQ_REMOVE(&state->addrs, dda, next); ipv6_freeaddr(dda); + freed = true; } } + + return freed; } #endif @@ -2607,7 +2611,11 @@ static void dhcp6_deprecateaddrs(struct ipv6_addrhead *addrs) { struct ipv6_addr *ia, *ian; +#ifndef SMALL + bool again; +#endif +again: TAILQ_FOREACH_SAFE(ia, addrs, next, ian) { if (ia->flags & IPV6_AF_EXTENDED) ; @@ -2629,7 +2637,9 @@ dhcp6_deprecateaddrs(struct ipv6_addrhead *addrs) /* If we delegated from this prefix, deprecate or remove * the delegations. */ if (ia->flags & IPV6_AF_PFXDELEGATION) - dhcp6_deprecatedele(ia); + again = dhcp6_deprecatedele(ia); + else + again = false; #endif if (ia->flags & IPV6_AF_REQUEST) { @@ -2642,6 +2652,11 @@ dhcp6_deprecateaddrs(struct ipv6_addrhead *addrs) if (!(ia->flags & IPV6_AF_EXTENDED)) ipv6_deleteaddr(ia); ipv6_freeaddr(ia); +#ifndef SMALL + /* Deletion may invalidate the next pointer so restart */ + if (again) + goto again; +#endif } } -- 2.47.3 From b6588aa6ab582937c8fb743fbb3efcb242bce97b Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 23 Jun 2026 14:06:47 +0100 Subject: [PATCH 12/16] IPv4: uset old_ia when adding an address causes early removal This is true for NOALIAS configurations or Linux. Reported by NVIDIA Project Vanessa --- src/ipv4.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ipv4.c b/src/ipv4.c index 6461d52a..813b5ec2 100644 --- a/src/ipv4.c +++ b/src/ipv4.c @@ -766,7 +766,7 @@ ipv4_applyaddr(void *arg) } /* ipv4_dadaddr() will overwrite this, we need it to purge later */ - old_ia = state->addr; + old_ia = ifp->options->options & DHCPCD_NOALIAS ? NULL : state->addr; ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); /* If the netmask or broadcast is different, re-add the addresss. @@ -784,8 +784,11 @@ ipv4_applyaddr(void *arg) /* Linux does not change netmask/broadcast address * for re-added addresses, so we need to delete the old one * first. */ - if (ia != NULL) + if (ia != NULL) { ipv4_deladdr(ia, 0); + if (ia == old_ia) + old_ia = NULL; + } #endif #ifndef IP_LIFETIME if (ipv4_daddaddr(ifp, lease) == -1 && errno != EEXIST) -- 2.47.3 From 1f5937625511ee806b314d3223717ed087b42dc4 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Tue, 23 Jun 2026 15:30:57 -0400 Subject: [PATCH 13/16] dhcp: add configurable backoff parameters for DHCPv4 (#593) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit In cloud and virtual environments the DHCP service is typically ready within hundreds of milliseconds of the interface coming up, and once ready, responds within single-digit milliseconds. The RFC 2131 defaults (4s initial interval, 64s backoff cap) are designed for congested broadcast networks and are unnecessarily conservative in this context. Add three new configuration options to tune DHCPv4 retransmission: initial_interval - initial retransmission interval (default 4s) backoff_cutoff - exponential backoff cap (default 64s) backoff_jitter - random jitter per retry (default ±1000ms) Defaults match RFC 2131 so existing behaviour is unchanged. Option naming aligns with dhclient (initial-interval, backoff-cutoff). Minimum of 1 is enforced at parse time for interval and cutoff; invalid values are logged and the default is used. These options are DHCPv4-only; DHCPv6 retransmission follows RFC 8415 constants and is not user-configurable. Closes #406 Signed-off-by: Chris Patterson --- src/dhcp.c | 18 ++++++++++++----- src/dhcpcd.conf.5.in | 46 ++++++++++++++++++++++++++++++++++++++++++++ src/if-options.c | 46 ++++++++++++++++++++++++++++++++++++++++++++ src/if-options.h | 15 +++++++++++++++ 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/src/dhcp.c b/src/dhcp.c index 915a6240..4e8e57ff 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -1821,15 +1821,23 @@ send_message(struct interface *ifp, uint8_t type, void (*callback)(void *)) state->xid); RT = 0; /* bogus gcc warning */ } else { + unsigned int jitter = ifo->backoff_jitter; + if (state->interval == 0) - state->interval = 4; + state->interval = ifo->initial_interval; else { + unsigned int cutoff = ifo->backoff_cutoff; + state->interval *= 2; - if (state->interval > 64) - state->interval = 64; + if (state->interval > cutoff) + state->interval = cutoff; } - RT = (state->interval * MSEC_PER_SEC) + - (arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC); + + /* Jitter is bounded at config time to the smallest possible + * interval, so the result can never underflow. */ + RT = state->interval * MSEC_PER_SEC + + arc4random_uniform(jitter * 2) - jitter; + /* No carrier? Don't bother sending the packet. * However, we do need to advance the timeout. */ if (!if_is_link_up(ifp)) diff --git a/src/dhcpcd.conf.5.in b/src/dhcpcd.conf.5.in index 0f663aae..6edf8bc0 100644 --- a/src/dhcpcd.conf.5.in +++ b/src/dhcpcd.conf.5.in @@ -303,6 +303,52 @@ You can use this option to stop this from happening. .It Ic fallback Ar profile Fall back to using this profile if DHCP fails. This allows you to configure a static profile instead of using ZeroConf. +.It Ic initial_interval Ar seconds +Set the initial DHCPv4 retransmission interval to +.Ar seconds . +The minimum value is 1 and the maximum is 4. +The default is 4 seconds as per RFC 2131. +This option only affects DHCPv4; +DHCPv6 retransmission is governed by RFC 8415. +See also +.Ic backoff_cutoff +and +.Ic backoff_jitter . +.It Ic backoff_cutoff Ar seconds +Cap the DHCPv4 exponential backoff interval at +.Ar seconds . +The minimum value is 1 and the maximum is 64. +The default is 64 seconds as per RFC 2131. +If +.Ar seconds +is less than +.Ic initial_interval +it will be raised to match it, since the cutoff is a cap on the +exponential growth and must not shrink the initial interval. +Setting both +.Ic initial_interval +and +.Ic backoff_cutoff +to 1 effectively disables exponential growth, so +retransmissions use only the initial interval plus jitter. +This option only affects DHCPv4; +DHCPv6 retransmission is governed by RFC 8415. +See also +.Ic initial_interval +and +.Ic backoff_jitter . +.It Ic backoff_jitter Ar milliseconds +Set the random jitter applied to each DHCPv4 retransmission interval. +The jitter is applied as \(+- +.Ar milliseconds . +The default is 1000 (\(+-1 second) as per RFC 2131, which is also the maximum. +A value of 0 disables jitter, producing deterministic retransmission timing. +This option only affects DHCPv4; +DHCPv6 retransmission is governed by RFC 8415. +See also +.Ic backoff_cutoff +and +.Ic initial_interval . .It Ic fallback_time Ar seconds Start fallback after .Ar seconds . diff --git a/src/if-options.c b/src/if-options.c index 0984db91..e9246f28 100644 --- a/src/if-options.c +++ b/src/if-options.c @@ -175,6 +175,9 @@ const struct option cf_options[] = { { "background", no_argument, NULL, 'b' }, { "fallback_time", required_argument, NULL, O_FALLBACK_TIME }, { "ipv4ll_time", required_argument, NULL, O_IPV4LL_TIME }, { "nosyslog", no_argument, NULL, O_NOSYSLOG }, + { "initial_interval", required_argument, NULL, O_INITIAL_INTERVAL }, + { "backoff_cutoff", required_argument, NULL, O_BACKOFF_CUTOFF }, + { "backoff_jitter", required_argument, NULL, O_BACKOFF_JITTER }, { NULL, 0, NULL, '\0' } }; static char * @@ -2563,6 +2566,33 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, logopts &= ~LOGERR_LOG; logsetopts(logopts); } break; + case O_INITIAL_INTERVAL: + ARG_REQUIRED; + ifo->initial_interval = (uint32_t)strtou(arg, NULL, 0, 1, + MAX_INITIAL_INTERVAL, &e); + if (e) { + logerrx("invalid initial interval: %s", arg); + return -1; + } + break; + case O_BACKOFF_CUTOFF: + ARG_REQUIRED; + ifo->backoff_cutoff = (uint32_t)strtou(arg, NULL, 0, 1, + MAX_BACKOFF_CUTOFF, &e); + if (e) { + logerrx("invalid backoff cutoff: %s", arg); + return -1; + } + break; + case O_BACKOFF_JITTER: + ARG_REQUIRED; + ifo->backoff_jitter = (uint32_t)strtou(arg, NULL, 0, 0, + MAX_BACKOFF_JITTER, &e); + if (e) { + logerrx("invalid backoff jitter: %s", arg); + return -1; + } + break; default: return 0; } @@ -2629,6 +2659,19 @@ finish_config(struct if_options *ifo) if (!(ifo->options & DHCPCD_IPV6RS)) ifo->options &= ~( DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS); + +#ifdef INET + /* The exponential backoff cutoff must not be lower than the initial + * interval, otherwise the retransmission sequence would shrink rather + * than grow up to the cap. Clamp the cutoff up to the initial + * interval to preserve the documented "cap" semantics. */ + if (ifo->backoff_cutoff < ifo->initial_interval) { + logwarnx("backoff_cutoff (%u) is less than initial_interval " + "(%u); raising backoff_cutoff to match", + ifo->backoff_cutoff, ifo->initial_interval); + ifo->backoff_cutoff = ifo->initial_interval; + } +#endif } static struct if_options * @@ -2648,6 +2691,9 @@ default_config(struct dhcpcd_ctx *ctx) #ifdef INET ifo->fallback_time = DEFAULT_FALLBACK; ifo->ipv4ll_time = DEFAULT_IPV4LL; + ifo->initial_interval = DEFAULT_INITIAL_INTERVAL; + ifo->backoff_cutoff = DEFAULT_BACKOFF_CUTOFF; + ifo->backoff_jitter = DEFAULT_BACKOFF_JITTER; #endif ifo->metric = -1; ifo->auth.options |= DHCPCD_AUTH_REQUIRE; diff --git a/src/if-options.h b/src/if-options.h index bf07eac2..6a2982f3 100644 --- a/src/if-options.h +++ b/src/if-options.h @@ -54,6 +54,15 @@ #define DEFAULT_REQUEST 180 /* secs to request, mirror DHCP6 */ #define DEFAULT_FALLBACK 5 /* secs until fallback */ #define DEFAULT_IPV4LL 5 /* secs until ipv4ll */ +/* DHCPv4 retransmission backoff defaults (RFC 2131); DHCPv4-only. */ +#define DEFAULT_INITIAL_INTERVAL 4 /* DHCP_BASE per RFC 2131 */ +#define DEFAULT_BACKOFF_CUTOFF 64 /* DHCP_MAX per RFC 2131 */ +#define DEFAULT_BACKOFF_JITTER 1000 /* +/- milliseconds */ + +/* Upper bounds to keep the retransmit timeout arithmetic well within range. */ +#define MAX_INITIAL_INTERVAL 4 /* DHCP_BASE per RFC 2131 */ +#define MAX_BACKOFF_CUTOFF 64 /* DHCP_MAX per RFC 2131 */ +#define MAX_BACKOFF_JITTER 1000 /* +/- milliseconds, per RFC 2131 */ #ifndef HOSTNAME_MAX_LEN #define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ @@ -191,6 +200,9 @@ #define O_VSIO O_BASE + 57 #define O_VSIO6 O_BASE + 58 #define O_NOSYSLOG O_BASE + 59 +#define O_INITIAL_INTERVAL O_BASE + 60 +#define O_BACKOFF_CUTOFF O_BASE + 61 +#define O_BACKOFF_JITTER O_BASE + 62 extern const struct option cf_options[]; @@ -258,6 +270,9 @@ struct if_options { uint32_t request_time; uint32_t fallback_time; uint32_t ipv4ll_time; + uint32_t initial_interval; + uint32_t backoff_cutoff; + uint32_t backoff_jitter; unsigned long long options; bool randomise_hwaddr; -- 2.47.3 From 59b32c809c917047102846edccf60bdf28508c4c Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Fri, 26 Jun 2026 14:43:25 +0100 Subject: [PATCH 14/16] privsep: Fix compile without INET --- src/privsep-inet.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/privsep-inet.c b/src/privsep-inet.c index 8fa0b04c..a8f6f5ea 100644 --- a/src/privsep-inet.c +++ b/src/privsep-inet.c @@ -506,10 +506,12 @@ ps_inet_recvmsgpspcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) #endif switch (psm->ps_cmd) { +#ifdef INET6 case PS_ND: if (!ps_inet_validnd(msg)) return -1; break; +#endif default: errno = EINVAL; return -1; -- 2.47.3 From a7935e28de70c918e1a735913546ac5b48c7e38c Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 29 Jun 2026 22:41:39 +0100 Subject: [PATCH 15/16] options: Introduce policy groups Remove the old bitmask array which we used to store which options to request, remove, require, etc and replace with a more generic policy group. The policy group structure holds an array of option numbers in each list - request, remove, require, etc. Add a suite of helper functions around this to allow adding, removing, checking, walking, etc. This allows a much easier use of this in DHCP processing but more importantly allows us to remove or reject options we do not have a definition for. --- src/dhcp-common.c | 203 +++++++++++++++++++++++++++++++----- src/dhcp-common.h | 39 ++++--- src/dhcp.c | 136 +++++++++++++++--------- src/dhcp6.c | 158 +++++++++++++++++----------- src/dhcpcd.conf.5.in | 36 ++++--- src/if-options.c | 241 ++++++++++++++++++++++++------------------- src/if-options.h | 34 +++--- src/ipv4.c | 2 +- src/ipv6nd.c | 122 +++++++++++++--------- 9 files changed, 636 insertions(+), 335 deletions(-) diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 045c2b01..97e80635 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -147,6 +147,23 @@ dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols) fflush(stdout); } +static char dhcp_option_string_buf[16]; +const char * +dhcp_option_string(const struct dhcp_opt *opts, size_t nopts, uint32_t option) +{ + size_t n; + + for (n = 0; n < nopts; n++, opts++) { + if (opts->option == option) + return opts->var; + } + + if (snprintf(dhcp_option_string_buf, sizeof(dhcp_option_string_buf), + "%u", option) == -1) + return NULL; + return dhcp_option_string_buf; +} + struct dhcp_opt * vivso_find(uint32_t iana_en, const void *arg) { @@ -192,14 +209,110 @@ dhcp_vendor(char *str, size_t len) } int -make_option_mask(const struct dhcp_opt *dopts, size_t dopts_len, - const struct dhcp_opt *odopts, size_t odopts_len, uint8_t *mask, +dho_policy_has(const struct dho_policy *policy, uint32_t option) +{ + size_t i; + + for (i = 0; i < policy->dhop_policy_len; i++) { + if (policy->dhop_policy[i] == option) + return 1; + } + + return 0; +} + +int +dho_policy_allowed(const struct dho_policy_group *pg, uint32_t option) +{ + if (pg->dhop_allow.dhop_policy_len == 0) { + /* No allowed policy has been set, so be permissive. */ + return dho_policy_has(&pg->dhop_remove, option) ? 0 : 1; + } + + if (dho_policy_has(&pg->dhop_allow, option) || + dho_policy_has(&pg->dhop_request, option)) + return 1; + return 0; +} + +int +dho_policy_requested(const struct dho_policy_group *pg, + const struct dhcp_opt *dho) +{ + if (dho->type & OT_NOREQ) + return 0; + if (!(dho->type & OT_REQUEST) && + !dho_policy_has(&pg->dhop_request, dho->option)) + return 0; + if (dho_policy_has(&pg->dhop_allow, dho->option)) + return 1; + if (dho_policy_has(&pg->dhop_remove, dho->option)) + return 0; + return 1; +} + +static uint32_t * +dho_policy_find(struct dho_policy *policy, uint32_t option, + uint32_t **free_option) +{ + size_t i; + + for (i = 0; i < policy->dhop_policy_len; i++) { + if (policy->dhop_policy[i] == option) + return &policy->dhop_policy[i]; + if (policy->dhop_policy[i] == 0 && free_option != NULL && + *free_option == NULL) + *free_option = &policy->dhop_policy[i]; + } + + return 0; +} + +int +dho_policy_add(struct dho_policy *policy, uint32_t option) +{ + uint32_t *np, *free_option = NULL; + + if (dho_policy_find(policy, option, &free_option) != NULL) + return 0; + + if (free_option != NULL) { + *free_option = option; + return 1; + } + + np = reallocarray(policy->dhop_policy, policy->dhop_policy_len + 1, + sizeof(*policy->dhop_policy)); + if (np == NULL) + return -1; + + np[policy->dhop_policy_len] = option; + policy->dhop_policy = np; + policy->dhop_policy_len++; + return 1; +} + +int +dho_policy_del(struct dho_policy *policy, uint32_t option) +{ + uint32_t *o; + + o = dho_policy_find(policy, option, NULL); + if (o == NULL) + return 0; + + *o = 0; + return 1; +} + +int +dho_policy_set(const struct dho_policy_ctx *pctx, struct dho_policy *policy, const char *opts, int add) { char *token, *o, *p; const struct dhcp_opt *opt; - int match, e; - unsigned int n; + int match, e, err = -1; + unsigned int n, max = UINT16_MAX; size_t i; if (opts == NULL) @@ -210,53 +323,89 @@ make_option_mask(const struct dhcp_opt *dopts, size_t dopts_len, continue; if (strncmp(token, "dhcp6_", 6) == 0) token += 6; - if (strncmp(token, "nd_", 3) == 0) + else if (strncmp(token, "nd_", 3) == 0) token += 3; + else + max = UINT8_MAX; + n = (unsigned int)strtou(token, NULL, 0, 1, max, &e); match = 0; - for (i = 0, opt = odopts; i < odopts_len; i++, opt++) { + for (i = 0, opt = pctx->odopts; i < pctx->odopts_len; + i++, opt++) { if (opt->var == NULL || opt->option == 0) continue; /* buggy dhcpcd-definitions.conf */ if (strcmp(opt->var, token) == 0) match = 1; - else { - n = (unsigned int)strtou(token, NULL, 0, 0, - UINT_MAX, &e); - if (e == 0 && opt->option == n) - match = 1; - } + else if (e == 0 && opt->option == n) + match = 1; if (match) break; } if (match == 0) { - for (i = 0, opt = dopts; i < dopts_len; i++, opt++) { + for (i = 0, opt = pctx->dopts; i < pctx->dopts_len; + i++, opt++) { if (strcmp(opt->var, token) == 0) match = 1; - else { - n = (unsigned int)strtou(token, NULL, 0, - 0, UINT_MAX, &e); - if (e == 0 && opt->option == n) - match = 1; - } + else if (e == 0 && opt->option == n) + match = 1; if (match) break; } } - if (!match || !opt->option) { - free(o); + if ((!match || !opt->option) && e != 0) { errno = ENOENT; - return -1; + goto out; } - if (add == 2 && !(opt->type & OT_ADDRIPV4)) { - free(o); + if (match && add == 2 && !(opt->type & OT_ADDRIPV4)) { errno = EINVAL; - return -1; + goto out; } + if (match) + n = opt->option; if (add == 1 || add == 2) - add_option_mask(mask, opt->option); + dho_policy_add(policy, n); else - del_option_mask(mask, opt->option); + dho_policy_del(policy, n); } + + err = 0; + +out: free(o); + return err; +} + +/* Pass by value because we don't free the holder */ +void +dho_policy_free(struct dho_policy policy) +{ + free(policy.dhop_policy); +} + +void +dho_policy_group_free(struct dho_policy_group pg) +{ + dho_policy_free(pg.dhop_request); + dho_policy_free(pg.dhop_require); + dho_policy_free(pg.dhop_allow); + dho_policy_free(pg.dhop_remove); + dho_policy_free(pg.dhop_reject); +} + +int +dho_policy_check(const struct dho_policy *policy, + int (*check)(uint32_t, void *), void *check_ctx) +{ + size_t i; + int result; + + for (i = 0; i < policy->dhop_policy_len; i++) { + if (policy->dhop_policy[i] == 0) + continue; + result = check(policy->dhop_policy[i], check_ctx); + if (result != 0) + return result; + } + return 0; } diff --git a/src/dhcp-common.h b/src/dhcp-common.h index cdc5dded..fcc974ec 100644 --- a/src/dhcp-common.h +++ b/src/dhcp-common.h @@ -77,14 +77,6 @@ #define OT_URI (1 << 29) #define OT_TRUNCATED (1 << 30) -#define DHC_REQ(r, n, o) \ - (has_option_mask((r), (o)) && !has_option_mask((n), (o))) - -#define DHC_REQOPT(o, r, n) \ - (!((o)->type & OT_NOREQ) && \ - ((o)->type & OT_REQUEST || has_option_mask((r), (o)->option)) && \ - !has_option_mask((n), (o)->option)) - struct dhcp_opt { uint32_t option; /* Also used for IANA Enterpise Number */ int type; @@ -104,6 +96,13 @@ struct dhcp_opt { size_t encopts_len; }; +struct dho_policy_ctx { + const struct dhcp_opt *dopts; + size_t dopts_len; + const struct dhcp_opt *odopts; + size_t odopts_len; +}; + const char *dhcp_get_hostname(struct dhcpcd_ctx *, char *, size_t, const struct if_options *); struct dhcp_opt *vivso_find(uint32_t, const void *); @@ -111,14 +110,22 @@ struct dhcp_opt *vivso_find(uint32_t, const void *); ssize_t dhcp_vendor(char *, size_t); void dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols); -#define add_option_mask(var, val) \ - ((var)[(val) >> 3] = (uint8_t)((var)[(val) >> 3] | 1 << ((val) & 7))) -#define del_option_mask(var, val) \ - ((var)[(val) >> 3] = (uint8_t)((var)[(val) >> 3] & ~(1 << ((val) & 7)))) -#define has_option_mask(var, val) \ - ((var)[(val) >> 3] & (uint8_t)(1 << ((val) & 7))) -int make_option_mask(const struct dhcp_opt *, size_t, const struct dhcp_opt *, - size_t, uint8_t *, const char *, int); +const char *dhcp_option_string(const struct dhcp_opt *, size_t, uint32_t); + +int dho_policy_has(const struct dho_policy *policy, uint32_t option); +int dho_policy_add(struct dho_policy *policy, uint32_t option); +int dho_policy_del(struct dho_policy *policy, uint32_t option); +int dho_policy_set(const struct dho_policy_ctx *, struct dho_policy *policy, + const char *opts, int add); +void dho_policy_free(struct dho_policy); +void dho_policy_group_free(struct dho_policy_group); +int dho_policy_check(const struct dho_policy *, int (*)(uint32_t, void *), + void *); + +int dho_policy_requested(const struct dho_policy_group *, + const struct dhcp_opt *); +int dho_policy_removed(const struct dho_policy_group *, uint32_t); +int dho_policy_allowed(const struct dho_policy_group *, uint32_t); size_t encode_rfc1035(const char *src, uint8_t *dst); ssize_t decode_rfc1035(char *, size_t, const uint8_t *, size_t); diff --git a/src/dhcp.c b/src/dhcp.c index 4e8e57ff..842eb6c1 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -557,6 +557,7 @@ get_option_routes(rb_tree_t *routes, struct interface *ifp, const struct bootp *bootp, size_t bootp_len) { struct if_options *ifo = ifp->options; + struct dho_policy_group *pg = &ifo->dhopg_dhcp; const uint8_t *p; const uint8_t *e; struct rt *rt = NULL; @@ -566,12 +567,12 @@ get_option_routes(rb_tree_t *routes, struct interface *ifp, int n; /* If we have CSR's then we MUST use these only */ - if (!has_option_mask(ifo->nomask, DHO_CSR)) + if (dho_policy_allowed(pg, DHO_CSR)) p = get_option(ifp->ctx, bootp, bootp_len, DHO_CSR, &len); else p = NULL; /* Check for crappy MS option */ - if (!p && !has_option_mask(ifo->nomask, DHO_MSCSR)) { + if (!p && dho_policy_allowed(pg, DHO_MSCSR)) { p = get_option(ifp->ctx, bootp, bootp_len, DHO_MSCSR, &len); if (p) csr = "MS "; @@ -591,7 +592,7 @@ get_option_routes(rb_tree_t *routes, struct interface *ifp, n = 0; /* OK, get our static routes first. */ - if (!has_option_mask(ifo->nomask, DHO_STATICROUTE)) + if (dho_policy_allowed(pg, DHO_STATICROUTE)) p = get_option(ifp->ctx, bootp, bootp_len, DHO_STATICROUTE, &len); else @@ -632,7 +633,7 @@ get_option_routes(rb_tree_t *routes, struct interface *ifp, } /* Now grab our routers */ - if (!has_option_mask(ifo->nomask, DHO_ROUTER)) + if (dho_policy_allowed(pg, DHO_ROUTER)) p = get_option(ifp->ctx, bootp, bootp_len, DHO_ROUTER, &len); else p = NULL; @@ -660,13 +661,15 @@ uint16_t dhcp_get_mtu(const struct interface *ifp) { const struct dhcp_state *state; + const struct if_options *ifo = ifp->options; + const struct dho_policy_group *pg = &ifo->dhopg_dhcp; uint16_t mtu; - if (ifp->options->mtu) - return (uint16_t)ifp->options->mtu; + if (ifo->mtu) + return (uint16_t)ifo->mtu; mtu = 0; /* bogus gcc warning */ if ((state = D_CSTATE(ifp)) == NULL || - has_option_mask(ifp->options->nomask, DHO_MTU) || + !dho_policy_allowed(pg, DHO_MTU) || get_option_uint16(ifp->ctx, &mtu, state->new, state->new_len, DHO_MTU) == -1) return 0; @@ -797,6 +800,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) size_t len, i; const struct dhcp_opt *opt; struct if_options *ifo = ifp->options; + const struct dho_policy_group *pg = &ifo->dhopg_dhcp; const struct dhcp_state *state = D_CSTATE(ifp); const struct dhcp_lease *lease = &state->lease; char hbuf[HOSTNAME_MAX_LEN + 1]; @@ -967,12 +971,12 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) *p++ = 0; for (i = 0, opt = ctx->dhcp_opts; i < ctx->dhcp_opts_len; i++, opt++) { - if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) - continue; if (type == DHCP_INFORM && (opt->option == DHO_RENEWALTIME || opt->option == DHO_REBINDTIME)) continue; + if (!dho_policy_requested(pg, opt)) + continue; AREA_FIT(1); *p++ = (uint8_t)opt->option; } @@ -984,19 +988,18 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) break; if (lp < p) continue; - if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) - continue; if (type == DHCP_INFORM && (opt->option == DHO_RENEWALTIME || opt->option == DHO_REBINDTIME)) continue; + if (!dho_policy_requested(pg, opt)) + continue; AREA_FIT(1); *p++ = (uint8_t)opt->option; } *n_params = (uint8_t)(p - n_params - 1); - if (mtu != -1 && - !(has_option_mask(ifo->nomask, DHO_MAXMESSAGESIZE))) { + if (mtu != -1 && (dho_policy_allowed(pg, DHO_MAXMESSAGESIZE))) { AREA_CHECK(2); *p++ = DHO_MAXMESSAGESIZE; *p++ = 2; @@ -1006,7 +1009,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) } if (ifo->userclass[0] && - !has_option_mask(ifo->nomask, DHO_USERCLASS)) { + dho_policy_allowed(pg, DHO_USERCLASS)) { AREA_CHECK(ifo->userclass[0]); *p++ = DHO_USERCLASS; memcpy(p, ifo->userclass, @@ -1022,8 +1025,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) p += state->clientid[0] + 1; } - if (DHCP_DIR(type) && - !has_option_mask(ifo->nomask, DHO_VENDORCLASSID) && + if (DHCP_DIR(type) && dho_policy_allowed(pg, DHO_VENDORCLASSID) && ifo->vendorclassid[0]) { AREA_CHECK(ifo->vendorclassid[0]); *p++ = DHO_VENDORCLASSID; @@ -1033,7 +1035,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) } if (type == DHCP_DISCOVER && !(ctx->options & DHCPCD_TEST) && - DHC_REQ(ifo->requestmask, ifo->nomask, DHO_RAPIDCOMMIT)) { + dho_policy_allowed(pg, DHO_RAPIDCOMMIT)) { /* RFC 4039 Section 3 */ AREA_CHECK(0); *p++ = DHO_RAPIDCOMMIT; @@ -1114,7 +1116,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) /* RFC 2563 Auto Configure */ if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL && - !(has_option_mask(ifo->nomask, DHO_AUTOCONFIGURE))) { + dho_policy_allowed(pg, DHO_AUTOCONFIGURE)) { AREA_CHECK(1); *p++ = DHO_AUTOCONFIGURE; *p++ = 1; @@ -1130,8 +1132,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) } #ifndef SMALL - if (ifo->vivco_len && - !has_option_mask(ifo->nomask, DHO_VIVCO)) { + if (ifo->vivco_len && dho_policy_allowed(pg, DHO_VIVCO)) { struct vivco *vivco = ifo->vivco; size_t vlen = ifo->vivco_len; struct rfc3396_ctx rctx = { @@ -1157,7 +1158,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) } } - if (ifo->vsio_len && !has_option_mask(ifo->nomask, DHO_VIVSO)) { + if (ifo->vsio_len && dho_policy_allowed(pg, DHO_VIVSO)) { struct vsio *vso = ifo->vsio; size_t vlen = ifo->vsio_len; struct vsio_so *so; @@ -1201,7 +1202,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) #ifdef AUTH if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != DHCPCD_AUTH_SENDREQUIRE && - !has_option_mask(ifo->nomask, DHO_FORCERENEW_NONCE)) { + dho_policy_allowed(pg, DHO_FORCERENEW_NONCE)) { /* We support HMAC-MD5 */ AREA_CHECK(1); *p++ = DHO_FORCERENEW_NONCE; @@ -1362,6 +1363,7 @@ dhcp_env(FILE *fenv, const char *prefix, const struct interface *ifp, const struct bootp *bootp, size_t bootp_len) { const struct if_options *ifo; + const struct dho_policy_group *pg; const uint8_t *p; struct in_addr addr; struct in_addr net; @@ -1373,6 +1375,8 @@ dhcp_env(FILE *fenv, const char *prefix, const struct interface *ifp, uint32_t en; ifo = ifp->options; + pg = &ifo->dhopg_dhcp; + if (get_option_uint8(ifp->ctx, &overl, bootp, bootp_len, DHO_OPTSOVERLOADED) == -1) overl = 0; @@ -1432,7 +1436,7 @@ dhcp_env(FILE *fenv, const char *prefix, const struct interface *ifp, for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->dhcp_opts_len; i++, opt++) { - if (has_option_mask(ifo->nomask, opt->option)) + if (dho_policy_has(&pg->dhop_remove, opt->option)) continue; if (dhcp_getoverride(ifo, opt->option)) continue; @@ -1458,7 +1462,7 @@ dhcp_env(FILE *fenv, const char *prefix, const struct interface *ifp, for (i = 0, opt = ifo->dhcp_override; i < ifo->dhcp_override_len; i++, opt++) { - if (has_option_mask(ifo->nomask, opt->option)) + if (dho_policy_has(&pg->dhop_remove, opt->option)) continue; p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl); if (p == NULL) @@ -3036,12 +3040,52 @@ dhcp_redirect_dhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, } } +struct dhcp_policy { + struct dhcpcd_ctx *ctx; + struct bootp *bootp; + size_t bootp_len; + uint8_t type; +}; + +static int +dhcp_policy_reject(uint32_t option, void *arg) +{ + struct dhcp_policy *dp = arg; + + if (get_option(dp->ctx, dp->bootp, dp->bootp_len, (uint8_t)option, + NULL)) + return 1; + + return 0; +} + +static int +dhcp_policy_require(uint32_t option, void *arg) +{ + struct dhcp_policy *dp = arg; + + if (get_option(dp->ctx, dp->bootp, dp->bootp_len, (uint8_t)option, + NULL)) + return 0; + + /* If we are BOOTP, then ignore the need for serverid. + * To ignore BOOTP, require dhcp_message_type. + * However, nothing really stops BOOTP from providing + * DHCP style options as well so the above isn't + * always true. */ + if (dp->type == 0 && option == DHO_SERVERID) + return 0; + + return -1; +} + static void dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, const struct in_addr *from) { struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; + struct dho_policy_group *pg = &ifo->dhopg_dhcp; struct dhcp_lease *lease = &state->lease; uint8_t type; struct in_addr addr; @@ -3050,6 +3094,12 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, bool bootp_copied; uint32_t v6only_time = 0; bool use_v6only = false, has_auto_conf = false; + struct dhcp_policy dp = { + .ctx = ifp->ctx, + .bootp = bootp, + .bootp_len = bootp_len, + }; + #ifdef AUTH const uint8_t *auth; size_t auth_len; @@ -3205,17 +3255,14 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, state->interval = 0; /* Ensure that no reject options are present */ - for (i = 1; i < 255; i++) { - if (has_option_mask(ifo->rejectmask, i) && - get_option(ifp->ctx, bootp, bootp_len, (uint8_t)i, NULL)) { - LOGDHCP(LOG_WARNING, "reject DHCP"); - return; - } + if (dho_policy_check(&pg->dhop_reject, dhcp_policy_reject, &dp) == 1) { + LOGDHCP(LOG_WARNING, "reject DHCP"); + return; } if (type == DHCP_NAK) { /* For NAK, only check if we require the ServerID */ - if (has_option_mask(ifo->requiremask, DHO_SERVERID) && + if (dho_policy_has(&pg->dhop_require, DHO_SERVERID) && get_option_addr(ifp->ctx, &addr, bootp, bootp_len, DHO_SERVERID) == -1) { LOGDHCP(LOG_WARNING, "reject NAK"); @@ -3245,22 +3292,14 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, } /* Ensure that all required options are present */ - for (i = 1; i < 255; i++) { - if (has_option_mask(ifo->requiremask, i) && - !get_option(ifp->ctx, bootp, bootp_len, (uint8_t)i, NULL)) { - /* If we are BOOTP, then ignore the need for serverid. - * To ignore BOOTP, require dhcp_message_type. - * However, nothing really stops BOOTP from providing - * DHCP style options as well so the above isn't - * always true. */ - if (type == 0 && i == DHO_SERVERID) - continue; - LOGDHCP(LOG_WARNING, "reject DHCP"); - return; - } + dp.type = type; + if (dho_policy_check(&pg->dhop_require, dhcp_policy_require, &dp) == + -1) { + LOGDHCP(LOG_WARNING, "reject DHCP"); + return; } - if (has_option_mask(ifo->requestmask, DHO_IPV6_PREFERRED_ONLY)) { + if (dho_policy_allowed(pg, DHO_IPV6_PREFERRED_ONLY)) { if (get_option_uint32(ifp->ctx, &v6only_time, bootp, bootp_len, DHO_IPV6_PREFERRED_ONLY) == 0 && (state->state == DHS_DISCOVER || @@ -3366,7 +3405,7 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, /* Test for rapid commit in the OFFER */ if (!(ifp->ctx->options & DHCPCD_TEST) && - has_option_mask(ifo->requestmask, DHO_RAPIDCOMMIT) && + dho_policy_allowed(pg, DHO_RAPIDCOMMIT) && get_option(ifp->ctx, bootp, bootp_len, DHO_RAPIDCOMMIT, NULL)) { state->state = DHS_REQUEST; @@ -3428,7 +3467,7 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, if (state->state == DHS_DISCOVER) { /* We only allow ACK of rapid commit DISCOVER. */ - if (has_option_mask(ifo->requestmask, + if (dho_policy_has(&pg->dhop_request, DHO_RAPIDCOMMIT) && get_option(ifp->ctx, bootp, bootp_len, DHO_RAPIDCOMMIT, NULL)) @@ -4344,7 +4383,8 @@ dhcp_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid) if (ifp->flags & IFF_POINTOPOINT) { for (i = 1; i < 255; i++) - if (i != DHO_ROUTER && has_option_mask(ifo->dstmask, i)) + if (i != DHO_ROUTER && + dho_policy_has(&ifo->dhop_destination, i)) dhcp_message_add_addr(state->new, i, ia->brd); } diff --git a/src/dhcp6.c b/src/dhcp6.c index 02464eb3..b11e4003 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -703,6 +703,7 @@ dhcp6_makemessage(struct interface *ifp) uint16_t si_len, uni_len, n_options; uint8_t *o_lenp; struct if_options *ifo = ifp->options; + const struct dho_policy_group *pg = &ifo->dhopg_dhcp6; const struct dhcp_opt *opt, *opt2; const struct ipv6_addr *ap; char hbuf[HOSTNAME_MAX_LEN + 1]; @@ -794,7 +795,7 @@ dhcp6_makemessage(struct interface *ifp) } if (n < ifo->dhcp6_override_len) continue; - if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + if (!dho_policy_requested(pg, opt)) continue; n_options++; len += sizeof(o.len); @@ -802,7 +803,7 @@ dhcp6_makemessage(struct interface *ifp) #ifndef SMALL for (l = 0, opt = ifo->dhcp6_override; l < ifo->dhcp6_override_len; l++, opt++) { - if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + if (!dho_policy_requested(pg, opt)) continue; n_options++; len += sizeof(o.len); @@ -820,15 +821,13 @@ dhcp6_makemessage(struct interface *ifp) len += sizeof(o) + 1 + hl; } - if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) && - ifo->mudurl[0]) + if (dho_policy_allowed(pg, D6_OPTION_MUDURL) && ifo->mudurl[0]) len += sizeof(o) + ifo->mudurl[0]; #ifdef AUTH if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != DHCPCD_AUTH_SENDREQUIRE && - DHC_REQ(ifo->requestmask6, ifo->nomask6, - D6_OPTION_RECONF_ACCEPT)) + dho_policy_allowed(pg, D6_OPTION_RECONF_ACCEPT)) len += sizeof(o); /* Reconfigure Accept */ #endif } @@ -843,13 +842,13 @@ dhcp6_makemessage(struct interface *ifp) len += sizeof(o) + ctx->duid_len; } - if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS)) + if (dho_policy_allowed(pg, D6_OPTION_USER_CLASS)) len += dhcp6_makeuser(NULL, ifp); #ifndef SMALL - if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS)) + if (dho_policy_allowed(pg, D6_OPTION_VENDOR_CLASS)) len += dhcp6_makevendor(NULL, ifp); - if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS)) + if (dho_policy_allowed(pg, D6_OPTION_VENDOR_OPTS)) len += dhcp6_makevendoropts(NULL, ifp); #endif @@ -921,7 +920,7 @@ dhcp6_makemessage(struct interface *ifp) } if (state->state == DH6S_DISCOVER && !(ctx->options & DHCPCD_TEST) && - DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT)) + dho_policy_allowed(pg, D6_OPTION_RAPID_COMMIT)) len += sizeof(o); if (m == NULL) { @@ -933,7 +932,7 @@ dhcp6_makemessage(struct interface *ifp) case DH6S_REQUEST: /* FALLTHROUGH */ case DH6S_RENEW: /* FALLTHROUGH */ case DH6S_RELEASE: - if (has_option_mask(ifo->nomask6, D6_OPTION_UNICAST)) { + if (!dho_policy_allowed(pg, D6_OPTION_UNICAST)) { unicast = NULL; break; } @@ -1127,7 +1126,7 @@ dhcp6_makemessage(struct interface *ifp) if (n < ifo->dhcp6_override_len) continue; #endif - if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + if (!dho_policy_requested(pg, opt)) continue; o.code = htons((uint16_t)opt->option); memcpy(p, &o.code, sizeof(o.code)); @@ -1137,7 +1136,7 @@ dhcp6_makemessage(struct interface *ifp) #ifndef SMALL for (l = 0, opt = ifo->dhcp6_override; l < ifo->dhcp6_override_len; l++, opt++) { - if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + if (!dho_policy_requested(pg, opt)) continue; o.code = htons((uint16_t)opt->option); memcpy(p, &o.code, sizeof(o.code)); @@ -1159,16 +1158,16 @@ dhcp6_makemessage(struct interface *ifp) COPYIN(D6_OPTION_ELAPSED, &si_len, sizeof(si_len)); if (state->state == DH6S_DISCOVER && !(ctx->options & DHCPCD_TEST) && - DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT)) + dho_policy_allowed(pg, D6_OPTION_RAPID_COMMIT)) COPYIN1(D6_OPTION_RAPID_COMMIT, 0); - if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS)) + if (dho_policy_allowed(pg, D6_OPTION_USER_CLASS)) p += dhcp6_makeuser(p, ifp); #ifndef SMALL - if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS)) + if (dho_policy_allowed(pg, D6_OPTION_VENDOR_CLASS)) p += dhcp6_makevendor(p, ifp); - if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS)) + if (dho_policy_allowed(pg, D6_OPTION_VENDOR_OPTS)) p += dhcp6_makevendoropts(p, ifp); #endif @@ -1199,16 +1198,14 @@ dhcp6_makemessage(struct interface *ifp) memcpy(o_lenp, &o.len, sizeof(o.len)); } - if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) && - ifo->mudurl[0]) + if (dho_policy_allowed(pg, D6_OPTION_MUDURL) && ifo->mudurl[0]) COPYIN(D6_OPTION_MUDURL, ifo->mudurl + 1, ifo->mudurl[0]); #ifdef AUTH if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != DHCPCD_AUTH_SENDREQUIRE && - DHC_REQ(ifo->requestmask6, ifo->nomask6, - D6_OPTION_RECONF_ACCEPT)) + dho_policy_allowed(pg, D6_OPTION_RECONF_ACCEPT)) COPYIN1(D6_OPTION_RECONF_ACCEPT, 0); #endif } @@ -1695,6 +1692,7 @@ dhcp6_startdiscover(void *arg) { struct interface *ifp; struct if_options *ifo; + struct dho_policy_group *pg; struct dhcp6_state *state; int llevel; struct ipv6_addr *ia; @@ -1702,13 +1700,14 @@ dhcp6_startdiscover(void *arg) ifp = arg; state = D6_STATE(ifp); ifo = ifp->options; + pg = &ifo->dhopg_dhcp6; #ifndef SMALL if (state->reason == NULL || strcmp(state->reason, "TIMEOUT6") != 0) dhcp6_delete_delegates(ifp); #endif /* Ensure we never request INFO_REFRESH_TIME, * this only belongs in Information-Request messages */ - del_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME); + dho_policy_del(&pg->dhop_request, D6_OPTION_INFO_REFRESH_TIME); if (state->new == NULL && !state->failed) llevel = LOG_INFO; @@ -1743,6 +1742,7 @@ dhcp6_startinform(void *arg) struct dhcp6_state *state; int llevel; struct if_options *ifo; + struct dho_policy_group *pg; ifp = arg; state = D6_STATE(ifp); @@ -1757,7 +1757,8 @@ dhcp6_startinform(void *arg) state->MRC = 0; /* Ensure we always request INFO_REFRESH_TIME as per rfc8415 */ - add_option_mask(ifo->requestmask6, D6_OPTION_INFO_REFRESH_TIME); + pg = &ifo->dhopg_dhcp6; + dho_policy_add(&pg->dhop_request, D6_OPTION_INFO_REFRESH_TIME); if (dhcp6_makemessage(ifp) == -1) { logerr("%s: %s", __func__, ifp->name); @@ -3405,25 +3406,62 @@ dhcp6_adjust_max_rt(struct interface *ifp, struct dhcp6_message *r, size_t len) } } +struct dhcp6_policy { + const struct dhcpcd_ctx *ctx; + const char *ifname; + const char *sfrom; + struct dhcp6_message *msg; + size_t len; +}; + +static int +dhcp6_require(uint32_t option, void *arg) +{ + struct dhcp6_policy *ctx = arg; + void *o; + + o = dhcp6_findmoption(ctx->msg, ctx->len, (uint16_t)option, NULL); + if (o != NULL) + return 0; + + logwarnx("%s: reject DHCPv6 (missing option %u) from %s", ctx->ifname, + option, ctx->sfrom); + return -1; +} + +static int +dhcp6_reject(uint32_t option, void *arg) +{ + struct dhcp6_policy *ctx = arg; + void *o; + + o = dhcp6_findmoption(ctx->msg, ctx->len, (uint16_t)option, NULL); + if (o == NULL) + return 0; + + logwarnx("%s: reject DHCPv6 (option %u) from %s", ctx->ifname, option, + ctx->sfrom); + return -1; +} + static void dhcp6_recvif(struct interface *ifp, const char *sfrom, struct dhcp6_message *r, size_t len) { - struct dhcpcd_ctx *ctx; - size_t i; const char *op; struct dhcp6_state *state; + struct dhcp6_policy policy = { .sfrom = sfrom, .msg = r, .len = len }; + int err; uint8_t *o, preference = 0; uint16_t ol; - const struct dhcp_opt *opt; const struct if_options *ifo; + const struct dho_policy_group *pg; bool valid_op; #ifdef AUTH uint8_t *auth; uint16_t auth_len; #endif - ctx = ifp->ctx; state = D6_STATE(ifp); if (state == NULL || state->send == NULL) { logdebugx("%s: DHCPv6 reply received but not running", @@ -3446,21 +3484,16 @@ dhcp6_recvif(struct interface *ifp, const char *sfrom, struct dhcp6_message *r, } ifo = ifp->options; - for (i = 0, opt = ctx->dhcp6_opts; i < ctx->dhcp6_opts_len; - i++, opt++) { - if (has_option_mask(ifo->requiremask6, opt->option) && - !dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL)) { - logwarnx("%s: reject DHCPv6 (no option %s) from %s", - ifp->name, opt->var, sfrom); - return; - } - if (has_option_mask(ifo->rejectmask6, opt->option) && - dhcp6_findmoption(r, len, (uint16_t)opt->option, NULL)) { - logwarnx("%s: reject DHCPv6 (option %s) from %s", - ifp->name, opt->var, sfrom); - return; - } - } + pg = &ifo->dhopg_dhcp6; + policy.ifname = ifp->name; + + err = dho_policy_check(&pg->dhop_require, dhcp6_require, &policy); + if (err == -1) + return; + + err = dho_policy_check(&pg->dhop_reject, dhcp6_reject, &policy); + if (err == -1) + return; #ifdef AUTH /* Authenticate the message */ @@ -3506,8 +3539,7 @@ dhcp6_recvif(struct interface *ifp, const char *sfrom, struct dhcp6_message *r, case DH6S_DISCOVER: /* Only accept REPLY in DISCOVER for RAPID_COMMIT. * Normally we get an ADVERTISE for a DISCOVER. */ - if (!has_option_mask(ifo->requestmask6, - D6_OPTION_RAPID_COMMIT) || + if (!dho_policy_allowed(pg, D6_OPTION_RAPID_COMMIT) || !dhcp6_findmoption(r, len, D6_OPTION_RAPID_COMMIT, NULL)) { valid_op = false; @@ -3991,8 +4023,8 @@ dhcp6_start1(void *arg) struct interface *ifp = arg; struct dhcpcd_ctx *ctx = ifp->ctx; struct if_options *ifo = ifp->options; + struct dho_policy_group *pg = &ifo->dhopg_dhcp6; struct dhcp6_state *state; - size_t i; const struct dhcp_compat *dhc; if ((ctx->options & (DHCPCD_MANAGER | DHCPCD_PRIVSEP)) == @@ -4019,25 +4051,33 @@ dhcp6_start1(void *arg) state = D6_STATE(ifp); /* If no DHCPv6 options are configured, match configured DHCPv4 options to DHCPv6 equivalents. */ - for (i = 0; i < sizeof(ifo->requestmask6); i++) { - if (ifo->requestmask6[i] != '\0') - break; - } - if (i == sizeof(ifo->requestmask6)) { + if (pg->dhop_request.dhop_policy_len == 0) { + const struct dho_policy_group *dpg = &ifo->dhopg_dhcp; + int err; + for (dhc = dhcp_compats; dhc->dhcp_opt; dhc++) { - if (DHC_REQ(ifo->requestmask, ifo->nomask, - dhc->dhcp_opt)) - add_option_mask(ifo->requestmask6, - dhc->dhcp6_opt); + if (!dho_policy_has(&dpg->dhop_request, dhc->dhcp_opt)) + continue; + err = dho_policy_add(&pg->dhop_request, dhc->dhcp6_opt); + if (err == -1) { + logerr(__func__); + return; + } + } + if (ifo->fqdn != FQDN_DISABLE || + ifo->options & DHCPCD_HOSTNAME) { + err = dho_policy_add(&pg->dhop_request, D6_OPTION_FQDN); + if (err == -1) { + logerr(__func__); + return; + } } - if (ifo->fqdn != FQDN_DISABLE || ifo->options & DHCPCD_HOSTNAME) - add_option_mask(ifo->requestmask6, D6_OPTION_FQDN); } #ifndef SMALL /* Rapid commit won't work with Prefix Delegation Exclusion */ if (dhcp6_findselfsla(ifp)) - del_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT); + dho_policy_del(&pg->dhop_request, D6_OPTION_RAPID_COMMIT); #endif if (state->state == DH6S_INFORM) @@ -4332,6 +4372,7 @@ dhcp6_env(FILE *fp, const char *prefix, const struct interface *ifp, const struct dhcp6_message *m, size_t len) { const struct if_options *ifo; + const struct dho_policy_group *pg; struct dhcp_opt *opt, *vo; const uint8_t *p; struct dhcp6_option o; @@ -4356,6 +4397,7 @@ dhcp6_env(FILE *fp, const char *prefix, const struct interface *ifp, } ifo = ifp->options; + pg = &ifo->dhopg_dhcp6; ctx = ifp->ctx; /* Zero our indexes */ @@ -4387,7 +4429,7 @@ dhcp6_env(FILE *fp, const char *prefix, const struct interface *ifp, break; } o.code = ntohs(o.code); - if (has_option_mask(ifo->nomask6, o.code)) + if (!dho_policy_allowed(pg, o.code)) continue; for (i = 0, opt = ifo->dhcp6_override; i < ifo->dhcp6_override_len; i++, opt++) diff --git a/src/dhcpcd.conf.5.in b/src/dhcpcd.conf.5.in index 6edf8bc0..82425b1c 100644 --- a/src/dhcpcd.conf.5.in +++ b/src/dhcpcd.conf.5.in @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd June 12, 2025 +.Dd June 29, 2026 .Dt DHCPCD.CONF 5 .Os .Sh NAME @@ -61,13 +61,16 @@ which is a space or comma separated list of patterns passed to .It Ic anonymous Enables Anonymity Profiles for DHCP, RFC 7844. Any DUID is ignored and ClientID is set to LL only. -All non essential options are then masked at this point, -but they could be unmasked by explicitly requesting the option -.Sy after -the -.Ic anonymous -option is processed. -As such, the +Then +.Nm +will only +.Ic allow +essential options. +Non essential options can be allowed by use of +.Ic allow +or +.Ic request . +The .Ic anonymous option .Sy should @@ -646,18 +649,23 @@ then DHCPv4 options are mapped to equivalent DHCPv6 options. .Pp Prepend nd_ to .Ar option -to handle ND options, but this only works for the -.Ic nooption , -.Ic reject -and -.Ic require -options. +to handle ND options. .Pp To see a list of options you can use, call .Nm dhcpcd with the .Fl V , Fl Fl variables argument. +.It Ic allow Ar option +Opposite of +.Ic nooption . +If there are no +.Ic allow +options then we negate +.Ic nooption +when working out what options we can send and receive. +.Ic option +is always allowed. .It Ic nooption Ar option Remove the option from the message before it's processed. .It Ic require Ar option diff --git a/src/if-options.c b/src/if-options.c index e9246f28..b821327a 100644 --- a/src/if-options.c +++ b/src/if-options.c @@ -178,6 +178,7 @@ const struct option cf_options[] = { { "background", no_argument, NULL, 'b' }, { "initial_interval", required_argument, NULL, O_INITIAL_INTERVAL }, { "backoff_cutoff", required_argument, NULL, O_BACKOFF_CUTOFF }, { "backoff_jitter", required_argument, NULL, O_BACKOFF_JITTER }, + { "allow", required_argument, NULL, O_ALLOW }, { NULL, 0, NULL, '\0' } }; static char * @@ -507,60 +508,32 @@ parse_addr(__unused struct in_addr *addr, __unused struct in_addr *net, #endif static void -set_option_space(struct dhcpcd_ctx *ctx, const char *arg, - const struct dhcp_opt **d, size_t *dl, const struct dhcp_opt **od, - size_t *odl, struct if_options *ifo, uint8_t *request[], uint8_t *require[], - uint8_t *no[], uint8_t *reject[]) +set_option_space(struct dhcpcd_ctx *ctx, struct if_options *ifo, + const char *arg, struct dho_policy_ctx *pctx, struct dho_policy_group **pg) { -#if !defined(INET) && !defined(INET6) - UNUSED(ctx); -#endif - -#ifdef INET6 if (strncmp(arg, "nd_", strlen("nd_")) == 0) { - *d = ctx->nd_opts; - *dl = ctx->nd_opts_len; - *od = ifo->nd_override; - *odl = ifo->nd_override_len; - *request = ifo->requestmasknd; - *require = ifo->requiremasknd; - *no = ifo->nomasknd; - *reject = ifo->rejectmasknd; + pctx->dopts = ctx->nd_opts; + pctx->dopts_len = ctx->nd_opts_len; + pctx->odopts = ifo->nd_override; + pctx->odopts_len = ifo->nd_override_len; + *pg = &ifo->dhopg_nd; return; } -#ifdef DHCP6 if (strncmp(arg, "dhcp6_", strlen("dhcp6_")) == 0) { - *d = ctx->dhcp6_opts; - *dl = ctx->dhcp6_opts_len; - *od = ifo->dhcp6_override; - *odl = ifo->dhcp6_override_len; - *request = ifo->requestmask6; - *require = ifo->requiremask6; - *no = ifo->nomask6; - *reject = ifo->rejectmask6; + pctx->dopts = ctx->dhcp6_opts; + pctx->dopts_len = ctx->dhcp6_opts_len; + pctx->odopts = ifo->dhcp6_override; + pctx->odopts_len = ifo->dhcp6_override_len; + *pg = &ifo->dhopg_dhcp6; return; } -#endif -#else - UNUSED(arg); -#endif -#ifdef INET - *d = ctx->dhcp_opts; - *dl = ctx->dhcp_opts_len; - *od = ifo->dhcp_override; - *odl = ifo->dhcp_override_len; -#else - *d = NULL; - *dl = 0; - *od = NULL; - *odl = 0; -#endif - *request = ifo->requestmask; - *require = ifo->requiremask; - *no = ifo->nomask; - *reject = ifo->rejectmask; + pctx->dopts = ctx->dhcp_opts; + pctx->dopts_len = ctx->dhcp_opts_len; + pctx->odopts = ifo->dhcp_override; + pctx->odopts_len = ifo->dhcp_override_len; + *pg = &ifo->dhopg_dhcp; } void @@ -633,6 +606,48 @@ strend(char *s) } #endif +static int +set_default_allow(struct if_options *ifo, struct dho_policy_group *pg) +{ + if (pg->dhop_allow.dhop_policy_len != 0) + return 0; + + /* Allow the bare minimum through */ +#ifdef INET + if (pg == &ifo->dhopg_dhcp) { + struct dho_policy *p = &pg->dhop_allow; + + if (dho_policy_add(p, DHO_SUBNETMASK) == -1 || + dho_policy_add(p, DHO_SUBNETMASK) == -1 || + dho_policy_add(p, DHO_CSR) == -1 || + dho_policy_add(p, DHO_ROUTER) == -1 || + dho_policy_add(p, DHO_DNSSERVER) == -1 || + dho_policy_add(p, DHO_DNSDOMAIN) == -1 || + dho_policy_add(p, DHO_BROADCAST) == -1 || + dho_policy_add(p, DHO_STATICROUTE) == -1 || + dho_policy_add(p, DHO_SERVERID) == -1 || + dho_policy_add(p, DHO_RENEWALTIME) == -1 || + dho_policy_add(p, DHO_REBINDTIME) == -1 || + dho_policy_add(p, DHO_DNSSEARCH) == -1) + return -1; + } +#endif + +#ifdef DHCP6 + if (pg == &ifo->dhopg_dhcp6) { + struct dho_policy *p = &pg->dhop_allow; + + if (dho_policy_add(p, D6_OPTION_DNS_SERVERS) == -1 || + dho_policy_add(p, D6_OPTION_DOMAIN_LIST) == -1 || + dho_policy_add(p, D6_OPTION_SOL_MAX_RT) == -1 || + dho_policy_add(p, D6_OPTION_INF_MAX_RT) == -1) + return -1; + } +#endif + + return 0; +} + static int parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, int opt, char *arg, struct dhcp_opt **ldop, struct dhcp_opt **edop) @@ -644,10 +659,10 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ssize_t s; struct in_addr addr, addr2; in_addr_t *naddr; - const struct dhcp_opt *d, *od; - uint8_t *request, *require, *no, *reject; + struct dho_policy_group *pg; + struct dho_policy_ctx pctx; struct dhcp_opt **dop, *ndop; - size_t *dop_len, dl, odl; + size_t *dop_len, dl; struct group *grp; #ifdef AUTH struct token *token; @@ -809,11 +824,10 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, - &require, &no, &reject); - if (make_option_mask(d, dl, od, odl, request, arg, 1) != 0 || - make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || - make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) { + set_option_space(ctx, ifo, arg, &pctx, &pg); + if (dho_policy_set(&pctx, &pg->dhop_request, arg, 1) != 0 || + dho_policy_set(&pctx, &pg->dhop_remove, arg, -1) != 0 || + dho_policy_set(&pctx, &pg->dhop_reject, arg, -1) != 0) { logerrx("unknown option: %s", arg); return -1; } @@ -822,11 +836,10 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, - &require, &no, &reject); - if (make_option_mask(d, dl, od, odl, reject, arg, 1) != 0 || - make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || - make_option_mask(d, dl, od, odl, require, arg, -1) != 0) { + set_option_space(ctx, ifo, arg, &pctx, &pg); + if (dho_policy_set(&pctx, &pg->dhop_reject, arg, 1) != 0 || + dho_policy_set(&pctx, &pg->dhop_request, arg, -1) != 0 || + dho_policy_set(&pctx, &pg->dhop_require, arg, -1) != 0) { logerrx("unknown option: %s", arg); return -1; } @@ -851,7 +864,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, } i = parse_addr(&ifo->req_addr, &ifo->req_mask, arg); if (p != NULL) { - /* Ensure the original string is preserved */ + /* Ensure the original string is + * preserved */ *p++ = '/'; if (i == 0) i = parse_addr(&ifo->req_brd, NULL, p); @@ -1060,7 +1074,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, return -1; } - /* If vendor starts with , then it is not encapsulated */ + /* If vendor starts with , then it is not encapsulated + */ if (p == arg) { arg++; s = parse_string((char *)ifo->vendor + 1, @@ -1121,7 +1136,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, p = UNCONST(arg); // Generally it's --waitip=46, but some expect // --waitip="4 6" to work as well. - // It's easier to allow it rather than have confusing docs. + // It's easier to allow it rather than have confusing + // docs. while (p != NULL && p[0] != '\0') { if (p[0] == '4' || p[1] == '4') ifo->options |= DHCPCD_WAITIP4; @@ -1181,13 +1197,13 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, else { dl = hwaddr_aton(NULL, arg); if (dl != 0) { - no = realloc(ctx->duid, dl); - if (no == NULL) + void *nduid = realloc(ctx->duid, dl); + if (nduid == NULL) { logerrx(__func__); - else { - ctx->duid = no; - ctx->duid_len = hwaddr_aton(no, arg); + return -1; } + ctx->duid = nduid; + ctx->duid_len = hwaddr_aton(nduid, arg); } } break; @@ -1252,11 +1268,10 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, - &require, &no, &reject); - if (make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || - make_option_mask(d, dl, od, odl, require, arg, -1) != 0 || - make_option_mask(d, dl, od, odl, no, arg, 1) != 0) { + set_option_space(ctx, ifo, arg, &pctx, &pg); + if (dho_policy_set(&pctx, &pg->dhop_request, arg, -1) != 0 || + dho_policy_set(&pctx, &pg->dhop_require, arg, -1) != 0 || + dho_policy_set(&pctx, &pg->dhop_remove, arg, 1) != 0) { logerrx("unknown option: %s", arg); return -1; } @@ -1265,12 +1280,11 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, - &require, &no, &reject); - if (make_option_mask(d, dl, od, odl, require, arg, 1) != 0 || - make_option_mask(d, dl, od, odl, request, arg, 1) != 0 || - make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || - make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) { + set_option_space(ctx, ifo, arg, &pctx, &pg); + if (dho_policy_set(&pctx, &pg->dhop_require, arg, 1) != 0 || + dho_policy_set(&pctx, &pg->dhop_request, arg, 1) != 0 || + dho_policy_set(&pctx, &pg->dhop_remove, arg, -1) != 0 || + dho_policy_set(&pctx, &pg->dhop_reject, arg, -1) != 0) { logerrx("unknown option: %s", arg); return -1; } @@ -1495,35 +1509,40 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, case O_NOIPV6: ifo->options &= ~DHCPCD_IPV6; break; + case O_ALLOW: + ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; + set_option_space(ctx, ifo, arg, &pctx, &pg); + if (set_default_allow(ifo, pg)) { + logerr("%s: set_default_allow", __func__); + return -1; + } + if (dho_policy_set(&pctx, &pg->dhop_allow, arg, 1) != 0 || + dho_policy_set(&pctx, &pg->dhop_remove, arg, -1) != 0 || + dho_policy_set(&pctx, &pg->dhop_reject, arg, -1) != 0) { + logerrx("unknown option: %s", arg); + return -1; + } + break; case O_ANONYMOUS: ifo->options |= DHCPCD_ANONYMOUS; ifo->options &= ~DHCPCD_HOSTNAME; ifo->fqdn = FQDN_DISABLE; - /* Block everything */ - memset(ifo->nomask, 0xff, sizeof(ifo->nomask)); - memset(ifo->nomask6, 0xff, sizeof(ifo->nomask6)); - /* Allow the bare minimum through */ #ifdef INET - del_option_mask(ifo->nomask, DHO_SUBNETMASK); - del_option_mask(ifo->nomask, DHO_CSR); - del_option_mask(ifo->nomask, DHO_ROUTER); - del_option_mask(ifo->nomask, DHO_DNSSERVER); - del_option_mask(ifo->nomask, DHO_DNSDOMAIN); - del_option_mask(ifo->nomask, DHO_BROADCAST); - del_option_mask(ifo->nomask, DHO_STATICROUTE); - del_option_mask(ifo->nomask, DHO_SERVERID); - del_option_mask(ifo->nomask, DHO_RENEWALTIME); - del_option_mask(ifo->nomask, DHO_REBINDTIME); - del_option_mask(ifo->nomask, DHO_DNSSEARCH); + if (set_default_allow(ifo, &ifo->dhopg_dhcp) == -1) { + logerr("%s: set_defaut_allow DHCP", __func__); + return -1; + } #endif #ifdef DHCP6 - del_option_mask(ifo->nomask6, D6_OPTION_DNS_SERVERS); - del_option_mask(ifo->nomask6, D6_OPTION_DOMAIN_LIST); - del_option_mask(ifo->nomask6, D6_OPTION_SOL_MAX_RT); - del_option_mask(ifo->nomask6, D6_OPTION_INF_MAX_RT); + if (set_default_allow(ifo, &ifo->dhopg_dhcp6) == -1) { + logerr("%s: set_default_allow DHCPv6", __func__); + return -1; + } #endif break; @@ -1553,9 +1572,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, - &require, &no, &reject); - if (make_option_mask(d, dl, od, odl, ifo->dstmask, arg, 2) != + set_option_space(ctx, ifo, arg, &pctx, &pg); + if (dho_policy_set(&pctx, &ifo->dhop_destination, arg, 2) != 0) { if (errno == EINVAL) logerrx("option does not take" @@ -2661,10 +2679,11 @@ finish_config(struct if_options *ifo) DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS); #ifdef INET - /* The exponential backoff cutoff must not be lower than the initial - * interval, otherwise the retransmission sequence would shrink rather - * than grow up to the cap. Clamp the cutoff up to the initial - * interval to preserve the documented "cap" semantics. */ + /* The exponential backoff cutoff must not be lower than the + * initial interval, otherwise the retransmission sequence would + * shrink rather than grow up to the cap. Clamp the cutoff up to + * the initial interval to preserve the documented "cap" + * semantics. */ if (ifo->backoff_cutoff < ifo->initial_interval) { logwarnx("backoff_cutoff (%u) is less than initial_interval " "(%u); raising backoff_cutoff to match", @@ -2958,8 +2977,8 @@ read_config(struct dhcpcd_ctx *ctx, const char *ifname, const char *ssid, skip = 1; continue; } - /* Skip arping if we have selected a profile but not parsing - * one. */ + /* Skip arping if we have selected a profile but not + * parsing one. */ if (profile && !have_profile && strcmp(option, "arping") == 0) continue; if (skip) @@ -3090,10 +3109,16 @@ free_options(struct dhcpcd_ctx *ctx, struct if_options *ifo) free(ifo->config); } + dho_policy_group_free(ifo->dhopg_dhcp); + dho_policy_free(ifo->dhop_destination); + + dho_policy_group_free(ifo->dhopg_nd); + dho_policy_group_free(ifo->dhopg_dhcp6); + #ifdef RT_FREE_ROUTE_TABLE - /* Stupidly, we don't know the interface when creating the options. - * As such, make sure each route has one so they can goto the - * free list. */ + /* Stupidly, we don't know the interface when creating the + * options. As such, make sure each route has one so they can + * goto the free list. */ ifp = ctx->ifaces != NULL ? TAILQ_FIRST(ctx->ifaces) : NULL; if (ifp != NULL) { RB_TREE_FOREACH(rt, &ifo->routes) diff --git a/src/if-options.h b/src/if-options.h index 6a2982f3..ecb45f47 100644 --- a/src/if-options.h +++ b/src/if-options.h @@ -203,6 +203,7 @@ #define O_INITIAL_INTERVAL O_BASE + 60 #define O_BACKOFF_CUTOFF O_BASE + 61 #define O_BACKOFF_JITTER O_BASE + 62 +#define O_ALLOW O_BASE + 63 extern const struct option cf_options[]; @@ -247,23 +248,24 @@ struct vsio { }; #endif +#define OPTION_POLICY_MAX 5 +struct dho_policy { + uint32_t *dhop_policy; + size_t dhop_policy_len; +}; + +struct dho_policy_group { + struct dho_policy dhop_request; + struct dho_policy dhop_require; + struct dho_policy dhop_allow; + struct dho_policy dhop_remove; + struct dho_policy dhop_reject; +}; + struct if_options { time_t mtime; uint8_t iaid[4]; int metric; - uint8_t requestmask[256 / NBBY]; - uint8_t requiremask[256 / NBBY]; - uint8_t nomask[256 / NBBY]; - uint8_t rejectmask[256 / NBBY]; - uint8_t dstmask[256 / NBBY]; - uint8_t requestmasknd[(UINT16_MAX + 1) / NBBY]; - uint8_t requiremasknd[(UINT16_MAX + 1) / NBBY]; - uint8_t nomasknd[(UINT16_MAX + 1) / NBBY]; - uint8_t rejectmasknd[(UINT16_MAX + 1) / NBBY]; - uint8_t requestmask6[(UINT16_MAX + 1) / NBBY]; - uint8_t requiremask6[(UINT16_MAX + 1) / NBBY]; - uint8_t nomask6[(UINT16_MAX + 1) / NBBY]; - uint8_t rejectmask6[(UINT16_MAX + 1) / NBBY]; uint32_t leasetime; uint32_t timeout; uint32_t reboot; @@ -276,6 +278,12 @@ struct if_options { unsigned long long options; bool randomise_hwaddr; + struct dho_policy_group dhopg_dhcp; + struct dho_policy dhop_destination; + + struct dho_policy_group dhopg_nd; + struct dho_policy_group dhopg_dhcp6; + struct in_addr req_addr; struct in_addr req_mask; struct in_addr req_brd; diff --git a/src/ipv4.c b/src/ipv4.c index 813b5ec2..892d60bb 100644 --- a/src/ipv4.c +++ b/src/ipv4.c @@ -338,7 +338,7 @@ inet_dhcproutes(rb_tree_t *routes, struct interface *ifp, bool *have_default) /* If configured, install a gateway to the desintion * for P2P interfaces. */ if (ifp->flags & IFF_POINTOPOINT && - has_option_mask(ifp->options->dstmask, DHO_ROUTER)) { + dho_policy_has(&ifp->options->dhop_destination, DHO_ROUTER)) { if ((rt = rt_new(ifp)) == NULL) return -1; in.s_addr = INADDR_ANY; diff --git a/src/ipv6nd.c b/src/ipv6nd.c index 2d45b8e9..a2693913 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -944,29 +944,74 @@ dhcp6_start(__unused struct interface *ifp, __unused enum DH6S init_state) } #endif +struct nd_policy_ctx { + struct dhcpcd_ctx *ctx; + int loglevel; + const char *ifname; + const char *sfrom; + struct icmp6_hdr *icp; + size_t len; +}; + +static int +nd_require(uint32_t option, void *arg) +{ + struct nd_policy_ctx *nd_ctx = arg; + size_t len = nd_ctx->len, olen; + uint8_t *p; + struct nd_opt_hdr ndo; + + struct dhcpcd_ctx *ctx = nd_ctx->ctx; + const char *soption; + + len -= sizeof(struct nd_router_advert); + p = ((uint8_t *)nd_ctx->icp) + sizeof(struct nd_router_advert); + for (; len > 0; p += olen, len -= olen) { + if (len < sizeof(ndo)) + break; + memcpy(&ndo, p, sizeof(ndo)); + olen = (size_t)ndo.nd_opt_len * 8; + if (olen > len) + break; + if (ndo.nd_opt_type == option) + return 0; + } + + soption = dhcp_option_string(ctx->nd_opts, ctx->nd_opts_len, option); + logmessage(nd_ctx->loglevel, + "%s: reject RA (missing option %s) from %s", nd_ctx->ifname, + soption, nd_ctx->sfrom); + return -1; +} + static void ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from, const char *sfrom, struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit) { - size_t i, olen, rlen; + size_t olen, rlen; struct nd_router_advert *nd_ra; struct nd_opt_hdr ndo; + struct nd_policy_ctx policy = { + .ctx = ctx, + .icp = icp, + .len = len, + .sfrom = sfrom, + }; struct nd_opt_prefix_info pi; struct nd_opt_mtu mtu; struct nd_opt_rdnss rdnss; struct nd_opt_ri ri; struct routeinfo *rinfo; struct if_options *ifo; + const struct dho_policy_group *pg; uint8_t *p; struct ra *rap; struct in6_addr pi_prefix; struct ipv6_addr *ia; - struct dhcp_opt *dho; - bool new_rap, new_data, has_address, has_opt; + bool new_rap, new_data, has_address; uint32_t old_lifetime; - int ifmtu; - int loglevel; + int err, ifmtu, loglevel; unsigned int flags; #ifdef IPV6_MANAGETEMPADDR bool new_ia; @@ -1042,11 +1087,16 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from, nd_ra = (struct nd_router_advert *)icp; - /* Validate */ loglevel = rap == NULL || rap->willexpire || !rap->isreachable ? LOG_ERR : LOG_DEBUG; + + policy.loglevel = loglevel; + policy.ifname = ifp->name; ifo = ifp->options; + pg = &ifo->dhopg_nd; + + /* Validate */ rlen = len; len -= sizeof(struct nd_router_advert); p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); @@ -1071,50 +1121,20 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from, break; } - if (has_option_mask(ifo->rejectmasknd, ndo.nd_opt_type)) { - for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len; - i++, dho++) { - if (dho->option == ndo.nd_opt_type) - break; - } - if (i == ctx->nd_opts_len) - logmessage(loglevel, - "%s: reject RA (option %d) from %s", - ifp->name, ndo.nd_opt_type, sfrom); - else - logmessage(loglevel, - "%s: reject RA (option %s) from %s", - ifp->name, dho->var, sfrom); + if (!dho_policy_allowed(pg, ndo.nd_opt_type)) { + const char *soption = dhcp_option_string(ctx->nd_opts, + ctx->nd_opts_len, ndo.nd_opt_type); + logmessage(loglevel, + "%s: reject RA (option %s) from %s", ifp->name, + soption, sfrom); return; } } + len = rlen; - for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len; i++, dho++) { - if (!has_option_mask(ifo->requiremasknd, dho->option)) - continue; - len = rlen; - len -= sizeof(struct nd_router_advert); - p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); - has_opt = false; - for (; len > 0; p += olen, len -= olen) { - if (len < sizeof(ndo)) - break; - memcpy(&ndo, p, sizeof(ndo)); - olen = (size_t)ndo.nd_opt_len * 8; - if (olen > len) - break; - if (ndo.nd_opt_type == dho->option) { - has_opt = true; - break; - } - } - if (has_opt) - continue; - logmessage(loglevel, "%s: reject RA (missing %s) from %s", - ifp->name, dho->var, sfrom); + err = dho_policy_check(&pg->dhop_require, nd_require, &policy); + if (err == -1) return; - } - len = rlen; /* We don't want to spam the log with the fact we got an RA every * 30 seconds or so, so only spam the log if it's different. */ @@ -1207,7 +1227,7 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from, if (olen > len) break; - if (has_option_mask(ifp->options->nomasknd, ndo.nd_opt_type)) + if (!dho_policy_allowed(pg, ndo.nd_opt_type)) continue; switch (ndo.nd_opt_type) { @@ -1613,12 +1633,14 @@ ipv6nd_env(FILE *fp, const struct interface *ifp) struct ipv6_addr *ia; struct timespec now; int pref; + const struct dho_policy_group *pg; clock_gettime(CLOCK_MONOTONIC, &now); i = n = 0; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface != ifp || rap->expired) continue; + pg = &rap->iface->options->dhopg_nd; i++; snprintf(ndprefix, sizeof(ndprefix), "nd%zu", i); if (efprintf(fp, "%s_from=%s", ndprefix, rap->sfrom) == -1) @@ -1666,8 +1688,7 @@ ipv6nd_env(FILE *fp, const struct interface *ifp) errno = EINVAL; break; } - if (has_option_mask(rap->iface->options->nomasknd, - ndo.nd_opt_type)) + if (!dho_policy_allowed(pg, ndo.nd_opt_type)) continue; for (j = 0, opt = rap->iface->options->nd_override; j < rap->iface->options->nd_override_len; @@ -1730,6 +1751,7 @@ void ipv6nd_expirera(void *arg) { struct interface *ifp; + const struct dho_policy_group *pg; struct ra *rap, *ran; struct timespec now; bool expired, valid; @@ -1754,6 +1776,7 @@ ipv6nd_expirera(void *arg) if (rap->iface != ifp || rap->expired) continue; valid = false; + pg = &rap->iface->options->dhopg_nd; /* lifetime may be set to infinite by rfc4191 route information */ if (rap->lifetime) { @@ -1831,8 +1854,7 @@ ipv6nd_expirera(void *arg) break; } - if (has_option_mask(rap->iface->options->nomasknd, - ndo.nd_opt_type)) + if (!dho_policy_allowed(pg, ndo.nd_opt_type)) continue; switch (ndo.nd_opt_type) { -- 2.47.3 From 8b312918d8f1885d7fe8fcc03a7e06ae8a0314b4 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 30 Jun 2026 13:29:28 +0100 Subject: [PATCH 16/16] Build all the targets on macos * Build all the targets on macos --- .github/workflows/build.yml | 12 +++++++++- src/dhcp6.c | 19 ++++++++++++++- src/if-linux.c | 2 ++ src/if-options.c | 46 +++++++++++++++++++++++++++++-------- src/if-options.h | 6 +++++ 5 files changed, 74 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33f3166f..83fee113 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,13 +17,23 @@ jobs: strategy: matrix: os: [ macos-26 ] + args: + - --sanitize + - --disable-ipv4 + - --disable-arp + - --disable-ipv4ll + - --disable-ipv6 + - --disable-dhcp6 + cppflags: + - + - -DSMALL runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Configure - run: ./configure --sanitize + run: CPPFLAGS="${{ matrix.cppflags }}" ./configure ${{ matrix.args }} - name: Build run: make diff --git a/src/dhcp6.c b/src/dhcp6.c index b11e4003..6ee4dede 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -154,6 +154,7 @@ struct dhcp_compat { * But we can support both as the hook scripts will uniqify the * results if the server returns both options. */ +#ifdef INET static const struct dhcp_compat dhcp_compats[] = { { DHO_DNSSERVER, D6_OPTION_DNS_SERVERS }, { DHO_HOSTNAME, D6_OPTION_FQDN }, { DHO_DNSDOMAIN, D6_OPTION_FQDN }, @@ -164,6 +165,7 @@ static const struct dhcp_compat dhcp_compats[] = { { DHO_DNSSERVER, { DHO_FQDN, D6_OPTION_FQDN }, { DHO_VIVCO, D6_OPTION_VENDOR_CLASS }, { DHO_VIVSO, D6_OPTION_VENDOR_OPTS }, { DHO_DNSSEARCH, D6_OPTION_DOMAIN_LIST }, { 0, 0 } }; +#endif static const char *const dhcp6_statuses[] = { "Success", "Unspecified Failure", "No Addresses Available", "No Binding", "Not On Link", "Use Multicast", @@ -4025,7 +4027,6 @@ dhcp6_start1(void *arg) struct if_options *ifo = ifp->options; struct dho_policy_group *pg = &ifo->dhopg_dhcp6; struct dhcp6_state *state; - const struct dhcp_compat *dhc; if ((ctx->options & (DHCPCD_MANAGER | DHCPCD_PRIVSEP)) == DHCPCD_MANAGER && @@ -4052,9 +4053,13 @@ dhcp6_start1(void *arg) /* If no DHCPv6 options are configured, match configured DHCPv4 options to DHCPv6 equivalents. */ if (pg->dhop_request.dhop_policy_len == 0) { +#ifdef INET + const struct dhcp_compat *dhc; const struct dho_policy_group *dpg = &ifo->dhopg_dhcp; +#endif int err; +#ifdef INET for (dhc = dhcp_compats; dhc->dhcp_opt; dhc++) { if (!dho_policy_has(&dpg->dhop_request, dhc->dhcp_opt)) continue; @@ -4064,6 +4069,18 @@ dhcp6_start1(void *arg) return; } } +#else + err = dho_policy_add(&pg->dhop_request, D6_OPTION_DNS_SERVERS); + if (err == -1) { + logerr(__func__); + return; + } + err = dho_policy_add(&pg->dhop_request, D6_OPTION_DOMAIN_LIST); + if (err == -1) { + logerr(__func__); + return; + } +#endif if (ifo->fqdn != FQDN_DISABLE || ifo->options & DHCPCD_HOSTNAME) { err = dho_policy_add(&pg->dhop_request, D6_OPTION_FQDN); diff --git a/src/if-linux.c b/src/if-linux.c index 41893815..d7b8330b 100644 --- a/src/if-linux.c +++ b/src/if-linux.c @@ -170,7 +170,9 @@ static const uint8_t ipv4_bcast_addr[] = { }; #endif +#ifdef INET static int if_addressexists(struct interface *, struct in_addr *); +#endif #define PROC_PROMOTE "/proc/sys/net/ipv4/conf/%s/promote_secondaries" #define SYS_BRIDGE "/sys/class/net/%s/bridge/bridge_id" diff --git a/src/if-options.c b/src/if-options.c index b821327a..e584a2d5 100644 --- a/src/if-options.c +++ b/src/if-options.c @@ -507,33 +507,49 @@ parse_addr(__unused struct in_addr *addr, __unused struct in_addr *net, } #endif -static void +static int set_option_space(struct dhcpcd_ctx *ctx, struct if_options *ifo, const char *arg, struct dho_policy_ctx *pctx, struct dho_policy_group **pg) { if (strncmp(arg, "nd_", strlen("nd_")) == 0) { +#ifdef INET6 pctx->dopts = ctx->nd_opts; pctx->dopts_len = ctx->nd_opts_len; pctx->odopts = ifo->nd_override; pctx->odopts_len = ifo->nd_override_len; *pg = &ifo->dhopg_nd; - return; + return 0; +#else + errno = EPFNOSUPPORT; + return -1; +#endif } if (strncmp(arg, "dhcp6_", strlen("dhcp6_")) == 0) { +#ifdef DHCP6 pctx->dopts = ctx->dhcp6_opts; pctx->dopts_len = ctx->dhcp6_opts_len; pctx->odopts = ifo->dhcp6_override; pctx->odopts_len = ifo->dhcp6_override_len; *pg = &ifo->dhopg_dhcp6; - return; + return 0; +#else + errno = EPFNOSUPPORT; + return -1; +#endif } +#ifdef INET pctx->dopts = ctx->dhcp_opts; pctx->dopts_len = ctx->dhcp_opts_len; pctx->odopts = ifo->dhcp_override; pctx->odopts_len = ifo->dhcp_override_len; *pg = &ifo->dhopg_dhcp; + return 0; +#else + errno = EPFNOSUPPORT; + return -1; +#endif } void @@ -824,7 +840,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, ifo, arg, &pctx, &pg); + if (set_option_space(ctx, ifo, arg, &pctx, &pg) == -1) + return 0; if (dho_policy_set(&pctx, &pg->dhop_request, arg, 1) != 0 || dho_policy_set(&pctx, &pg->dhop_remove, arg, -1) != 0 || dho_policy_set(&pctx, &pg->dhop_reject, arg, -1) != 0) { @@ -836,7 +853,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, ifo, arg, &pctx, &pg); + if (set_option_space(ctx, ifo, arg, &pctx, &pg) == -1) + return 0; if (dho_policy_set(&pctx, &pg->dhop_reject, arg, 1) != 0 || dho_policy_set(&pctx, &pg->dhop_request, arg, -1) != 0 || dho_policy_set(&pctx, &pg->dhop_require, arg, -1) != 0) { @@ -1268,7 +1286,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, ifo, arg, &pctx, &pg); + if (set_option_space(ctx, ifo, arg, &pctx, &pg) == -1) + return 0; if (dho_policy_set(&pctx, &pg->dhop_request, arg, -1) != 0 || dho_policy_set(&pctx, &pg->dhop_require, arg, -1) != 0 || dho_policy_set(&pctx, &pg->dhop_remove, arg, 1) != 0) { @@ -1280,7 +1299,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, ifo, arg, &pctx, &pg); + if (set_option_space(ctx, ifo, arg, &pctx, &pg) == -1) + return 0; if (dho_policy_set(&pctx, &pg->dhop_require, arg, 1) != 0 || dho_policy_set(&pctx, &pg->dhop_request, arg, 1) != 0 || dho_policy_set(&pctx, &pg->dhop_remove, arg, -1) != 0 || @@ -1513,7 +1533,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, ifo, arg, &pctx, &pg); + if (set_option_space(ctx, ifo, arg, &pctx, &pg) == -1) + return 0; if (set_default_allow(ifo, pg)) { logerr("%s: set_default_allow", __func__); return -1; @@ -1572,7 +1593,8 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ARG_REQUIRED; if (ctx->options & DHCPCD_PRINT_PIDFILE) break; - set_option_space(ctx, ifo, arg, &pctx, &pg); + if (set_option_space(ctx, ifo, arg, &pctx, &pg) == -1) + return 0; if (dho_policy_set(&pctx, &ifo->dhop_destination, arg, 2) != 0) { if (errno == EINVAL) @@ -3109,11 +3131,17 @@ free_options(struct dhcpcd_ctx *ctx, struct if_options *ifo) free(ifo->config); } +#ifdef INET dho_policy_group_free(ifo->dhopg_dhcp); dho_policy_free(ifo->dhop_destination); +#endif +#ifdef INET6 dho_policy_group_free(ifo->dhopg_nd); +#endif +#ifdef DHCP6 dho_policy_group_free(ifo->dhopg_dhcp6); +#endif #ifdef RT_FREE_ROUTE_TABLE /* Stupidly, we don't know the interface when creating the diff --git a/src/if-options.h b/src/if-options.h index ecb45f47..288426ce 100644 --- a/src/if-options.h +++ b/src/if-options.h @@ -278,11 +278,17 @@ struct if_options { unsigned long long options; bool randomise_hwaddr; +#ifdef INET struct dho_policy_group dhopg_dhcp; struct dho_policy dhop_destination; +#endif +#ifdef INET6 struct dho_policy_group dhopg_nd; +#endif +#ifdef DHCP6 struct dho_policy_group dhopg_dhcp6; +#endif struct in_addr req_addr; struct in_addr req_mask; -- 2.47.3