Skip to content

Commit dfb72f6

Browse files
committed
odhcp6c: fix handling of RFC6603 Prefix Exclude Option
Several bugs in the encoding and (more importantly) decoding of DHCPV6_OPT_PD_EXCLUDE lead to the option being ignored in some received messages, or the excluded subnet id being mangled to an incorrect value. Fix both encoding and decoding, being more explicit and slightly more verbose for clarity. Signed-off-by: Mark H. Spatz <mark.h.spatz@gmail.com>
1 parent 24485bb commit dfb72f6

1 file changed

Lines changed: 33 additions & 25 deletions

File tree

src/dhcpv6.c

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -822,13 +822,15 @@ static void dhcpv6_send(enum dhcpv6_msg req_msg_type, uint8_t trid[3], uint32_t
822822
if (pd_entries[j].iaid != iaid)
823823
continue;
824824

825-
uint8_t ex_len = 0;
826-
if (pd_entries[j].exclusion_length > 0)
827-
ex_len = ((pd_entries[j].exclusion_length - pd_entries[j].length - 1) / 8) + 6;
828-
825+
uint8_t excl_subnet_id_nbits, excl_subnet_id_nbytes, excl_opt_len = 0;
826+
if (pd_entries[j].exclusion_length > 0) {
827+
excl_subnet_id_nbits = pd_entries[j].exclusion_length - pd_entries[j].length;
828+
excl_subnet_id_nbytes = ((excl_subnet_id_nbits - 1) / 8) + 1;
829+
excl_opt_len = excl_subnet_id_nbytes + DHCPV6_OPT_HDR_SIZE + 1;
830+
}
829831
struct dhcpv6_ia_prefix p = {
830832
.type = htons(DHCPV6_OPT_IA_PREFIX),
831-
.len = htons(sizeof(p) - DHCPV6_OPT_HDR_SIZE_U + ex_len),
833+
.len = htons(sizeof(p) - DHCPV6_OPT_HDR_SIZE_U + excl_opt_len),
832834
.prefix = pd_entries[j].length,
833835
.addr = pd_entries[j].target
834836
};
@@ -841,21 +843,22 @@ static void dhcpv6_send(enum dhcpv6_msg req_msg_type, uint8_t trid[3], uint32_t
841843
memcpy(ia_pd + ia_pd_len, &p, sizeof(p));
842844
ia_pd_len += sizeof(p);
843845

844-
if (ex_len) {
846+
if (excl_opt_len) {
845847
ia_pd[ia_pd_len++] = 0;
846848
ia_pd[ia_pd_len++] = DHCPV6_OPT_PD_EXCLUDE;
847849
ia_pd[ia_pd_len++] = 0;
848-
ia_pd[ia_pd_len++] = ex_len - DHCPV6_OPT_HDR_SIZE;
850+
ia_pd[ia_pd_len++] = excl_opt_len - DHCPV6_OPT_HDR_SIZE;
849851
ia_pd[ia_pd_len++] = pd_entries[j].exclusion_length;
850852

851-
uint32_t excl = ntohl(pd_entries[j].router.s6_addr32[1]);
852-
excl >>= (64 - pd_entries[j].exclusion_length);
853-
excl <<= 8 - ((pd_entries[j].exclusion_length - pd_entries[j].length) % 8);
853+
uint32_t excluded_bits = ntohl(pd_entries[j].router.s6_addr32[1]);
854+
excluded_bits >>= (64 - pd_entries[j].exclusion_length); /* Right align subnet ID bits */
855+
excluded_bits <<= (32 - excl_subnet_id_nbits); /* Left align subnet ID bits */
854856

855-
for (size_t k = ex_len - 5; k > 0; --k, excl >>= 8)
856-
ia_pd[ia_pd_len + k] = excl & 0xff;
857-
858-
ia_pd_len += ex_len - 5;
857+
/* Copy subnet ID bits into the option MSB first */
858+
for (size_t k = 0; k < excl_subnet_id_nbytes; ++k) {
859+
ia_pd[ia_pd_len++] = excluded_bits >> 24;
860+
excluded_bits <<= 8;
861+
}
859862
}
860863

861864
hdr->len = htons(ntohs(hdr->len) + ntohs(p.len) + 4U);
@@ -1826,9 +1829,10 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end, int *ret)
18261829

18271830
dhcpv6_log_status_code(code, "IA_PREFIX", status_msg, msg_len);
18281831
if (ret) *ret = 0; // renewal failed
1829-
} else if (stype == DHCPV6_OPT_PD_EXCLUDE && slen > 2) {
1832+
} else if (stype == DHCPV6_OPT_PD_EXCLUDE && slen >= 2) {
18301833
/* RFC 6603 §4.2 Prefix Exclude option */
18311834
uint8_t exclude_length = sdata[0];
1835+
uint8_t *excl_subnet_id = &sdata[1];
18321836
if (exclude_length > 64)
18331837
exclude_length = 64;
18341838

@@ -1837,21 +1841,19 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end, int *ret)
18371841
continue;
18381842
}
18391843

1840-
uint8_t bytes_needed = ((exclude_length - entry.length - 1) / 8) + 1;
1841-
if (slen <= bytes_needed) {
1844+
uint8_t excl_subnet_id_nbits = exclude_length - entry.length;
1845+
uint8_t excl_subnet_id_nbytes = ((excl_subnet_id_nbits - 1) / 8) + 1;
1846+
if ((excl_subnet_id + excl_subnet_id_nbytes) > (sdata + slen)) {
18421847
update_state = false;
18431848
continue;
18441849
}
18451850

1846-
// this decrements through the ipaddr bytes masking against
1847-
// the address in the option until byte 0, the option length field.
18481851
uint32_t excluded_bits = 0;
1849-
do {
1850-
excluded_bits = excluded_bits << 8 | sdata[bytes_needed];
1851-
} while (--bytes_needed);
1852-
1853-
excluded_bits >>= 8 - ((exclude_length - entry.length) % 8);
1854-
excluded_bits <<= 64 - exclude_length;
1852+
/* Copy subnet ID bits out of the option MSB first */
1853+
for(size_t i = 0; i < excl_subnet_id_nbytes; i++)
1854+
excluded_bits = (excluded_bits << 8) | excl_subnet_id[i];
1855+
excluded_bits >>= (8 * excl_subnet_id_nbytes) - excl_subnet_id_nbits; /* Right align subnet ID bits */
1856+
excluded_bits <<= (64 - exclude_length); /* Shift subnet ID bits into the low-order bits of the prefix */
18551857

18561858
// Re-using router field to hold the prefix
18571859
entry.router = entry.target; // base prefix
@@ -1867,6 +1869,12 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end, int *ret)
18671869
info("%s/%d preferred %d valid %d",
18681870
inet_ntop(AF_INET6, &entry.target, buf, sizeof(buf)),
18691871
entry.length, entry.preferred , entry.valid);
1872+
1873+
if (entry.exclusion_length) {
1874+
info("PD_EXCLUDE %s/%d",
1875+
inet_ntop(AF_INET6, &entry.router, buf, sizeof(buf)),
1876+
entry.exclusion_length);
1877+
}
18701878
}
18711879

18721880
entry.priority = 0;

0 commit comments

Comments
 (0)