blob: 9bce148befb0f7335ebd87e82a2ee37c85bc904b [file] [log] [blame]
From ccd28aa8243a7a53862560a66f924fb1c6f048d0 Mon Sep 17 00:00:00 2001
From: Paul Stewart <pstew@chromium.org>
Date: Mon, 11 May 2015 14:42:05 -0700
Subject: [PATCH] dhcpcd: Teach DHCP client to do unicast-ARP for gateway
Implement RFC-4436 ("Detecting Network Attachment in IPv4 (DNAv4)").
Keep track of the MAC address of the default gateway in a file as
a part of the arpgw change ("-R" option), and append this info to
the end of the saved lease.
Implement a separate command line option ("-P") which will use
the MAC address stored in the lease to do a unicast ARP to the
default gateway saved in the lease. If this succeeds, notify
listeners that this succeeded, along with the contents of the lease,
but do not stop the normal DHCP process. This returns immediate,
fast feedback that our lease will likely work, but continues the
DHCP process so we know for sure.
BUG=chromium-os:25717
TEST=Manual: Hex dumps of the lease file with or without the "-R"
option set. Ensure gateway MAC address appears in the lease when run
to success with "-R", and that a successive run without "-R" casuses
the MAC to disappear. tcpdump packet captures of DHCP and ARP traffic
when running with and without the "-P" option set after a previous run
with or without the "-R" option set.
Reviewed-on: https://gerrit.chromium.org/gerrit/22643
---
arp.c | 19 ++++++--
arp.h | 2 +
dhcp.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------
dhcp.h | 7 +++
if-bsd.c | 7 ++-
if-linux.c | 10 ++--
if-options.c | 4 ++
if-options.h | 3 +-
if.h | 2 +-
9 files changed, 180 insertions(+), 30 deletions(-)
diff --git a/arp.c b/arp.c
index abb6d4e..5fb23a3 100644
--- a/arp.c
+++ b/arp.c
@@ -55,7 +55,8 @@
(sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN))
static ssize_t
-arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip)
+arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip,
+ const uint8_t *dest_hw_addr)
{
uint8_t arp_buffer[ARP_LEN];
struct arphdr ar;
@@ -85,9 +86,13 @@ arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip)
APPEND(&ar, sizeof(ar));
APPEND(ifp->hwaddr, ifp->hwlen);
APPEND(&sip, sizeof(sip));
- ZERO(ifp->hwlen);
+ if (dest_hw_addr)
+ APPEND(dest_hw_addr, ifp->hwlen);
+ else
+ ZERO(ifp->hwlen);
APPEND(&tip, sizeof(tip));
- return if_sendrawpacket(ifp, ETHERTYPE_ARP, arp_buffer, len);
+ return if_sendrawpacket(ifp, ETHERTYPE_ARP, arp_buffer, len,
+ dest_hw_addr);
eexit:
errno = ENOBUFS;
@@ -232,7 +237,8 @@ arp_announce1(void *arg)
"%s: ARP announcing %s (%d of %d)",
ifp->name, inet_ntoa(astate->addr),
astate->claims, ANNOUNCE_NUM);
- if (arp_request(ifp, astate->addr.s_addr, astate->addr.s_addr) == -1)
+ if (arp_request(ifp, astate->addr.s_addr, astate->addr.s_addr,
+ NULL) == -1)
logger(ifp->ctx, LOG_ERR, "send_arp: %m");
eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT,
astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced,
@@ -262,6 +268,7 @@ arp_probe1(void *arg)
struct arp_state *astate = arg;
struct interface *ifp = astate->iface;
struct timespec tv;
+ uint8_t *dest_hwaddr = NULL;
if (++astate->probes < PROBE_NUM) {
tv.tv_sec = PROBE_MIN;
@@ -279,8 +286,10 @@ arp_probe1(void *arg)
ifp->name, inet_ntoa(astate->addr),
astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM,
timespec_to_double(&tv));
+ if (astate->dest_hwlen == ifp->hwlen)
+ dest_hwaddr = astate->dest_hwaddr;
if (arp_request(ifp, astate->src_addr.s_addr,
- astate->addr.s_addr) == -1)
+ astate->addr.s_addr, dest_hwaddr) == -1)
logger(ifp->ctx, LOG_ERR, "send_arp: %m");
}
diff --git a/arp.h b/arp.h
index 8c50092..0daf615 100644
--- a/arp.h
+++ b/arp.h
@@ -63,6 +63,8 @@ struct arp_state {
int probes;
int claims;
struct in_addr failed;
+ uint8_t dest_hwlen;
+ unsigned char dest_hwaddr[HWADDR_LEN];
};
TAILQ_HEAD(arp_statehead, arp_state);
diff --git a/dhcp.c b/dhcp.c
index 9cb50a7..93a329f 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -1070,6 +1070,8 @@ write_lease(const struct interface *ifp, const struct dhcp_message *dhcp)
uint8_t l;
uint8_t o = 0;
const struct dhcp_state *state = D_CSTATE(ifp);
+ uint8_t write_buffer[sizeof(*dhcp) + sizeof(state->server_info) + 1];
+ uint8_t *w;
/* We don't write BOOTP leases */
if (IS_BOOTP(ifp, dhcp)) {
@@ -1100,7 +1102,18 @@ write_lease(const struct interface *ifp, const struct dhcp_message *dhcp)
p += l;
}
}
- bytes = write(fd, dhcp, len);
+
+ memcpy(write_buffer, dhcp, len);
+ w = write_buffer + len;
+
+ /* Copy in server info if this is available. */
+ if (state->server_info.gw_hwlen != 0) {
+ *w++ = DHO_END;
+ memcpy(w, &state->server_info, sizeof(state->server_info));
+ len += sizeof(state->server_info) + 1;
+ }
+
+ bytes = write(fd, write_buffer, len);
close(fd);
return bytes;
}
@@ -1111,11 +1124,18 @@ read_lease(struct interface *ifp)
int fd;
struct dhcp_message *dhcp;
struct dhcp_state *state = D_STATE(ifp);
+ uint8_t read_buffer[sizeof(*dhcp) + sizeof(state->server_info) + 1];
+ const uint8_t *options_startp =
+ read_buffer + offsetof(struct dhcp_message, options);
+ const uint8_t *options_endp = options_startp + sizeof(dhcp->options);
+ uint8_t option_len;
+ uint8_t option_type = 0;
ssize_t bytes;
const uint8_t *auth;
uint8_t type;
size_t auth_len;
+ memset(&state->server_info, 0, sizeof(state->server_info));
fd = open(state->leasefile, O_RDONLY);
if (fd == -1) {
if (errno != ENOENT)
@@ -1125,17 +1145,31 @@ read_lease(struct interface *ifp)
}
logger(ifp->ctx, LOG_DEBUG, "%s: reading lease `%s'",
ifp->name, state->leasefile);
+ bytes = read(fd, read_buffer, sizeof(read_buffer));
+ close(fd);
+
+ /* Lease file should at minimum contain all fields before options. */
+ if (read_buffer + bytes < options_startp)
+ return NULL;
+
dhcp = calloc(1, sizeof(*dhcp));
if (dhcp == NULL) {
- close(fd);
return NULL;
}
- bytes = read(fd, dhcp, sizeof(*dhcp));
- close(fd);
- if (bytes < 0) {
- free(dhcp);
- return NULL;
+
+ if (options_endp > read_buffer + bytes)
+ options_endp = read_buffer + bytes;
+
+ while (options_startp < options_endp) {
+ option_type = *options_startp++;
+ if (option_type == DHO_END)
+ break;
+ if (option_type != DHO_PAD) {
+ option_len = *options_startp++;
+ options_startp += option_len;
+ }
}
+ memcpy(dhcp, read_buffer, options_startp - read_buffer);
/* We may have found a BOOTP server */
if (get_option_uint8(ifp->ctx, &type, dhcp, DHO_MESSAGETYPE) == -1)
@@ -1162,6 +1196,27 @@ read_lease(struct interface *ifp)
"%s: accepted reconfigure key", ifp->name);
}
+ /*
+ * DHCP server information is stored after the DHO_END character
+ * in the lease file. The first byte of the server information
+ * is the length of the gateway hardware address.
+ */
+ options_endp = read_buffer + bytes;
+ if (options_startp >= options_endp ||
+ options_startp + sizeof(state->server_info) > options_endp)
+ return dhcp;
+
+ logger(ifp->ctx, LOG_DEBUG, "%s: found server info in lease '%s'",
+ ifp->name, state->leasefile);
+
+ memcpy(&state->server_info, options_startp, sizeof(state->server_info));
+ if (state->server_info.gw_hwlen != ifp->hwlen) {
+ logger(ifp->ctx, LOG_ERR, "%s: lease file %s has incompatible"
+ "MAC address length %d (expected %zd)",
+ ifp->name, state->leasefile,
+ state->server_info.gw_hwlen, ifp->hwlen);
+ memset(&state->server_info, 0, sizeof(state->server_info));
+ }
return dhcp;
}
@@ -1681,7 +1736,7 @@ send_message(struct interface *ifp, uint8_t type,
logger(ifp->ctx, LOG_ERR, "dhcp_makeudppacket: %m");
} else {
r = if_sendrawpacket(ifp, ETHERTYPE_IP,
- (uint8_t *)udp, ulen);
+ (uint8_t *)udp, ulen, NULL);
free(udp);
}
/* If we failed to send a raw packet this normally means
@@ -2129,6 +2184,8 @@ dhcp_reboot_newopts(struct interface *ifp, unsigned long long oldopts)
}
}
+static void start_unicast_arp(struct interface *ifp);
+
static void
dhcp_reboot(struct interface *ifp)
{
@@ -2150,6 +2207,9 @@ dhcp_reboot(struct interface *ifp)
dhcp_static(ifp);
return;
}
+ if (ifo->options & DHCPCD_UNICAST_ARP) {
+ start_unicast_arp(ifp);
+ }
if (ifo->options & DHCPCD_INFORM) {
logger(ifp->ctx, LOG_INFO, "%s: informing address of %s",
ifp->name, inet_ntoa(state->lease.addr));
@@ -2342,10 +2402,23 @@ whitelisted_ip(const struct if_options *ifo, in_addr_t addr)
}
static void
-dhcp_probe_gw_timeout(struct arp_state *astate) {
+save_gateway_addr(struct interface *ifp, const uint8_t *gw_hwaddr)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ memcpy(state->server_info.gw_hwaddr, gw_hwaddr, ifp->hwlen);
+ state->server_info.gw_hwlen = ifp->hwlen;
+}
+
+static void
+dhcp_probe_gw_timeout(struct arp_state *astate)
+{
struct dhcp_state *state = D_STATE(astate->iface);
- /* Allow ourselves to fail only once this way */
+ /* Ignore unicast ARP failures. */
+ if (astate->dest_hwlen)
+ return;
+
+ /* Probegw failure, allow ourselves to fail only once this way */
logger(astate->iface->ctx, LOG_ERR,
"%s: Probe gateway %s timed out ",
astate->iface->name, inet_ntoa(astate->addr));
@@ -2373,14 +2446,22 @@ dhcp_probe_gw_response(struct arp_state *astate, const struct arp_msg *amsg)
amsg &&
amsg->tip.s_addr == astate->src_addr.s_addr &&
amsg->sip.s_addr == astate->addr.s_addr) {
- dhcp_close(astate->iface);
- eloop_timeout_delete(astate->iface->ctx->eloop,
- NULL, astate->iface);
-#ifdef IN_IFF_TENTATIVE
- ipv4_finaliseaddr(astate->iface);
-#else
- dhcp_bind(astate->iface, NULL);
-#endif
+ if (astate->dest_hwlen) {
+ /* Response to unicast ARP. */
+ /* TODO(zqiu): notify listener. */
+ } else {
+ /* Response to arpgw request. */
+ save_gateway_addr(astate->iface, amsg->sha);
+
+ dhcp_close(astate->iface);
+ eloop_timeout_delete(astate->iface->ctx->eloop,
+ NULL, astate->iface);
+ #ifdef IN_IFF_TENTATIVE
+ ipv4_finaliseaddr(astate->iface);
+ #else
+ dhcp_bind(astate->iface, NULL);
+ #endif
+ }
arp_close(astate->iface);
}
}
@@ -2407,6 +2488,45 @@ dhcp_probe_gw(struct interface *ifp)
}
static void
+start_unicast_arp(struct interface *ifp)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+ struct in_addr gwa;
+ struct in_addr src_addr;
+ struct arp_state *astate;
+
+ if (!state->offer)
+ return;
+
+ if (!state->lease.frominfo)
+ return;
+
+ if (state->server_info.gw_hwlen != ifp->hwlen)
+ return;
+
+ if (get_option_addr(ifp->ctx, &gwa, state->offer, DHO_ROUTER))
+ return;
+
+ astate = arp_new(ifp, &gwa);
+ if (!astate)
+ return;
+ if (state->offer->yiaddr)
+ astate->src_addr.s_addr = state->offer->yiaddr;
+ else
+ astate->src_addr.s_addr = state->offer->ciaddr;
+ astate->probed_cb = dhcp_probe_gw_timeout;
+ astate->conflicted_cb = dhcp_probe_gw_response;
+ astate->dest_hwlen = state->server_info.gw_hwlen;
+ memcpy(astate->dest_hwaddr, state->server_info.gw_hwaddr,
+ state->server_info.gw_hwlen);
+
+ arp_probe(astate);
+
+ /* Invalidate our gateway address until the next successful PROBEGW. */
+ state->server_info.gw_hwlen = 0;
+}
+
+static void
dhcp_arp_probed(struct arp_state *astate)
{
struct dhcp_state *state;
diff --git a/dhcp.h b/dhcp.h
index 85ce06f..acef896 100644
--- a/dhcp.h
+++ b/dhcp.h
@@ -185,6 +185,12 @@ struct dhcp_lease {
uint32_t cookie;
};
+/* Extra data about servers stored in the lease file after the dhcp_message */
+struct dhcp_server_info {
+ uint8_t gw_hwlen;
+ unsigned char gw_hwaddr[HWADDR_LEN];
+};
+
enum DHS {
DHS_INIT,
DHS_DISCOVER,
@@ -236,6 +242,7 @@ struct dhcp_state {
unsigned int conflicts;
time_t defend;
char randomstate[128];
+ struct dhcp_server_info server_info;
};
#define D_STATE(ifp) \
diff --git a/if-bsd.c b/if-bsd.c
index 14ea913..ec4d4ba 100644
--- a/if-bsd.c
+++ b/if-bsd.c
@@ -376,7 +376,7 @@ eexit:
ssize_t
if_sendrawpacket(const struct interface *ifp, uint16_t protocol,
- const void *data, size_t len)
+ const void *data, size_t len, const uint8_t *dest_hw_addr)
{
struct iovec iov[2];
struct ether_header hw;
@@ -384,7 +384,10 @@ if_sendrawpacket(const struct interface *ifp, uint16_t protocol,
const struct dhcp_state *state;
memset(&hw, 0, ETHER_HDR_LEN);
- memset(&hw.ether_dhost, 0xff, ETHER_ADDR_LEN);
+ if (dest_hw_addr)
+ memcpy(&hw.ether_dhost, dest_hw_addr, ETHER_ADDR_LEN);
+ else
+ memset(&hw.ether_dhost, 0xff, ETHER_ADDR_LEN);
hw.ether_type = htons(protocol);
iov[0].iov_base = &hw;
iov[0].iov_len = ETHER_HDR_LEN;
diff --git a/if-linux.c b/if-linux.c
index 9871d6b..e4847c6 100644
--- a/if-linux.c
+++ b/if-linux.c
@@ -1194,7 +1194,7 @@ eexit:
ssize_t
if_sendrawpacket(const struct interface *ifp, uint16_t protocol,
- const void *data, size_t len)
+ const void *data, size_t len, const uint8_t *dest_hw_addr)
{
const struct dhcp_state *state;
union sockunion {
@@ -1213,8 +1213,12 @@ if_sendrawpacket(const struct interface *ifp, uint16_t protocol,
if (ifp->family == ARPHRD_INFINIBAND)
memcpy(&su.sll.sll_addr,
&ipv4_bcast_addr, sizeof(ipv4_bcast_addr));
- else
- memset(&su.sll.sll_addr, 0xff, ifp->hwlen);
+ else {
+ if (dest_hw_addr)
+ memcpy(&su.sll.sll_addr, dest_hw_addr, ifp->hwlen);
+ else
+ memset(&su.sll.sll_addr, 0xff, ifp->hwlen);
+ }
state = D_CSTATE(ifp);
if (protocol == ETHERTYPE_ARP)
fd = state->arp_fd;
diff --git a/if-options.c b/if-options.c
index c6226dd..8680b5b 100644
--- a/if-options.c
+++ b/if-options.c
@@ -141,6 +141,7 @@ const struct option cf_options[] = {
{"noipv4ll", no_argument, NULL, 'L'},
{"master", no_argument, NULL, 'M'},
{"nooption", optional_argument, NULL, 'O'},
+ {"unicast", no_argument, NULL, 'P'},
{"require", required_argument, NULL, 'Q'},
{"arpgw", no_argument, NULL, 'R'},
{"static", required_argument, NULL, 'S'},
@@ -988,6 +989,9 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo,
return -1;
}
break;
+ case 'P':
+ ifo->options |= DHCPCD_UNICAST_ARP;
+ break;
case 'Q':
arg = set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo,
&request, &require, &no, &reject);
diff --git a/if-options.h b/if-options.h
index 153f9b1..c6fdb11 100644
--- a/if-options.h
+++ b/if-options.h
@@ -42,7 +42,7 @@
/* Don't set any optional arguments here so we retain POSIX
* compatibility with getopt */
#define IF_OPTS "46bc:de:f:gh:i:j:kl:m:no:pqr:s:t:u:v:wxy:z:" \
- "ABC:DEF:GHI:JKLMO:Q:RS:TUVW:X:Z:"
+ "ABC:DEF:GHI:JKLMO:PQ:RS:TUVW:X:Z:"
#define DEFAULT_TIMEOUT 30
#define DEFAULT_REBOOT 5
@@ -112,6 +112,7 @@
#define DHCPCD_IPV6RA_ACCEPT_NOPUBLIC (1ULL << 56)
#define DHCPCD_BOOTP (1ULL << 57)
#define DHCPCD_ARPGW (1ULL << 58)
+#define DHCPCD_UNICAST_ARP (1ULL << 59)
#define DHCPCD_WARNINGS (DHCPCD_CSR_WARNED | \
DHCPCD_ROUTER_HOST_ROUTE_WARNED)
diff --git a/if.h b/if.h
index d9ef718..cda4c01 100644
--- a/if.h
+++ b/if.h
@@ -128,7 +128,7 @@ int if_managelink(struct dhcpcd_ctx *);
extern const char *if_pfname;
int if_openrawsocket(struct interface *, uint16_t);
ssize_t if_sendrawpacket(const struct interface *,
- uint16_t, const void *, size_t);
+ uint16_t, const void *, size_t, const uint8_t *dest_hw_addr);
ssize_t if_readrawpacket(struct interface *, uint16_t, void *, size_t, int *);
int if_address(const struct interface *,
--
2.2.0.rc0.207.ga3a616c