diff options
author | Samuel Tan <samueltan@google.com> | 2015-08-13 16:47:36 -0700 |
---|---|---|
committer | Samuel Tan <samueltan@google.com> | 2015-08-18 13:47:06 -0700 |
commit | 9177c6fae9fb7aecf0a0d378050788ba609a5a41 (patch) | |
tree | 8902f43a3aec7f8c6e72472d1778afd821a98596 | |
parent | 9ae79b8946de6ac1e295417875f2ac7316dd0f80 (diff) | |
download | dhcpcd-6.8.2-9177c6fae9fb7aecf0a0d378050788ba609a5a41.tar.gz |
[PATCH] Merge in DHCP options from the orignal offer
Cherry-picked from
https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay/+/
master/net-misc/dhcpcd/files/patches/dhcpcd-6.8.2-Merge-in-DHCP-options-
from-the-original-offer.patch.
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: 22956197
Change-Id: I9fa006535a67cdb3e25f7d02a8a81d051033922e
Reviewed-on: https://chromium-review.googlesource.com/195270
-rw-r--r-- | dhcp.c | 138 | ||||
-rw-r--r-- | dhcp.h | 12 | ||||
-rw-r--r-- | if-options.h | 11 |
3 files changed, 157 insertions, 4 deletions
@@ -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 { @@ -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]; |