summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Härdeman2025-10-09 08:38:23 +0000
committerÁlvaro Fernández Rojas2025-11-10 12:05:34 +0000
commite63ec4001f837fd26909ba427d2efd6150ced68f (patch)
tree4228c817f6bd2b916e5a42aba6e8574b4ea3406d
parent29357349b33a171f276fb248d416ef3239dad3c3 (diff)
downloadodhcpd-e63ec4001f837fd26909ba427d2efd6150ced68f.tar.gz
dhcpv4: add support for RFC4361-style clientid
This adds support for DHCPv6-style client identifiers to the dhcpv4 codebase, as per RFC4361. It also ensures RFC6842-compliance by including the client identifier (no matter which kind it is) in replies. Signed-off-by: David Härdeman <david@hardeman.nu> Link: https://github.com/openwrt/odhcpd/pull/299 Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
-rw-r--r--src/dhcpv4.c103
-rw-r--r--src/dhcpv4.h4
-rw-r--r--src/odhcpd.h5
3 files changed, 98 insertions, 14 deletions
diff --git a/src/dhcpv4.c b/src/dhcpv4.c
index b64b1c3..73bb66c 100644
--- a/src/dhcpv4.c
+++ b/src/dhcpv4.c
@@ -348,14 +348,16 @@ void dhcpv4_free_lease(struct dhcpv4_lease *lease)
}
static struct dhcpv4_lease *
-dhcpv4_alloc_lease(struct interface *iface, const uint8_t *hwaddr, size_t hwaddr_len)
+dhcpv4_alloc_lease(struct interface *iface, const uint8_t *hwaddr,
+ size_t hwaddr_len, const uint8_t *duid, size_t duid_len,
+ uint32_t iaid)
{
struct dhcpv4_lease *lease;
if (!iface || !hwaddr || hwaddr_len == 0 || hwaddr_len > sizeof(lease->hwaddr))
return NULL;
- lease = calloc(1, sizeof(*lease));
+ lease = calloc(1, sizeof(*lease) + duid_len);
if (!lease)
return NULL;
@@ -363,6 +365,11 @@ dhcpv4_alloc_lease(struct interface *iface, const uint8_t *hwaddr, size_t hwaddr
lease->hwaddr_len = hwaddr_len;
memcpy(lease->hwaddr, hwaddr, hwaddr_len);
+ if (duid_len > 0) {
+ lease->duid_len = duid_len;
+ memcpy(lease->duid, duid, duid_len);
+ lease->iaid = iaid;
+ }
lease->iface = iface;
return lease;
@@ -477,15 +484,55 @@ static struct dhcpv4_lease *find_lease_by_hwaddr(struct interface *iface, const
}
static struct dhcpv4_lease *
+find_lease_by_duid_iaid(struct interface *iface, const uint8_t *duid,
+ size_t duid_len, uint32_t iaid)
+{
+ struct dhcpv4_lease *lease;
+
+ list_for_each_entry(lease, &iface->dhcpv4_leases, head) {
+ if (lease->duid_len != duid_len || lease->iaid != iaid)
+ continue;
+ if (!memcmp(lease->duid, duid, duid_len))
+ return lease;
+ }
+
+ return NULL;
+}
+
+static struct dhcpv4_lease *
dhcpv4_lease(struct interface *iface, enum dhcpv4_msg req_msg, const uint8_t *req_mac,
- const uint32_t req_addr, uint32_t *req_leasetime, const char *req_hostname,
- const size_t req_hostname_len, const bool req_accept_fr, bool *reply_incl_fr,
+ const uint8_t *clid, size_t clid_len, const uint32_t req_addr,
+ uint32_t *req_leasetime, const char *req_hostname, const size_t
+ req_hostname_len, const bool req_accept_fr, bool *reply_incl_fr,
uint32_t *fr_serverid)
{
- struct dhcpv4_lease *lease = find_lease_by_hwaddr(iface, req_mac);
- struct lease_cfg *lease_cfg = config_find_lease_cfg_by_mac(req_mac);
+ struct dhcpv4_lease *lease = NULL;
+ struct lease_cfg *lease_cfg = NULL;
+ const uint8_t *duid = NULL;
+ size_t duid_len = 0;
+ uint32_t iaid = 0;
time_t now = odhcpd_time();
+ // RFC4361, §6.1, §6.3 - MUST use clid if provided, MAY use chaddr
+ if (clid && clid_len > (1 + sizeof(iaid) + DUID_MIN_LEN) &&
+ clid[0] == DHCPV4_CLIENTID_TYPE_DUID_IAID &&
+ clid_len <= (1 + sizeof(iaid) + DUID_MAX_LEN)) {
+ memcpy(&iaid, &clid[1], sizeof(uint32_t));
+ iaid = ntohl(iaid);
+
+ duid = &clid[1 + sizeof(iaid)];
+ duid_len = clid_len - (1 + sizeof(iaid));
+
+ lease = find_lease_by_duid_iaid(iface, duid, duid_len, iaid);
+ lease_cfg = config_find_lease_cfg_by_duid_and_iaid(duid, duid_len, iaid);
+ }
+
+ if (!lease)
+ lease = find_lease_by_hwaddr(iface, req_mac);
+
+ if (!lease_cfg)
+ lease_cfg = config_find_lease_cfg_by_mac(req_mac);
+
/*
* If we found a static lease cfg, but no old assignment for this
* hwaddr, we need to clear out any old assignments given to other
@@ -527,8 +574,9 @@ dhcpv4_lease(struct interface *iface, enum dhcpv4_msg req_msg, const uint8_t *re
if (!(lease->flags & OAF_STATIC) || lease->lease_cfg->ipaddr != lease->addr) {
memset(lease->hwaddr, 0, sizeof(lease->hwaddr));
lease->valid_until = now + 3600; /* Block address for 1h */
- } else
+ } else {
lease->valid_until = now - 1;
+ }
break;
case DHCPV4_MSG_DISCOVER:
@@ -554,7 +602,7 @@ dhcpv4_lease(struct interface *iface, enum dhcpv4_msg req_msg, const uint8_t *re
if (!lease) {
/* Create new binding */
- lease = dhcpv4_alloc_lease(iface, req_mac, ETH_ALEN);
+ lease = dhcpv4_alloc_lease(iface, req_mac, ETH_ALEN, duid, duid_len, iaid);
if (!lease) {
warn("Failed to allocate memory for DHCPv4 lease on interface %s", iface->ifname);
return NULL;
@@ -701,6 +749,8 @@ enum {
IOV_HEADER = 0,
IOV_MESSAGE,
IOV_SERVERID,
+ IOV_CLIENTID,
+ IOV_CLIENTID_DATA,
IOV_NETMASK,
IOV_ROUTER,
IOV_ROUTER_ADDR,
@@ -740,6 +790,8 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
uint32_t req_leasetime = 0;
char *req_hostname = NULL;
size_t req_hostname_len = 0;
+ uint8_t *req_clientid = NULL;
+ size_t req_clientid_len = 0;
bool req_accept_fr = false;
/* Reply variables */
@@ -770,6 +822,9 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
.len = sizeof(struct in_addr),
.data = iface->dhcpv4_local.s_addr,
};
+ struct dhcpv4_option reply_clientid = {
+ .code = DHCPV4_OPT_CLIENTID,
+ };
struct dhcpv4_option_u32 reply_netmask = {
.code = DHCPV4_OPT_NETMASK,
.len = sizeof(uint32_t),
@@ -835,6 +890,8 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
[IOV_HEADER] = { &reply, sizeof(reply) },
[IOV_MESSAGE] = { &reply_msg, sizeof(reply_msg) },
[IOV_SERVERID] = { &reply_serverid, sizeof(reply_serverid) },
+ [IOV_CLIENTID] = { &reply_clientid, 0 },
+ [IOV_CLIENTID_DATA] = { NULL, 0 },
[IOV_NETMASK] = { &reply_netmask, 0 },
[IOV_ROUTER] = { &reply_router, 0 },
[IOV_ROUTER_ADDR] = { NULL, 0 },
@@ -871,6 +928,7 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
DHCPV4_OPT_LEASETIME,
DHCPV4_OPT_RENEW,
DHCPV4_OPT_REBIND,
+ DHCPV4_OPT_CLIENTID, // Must be in reply if present in req, RFC6842, §3
DHCPV4_OPT_AUTHENTICATION,
DHCPV4_OPT_SEARCH_DOMAIN,
DHCPV4_OPT_FORCERENEW_NONCE_CAPABLE,
@@ -927,6 +985,12 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
req_opts_len = opt->len;
}
break;
+ case DHCPV4_OPT_CLIENTID:
+ if (opt->len >= 2) {
+ req_clientid = opt->data;
+ req_clientid_len = opt->len;
+ }
+ break;
case DHCPV4_OPT_LEASETIME:
if (opt->len == 4) {
memcpy(&req_leasetime, opt->data, 4);
@@ -953,16 +1017,18 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
case DHCPV4_MSG_DECLINE:
_fallthrough;
case DHCPV4_MSG_RELEASE:
- dhcpv4_lease(iface, req_msg, req->chaddr, req_addr,
- &req_leasetime, req_hostname, req_hostname_len,
- req_accept_fr, &reply_incl_fr, &fr_serverid);
+ dhcpv4_lease(iface, req_msg, req->chaddr, req_clientid,
+ req_clientid_len, req_addr, &req_leasetime,
+ req_hostname, req_hostname_len, req_accept_fr,
+ &reply_incl_fr, &fr_serverid);
return;
case DHCPV4_MSG_DISCOVER:
_fallthrough;
case DHCPV4_MSG_REQUEST:
- lease = dhcpv4_lease(iface, req_msg, req->chaddr, req_addr,
- &req_leasetime, req_hostname, req_hostname_len,
- req_accept_fr, &reply_incl_fr, &fr_serverid);
+ lease = dhcpv4_lease(iface, req_msg, req->chaddr, req_clientid,
+ req_clientid_len, req_addr, &req_leasetime,
+ req_hostname, req_hostname_len, req_accept_fr,
+ &reply_incl_fr, &fr_serverid);
break;
default:
return;
@@ -1103,6 +1169,15 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
iov[IOV_REBIND].iov_len = sizeof(reply_rebind);
break;
+ case DHCPV4_OPT_CLIENTID:
+ if (!req_clientid)
+ break;
+ reply_clientid.len = req_clientid_len;
+ iov[IOV_CLIENTID].iov_len = sizeof(reply_clientid);
+ iov[IOV_CLIENTID_DATA].iov_base = req_clientid;
+ iov[IOV_CLIENTID_DATA].iov_len = req_clientid_len;
+ break;
+
case DHCPV4_OPT_AUTHENTICATION:
if (!lease || !reply_incl_fr || req_msg != DHCPV4_MSG_REQUEST)
break;
diff --git a/src/dhcpv4.h b/src/dhcpv4.h
index c3a9bcd..9b28059 100644
--- a/src/dhcpv4.h
+++ b/src/dhcpv4.h
@@ -25,6 +25,9 @@
#define DHCPV4_FR_MIN_DELAY 500
#define DHCPV4_FR_MAX_FUZZ 500
+// RFC4361, §6.1
+#define DHCPV4_CLIENTID_TYPE_DUID_IAID 255
+
enum dhcpv4_op {
DHCPV4_OP_BOOTREQUEST = 1,
DHCPV4_OP_BOOTREPLY = 2,
@@ -70,6 +73,7 @@ enum dhcpv4_opt {
DHCPV4_OPT_REQOPTS = 55,
DHCPV4_OPT_RENEW = 58,
DHCPV4_OPT_REBIND = 59,
+ DHCPV4_OPT_CLIENTID = 61,
DHCPV4_OPT_USER_CLASS = 77,
DHCPV4_OPT_AUTHENTICATION = 90,
DHCPV4_OPT_SEARCH_DOMAIN = 119,
diff --git a/src/odhcpd.h b/src/odhcpd.h
index 6449ae1..23f82d8 100644
--- a/src/odhcpd.h
+++ b/src/odhcpd.h
@@ -249,6 +249,11 @@ struct dhcpv4_lease {
unsigned fr_cnt; // FR messages sent
uint8_t key[16]; // FR nonce
struct odhcpd_ref_ip *fr_ip; // FR message old serverid/IP
+
+ // RFC4361
+ uint32_t iaid;
+ uint8_t duid_len;
+ uint8_t duid[];
};
struct dhcpv6_lease {