blob: 84208d3c53c9460839dbaf15b30004fa03aee3b0 [file] [log] [blame]
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