Skip to content

Commit bb65e8c

Browse files
committed
dhcpv6: implement RFC9686
Registering Self-Generated IPv6 Addresses Using DHCPv6 This functionality registers a clients 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 | | | ``` Closes openwrt#349 Signed-off-by: Paul Donald <newtwen+github@gmail.com> Link: openwrt#353
1 parent 017b1c7 commit bb65e8c

3 files changed

Lines changed: 135 additions & 6 deletions

File tree

src/dhcpv6-ia.c

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,9 @@ static void dhcpv6_log(uint8_t msgtype, struct interface *iface, time_t now,
862862
case DHCPV6_MSG_DECLINE:
863863
type = "DECLINE";
864864
break;
865+
case DHCPV6_MSG_ADDR_REG_INFORM:
866+
type = "ADDR-REG-INFORM";
867+
break;
865868
}
866869

867870
switch (code) {
@@ -985,6 +988,8 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
985988
bool is_pd = (otype == DHCPV6_OPT_IA_PD);
986989
bool is_na = (otype == DHCPV6_OPT_IA_NA);
987990
bool ia_addr_present = false;
991+
uint32_t reg_addr_valid_lt = 0;
992+
uint32_t reg_addr_preferred_lt = 0;
988993
if (!is_pd && !is_na)
989994
continue;
990995

@@ -1052,6 +1057,19 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
10521057
if (stype != DHCPV6_OPT_IA_ADDR || slen < sizeof(struct dhcpv6_ia_addr) - 4)
10531058
continue;
10541059

1060+
if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM) {
1061+
struct dhcpv6_ia_addr *ia_addr = (struct dhcpv6_ia_addr *)&sdata[-4];
1062+
/* RFC9686 §4.2 "fate sharing" or §4.2.1
1063+
* Servers MUST discard any ADDR-REG-INFORM messages where
1064+
* the IP address in the IA Address option does not match the
1065+
* source address of the original ADDR-REG-INFORM message sent
1066+
* by the client */
1067+
if(memcmp(&ia_addr->addr, &addr->sin6_addr, sizeof(struct in6_addr)) != 0)
1068+
return -1;
1069+
reg_addr_valid_lt = ntohl(ia_addr->valid_lt);
1070+
reg_addr_preferred_lt = ntohl(ia_addr->preferred_lt);
1071+
}
1072+
10551073
ia_addr_present = true;
10561074
}
10571075
}
@@ -1138,9 +1156,31 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
11381156

11391157
if (hdr->msg_type == DHCPV6_MSG_SOLICIT ||
11401158
hdr->msg_type == DHCPV6_MSG_REQUEST ||
1141-
(hdr->msg_type == DHCPV6_MSG_REBIND && !a)) {
1159+
(hdr->msg_type == DHCPV6_MSG_REBIND && !a) ||
1160+
(hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM && !a)) {
11421161
bool assigned = !!a;
11431162

1163+
if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM) {
1164+
/* RFC9686 - Address Registration
1165+
* Handle client-initiated address registration (e.g. for SLAAC addresses).
1166+
* The client registers addresses it has configured.
1167+
*/
1168+
char addrbuf[INET6_ADDRSTRLEN];
1169+
inet_ntop(AF_INET6, &addr->sin6_addr, addrbuf, sizeof(addrbuf));
1170+
1171+
if (ia_addr_present && !dhcpv6_ia_on_link(ia, a, iface)) {
1172+
/* RFC9686 §4.2.1 - the server MUST drop the message and SHOULD log this fact */
1173+
notice("Client %s attempted to register an address not on this link", addrbuf);
1174+
return -1;
1175+
} else if (!ia_addr_present) {
1176+
/* RFC9686 §4.2.1
1177+
* Servers MUST discard any ADDR-REG-INFORM messages where
1178+
* the message does not include the IA Address option */
1179+
notice("Client %s included no IA Address option in ADDR-REG-INFORM", addrbuf);
1180+
return -1;
1181+
}
1182+
}
1183+
11441184
if (!a) {
11451185
if ((!iface->no_dynamic_dhcp || (lease_cfg && is_na)) &&
11461186
(iface->dhcpv6_pd || iface->dhcpv6_na)) {
@@ -1162,6 +1202,13 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
11621202
a->iface = iface;
11631203
a->flags = (is_pd ? OAF_DHCPV6_PD : OAF_DHCPV6_NA);
11641204

1205+
if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM) {
1206+
1207+
a->valid_until = now + reg_addr_valid_lt;
1208+
a->preferred_until = now + reg_addr_preferred_lt;
1209+
1210+
}
1211+
11651212
if (first)
11661213
memcpy(a->key, first->key, sizeof(a->key));
11671214
else
@@ -1237,7 +1284,8 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
12371284
} else if (assigned &&
12381285
((hdr->msg_type == DHCPV6_MSG_SOLICIT && rapid_commit) ||
12391286
hdr->msg_type == DHCPV6_MSG_REQUEST ||
1240-
hdr->msg_type == DHCPV6_MSG_REBIND)) {
1287+
hdr->msg_type == DHCPV6_MSG_REBIND ||
1288+
hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM)) {
12411289
if (hostname_len > 0 && (!a->lease_cfg || !a->lease_cfg->hostname)) {
12421290
char *tmp = realloc(a->hostname, hostname_len + 1);
12431291
if (tmp) {
@@ -1248,8 +1296,16 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
12481296
}
12491297
}
12501298
a->accept_fr_nonce = accept_reconf;
1251-
a->bound = true;
1252-
apply_lease(a, true);
1299+
/* RFC9686 §4.6.3 If the server receives a message with a
1300+
* valid-lifetime of zero, it MUST act as if the address has expired.
1301+
* A preferred lifetime of 0 means deprecated but still valid. */
1302+
if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM && reg_addr_valid_lt == 0) {
1303+
a->bound = false;
1304+
apply_lease(a, false);
1305+
} else {
1306+
a->bound = true;
1307+
apply_lease(a, true);
1308+
}
12531309
} else if (!assigned) {
12541310
/* Clean up failed assignment */
12551311
dhcpv6_free_lease(a);
@@ -1295,6 +1351,9 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
12951351
buf += ia_response_len;
12961352
buflen -= ia_response_len;
12971353
response_len += ia_response_len;
1354+
/* RFC9686 §4.2.1 If the message passes the verification, the server:
1355+
* MUST log the address registration information.
1356+
* odhcpd logs this here anyway as part of normal operation. */
12981357
dhcpv6_log(hdr->msg_type, iface, now, duidbuf, is_pd, a, status);
12991358
}
13001359

src/dhcpv6.c

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ enum {
190190
IOV_TZDB_TZ_STR,
191191
IOV_CAPT_PORTAL,
192192
IOV_CAPT_PORTAL_URI,
193+
IOV_REG_ENABLE,
193194
IOV_TOTAL
194195
};
195196

@@ -347,6 +348,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
347348
/* if we include DHCPV4 support, handle this message type */
348349
case DHCPV6_MSG_DHCPV4_QUERY:
349350
#endif
351+
case DHCPV6_MSG_ADDR_REG_INFORM:
350352
break;
351353
/* Invalid message types for clients i.e. server messages */
352354
case DHCPV6_MSG_ADVERTISE:
@@ -358,6 +360,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
358360
case DHCPV6_MSG_DHCPV4_QUERY:
359361
#endif
360362
case DHCPV6_MSG_DHCPV4_RESPONSE:
363+
case DHCPV6_MSG_ADDR_REG_REPLY:
361364
default:
362365
return;
363366
}
@@ -424,6 +427,15 @@ static void handle_client_request(void *addr, void *data, size_t len,
424427
} refresh = {htons(DHCPV6_OPT_INFO_REFRESH), htons(sizeof(uint32_t)),
425428
htonl(600)};
426429

430+
struct _o_packed {
431+
uint16_t type;
432+
uint16_t len;
433+
} reg_enable = {
434+
htons(DHCPV6_OPT_ADDR_REG_ENABLE),
435+
htons(0),
436+
};
437+
bool client_addr_reg_enable = false;
438+
427439
struct in6_addr *dns_addrs6 = NULL, dns_addr6;
428440
size_t dns_addrs6_cnt = 0;
429441

@@ -611,6 +623,10 @@ static void handle_client_request(void *addr, void *data, size_t len,
611623
memcpy(&d6dnr->len, &d6dnr_len_be, sizeof(d6dnr_len_be));
612624
}
613625
break;
626+
627+
case DHCPV6_OPT_ADDR_REG_ENABLE:
628+
client_addr_reg_enable = true;
629+
break;
614630
}
615631
}
616632

@@ -667,7 +683,8 @@ static void handle_client_request(void *addr, void *data, size_t len,
667683
[IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0},
668684
[IOV_CAPT_PORTAL] = {&capt_portal, capt_portal_len ? sizeof(capt_portal) : 0},
669685
[IOV_CAPT_PORTAL_URI] = {capt_portal_ptr, capt_portal_len ? capt_portal_len : 0},
670-
[IOV_BOOTFILE_URL] = {NULL, 0}
686+
[IOV_BOOTFILE_URL] = {NULL, 0},
687+
[IOV_REG_ENABLE] = {&reg_enable, client_addr_reg_enable ? sizeof(reg_enable) : 0},
671688
};
672689

673690
if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW)
@@ -687,13 +704,22 @@ static void handle_client_request(void *addr, void *data, size_t len,
687704
memcpy(clientid.buf, odata, olen);
688705
iov[IOV_CLIENTID].iov_len = offsetof(typeof(clientid), buf) + olen;
689706
} else if (otype == DHCPV6_OPT_SERVERID) {
707+
/* RFC9686 §4.2.1 Servers MUST discard any ADDR-REG-INFORM that
708+
* includes a Server Identifier option */
709+
if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM)
710+
return;
690711
if (olen != ntohs(dest.serverid_length) ||
691712
memcmp(odata, &dest.serverid_buf, olen))
692713
return; /* Not for us */
693714
} else if (otype == DHCPV6_OPT_RAPID_COMMIT && hdr->msg_type == DHCPV6_MSG_SOLICIT) {
694715
iov[IOV_RAPID_COMMIT].iov_len = sizeof(rapid_commit);
695716
o_rapid_commit = true;
696717
} else if (otype == DHCPV6_OPT_ORO) {
718+
/* RFC9686 §4.2.1 Servers MUST discard any ADDR-REG-INFORM that
719+
* includes an Option Request option */
720+
if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM)
721+
return;
722+
697723
for (int i=0; i < olen/2; i++) {
698724
uint16_t option = ntohs(((uint16_t *)odata)[i]);
699725

@@ -751,6 +777,13 @@ static void handle_client_request(void *addr, void *data, size_t len,
751777

752778
if (hdr->msg_type == DHCPV6_MSG_SOLICIT && !o_rapid_commit) {
753779
dest.msg_type = DHCPV6_MSG_ADVERTISE;
780+
} else if (hdr->msg_type == DHCPV6_MSG_ADDR_REG_INFORM) {
781+
/* RFC9686 §4.2 The client MUST include the Client Identifier option or
782+
* §4.2.1 Servers MUST discard any ADDR-REG-INFORM messages that do not
783+
* include a Client Identifier option */
784+
if (clientid.len == 0)
785+
return;
786+
dest.msg_type = DHCPV6_MSG_ADDR_REG_REPLY;
754787
} else if (hdr->msg_type == DHCPV6_MSG_INFORMATION_REQUEST) {
755788
iov[IOV_REFRESH].iov_base = &refresh;
756789
iov[IOV_REFRESH].iov_len = sizeof(refresh);
@@ -805,7 +838,8 @@ static void handle_client_request(void *addr, void *data, size_t len,
805838
iov[IOV_POSIX_TZ].iov_len + iov[IOV_POSIX_TZ_STR].iov_len +
806839
iov[IOV_TZDB_TZ].iov_len + iov[IOV_TZDB_TZ_STR].iov_len +
807840
iov[IOV_CAPT_PORTAL].iov_len + iov[IOV_CAPT_PORTAL_URI].iov_len +
808-
iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].iov_len -
841+
iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].iov_len +
842+
iov[IOV_REG_ENABLE].iov_len -
809843
(4 + opts_end - opts));
810844

811845
debug("Sending a DHCPv6-%s on %s", iov[IOV_NESTED].iov_len ? "relay-reply" : "reply", iface->name);
@@ -957,6 +991,10 @@ static void relay_client_request(struct sockaddr_in6 *source,
957991
struct interface *c;
958992
struct odhcpd_ipaddr *ip;
959993
struct sockaddr_in6 s;
994+
uint16_t otype, olen;
995+
uint8_t *start = (uint8_t *)&data, *odata;
996+
const uint8_t *end = (uint8_t *)data + len;
997+
960998

961999
switch (h->msg_type) {
9621000
/* Valid message types from clients */
@@ -970,13 +1008,15 @@ static void relay_client_request(struct sockaddr_in6 *source,
9701008
case DHCPV6_MSG_INFORMATION_REQUEST:
9711009
case DHCPV6_MSG_RELAY_FORW:
9721010
case DHCPV6_MSG_DHCPV4_QUERY:
1011+
case DHCPV6_MSG_ADDR_REG_INFORM:
9731012
break;
9741013
/* Invalid message types from clients i.e. server messages */
9751014
case DHCPV6_MSG_ADVERTISE:
9761015
case DHCPV6_MSG_REPLY:
9771016
case DHCPV6_MSG_RECONFIGURE:
9781017
case DHCPV6_MSG_RELAY_REPL:
9791018
case DHCPV6_MSG_DHCPV4_RESPONSE:
1019+
case DHCPV6_MSG_ADDR_REG_REPLY:
9801020
return;
9811021
default:
9821022
break;
@@ -991,6 +1031,31 @@ static void relay_client_request(struct sockaddr_in6 *source,
9911031
hdr.hop_count = h->hop_count + 1;
9921032
}
9931033

1034+
/* RFC9686 §4.2 "fate sharing" or
1035+
* RFC9686 §4.2.1 Servers MUST discard any ADDR-REG-INFORM messages where
1036+
* the IP address in the IA Address option does not match the source address
1037+
* of the original ADDR-REG-INFORM message sent by the client.
1038+
* The source address of the original message is the source IP address of
1039+
* the packet if it is not (yet) relayed. */
1040+
if (h->msg_type == DHCPV6_MSG_ADDR_REG_INFORM) {
1041+
dhcpv6_for_each_option(start, end, otype, olen, odata) {
1042+
bool is_na = (otype == DHCPV6_OPT_IA_NA);
1043+
struct dhcpv6_ia_hdr *ia = (struct dhcpv6_ia_hdr*)&odata[-4];
1044+
if (is_na) {
1045+
uint8_t *sdata;
1046+
uint16_t stype, slen;
1047+
dhcpv6_for_each_sub_option(&ia[1], odata + olen, stype, slen, sdata) {
1048+
if (stype != DHCPV6_OPT_IA_ADDR || slen < sizeof(struct dhcpv6_ia_addr) - 4)
1049+
return;
1050+
1051+
struct dhcpv6_ia_addr *ia_addr = (struct dhcpv6_ia_addr *)&sdata[-4];
1052+
if (memcmp(&ia_addr->addr, &source->sin6_addr, sizeof(struct in6_addr)) != 0)
1053+
return;
1054+
}
1055+
}
1056+
}
1057+
}
1058+
9941059
/* use memcpy here as the destination fields are unaligned */
9951060
memcpy(&hdr.peer_address, &source->sin6_addr, sizeof(struct in6_addr));
9961061
memcpy(&hdr.interface_id_data, &iface->ifindex, sizeof(iface->ifindex));

src/dhcpv6.h

Lines changed: 5 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

0 commit comments

Comments
 (0)