From: David Härdeman Date: Sun, 23 Nov 2025 18:17:04 +0000 (+0100) Subject: dhcpv4: support IPv6-only preferred (RFC8925) X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=338ca8abb950e4e0448a13d50d6d6567a167d016;p=project%2Fodhcpd.git dhcpv4: support IPv6-only preferred (RFC8925) This adds support for RFC8925/IPv6-only preferred to the DHCPv4 server. Closes: https://github.com/openwrt/odhcpd/pull/235 Signed-off-by: David Härdeman Link: https://github.com/openwrt/odhcpd/pull/327 Signed-off-by: Álvaro Fernández Rojas --- diff --git a/README.md b/README.md index 663986a..3907089 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ and may also receive information from ubus | ntp |list |``| NTP servers to announce accepts IPv4 and IPv6 | | upstream |list | - | A list of interfaces which can be used as a source of configuration information (e.g. for NTP servers, if not set explicitly). | | captive_portal_uri |string | no | The API URI to be sent in RFC8910 captive portal options, via DHCPv4, DHCPv6, and ICMPv6 RA. | +| ipv6_only_preferred |integer| 0 | Indicate that IPv6-only mode is preferred (RFC8925) [V6ONLY_WAIT time in seconds] | [//]: # "dhcpv6_raw - string - not documented, may change when generic DHCPv4/DHCPv6 options are added" diff --git a/src/config.c b/src/config.c index b7f32ee..ec110b5 100644 --- a/src/config.c +++ b/src/config.c @@ -22,6 +22,7 @@ #include "odhcpd.h" #include "router.h" #include "dhcpv6-pxe.h" +#include "dhcpv4.h" static struct blob_buf b; @@ -139,6 +140,7 @@ enum { IFACE_ATTR_MAX_VALID_LIFETIME, IFACE_ATTR_NTP, IFACE_ATTR_CAPTIVE_PORTAL_URI, + IFACE_ATTR_IPV6_ONLY_PREFERRED, IFACE_ATTR_MAX }; @@ -192,6 +194,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [IFACE_ATTR_MAX_VALID_LIFETIME] = { .name = "max_valid_lifetime", .type = BLOBMSG_TYPE_STRING }, [IFACE_ATTR_NTP] = { .name = "ntp", .type = BLOBMSG_TYPE_ARRAY }, [IFACE_ATTR_CAPTIVE_PORTAL_URI] = { .name = "captive_portal_uri", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_IPV6_ONLY_PREFERRED] = { .name = "ipv6_only_preferred", .type = BLOBMSG_TYPE_INT32 }, }; const struct uci_blob_param_list interface_attr_list = { @@ -1652,6 +1655,19 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr } } + if ((c = tb[IFACE_ATTR_IPV6_ONLY_PREFERRED])) { + uint32_t v6only_wait = blobmsg_get_u32(c); + + if (v6only_wait > 0 && v6only_wait < DHCPV4_MIN_V6ONLY_WAIT) { + warn("Invalid %s value configured for interface '%s', clamped to %d", + iface_attrs[IFACE_ATTR_IPV6_ONLY_PREFERRED].name, + iface->name, DHCPV4_MIN_V6ONLY_WAIT); + v6only_wait = DHCPV4_MIN_V6ONLY_WAIT; + } + + iface->dhcpv4_v6only_wait = v6only_wait; + } + if ((c = tb[IFACE_ATTR_RA_PREFERENCE])) { const char *prio = blobmsg_get_string(c); diff --git a/src/dhcpv4.c b/src/dhcpv4.c index 97c1ec2..17c34ba 100644 --- a/src/dhcpv4.c +++ b/src/dhcpv4.c @@ -789,6 +789,7 @@ enum { IOV_DNR, IOV_DNR_BODY, IOV_CAPTIVE_PORTAL, + IOV_IPV6_ONLY_PREF, IOV_END, IOV_PADDING, IOV_TOTAL @@ -810,6 +811,7 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len, uint8_t *req_clientid = NULL; size_t req_clientid_len = 0; bool req_accept_fr = false; + bool ipv6_only = false; /* Reply variables */ struct dhcpv4_message reply = { @@ -901,6 +903,11 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len, struct dhcpv4_option reply_dnr = { .code = DHCPV4_OPT_DNR, }; + struct dhcpv4_option_u32 reply_ipv6_only = { + .code = DHCPV4_OPT_IPV6_ONLY_PREFERRED, + .len = sizeof(uint32_t), + .data = htonl(iface->dhcpv4_v6only_wait), + }; uint8_t reply_end = DHCPV4_OPT_END; struct iovec iov[IOV_TOTAL] = { @@ -931,6 +938,7 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len, [IOV_DNR] = { &reply_dnr, 0 }, [IOV_DNR_BODY] = { NULL, 0 }, [IOV_CAPTIVE_PORTAL] = { NULL, 0 }, + [IOV_IPV6_ONLY_PREF] = { &reply_ipv6_only, 0 }, [IOV_END] = { &reply_end, sizeof(reply_end) }, [IOV_PADDING] = { NULL, 0 }, }; @@ -986,10 +994,12 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len, return; break; case DHCPV4_OPT_REQOPTS: - if (opt->len > 0) { - req_opts = opt->data; - req_opts_len = opt->len; - } + req_opts = opt->data; + req_opts_len = opt->len; + if (iface->dhcpv4_v6only_wait) + for (uint8_t i = 0; i < opt->len; i++) + if (opt->data[i] == DHCPV4_OPT_IPV6_ONLY_PREFERRED) + ipv6_only = true; break; case DHCPV4_OPT_CLIENTID: if (opt->len >= 2) { @@ -1028,6 +1038,9 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len, &reply_incl_fr, &fr_serverid); return; case DHCPV4_MSG_DISCOVER: + if (ipv6_only) + break; + _o_fallthrough; case DHCPV4_MSG_REQUEST: lease = dhcpv4_lease(iface, req_msg, req->chaddr, req_clientid, req_clientid_len, req_addr, &req_leasetime, @@ -1041,7 +1054,7 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len, /* We are at the point where we know the client expects a reply */ switch (req_msg) { case DHCPV4_MSG_DISCOVER: - if (!lease) + if (!lease && !ipv6_only) return; reply_msg.data = DHCPV4_MSG_OFFER; break; @@ -1296,6 +1309,10 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len, iov[IOV_DNR_BODY].iov_len = dnrs_len; break; + case DHCPV4_OPT_IPV6_ONLY_PREFERRED: + iov[IOV_IPV6_ONLY_PREF].iov_len = sizeof(reply_ipv6_only); + break; + case DHCPV4_OPT_CAPTIVE_PORTAL: size_t uri_len = iface->captive_portal_uri_len; if (uri_len == 0 || uri_len > UINT8_MAX) diff --git a/src/dhcpv4.h b/src/dhcpv4.h index 045f78b..e601da6 100644 --- a/src/dhcpv4.h +++ b/src/dhcpv4.h @@ -29,6 +29,9 @@ #define DHCPV4_FR_MIN_DELAY 500 #define DHCPV4_FR_MAX_FUZZ 500 +// RFC8925, §3.4 +#define DHCPV4_MIN_V6ONLY_WAIT 300 + // RFC4361, §6.1 #define DHCPV4_CLIENTID_TYPE_DUID_IAID 255 @@ -80,6 +83,7 @@ enum dhcpv4_opt { DHCPV4_OPT_CLIENTID = 61, DHCPV4_OPT_USER_CLASS = 77, DHCPV4_OPT_AUTHENTICATION = 90, + DHCPV4_OPT_IPV6_ONLY_PREFERRED = 108, // RFC8925 DHCPV4_OPT_CAPTIVE_PORTAL = 114, // RFC8910 DHCPV4_OPT_DNS_DOMAIN_SEARCH = 119, DHCPV4_OPT_FORCERENEW_NONCE_CAPABLE = 145, diff --git a/src/odhcpd.h b/src/odhcpd.h index a5ba496..8a6461c 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -450,6 +450,7 @@ struct interface { struct in_addr *dhcpv4_routers; // IPv4 addresses for routers on this subnet size_t dhcpv4_routers_cnt; // Count of router addresses bool dhcpv4_forcereconf; + uint32_t dhcpv4_v6only_wait; // V6ONLY_WAIT for the IPv6-only preferred option (RFC8925) // DNS struct in_addr *dns_addrs4; // IPv4 DNS server addresses to announce