Skip to content

Commit 1498009

Browse files
committed
dhcpv6-ia: add support for RFC6603 Prefix Exclude Option
If option dhcpv6_pd_exclude is set in a dhcp config section (in addition to option dhcpv6_pd), consider delegating prefixes that include on-link prefixes. This allows an entire delegated prefix to be re-delegated to a downstream router under certain circumstances. Signed-off-by: Mark H. Spatz <mark.h.spatz@gmail.com>
1 parent 3cbbea8 commit 1498009

File tree

6 files changed

+90
-9
lines changed

6 files changed

+90
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ and may also receive information from ubus
9090
| dhcpv6_pd |bool | 1 | DHCPv6 stateful addressing hands out IA_PD - Internet Address - Prefix Delegation (PD) |
9191
| dhcpv6_pd_preferred |bool | 0 | Set the DHCPv6-PD Preferred (P) flag in outgoing ICMPv6 RA message PIOs (RFC9762); requires `dhcpv6` and `dhcpv6_pd`. |
9292
| dhcpv6_pd_min_len |integer| 62 | Minimum prefix length to delegate with IA_PD (adjusted, if necessary, to be longer than the interface prefix length). Range [1,64] |
93+
| dhcpv6_pd_exclude |bool | 0 | Allow delegation of a prefix containing the smaller on-link prefix (RFC6603); allows an entire interface prefix to be delegated. |
9394
| router |list |`<local address>`| IPv4 addresses of routers on a given subnet (provided via DHCPv4, should be in order of preference) |
9495
| dns |list |`<local address>`| DNS servers to announce, accepts IPv4 and IPv6 |
9596
| dnr |list |disabled| Encrypted DNS servers to announce, `<priority> <domain name> [<comma separated IP addresses> <SvcParams (key=value)>...]` |

src/config.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ enum {
113113
IFACE_ATTR_DHCPV6_PD_PREFERRED,
114114
IFACE_ATTR_DHCPV6_PD,
115115
IFACE_ATTR_DHCPV6_PD_MIN_LEN,
116+
IFACE_ATTR_DHCPV6_PD_EXCLUDE,
116117
IFACE_ATTR_DHCPV6_NA,
117118
IFACE_ATTR_DHCPV6_HOSTID_LEN,
118119
IFACE_ATTR_RA_DEFAULT,
@@ -167,6 +168,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
167168
[IFACE_ATTR_DHCPV6_PD_PREFERRED] = { .name = "dhcpv6_pd_preferred", .type = BLOBMSG_TYPE_BOOL },
168169
[IFACE_ATTR_DHCPV6_PD] = { .name = "dhcpv6_pd", .type = BLOBMSG_TYPE_BOOL },
169170
[IFACE_ATTR_DHCPV6_PD_MIN_LEN] = { .name = "dhcpv6_pd_min_len", .type = BLOBMSG_TYPE_INT32 },
171+
[IFACE_ATTR_DHCPV6_PD_EXCLUDE] = { .name = "dhcpv6_pd_exclude", .type = BLOBMSG_TYPE_BOOL },
170172
[IFACE_ATTR_DHCPV6_NA] = { .name = "dhcpv6_na", .type = BLOBMSG_TYPE_BOOL },
171173
[IFACE_ATTR_DHCPV6_HOSTID_LEN] = { .name = "dhcpv6_hostidlength", .type = BLOBMSG_TYPE_INT32 },
172174
[IFACE_ATTR_RA_DEFAULT] = { .name = "ra_default", .type = BLOBMSG_TYPE_INT32 },
@@ -324,6 +326,7 @@ static void set_interface_defaults(struct interface *iface)
324326
iface->dhcpv6_pd = true;
325327
iface->dhcpv6_pd_preferred = false;
326328
iface->dhcpv6_pd_min_len = PD_MIN_LEN_DEFAULT;
329+
iface->dhcpv6_pd_exclude = false;
327330
iface->dhcpv6_na = true;
328331
iface->dhcpv6_hostid_len = HOSTID_LEN_DEFAULT;
329332
iface->dns_service = true;
@@ -1465,6 +1468,9 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr
14651468
iface->dhcpv6_pd_min_len = pd_min_len;
14661469
}
14671470

1471+
if ((c = tb[IFACE_ATTR_DHCPV6_PD_EXCLUDE]))
1472+
iface->dhcpv6_pd_exclude = blobmsg_get_bool(c);
1473+
14681474
if ((c = tb[IFACE_ATTR_DHCPV6_NA]))
14691475
iface->dhcpv6_na = blobmsg_get_bool(c);
14701476

src/dhcpv6-ia.c

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -346,15 +346,24 @@ static bool assign_pd(struct interface *iface, struct dhcpv6_lease *assign)
346346
if (iface->addr6_len < 1)
347347
return false;
348348

349+
bool allow_exclude =
350+
iface->dhcpv6_pd_exclude &&
351+
(assign->flags & OAF_DHCPV6_PD_EXCLUDE) &&
352+
(assign->length < 64); /* Excluded prefix must be larger than the delegated prefix (RFC6604 § 4.2) */
353+
354+
const uint32_t asize = (1 << (64 - assign->length)) - 1;
355+
349356
/* Try honoring the hint first */
350-
uint32_t current = 1, asize = (1 << (64 - assign->length)) - 1;
357+
uint32_t current = allow_exclude ? 0 : 1;
351358
if (assign->assigned_subnet_id) {
352359
list_for_each_entry(c, &iface->ia_assignments, head) {
353360
if (c->flags & OAF_DHCPV6_NA)
354361
continue;
355362

356363
if (assign->assigned_subnet_id >= current && assign->assigned_subnet_id + asize < c->assigned_subnet_id) {
357364
list_add_tail(&assign->head, &c->head);
365+
debug("assign_pd chose subnet_id %08x on %s (hint honored)",
366+
assign->assigned_subnet_id, iface->name);
358367

359368
if (assign->bound)
360369
apply_lease(assign, true);
@@ -367,7 +376,7 @@ static bool assign_pd(struct interface *iface, struct dhcpv6_lease *assign)
367376
}
368377

369378
/* Fallback to a variable assignment */
370-
current = 1;
379+
current = allow_exclude ? 0 : 1;
371380
list_for_each_entry(c, &iface->ia_assignments, head) {
372381
if (c->flags & OAF_DHCPV6_NA)
373382
continue;
@@ -377,6 +386,8 @@ static bool assign_pd(struct interface *iface, struct dhcpv6_lease *assign)
377386
if (current + asize < c->assigned_subnet_id) {
378387
assign->assigned_subnet_id = current;
379388
list_add_tail(&assign->head, &c->head);
389+
debug("assign_pd chose subnet_id %08x on %s",
390+
assign->assigned_subnet_id, iface->name);
380391

381392
if (assign->bound)
382393
apply_lease(assign, true);
@@ -666,9 +677,42 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status,
666677
prefix_preferred_lt = prefix_valid_lt;
667678

668679
if (a->flags & OAF_DHCPV6_PD) {
680+
if (!valid_prefix_length(a, addrs[i].prefix_len))
681+
continue;
682+
683+
/* If assign_pd() chose subnet id 0, send a PD-Exclude option for the first /64 in the delegated prefix */
684+
struct {
685+
uint16_t option_code;
686+
uint16_t option_len;
687+
uint8_t prefix_len;
688+
} _o_packed o_pd_exl;
689+
size_t o_pd_exl_len = 0;
690+
if (a->assigned_subnet_id == 0) {
691+
const uint8_t excluded_prefix_len = 64;
692+
if (a->length < excluded_prefix_len) {
693+
uint8_t excl_subnet_id_nbits = excluded_prefix_len - a->length;
694+
uint8_t excl_subnet_id_nbytes = ((excl_subnet_id_nbits - 1) / 8) + 1;
695+
o_pd_exl_len = sizeof(o_pd_exl) + excl_subnet_id_nbytes;
696+
697+
/* Work around a bug in odhcp6c that ignores DHCPV6_OPT_PD_EXCLUDE with valid option length of 2. */
698+
if(o_pd_exl_len - DHCPV6_OPT_HDR_SIZE == 2)
699+
o_pd_exl_len++;
700+
701+
o_pd_exl.option_code = htons(DHCPV6_OPT_PD_EXCLUDE);
702+
o_pd_exl.option_len = htons(o_pd_exl_len - DHCPV6_OPT_HDR_SIZE);
703+
o_pd_exl.prefix_len = excluded_prefix_len;
704+
/* (IPv6 subnet ID field is all zeros) */
705+
} else {
706+
error("BUG: Can't exclude a prefix from from IA_PD of size %u on %s",
707+
a->length, iface->name);
708+
continue;
709+
}
710+
711+
}
712+
669713
struct dhcpv6_ia_prefix o_ia_p = {
670714
.type = htons(DHCPV6_OPT_IA_PREFIX),
671-
.len = htons(sizeof(o_ia_p) - DHCPV6_OPT_HDR_SIZE),
715+
.len = htons(sizeof(o_ia_p) - DHCPV6_OPT_HDR_SIZE + o_pd_exl_len),
672716
.preferred_lt = htonl(prefix_preferred_lt),
673717
.valid_lt = htonl(prefix_valid_lt),
674718
.prefix_len = a->length,
@@ -678,14 +722,17 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status,
678722
o_ia_p.addr.s6_addr32[1] |= htonl(a->assigned_subnet_id);
679723
o_ia_p.addr.s6_addr32[2] = o_ia_p.addr.s6_addr32[3] = 0;
680724

681-
if (!valid_prefix_length(a, addrs[i].prefix_len))
682-
continue;
683-
684-
if (buflen < ia_len + sizeof(o_ia_p))
725+
if (buflen < ia_len + sizeof(o_ia_p) + o_pd_exl_len)
685726
return 0;
686727

687728
memcpy(buf + ia_len, &o_ia_p, sizeof(o_ia_p));
688729
ia_len += sizeof(o_ia_p);
730+
731+
if(o_pd_exl_len) {
732+
memset(buf + ia_len, 0, o_pd_exl_len);
733+
memcpy(buf + ia_len, &o_pd_exl, sizeof(o_pd_exl));
734+
ia_len += o_pd_exl_len;
735+
}
689736
}
690737

691738
if (a->flags & OAF_DHCPV6_NA) {
@@ -955,6 +1002,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
9551002
uint8_t *duid = NULL, mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
9561003
size_t hostname_len = 0, response_len = 0;
9571004
bool notonlink = false, rapid_commit = false, accept_reconf = false;
1005+
bool oro_pd_exclude = false;
9581006
char duidbuf[DUID_HEXSTRLEN], hostname[256];
9591007

9601008
dhcpv6_for_each_option(start, end, otype, olen, odata) {
@@ -994,6 +1042,20 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
9941042
rapid_commit = true;
9951043
break;
9961044

1045+
case DHCPV6_OPT_ORO: {
1046+
size_t reqopts_cnt = olen / sizeof(uint16_t);
1047+
uint16_t* reqopts = (uint16_t *)odata;
1048+
for (size_t i = 0; i < reqopts_cnt; i++) {
1049+
uint16_t opt = ntohs(reqopts[i]);
1050+
switch (opt) {
1051+
case DHCPV6_OPT_PD_EXCLUDE:
1052+
oro_pd_exclude = true;
1053+
break;
1054+
}
1055+
}
1056+
break;
1057+
}
1058+
9971059
default:
9981060
break;
9991061
}
@@ -1180,7 +1242,12 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
11801242
a->length = reqlen;
11811243
a->peer = *addr;
11821244
a->iface = iface;
1183-
a->flags = is_pd ? OAF_DHCPV6_PD : OAF_DHCPV6_NA;
1245+
if (is_pd) {
1246+
a->flags = OAF_DHCPV6_PD;
1247+
if (oro_pd_exclude)
1248+
a->flags |= OAF_DHCPV6_PD_EXCLUDE;
1249+
} else
1250+
a->flags = OAF_DHCPV6_NA;
11841251
a->valid_until = now;
11851252
a->preferred_until = now;
11861253

src/dhcpv6-ia.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ struct in6_addr in6_from_prefix_and_iid(const struct odhcpd_ipaddr *prefix, uint
2727

2828
static inline bool valid_prefix_length(const struct dhcpv6_lease *a, const uint8_t prefix_length)
2929
{
30-
return a->length > prefix_length;
30+
/* If the assigned_subnet_id is 0, allow the interface's entire prefix to be delegated. */
31+
if (a->assigned_subnet_id == 0)
32+
return a->length >= prefix_length;
33+
else
34+
return a->length > prefix_length;
3135
}
3236

3337
static inline bool valid_addr(const struct odhcpd_ipaddr *addr, time_t now)

src/dhcpv6.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#define DHCPV6_OPT_BOOTFILE_URL 59
7171
#define DHCPV6_OPT_BOOTFILE_PARAM 60
7272
#define DHCPV6_OPT_CLIENT_ARCH 61
73+
#define DHCPV6_OPT_PD_EXCLUDE 67
7374
#define DHCPV6_OPT_SOL_MAX_RT 82
7475
#define DHCPV6_OPT_INF_MAX_RT 83
7576
#define DHCPV6_OPT_DHCPV4_MSG 87

src/odhcpd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ enum odhcpd_mode {
192192
enum odhcpd_assignment_flags {
193193
OAF_DHCPV6_NA = (1 << 0),
194194
OAF_DHCPV6_PD = (1 << 1),
195+
OAF_DHCPV6_PD_EXCLUDE = (1 << 2),
195196
};
196197

197198
#define DHCPV6_OPT_HDR_SIZE 4
@@ -468,6 +469,7 @@ struct interface {
468469
bool dhcpv6_na;
469470
uint32_t dhcpv6_hostid_len;
470471
uint32_t dhcpv6_pd_min_len; // minimum delegated prefix length
472+
bool dhcpv6_pd_exclude;
471473

472474
char *upstream;
473475
size_t upstream_len;

0 commit comments

Comments
 (0)