| From 40c8ab6602ffa5cb76d0fe92a6bdad91281a1f0c Mon Sep 17 00:00:00 2001 |
| From: Paul Stewart <pstew@chromium.org> |
| Date: Tue, 1 Jun 2021 21:02:29 +0000 |
| Subject: [PATCH 09/19] Merge in DHCP options from the original offer |
| |
| We've found that some APs respond to DHCP REQUEST messages with a |
| subset of the DHCP options that were present in the original DHCP |
| negotiation. Copy such options out of the stored lease and carry |
| them forward whenever a lease renewal succeeds. |
| |
| BUG=chromium:360452 |
| TEST=New test network_DhcpRenewWithOptionSubset |
| |
| Reviewed-on: https://chromium-review.googlesource.com/195270 |
| --- |
| src/dhcp.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++ |
| src/dhcp.h | 12 +++++ |
| src/if-options.h | 12 +++-- |
| 3 files changed, 156 insertions(+), 5 deletions(-) |
| |
| diff --git a/src/dhcp.c b/src/dhcp.c |
| index 97722a48..9cb64fbd 100644 |
| --- a/src/dhcp.c |
| +++ b/src/dhcp.c |
| @@ -2377,6 +2377,131 @@ dhcp_arp_announced(struct arp_state *state) |
| } |
| #endif /* ARP */ |
| |
| +static void |
| +init_option_iterator(const struct bootp *bootp, |
| + struct dhcp_option_iterator *iterator) |
| +{ |
| + iterator->bootp = bootp; |
| + iterator->ptr = bootp->vend; |
| + iterator->end = iterator->ptr + sizeof(bootp->vend); |
| + iterator->extra_option_locations = 0; |
| + iterator->extra_option_locations_set = 0; |
| +} |
| + |
| +static int |
| +iterate_next_option(struct dhcp_option_iterator *iterator, |
| + uint8_t *option, uint8_t *length, const uint8_t **value) |
| +{ |
| + uint8_t option_code; |
| + uint8_t option_len; |
| + |
| + /* Process special DHO_PAD and DHO_END opcodes. */ |
| + while (iterator->ptr < iterator->end) { |
| + if (*iterator->ptr == DHO_PAD) { |
| + iterator->ptr++; |
| + continue; |
| + } |
| + |
| + if (*iterator->ptr != DHO_END) |
| + break; |
| + |
| + if (iterator->extra_option_locations & |
| + OPTION_OVERLOADED_BOOT_FILE) { |
| + iterator->extra_option_locations &= |
| + ~OPTION_OVERLOADED_BOOT_FILE; |
| + iterator->ptr = iterator->bootp->file; |
| + iterator->end = iterator->ptr + |
| + sizeof(iterator->bootp->file); |
| + } else if (iterator->extra_option_locations & |
| + OPTION_OVERLOADED_SERVER_NAME) { |
| + iterator->extra_option_locations &= |
| + ~OPTION_OVERLOADED_SERVER_NAME; |
| + iterator->ptr = iterator->bootp->sname; |
| + iterator->end = iterator->ptr + |
| + sizeof(iterator->bootp->sname); |
| + } else |
| + return 0; |
| + } |
| + |
| + if (iterator->ptr + 2 > iterator->end) |
| + return 0; |
| + |
| + option_code = *iterator->ptr++; |
| + option_len = *iterator->ptr++; |
| + if (iterator->ptr + option_len > iterator->end) |
| + return 0; |
| + |
| + if (option_code == DHO_OPTSOVERLOADED && option_len > 0 && |
| + !iterator->extra_option_locations_set) { |
| + iterator->extra_option_locations = *iterator->ptr; |
| + iterator->extra_option_locations_set = 1; |
| + } |
| + |
| + if (option) |
| + *option = option_code; |
| + if (length) |
| + *length = option_len; |
| + if (value) |
| + *value = iterator->ptr; |
| + |
| + iterator->ptr += option_len; |
| + |
| + return 1; |
| +} |
| + |
| +static void |
| +merge_option_values(const struct bootp *src, |
| + struct bootp *dst, uint8_t *copy_options) |
| +{ |
| + uint8_t supplied_options[OPTION_MASK_SIZE]; |
| + struct dhcp_option_iterator dst_iterator; |
| + struct dhcp_option_iterator src_iterator; |
| + uint8_t option; |
| + const uint8_t *option_value; |
| + uint8_t option_length; |
| + uint8_t *out; |
| + const uint8_t *out_end; |
| + int added_options = 0; |
| + |
| + /* Traverse the destination message for options already supplied. */ |
| + memset(&supplied_options, 0, sizeof(supplied_options)); |
| + init_option_iterator(dst, &dst_iterator); |
| + while (iterate_next_option(&dst_iterator, &option, NULL, NULL)) { |
| + add_option_mask(supplied_options, option); |
| + } |
| + |
| + /* We will start merging options at the end of the last block |
| + * the iterator traversed to. The const cast below is safe since |
| + * this points to data within the (non-const) dst message. */ |
| + out = (uint8_t *) dst_iterator.ptr; |
| + out_end = dst_iterator.end; |
| + |
| + init_option_iterator(src, &src_iterator); |
| + while (iterate_next_option(&src_iterator, &option, &option_length, |
| + &option_value)) { |
| + if (has_option_mask(supplied_options, option) || |
| + !has_option_mask(copy_options, option)) |
| + continue; |
| + /* We need space for this option, plus a trailing DHO_END. */ |
| + if (out + option_length + 3 > out_end) { |
| + logerrx("%s: unable to fit option %d (length %d)", |
| + __func__, option, option_length); |
| + continue; |
| + } |
| + *out++ = option; |
| + *out++ = option_length; |
| + memcpy(out, option_value, option_length); |
| + out += option_length; |
| + added_options++; |
| + } |
| + |
| + if (added_options) { |
| + *out++ = DHO_END; |
| + loginfox("carrying over %d options from original offer", |
| + added_options); |
| + } |
| +} |
| + |
| void |
| dhcp_bind(struct interface *ifp) |
| { |
| @@ -2475,6 +2600,18 @@ dhcp_bind(struct interface *ifp) |
| else |
| state->reason = "BOUND"; |
| } |
| + |
| + if (state->old && state->old->yiaddr == state->new->yiaddr && |
| + (state->state == DHS_REBOOT || state->state == DHS_RENEW || |
| + state->state == DHS_REBIND)) { |
| + /* Some DHCP servers respond to REQUEST with a subset |
| + * of the original requested parameters. If they were not |
| + * supplied in the response to a renewal, we should assume |
| + * that it's reasonable to transfer them forward from the |
| + * original offer. */ |
| + merge_option_values(state->old, state->new, ifo->requestmask); |
| + } |
| + |
| if (lease->leasetime == ~0U) |
| lease->renewaltime = lease->rebindtime = lease->leasetime; |
| else { |
| diff --git a/src/dhcp.h b/src/dhcp.h |
| index 21716505..58c19075 100644 |
| --- a/src/dhcp.h |
| +++ b/src/dhcp.h |
| @@ -139,6 +139,10 @@ enum FQDN { |
| FQDN_BOTH = 0x31 |
| }; |
| |
| +/* Flags for the OPTIONSOVERLOADED field. */ |
| +#define OPTION_OVERLOADED_BOOT_FILE 1 |
| +#define OPTION_OVERLOADED_SERVER_NAME 2 |
| + |
| /* Sizes for BOOTP options */ |
| #define BOOTP_CHADDR_LEN 16 |
| #define BOOTP_SNAME_LEN 64 |
| @@ -174,6 +178,14 @@ struct bootp_pkt |
| struct bootp bootp; |
| }; |
| |
| +struct dhcp_option_iterator { |
| + const struct bootp *bootp; |
| + const uint8_t *ptr; |
| + const uint8_t *end; |
| + uint8_t extra_option_locations; |
| + uint8_t extra_option_locations_set; |
| +}; |
| + |
| struct dhcp_lease { |
| struct in_addr addr; |
| struct in_addr mask; |
| diff --git a/src/if-options.h b/src/if-options.h |
| index 8f820bc5..3ef02c2e 100644 |
| --- a/src/if-options.h |
| +++ b/src/if-options.h |
| @@ -127,6 +127,8 @@ |
| |
| #define DHCPCD_WARNINGS (DHCPCD_CSR_WARNED | \ |
| DHCPCD_ROUTER_HOST_ROUTE_WARNED) |
| +/* The number of bytes it takes to hold a flag for each of the 256 options. */ |
| +#define OPTION_MASK_SIZE (256 / NBBY) |
| |
| extern const struct option cf_options[]; |
| |
| @@ -162,11 +164,11 @@ struct if_options { |
| time_t mtime; |
| uint8_t iaid[4]; |
| int metric; |
| - uint8_t requestmask[256 / NBBY]; |
| - uint8_t requiremask[256 / NBBY]; |
| - uint8_t nomask[256 / NBBY]; |
| - uint8_t rejectmask[256 / NBBY]; |
| - uint8_t dstmask[256 / NBBY]; |
| + uint8_t requestmask[OPTION_MASK_SIZE]; |
| + uint8_t requiremask[OPTION_MASK_SIZE]; |
| + uint8_t nomask[OPTION_MASK_SIZE]; |
| + uint8_t rejectmask[OPTION_MASK_SIZE]; |
| + uint8_t dstmask[OPTION_MASK_SIZE]; |
| uint8_t requestmasknd[(UINT16_MAX + 1) / NBBY]; |
| uint8_t requiremasknd[(UINT16_MAX + 1) / NBBY]; |
| uint8_t nomasknd[(UINT16_MAX + 1) / NBBY]; |
| -- |
| 2.33.0.800.g4c38ced690-goog |
| |