Skip to content

Commit c7e00a8

Browse files
committed
dhcpv6: implement RFC9686
Registering Self-Generated IPv6 Addresses Using DHCPv6 This functionality registers a client SLAAC or static address in the DHCPv6 pool, primarily for diagnostic purposes as outlined in §1. Most of the machinery required for this functionality already exists, so we perform a few additional sets and checks in the IA code. The address registration mechanism overview: ``` +--------+ +------------------+ +---------------+ | CLIENT | | FIRST-HOP ROUTER | | DHCPv6 SERVER | +--------+ +---------+--------+ +-------+-------+ | SLAAC | | |<--------------------> | | | | | | | | src: link-local address | | --------------------------------------------> | | INFORMATION-REQUEST or SOLICIT/... | | - OPTION REQUEST OPTION | | -- OPTION_ADDR_REG_ENABLE | | | | ... | | | | | |<--------------------------------------------- | | REPLY or ADVERTISE MESSAGE | | - OPTION_ADDR_REG_ENABLE | | | | | | src: address being registered | | --------------------------------------------> | | ADDR-REG-INFORM MESSAGE |Register/ | |log addresses | | | | | <-------------------------------------------- | | ADDR-REG-REPLY MESSAGE | | | ``` Signed-off-by: Paul Donald <newtwen+github@gmail.com> Link: openwrt#353
1 parent cf51aeb commit c7e00a8

8 files changed

Lines changed: 300 additions & 6 deletions

File tree

src/config.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct sys_conf sys_conf = {
7171

7272
#define PD_MIN_LEN_MAX (64-2) // must delegate at least 2 bits of prefix
7373

74-
#define OAF_DHCPV6 (OAF_DHCPV6_NA | OAF_DHCPV6_PD)
74+
#define OAF_DHCPV6 (OAF_DHCPV6_NA | OAF_DHCPV6_PD | OAF_DHCPV6_ADDR_REG)
7575

7676
enum {
7777
IPV6_PXE_URL,

src/dhcpv6-ia.c

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,3 +1322,234 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
13221322
out:
13231323
return response_len;
13241324
}
1325+
1326+
/* Register or update address registration in the lease database */
1327+
static bool register_ia_addr_in_lease_db(struct sockaddr_in6 *source,
1328+
const uint8_t *clientid_data, uint16_t clientid_len,
1329+
const struct dhcpv6_ia_addr *ia_addr,
1330+
struct interface *iface)
1331+
{
1332+
/* RFC9686 §4.2.1: Lease lifetime is the valid-lifetime from IA Address option */
1333+
time_t now = odhcpd_time();
1334+
uint32_t valid_lt = ntohl(ia_addr->valid_lt);
1335+
time_t lease_end = now + valid_lt;
1336+
bool onlink = false;
1337+
1338+
/* Search for existing binding with this address */
1339+
struct dhcpv6_lease *binding = NULL, *lease = NULL;
1340+
list_for_each_entry(binding, &iface->ia_assignments, head) {
1341+
/* Check if this address is already registered by another client */
1342+
if ((binding->flags & OAF_DHCPV6_ADDR_REG) &&
1343+
memcmp(&binding->peer.sin6_addr, &ia_addr->addr, sizeof(struct in6_addr)) == 0) {
1344+
/* Found address registration for this address */
1345+
if (binding->duid_len == clientid_len &&
1346+
memcmp(binding->duid, clientid_data, clientid_len) == 0) {
1347+
/* Same client, update the lease */
1348+
lease = binding;
1349+
break;
1350+
} else {
1351+
/* RFC9686 §4.2.1: Different client, should log and update binding
1352+
* We'll update to the new client */
1353+
char addrbuf[INET6_ADDRSTRLEN];
1354+
inet_ntop(AF_INET6, &ia_addr->addr, addrbuf, sizeof(addrbuf));
1355+
debug("ADDR-REG: address collision for %s: was bound to "
1356+
"different client, updating", addrbuf);
1357+
lease = binding;
1358+
break;
1359+
}
1360+
}
1361+
}
1362+
1363+
if (!lease) {
1364+
/* Create new lease for this address registration */
1365+
lease = dhcpv6_alloc_lease(clientid_len);
1366+
if (!lease) {
1367+
char addrbuf[INET6_ADDRSTRLEN];
1368+
inet_ntop(AF_INET6, &ia_addr->addr, addrbuf, sizeof(addrbuf));
1369+
error("ADDR-REG: failed to allocate lease for %s", addrbuf);
1370+
return false;
1371+
}
1372+
1373+
/* Initialize lease structure */
1374+
lease->iface = iface;
1375+
lease->peer = *source;
1376+
lease->peer.sin6_addr = ia_addr->addr; /* Store full registered address */
1377+
lease->duid_len = clientid_len;
1378+
memcpy(lease->duid, clientid_data, clientid_len);
1379+
lease->length = 128;
1380+
1381+
/* Try to find matching interface prefix and extract host ID */
1382+
for (size_t i = 0; i < iface->addr6_len; i++) {
1383+
if (!valid_addr(&iface->addr6[i], now))
1384+
continue;
1385+
1386+
if (ADDR_MATCH_PIO_FILTER(&iface->addr6[i], iface))
1387+
continue;
1388+
1389+
/* RFC9686 §4.2.1: the server SHOULD verify that the address
1390+
* is "appropriate to the link" */
1391+
if (odhcpd_bmemcmp(&ia_addr->addr, &iface->addr6[i].addr.in6,
1392+
iface->addr6[i].prefix_len) == 0) {
1393+
/* Address is within this prefix - extract host ID portion */
1394+
onlink = true;
1395+
uint64_t host_id = 0;
1396+
memcpy(&host_id, &ia_addr->addr.s6_addr[8], 8);
1397+
lease->assigned_host_id = be64toh(host_id);
1398+
break;
1399+
}
1400+
}
1401+
1402+
if (onlink) {
1403+
/* Add to interface's lease list */
1404+
list_add(&lease->head, &iface->ia_assignments);
1405+
} else {
1406+
dhcpv6_free_lease(lease);
1407+
lease = NULL;
1408+
return onlink;
1409+
}
1410+
1411+
} else {
1412+
/* Update existing lease */
1413+
lease->peer = *source;
1414+
lease->peer.sin6_addr = ia_addr->addr; /* Update registered address */
1415+
lease->duid_len = clientid_len;
1416+
memcpy(lease->duid, clientid_data, clientid_len);
1417+
}
1418+
1419+
/* Update lifetimes */
1420+
lease->valid_until = lease_end;
1421+
lease->preferred_until = now + ntohl(ia_addr->preferred_lt);
1422+
lease->bound = true;
1423+
1424+
/* Mark flags - RFC9686 Address Registration lease */
1425+
lease->flags = OAF_DHCPV6_ADDR_REG;
1426+
1427+
char addrbuf[INET6_ADDRSTRLEN];
1428+
inet_ntop(AF_INET6, &ia_addr->addr, addrbuf, sizeof(addrbuf));
1429+
debug("ADDR-REG: registered %s for client with DUID length %d, "
1430+
"valid until %ld (in %u seconds)", addrbuf, clientid_len,
1431+
lease_end, valid_lt);
1432+
1433+
return true;
1434+
}
1435+
1436+
/* Send RFC9686 ADDR-REG-REPLY message to client */
1437+
static void send_ia_addr_reg_reply(struct sockaddr_in6 *source,
1438+
const struct dhcpv6_client_header *hdr,
1439+
const struct dhcpv6_ia_addr *ia_addr,
1440+
struct interface *iface)
1441+
{
1442+
/* RFC9686 §4.3: Reply MUST contain:
1443+
* - msg_type: ADDR-REG-REPLY (37)
1444+
* - transaction_id: copied from ADDR-REG-INFORM
1445+
* - IA Address option: identical to the one in the request
1446+
*/
1447+
1448+
struct _o_packed {
1449+
uint8_t msg_type;
1450+
uint8_t tr_id[3];
1451+
} reply = {
1452+
.msg_type = DHCPV6_MSG_ADDR_REG_REPLY,
1453+
};
1454+
1455+
/* Copy transaction ID from request */
1456+
memcpy(reply.tr_id, hdr->transaction_id, sizeof(reply.tr_id));
1457+
1458+
/* Prepare IA Address option (copy from request) */
1459+
struct iovec iov[2] = {
1460+
{&reply, sizeof(reply)},
1461+
{(void *)ia_addr, sizeof(struct dhcpv6_ia_addr)}
1462+
};
1463+
1464+
/* RFC9686 §4.3: If not relayed, destination is the address being registered.
1465+
* If relayed, we would construct Relay-reply (handled separately in relay_server_response).
1466+
* For direct replies, source is already the registered address. */
1467+
1468+
char addrbuf[INET6_ADDRSTRLEN];
1469+
inet_ntop(AF_INET6, &ia_addr->addr, addrbuf, sizeof(addrbuf));
1470+
debug("Sending ADDR-REG-REPLY for %s on %s", addrbuf, iface->name);
1471+
1472+
odhcpd_send(iface->dhcpv6_event.uloop.fd, source, iov, ARRAY_SIZE(iov), iface);
1473+
}
1474+
1475+
/* RFC9686 Address Registration Message Handler */
1476+
void handle_ia_addr_reg_inform(struct sockaddr_in6 *source,
1477+
const void *data, size_t len, struct interface *iface)
1478+
{
1479+
const struct dhcpv6_client_header *hdr = data;
1480+
uint8_t *opts = (uint8_t *)&hdr[1], *opts_end = (uint8_t *)data + len;
1481+
uint16_t otype, olen;
1482+
uint8_t *odata;
1483+
1484+
uint8_t *clientid_data = NULL;
1485+
uint16_t clientid_len = 0;
1486+
struct dhcpv6_ia_addr *ia_addr = NULL;
1487+
1488+
if (len < sizeof(*hdr)) {
1489+
debug("ADDR-REG-INFORM: message too short");
1490+
return;
1491+
}
1492+
1493+
/* RFC9686 §4.2: Client MUST include Client Identifier option */
1494+
/* RFC9686 §4.2: ADDR-REG-INFORM MUST NOT contain Server Identifier */
1495+
/* RFC9686 §4.2: MUST contain exactly one IA Address option */
1496+
1497+
dhcpv6_for_each_option(opts, opts_end, otype, olen, odata) {
1498+
switch (otype) {
1499+
case DHCPV6_OPT_CLIENTID:
1500+
if (olen > 0) {
1501+
clientid_data = odata;
1502+
clientid_len = olen;
1503+
}
1504+
break;
1505+
case DHCPV6_OPT_SERVERID:
1506+
/* RFC9686 §4.2: Server MUST discard messages with Server ID */
1507+
debug("ADDR-REG-INFORM: message contains Server Identifier, discarding");
1508+
return;
1509+
case DHCPV6_OPT_IA_ADDR:
1510+
if (olen == sizeof(struct dhcpv6_ia_addr) - 4) {
1511+
if (ia_addr != NULL) {
1512+
debug("ADDR-REG-INFORM: message contains more than one IA_ADDR, discarding");
1513+
return;
1514+
}
1515+
ia_addr = (struct dhcpv6_ia_addr *)&odata[-4];
1516+
}
1517+
break;
1518+
case DHCPV6_OPT_ORO:
1519+
/* RFC9686 §4.2: ADDR-REG-INFORM MUST NOT contain Option Request option */
1520+
debug("ADDR-REG-INFORM: message contains Option Request, discarding");
1521+
return;
1522+
default:
1523+
break;
1524+
}
1525+
}
1526+
1527+
/* Validate message contents */
1528+
if (!clientid_data || clientid_len == 0) {
1529+
debug("ADDR-REG-INFORM: missing Client Identifier option, discarding");
1530+
return;
1531+
}
1532+
1533+
if (!ia_addr) {
1534+
debug("ADDR-REG-INFORM: missing or invalid IA Address option, discarding");
1535+
return;
1536+
}
1537+
1538+
/* RFC9686 §4.2.1: Verify source address matches the IA Address option
1539+
* The message MUST be sent from the address being registered */
1540+
if (memcmp(&source->sin6_addr, &ia_addr->addr, sizeof(struct in6_addr)) != 0) {
1541+
debug("ADDR-REG-INFORM: source address does not match IA Address option");
1542+
return;
1543+
}
1544+
1545+
char addrbuf[INET6_ADDRSTRLEN];
1546+
inet_ntop(AF_INET6, &ia_addr->addr, addrbuf, sizeof(addrbuf));
1547+
debug("Got ADDR-REG-INFORM for %s on %s", addrbuf, iface->name);
1548+
1549+
/* Register address in lease database */
1550+
if(!register_ia_addr_in_lease_db(source, clientid_data, clientid_len, ia_addr, iface))
1551+
return;
1552+
1553+
/* Send ADDR-REG-REPLY response */
1554+
send_ia_addr_reg_reply(source, hdr, ia_addr, iface);
1555+
}

src/dhcpv6.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ static void relay_client_request(struct sockaddr_in6 *source,
3333
const void *data, size_t len, struct interface *iface);
3434
static void relay_server_response(uint8_t *data, size_t len);
3535

36+
void handle_ia_addr_reg_inform(struct sockaddr_in6 *source,
37+
const void *data, size_t len, struct interface *iface);
38+
3639
static void handle_dhcpv6(void *addr, void *data, size_t len,
3740
struct interface *iface, void *dest);
3841
static void handle_client_request(void *addr, void *data, size_t len,
@@ -169,6 +172,7 @@ enum {
169172
IOV_MAXRT,
170173
#define IOV_STAT IOV_MAXRT
171174
IOV_RAPID_COMMIT,
175+
IOV_ADDR_REG_ENABLE,
172176
IOV_DNS,
173177
IOV_DNS_ADDR,
174178
IOV_SEARCH,
@@ -347,6 +351,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
347351
/* if we include DHCPV4 support, handle this message type */
348352
case DHCPV6_MSG_DHCPV4_QUERY:
349353
#endif
354+
case DHCPV6_MSG_ADDR_REG_INFORM:
350355
break;
351356
/* Invalid message types for clients i.e. server messages */
352357
case DHCPV6_MSG_ADVERTISE:
@@ -358,12 +363,21 @@ static void handle_client_request(void *addr, void *data, size_t len,
358363
case DHCPV6_MSG_DHCPV4_QUERY:
359364
#endif
360365
case DHCPV6_MSG_DHCPV4_RESPONSE:
366+
case DHCPV6_MSG_ADDR_REG_REPLY:
361367
default:
362368
return;
363369
}
364370

365371
debug("Got a DHCPv6-request on %s", iface->name);
366372

373+
/* RFC9686 - Handle ADDR-REG-INFORM separately */
374+
if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM) {
375+
if (iface->dhcpv6 == MODE_SERVER) {
376+
handle_ia_addr_reg_inform((struct sockaddr_in6 *)addr, data, len, iface);
377+
}
378+
return;
379+
}
380+
367381
/* Construct reply message */
368382
struct _o_packed {
369383
uint8_t msg_type;
@@ -501,6 +515,16 @@ static void handle_client_request(void *addr, void *data, size_t len,
501515
uint16_t len;
502516
} capt_portal;
503517

518+
/* RFC9686 Address Registration Enable option */
519+
bool addr_reg_enable_want = false;
520+
struct {
521+
uint16_t type;
522+
uint16_t len;
523+
} addr_reg_enable = {
524+
htons(DHCPV6_OPT_ADDR_REG_ENABLE),
525+
0
526+
};
527+
504528
/* RFC8910 §2:
505529
* DHCP servers MAY send the Captive Portal option without any explicit request
506530
* If it is configured, send it.
@@ -611,6 +635,10 @@ static void handle_client_request(void *addr, void *data, size_t len,
611635
memcpy(&d6dnr->len, &d6dnr_len_be, sizeof(d6dnr_len_be));
612636
}
613637
break;
638+
case DHCPV6_OPT_ADDR_REG_ENABLE:
639+
/* RFC9686: Signal address registration support */
640+
addr_reg_enable_want = true;
641+
break;
614642
}
615643
}
616644

@@ -652,6 +680,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
652680
[IOV_DNS_ADDR] = { dns_addrs6, dns_addrs6_cnt * sizeof(*dns_addrs6) },
653681
[IOV_SEARCH] = { &dns_search_hdr, (dns_search_len) ? sizeof(dns_search_hdr) : 0 },
654682
[IOV_SEARCH_DOMAIN] = { dns_search, dns_search_len },
683+
[IOV_ADDR_REG_ENABLE] = {&addr_reg_enable, addr_reg_enable_want ? sizeof(addr_reg_enable) : 0},
655684
[IOV_PDBUF] = {pdbuf, 0},
656685
[IOV_DHCPV6_RAW] = {iface->dhcpv6_raw, iface->dhcpv6_raw_len},
657686
[IOV_NTP] = {&ntp, (ntp_cnt) ? sizeof(ntp) : 0},
@@ -798,6 +827,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
798827
iov[IOV_RAPID_COMMIT].iov_len + iov[IOV_DNS].iov_len +
799828
iov[IOV_DNS_ADDR].iov_len + iov[IOV_SEARCH].iov_len +
800829
iov[IOV_SEARCH_DOMAIN].iov_len + iov[IOV_PDBUF].iov_len +
830+
iov[IOV_ADDR_REG_ENABLE].iov_len +
801831
iov[IOV_DHCPV4O6_SERVER].iov_len +
802832
iov[IOV_DHCPV6_RAW].iov_len +
803833
iov[IOV_NTP].iov_len + iov[IOV_NTP_ADDR].iov_len +
@@ -873,6 +903,10 @@ static void relay_server_response(uint8_t *data, size_t len)
873903
/* If the payload is relay-reply we have to send to the server port */
874904
if (payload_data[0] == DHCPV6_MSG_RELAY_REPL) {
875905
target.sin6_port = htons(DHCPV6_SERVER_PORT);
906+
} else if (payload_data[0] == DHCPV6_MSG_ADDR_REG_REPLY) {
907+
/* RFC9686: Forward ADDR-REG-REPLY back to client */
908+
/* The client address is in the peer_address field of the relay message */
909+
/* For relayed ADDR-REG-REPLY, just forward as-is to client port */
876910
} else { /* Go through the payload data */
877911
struct dhcpv6_client_header *dch = (void*)payload_data;
878912
end = payload_data + payload_len;
@@ -970,13 +1004,15 @@ static void relay_client_request(struct sockaddr_in6 *source,
9701004
case DHCPV6_MSG_INFORMATION_REQUEST:
9711005
case DHCPV6_MSG_RELAY_FORW:
9721006
case DHCPV6_MSG_DHCPV4_QUERY:
1007+
case DHCPV6_MSG_ADDR_REG_INFORM:
9731008
break;
9741009
/* Invalid message types from clients i.e. server messages */
9751010
case DHCPV6_MSG_ADVERTISE:
9761011
case DHCPV6_MSG_REPLY:
9771012
case DHCPV6_MSG_RECONFIGURE:
9781013
case DHCPV6_MSG_RELAY_REPL:
9791014
case DHCPV6_MSG_DHCPV4_RESPONSE:
1015+
case DHCPV6_MSG_ADDR_REG_REPLY:
9801016
return;
9811017
default:
9821018
break;

src/dhcpv6.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
/* RFC7341 */
4242
#define DHCPV6_MSG_DHCPV4_QUERY 20
4343
#define DHCPV6_MSG_DHCPV4_RESPONSE 21
44+
/* RFC9686 */
45+
#define DHCPV6_MSG_ADDR_REG_INFORM 36
46+
#define DHCPV6_MSG_ADDR_REG_REPLY 37
4447

4548
#define DHCPV6_OPT_CLIENTID 1
4649
#define DHCPV6_OPT_SERVERID 2
@@ -77,6 +80,8 @@
7780
/* RFC8910 */
7881
#define DHCPV6_OPT_CAPTIVE_PORTAL 103
7982
#define DHCPV6_OPT_DNR 144
83+
/* RFC9686 */
84+
#define DHCPV6_OPT_ADDR_REG_ENABLE 148
8085

8186
#define DHCPV6_DUID_VENDOR 2
8287

@@ -158,6 +163,12 @@ struct dhcpv6_ia_addr {
158163
uint32_t valid_lt;
159164
} _o_packed;
160165

166+
/* RFC9686 - Address Registration Option (empty option, no data) */
167+
struct dhcpv6_addr_reg_enable {
168+
uint16_t type;
169+
uint16_t len;
170+
} _o_packed;
171+
161172
struct dhcpv6_cer_id {
162173
uint16_t type;
163174
uint16_t len;

0 commit comments

Comments
 (0)