| From 2d9ee9575953e6329819cae9b6311af438d9dac9 Mon Sep 17 00:00:00 2001 |
| From: Paul Stewart <pstew@chromium.org> |
| Date: Wed, 20 May 2015 17:54:11 -0700 |
| Subject: [PATCH] Merge in DHCP options from the orignal 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 |
| |
| --- |
| dhcp.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| dhcp.h | 12 ++++++ |
| if-options.h | 11 +++-- |
| 3 files changed, 157 insertions(+), 4 deletions(-) |
| |
| diff --git a/dhcp.c b/dhcp.c |
| index dfc2a30..feea34c 100644 |
| --- a/dhcp.c |
| +++ b/dhcp.c |
| @@ -1923,6 +1923,132 @@ dhcp_rebind(void *arg) |
| send_rebind(ifp); |
| } |
| |
| +static void |
| +init_option_iterator(const struct dhcp_message *message, |
| + struct dhcp_option_iterator *iterator) |
| +{ |
| + iterator->message = message; |
| + iterator->ptr = message->options; |
| + iterator->end = iterator->ptr + sizeof(message->options); |
| + 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->message->bootfile; |
| + iterator->end = iterator->ptr + |
| + sizeof(iterator->message->bootfile); |
| + } else if (iterator->extra_option_locations & |
| + OPTION_OVERLOADED_SERVER_NAME) { |
| + iterator->extra_option_locations &= |
| + ~OPTION_OVERLOADED_SERVER_NAME; |
| + iterator->ptr = iterator->message->servername; |
| + iterator->end = iterator->ptr + |
| + sizeof(iterator->message->servername); |
| + } 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_OPTIONSOVERLOADED && 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 dhcp_message *src, |
| + struct dhcp_message *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) { |
| + syslog(LOG_ERR, |
| + "%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; |
| + syslog(LOG_INFO, "carrying over %d options from original offer", |
| + added_options); |
| + } |
| +} |
| + |
| void |
| dhcp_bind(struct interface *ifp, struct arp_state *astate) |
| { |
| @@ -2024,6 +2150,18 @@ dhcp_bind(struct interface *ifp, struct arp_state *astate) |
| 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/dhcp.h b/dhcp.h |
| index e136a9c..e926b00 100644 |
| --- a/dhcp.h |
| +++ b/dhcp.h |
| @@ -142,6 +142,10 @@ enum FQDN { |
| /* Some crappy DHCP servers require the BOOTP minimum length */ |
| #define BOOTP_MESSAGE_LENTH_MIN 300 |
| |
| +/* Flags for the OPTIONSOVERLOADED field. */ |
| +#define OPTION_OVERLOADED_BOOT_FILE 1 |
| +#define OPTION_OVERLOADED_SERVER_NAME 2 |
| + |
| /* Don't import common.h as that defines __unused which causes problems |
| * on some Linux systems which define it as part of a structure */ |
| #if __GNUC__ > 2 || defined(__INTEL_COMPILER) |
| @@ -173,6 +177,14 @@ struct dhcp_message { |
| uint8_t options[DHCP_OPTION_LEN]; /* message options - cookie */ |
| } __packed; |
| |
| +struct dhcp_option_iterator { |
| + const struct dhcp_message *message; |
| + 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 net; |
| diff --git a/if-options.h b/if-options.h |
| index c6fdb11..f1c45b8 100644 |
| --- a/if-options.h |
| +++ b/if-options.h |
| @@ -120,6 +120,9 @@ |
| |
| extern const struct option cf_options[]; |
| |
| +/* The number of bytes it takes to hold a flag for each of the 256 options. */ |
| +#define OPTION_MASK_SIZE (256 / NBBY) |
| + |
| struct if_sla { |
| char ifname[IF_NAMESIZE]; |
| uint32_t sla; |
| @@ -149,10 +152,10 @@ 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 requestmask[OPTION_MASK_SIZE]; |
| + uint8_t requiremask[OPTION_MASK_SIZE]; |
| + uint8_t nomask[OPTION_MASK_SIZE]; |
| + uint8_t rejectmask[OPTION_MASK_SIZE]; |
| uint8_t requestmask6[(UINT16_MAX + 1) / NBBY]; |
| uint8_t requiremask6[(UINT16_MAX + 1) / NBBY]; |
| uint8_t nomask6[(UINT16_MAX + 1) / NBBY]; |
| -- |
| 2.2.0.rc0.207.ga3a616c |
| |