Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ and may also receive information from ubus
| dhcpv6_pd |bool | 1 | DHCPv6 stateful addressing hands out IA_PD - Internet Address - Prefix Delegation (PD) |
| dhcpv6_pd_preferred |bool | 0 | Set the DHCPv6-PD Preferred (P) flag in outgoing ICMPv6 RA message PIOs (RFC9762); requires `dhcpv6` and `dhcpv6_pd`. |
| 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] |
| 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. |
| router |list |`<local address>`| IPv4 addresses of routers on a given subnet (provided via DHCPv4, should be in order of preference) |
| dns |list |`<local address>`| DNS servers to announce, accepts IPv4 and IPv6 |
| dnr |list |disabled| Encrypted DNS servers to announce, `<priority> <domain name> [<comma separated IP addresses> <SvcParams (key=value)>...]` |
Expand Down
6 changes: 6 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ enum {
IFACE_ATTR_DHCPV6_PD_PREFERRED,
IFACE_ATTR_DHCPV6_PD,
IFACE_ATTR_DHCPV6_PD_MIN_LEN,
IFACE_ATTR_DHCPV6_PD_EXCLUDE,
IFACE_ATTR_DHCPV6_NA,
IFACE_ATTR_DHCPV6_HOSTID_LEN,
IFACE_ATTR_RA_DEFAULT,
Expand Down Expand Up @@ -167,6 +168,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
[IFACE_ATTR_DHCPV6_PD_PREFERRED] = { .name = "dhcpv6_pd_preferred", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_DHCPV6_PD] = { .name = "dhcpv6_pd", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_DHCPV6_PD_MIN_LEN] = { .name = "dhcpv6_pd_min_len", .type = BLOBMSG_TYPE_INT32 },
[IFACE_ATTR_DHCPV6_PD_EXCLUDE] = { .name = "dhcpv6_pd_exclude", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_DHCPV6_NA] = { .name = "dhcpv6_na", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_DHCPV6_HOSTID_LEN] = { .name = "dhcpv6_hostidlength", .type = BLOBMSG_TYPE_INT32 },
[IFACE_ATTR_RA_DEFAULT] = { .name = "ra_default", .type = BLOBMSG_TYPE_INT32 },
Expand Down Expand Up @@ -324,6 +326,7 @@ static void set_interface_defaults(struct interface *iface)
iface->dhcpv6_pd = true;
iface->dhcpv6_pd_preferred = false;
iface->dhcpv6_pd_min_len = PD_MIN_LEN_DEFAULT;
iface->dhcpv6_pd_exclude = false;
iface->dhcpv6_na = true;
iface->dhcpv6_hostid_len = HOSTID_LEN_DEFAULT;
iface->dns_service = true;
Expand Down Expand Up @@ -1465,6 +1468,9 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr
iface->dhcpv6_pd_min_len = pd_min_len;
}

if ((c = tb[IFACE_ATTR_DHCPV6_PD_EXCLUDE]))
iface->dhcpv6_pd_exclude = blobmsg_get_bool(c);

if ((c = tb[IFACE_ATTR_DHCPV6_NA]))
iface->dhcpv6_na = blobmsg_get_bool(c);

Expand Down
83 changes: 75 additions & 8 deletions src/dhcpv6-ia.c
Original file line number Diff line number Diff line change
Expand Up @@ -346,15 +346,24 @@ static bool assign_pd(struct interface *iface, struct dhcpv6_lease *assign)
if (iface->addr6_len < 1)
return false;

bool allow_exclude =
iface->dhcpv6_pd_exclude &&
(assign->flags & OAF_DHCPV6_PD_EXCLUDE) &&
(assign->length < 64); /* Excluded prefix must be larger than the delegated prefix (RFC6603 § 4.2) */

const uint32_t asize = (1 << (64 - assign->length)) - 1;

/* Try honoring the hint first */
uint32_t current = 1, asize = (1 << (64 - assign->length)) - 1;
uint32_t current = allow_exclude ? 0 : 1;
if (assign->assigned_subnet_id) {
list_for_each_entry(c, &iface->ia_assignments, head) {
if (c->flags & OAF_DHCPV6_NA)
continue;

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

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

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

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

if (a->flags & OAF_DHCPV6_PD) {
if (!valid_prefix_length(a, addrs[i].prefix_len))
continue;

/* If assign_pd() chose subnet id 0, send a PD-Exclude option for the first /64 in the delegated prefix */
struct {
uint16_t option_code;
uint16_t option_len;
uint8_t prefix_len;
} _o_packed o_pd_exl;
size_t o_pd_exl_len = 0;
if (a->assigned_subnet_id == 0) {
const uint8_t excluded_prefix_len = 64;

if (a->length >= excluded_prefix_len) {
error("BUG: Can't exclude a prefix from from IA_PD of size %u on %s",
a->length, iface->name);
continue;
}

uint8_t excl_subnet_id_nbits = excluded_prefix_len - a->length;
uint8_t excl_subnet_id_nbytes = ((excl_subnet_id_nbits - 1) / 8) + 1;
o_pd_exl_len = sizeof(o_pd_exl) + excl_subnet_id_nbytes;

/* Work around a bug in odhcp6c that ignores DHCPV6_OPT_PD_EXCLUDE with valid option length of 2. */
if(o_pd_exl_len - DHCPV6_OPT_HDR_SIZE == 2)
o_pd_exl_len++;

o_pd_exl.option_code = htons(DHCPV6_OPT_PD_EXCLUDE);
o_pd_exl.option_len = htons(o_pd_exl_len - DHCPV6_OPT_HDR_SIZE);
o_pd_exl.prefix_len = excluded_prefix_len;
/* (IPv6 subnet ID field is all zeros) */
}

struct dhcpv6_ia_prefix o_ia_p = {
.type = htons(DHCPV6_OPT_IA_PREFIX),
.len = htons(sizeof(o_ia_p) - DHCPV6_OPT_HDR_SIZE),
.len = htons(sizeof(o_ia_p) - DHCPV6_OPT_HDR_SIZE + o_pd_exl_len),
.preferred_lt = htonl(prefix_preferred_lt),
.valid_lt = htonl(prefix_valid_lt),
.prefix_len = a->length,
Expand All @@ -678,14 +722,17 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status,
o_ia_p.addr.s6_addr32[1] |= htonl(a->assigned_subnet_id);
o_ia_p.addr.s6_addr32[2] = o_ia_p.addr.s6_addr32[3] = 0;

if (!valid_prefix_length(a, addrs[i].prefix_len))
continue;

if (buflen < ia_len + sizeof(o_ia_p))
if (buflen < ia_len + sizeof(o_ia_p) + o_pd_exl_len)
return 0;

memcpy(buf + ia_len, &o_ia_p, sizeof(o_ia_p));
ia_len += sizeof(o_ia_p);

if(o_pd_exl_len) {
memset(buf + ia_len, 0, o_pd_exl_len);
memcpy(buf + ia_len, &o_pd_exl, sizeof(o_pd_exl));
ia_len += o_pd_exl_len;
}
}

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

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

case DHCPV6_OPT_ORO: {
size_t reqopts_cnt = olen / sizeof(uint16_t);
for (size_t i = 0; i < reqopts_cnt; i++) {
uint16_t opt = odata[i * sizeof(uint16_t)] << 8 |
odata[i * sizeof(uint16_t) + 1];
switch (opt) {
case DHCPV6_OPT_PD_EXCLUDE:
oro_pd_exclude = true;
break;
}
}
break;
}

default:
break;
}
Expand Down Expand Up @@ -1180,7 +1242,12 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
a->length = reqlen;
a->peer = *addr;
a->iface = iface;
a->flags = is_pd ? OAF_DHCPV6_PD : OAF_DHCPV6_NA;
if (is_pd) {
a->flags = OAF_DHCPV6_PD;
if (oro_pd_exclude)
a->flags |= OAF_DHCPV6_PD_EXCLUDE;
} else
a->flags = OAF_DHCPV6_NA;
a->valid_until = now;
a->preferred_until = now;

Expand Down
6 changes: 5 additions & 1 deletion src/dhcpv6-ia.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ struct in6_addr in6_from_prefix_and_iid(const struct odhcpd_ipaddr *prefix, uint

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

static inline bool valid_addr(const struct odhcpd_ipaddr *addr, time_t now)
Expand Down
1 change: 1 addition & 0 deletions src/dhcpv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
#define DHCPV6_OPT_BOOTFILE_URL 59
#define DHCPV6_OPT_BOOTFILE_PARAM 60
#define DHCPV6_OPT_CLIENT_ARCH 61
#define DHCPV6_OPT_PD_EXCLUDE 67
#define DHCPV6_OPT_SOL_MAX_RT 82
#define DHCPV6_OPT_INF_MAX_RT 83
#define DHCPV6_OPT_DHCPV4_MSG 87
Expand Down
2 changes: 2 additions & 0 deletions src/odhcpd.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ enum odhcpd_mode {
enum odhcpd_assignment_flags {
OAF_DHCPV6_NA = (1 << 0),
OAF_DHCPV6_PD = (1 << 1),
OAF_DHCPV6_PD_EXCLUDE = (1 << 2),
};

#define DHCPV6_OPT_HDR_SIZE 4
Expand Down Expand Up @@ -468,6 +469,7 @@ struct interface {
bool dhcpv6_na;
uint32_t dhcpv6_hostid_len;
uint32_t dhcpv6_pd_min_len; // minimum delegated prefix length
bool dhcpv6_pd_exclude;

char *upstream;
size_t upstream_len;
Expand Down