summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Härdeman2025-11-23 18:17:04 +0000
committerÁlvaro Fernández Rojas2025-11-28 06:58:58 +0000
commit338ca8abb950e4e0448a13d50d6d6567a167d016 (patch)
tree095b7def9626b4f529662c7fd654652d5cf270a6
parentd21e504b38ab4c880c43b7f1649104bb2f0d2d8b (diff)
downloadodhcpd-338ca8abb950e4e0448a13d50d6d6567a167d016.tar.gz
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 <david@hardeman.nu> Link: https://github.com/openwrt/odhcpd/pull/327 Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
-rw-r--r--README.md1
-rw-r--r--src/config.c16
-rw-r--r--src/dhcpv4.c27
-rw-r--r--src/dhcpv4.h4
-rw-r--r--src/odhcpd.h1
5 files changed, 44 insertions, 5 deletions
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 |`<local address>`| 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