Split the ARP resolution code to dhcp or ipv4ll where it belongs.
This allows us to probe IPv4LL while still DISCOVERing a DHCP lease.
#include <syslog.h>
#include <unistd.h>
-#define ELOOP_QUEUE 2
+#define ELOOP_QUEUE 5
#include "config.h"
#include "arp.h"
#include "ipv4.h"
return -1;
}
-static void
-arp_failure(struct interface *ifp)
-{
- const struct dhcp_state *state = D_CSTATE(ifp);
-
- /* If we failed without a magic cookie then we need to try
- * and defend our IPv4LL address. */
- if ((state->offer != NULL &&
- state->offer->cookie != htonl(MAGIC_COOKIE)) ||
- (state->new != NULL &&
- state->new->cookie != htonl(MAGIC_COOKIE)))
- {
- ipv4ll_handle_failure(ifp);
- return;
- }
-
- unlink(state->leasefile);
- if (!state->lease.frominfo)
- dhcp_decline(ifp);
- eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
- if (state->lease.frominfo)
- dhcpcd_startinterface(ifp);
- else
- eloop_timeout_add_sec(ifp->ctx->eloop,
- DHCP_ARP_FAIL, dhcpcd_startinterface, ifp);
-}
-
static void
arp_packet(void *arg)
{
const struct interface *ifn;
uint8_t arp_buffer[ARP_LEN];
struct arphdr ar;
- uint32_t reply_s;
- uint32_t reply_t;
- uint8_t *hw_s, *hw_t;
+ struct arp_msg arm;
ssize_t bytes;
struct dhcp_state *state;
- struct if_options *opts = ifp->options;
- const char *hwaddr;
- struct in_addr ina, fail;
- char hwbuf[HWADDR_LEN * 3];
+ struct arp_state *astate, *astaten;
+ unsigned char *hw_s, *hw_t;
int flags;
state = D_STATE(ifp);
if ((size_t)bytes < sizeof(ar))
continue;
memcpy(&ar, arp_buffer, sizeof(ar));
+ /* Families must match */
+ if (ar.ar_hrd != htons(ifp->family))
+ continue;
/* Protocol must be IP. */
if (ar.ar_pro != htons(ETHERTYPE_IP))
continue;
- if (ar.ar_pln != sizeof(reply_s))
+ if (ar.ar_pln != sizeof(arm.sip.s_addr))
continue;
/* Only these types are recognised */
if (ar.ar_op != htons(ARPOP_REPLY) &&
}
if (ifn)
continue;
- /* Copy out the IP addresses */
- memcpy(&reply_s, hw_s + ar.ar_hln, ar.ar_pln);
- memcpy(&reply_t, hw_t + ar.ar_hln, ar.ar_pln);
-
- /* Check for arping */
- if (state->arping_index &&
- state->arping_index <= opts->arping_len &&
- (reply_s == opts->arping[state->arping_index - 1] ||
- (reply_s == 0 &&
- reply_t == opts->arping[state->arping_index - 1])))
- {
- ina.s_addr = reply_s;
- hwaddr = hwaddr_ntoa((unsigned char *)hw_s,
- (size_t)ar.ar_hln, hwbuf, sizeof(hwbuf));
- syslog(LOG_INFO,
- "%s: found %s on hardware address %s",
- ifp->name, inet_ntoa(ina), hwaddr);
- if (dhcpcd_selectprofile(ifp, hwaddr) == -1 &&
- dhcpcd_selectprofile(ifp, inet_ntoa(ina)) == -1)
- {
- state->probes = 0;
- /* We didn't find a profile for this
- * address or hwaddr, so move to the next
- * arping profile */
- if (state->arping_index <
- ifp->options->arping_len)
- {
- arp_probe(ifp);
- return;
- }
- }
- dhcp_close(ifp);
- eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
- dhcpcd_startinterface(ifp);
- return;
- }
-
- fail.s_addr = 0;
- /* RFC 2131 3.1.5, Client-server interaction
- * RFC 3927 2.2.1, Probe Conflict Detection */
- if (state->offer &&
- (reply_s == state->offer->yiaddr ||
- (reply_s == 0 && reply_t == state->offer->yiaddr)))
- fail.s_addr = state->offer->yiaddr;
-
- /* RFC 3927 2.5, Conflict Defense */
- if (IN_LINKLOCAL(htonl(state->addr.s_addr)) &&
- reply_s == state->addr.s_addr)
- fail.s_addr = state->addr.s_addr;
-
- if (fail.s_addr) {
- state->fail = fail;
- syslog(LOG_ERR, "%s: hardware address %s claims %s",
- ifp->name,
- hwaddr_ntoa((unsigned char *)hw_s,
- (size_t)ar.ar_hln, hwbuf, sizeof(hwbuf)),
- inet_ntoa(state->fail));
- errno = EEXIST;
- arp_failure(ifp);
- return;
+ /* Copy out the HW and IP addresses */
+ memcpy(&arm.sha, hw_s, ar.ar_hln);
+ memcpy(&arm.sip.s_addr, hw_s + ar.ar_hln, ar.ar_pln);
+ memcpy(&arm.tha, hw_t, ar.ar_hln);
+ memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln);
+
+ /* Run the conflicts */
+ TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) {
+ astate->conflicted_cb(astate, &arm);
}
}
}
-void
-arp_announce(void *arg)
+static void
+arp_open(struct interface *ifp)
{
- struct interface *ifp = arg;
- struct dhcp_state *state = D_STATE(ifp);
- struct timeval tv;
+ struct dhcp_state *state;
- if (state->new == NULL)
- return;
+ state = D_STATE(ifp);
if (state->arp_fd == -1) {
state->arp_fd = if_openrawsocket(ifp, ETHERTYPE_ARP);
if (state->arp_fd == -1) {
eloop_event_add(ifp->ctx->eloop, state->arp_fd,
arp_packet, ifp, NULL, NULL);
}
- if (++state->claims < ANNOUNCE_NUM)
+}
+
+static void
+arp_announced(void *arg)
+{
+ struct arp_state *astate = arg;
+
+ if (astate->announced_cb) {
+ astate->announced_cb(astate);
+ return;
+ }
+
+ /* Nothing more to do, so free us */
+ arp_free(astate);
+}
+
+static void
+arp_announce1(void *arg)
+{
+ struct arp_state *astate = arg;
+ struct interface *ifp = astate->iface;
+
+ if (++astate->claims < ANNOUNCE_NUM)
syslog(LOG_DEBUG,
"%s: sending ARP announce (%d of %d), "
"next in %d.0 seconds",
- ifp->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
+ ifp->name, astate->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
else
syslog(LOG_DEBUG,
"%s: sending ARP announce (%d of %d)",
- ifp->name, state->claims, ANNOUNCE_NUM);
+ ifp->name, astate->claims, ANNOUNCE_NUM);
if (arp_send(ifp, ARPOP_REQUEST,
- state->new->yiaddr, state->new->yiaddr) == -1)
+ astate->addr.s_addr, astate->addr.s_addr) == -1)
syslog(LOG_ERR, "send_arp: %m");
- if (state->claims < ANNOUNCE_NUM) {
- eloop_timeout_add_sec(ifp->ctx->eloop,
- ANNOUNCE_WAIT, arp_announce, ifp);
- return;
- }
- if (state->new->cookie != htonl(MAGIC_COOKIE)) {
- /* Reset the conflict counter when we finish announcing. */
- eloop_timeout_add_sec(ifp->ctx->eloop,
- ANNOUNCE_WAIT, ipv4ll_claimed, ifp);
- /* Check if doing DHCP */
- if (!(ifp->options->options & DHCPCD_DHCP))
- return;
- /* We should pretend to be at the end
- * of the DHCP negotation cycle unless we rebooted */
- if (state->interval)
- state->interval = 64 + DHCP_RAND_MIN;
- else
- state->interval = ANNOUNCE_WAIT;
- state->probes = 0;
- state->claims = 0;
- tv.tv_sec = state->interval;
- tv.tv_usec = (suseconds_t)arc4random_uniform(
- (DHCP_RAND_MAX - DHCP_RAND_MIN) * 1000000);
- timernorm(&tv);
- eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcp_discover, ifp);
- } else {
- eloop_event_delete(ifp->ctx->eloop, state->arp_fd, 0);
- close(state->arp_fd);
- state->arp_fd = -1;
- }
+ eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT,
+ astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced,
+ astate);
}
void
-arp_probe(void *arg)
+arp_announce(struct arp_state *astate)
{
- struct interface *ifp = arg;
- struct dhcp_state *state = D_STATE(ifp);
- struct in_addr addr;
- struct timeval tv;
- int arping = 0;
- if (state->arp_fd == -1) {
- state->arp_fd = if_openrawsocket(ifp, ETHERTYPE_ARP);
- if (state->arp_fd == -1) {
- syslog(LOG_ERR, "%s: %s: %m", __func__, ifp->name);
- return;
- }
- eloop_event_add(ifp->ctx->eloop,
- state->arp_fd, arp_packet, ifp, NULL, NULL);
- }
+ arp_open(astate->iface);
+ astate->claims = 0;
+ arp_announce1(astate);
+}
- if (state->arping_index < ifp->options->arping_len) {
- addr.s_addr = ifp->options->arping[state->arping_index];
- arping = 1;
- } else if (state->offer) {
- if (state->offer->yiaddr)
- addr.s_addr = state->offer->yiaddr;
- else
- addr.s_addr = state->offer->ciaddr;
- } else
- addr.s_addr = state->addr.s_addr;
-
- if (state->probes == 0) {
- if (arping)
- syslog(LOG_DEBUG, "%s: searching for %s",
- ifp->name, inet_ntoa(addr));
- else
- syslog(LOG_DEBUG, "%s: checking for %s",
- ifp->name, inet_ntoa(addr));
- }
- if (++state->probes < PROBE_NUM) {
+static void
+arp_probed(void *arg)
+{
+ struct arp_state *astate = arg;
+
+ astate->probed_cb(astate);
+}
+
+static void
+arp_probe1(void *arg)
+{
+ struct arp_state *astate = arg;
+ struct interface *ifp = astate->iface;
+ struct timeval tv;
+
+ if (++astate->probes < PROBE_NUM) {
tv.tv_sec = PROBE_MIN;
tv.tv_usec = (suseconds_t)arc4random_uniform(
(PROBE_MAX - PROBE_MIN) * 1000000);
timernorm(&tv);
- eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe, ifp);
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe1, astate);
} else {
tv.tv_sec = ANNOUNCE_WAIT;
tv.tv_usec = 0;
- if (arping) {
- state->probes = 0;
- if (++state->arping_index < ifp->options->arping_len)
- eloop_timeout_add_tv(ifp->ctx->eloop,
- &tv, arp_probe, ifp);
- else
- eloop_timeout_add_tv(ifp->ctx->eloop,
- &tv, dhcpcd_startinterface, ifp);
- } else
- eloop_timeout_add_tv(ifp->ctx->eloop,
- &tv, dhcp_bind, ifp);
+ eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probed, astate);
}
syslog(LOG_DEBUG,
"%s: sending ARP probe (%d of %d), next in %0.1f seconds",
- ifp->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM,
+ ifp->name, astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM,
timeval_to_double(&tv));
- if (arp_send(ifp, ARPOP_REQUEST, 0, addr.s_addr) == -1)
+ if (arp_send(ifp, ARPOP_REQUEST, 0, astate->addr.s_addr) == -1)
syslog(LOG_ERR, "send_arp: %m");
}
void
-arp_start(struct interface *ifp)
+arp_probe(struct arp_state *astate)
{
- struct dhcp_state *state = D_STATE(ifp);
- state->probes = 0;
- state->arping_index = 0;
- arp_probe(ifp);
+ arp_open(astate->iface);
+ astate->probes = 0;
+ syslog(LOG_DEBUG, "%s: probing for %s",
+ astate->iface->name, inet_ntoa(astate->addr));
+ arp_probe1(astate);
+}
+
+
+struct arp_state *
+arp_new(struct interface *ifp) {
+ struct arp_state *astate;
+ struct dhcp_state *state;
+
+ astate = calloc(1, sizeof(*astate));
+ if (astate == NULL) {
+ syslog(LOG_ERR, "%s: %s: %m", ifp->name, __func__);
+ return NULL;
+ }
+
+ astate->iface = ifp;
+ state = D_STATE(ifp);
+ TAILQ_INSERT_TAIL(&state->arp_states, astate, next);
+ return astate;
+}
+
+void
+arp_free(struct arp_state *astate)
+{
+ struct dhcp_state *state;
+
+ state = D_STATE(astate->iface);
+ TAILQ_REMOVE(&state->arp_states, astate, next);
+ free(astate);
}
void
arp_close(struct interface *ifp)
{
struct dhcp_state *state = D_STATE(ifp);
+ struct arp_state *astate;
if (state == NULL)
return;
close(state->arp_fd);
state->arp_fd = -1;
}
-}
+ while ((astate = TAILQ_FIRST(&state->arp_states))) {
+ TAILQ_REMOVE(&state->arp_states, astate, next);
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, astate);
+ free(astate);
+ }
+}
#include "dhcpcd.h"
-void arp_announce(void *);
-void arp_probe(void *);
-void arp_start(struct interface *);
+struct arp_msg {
+ uint16_t op;
+ unsigned char sha[HWADDR_LEN];
+ struct in_addr sip;
+ unsigned char tha[HWADDR_LEN];
+ struct in_addr tip;
+};
+
+struct arp_state {
+ TAILQ_ENTRY(arp_state) next;
+ struct interface *iface;
+
+ void (*probed_cb)(struct arp_state *);
+ void (*announced_cb)(struct arp_state *);
+ void (*conflicted_cb)(struct arp_state *, const struct arp_msg *);
+
+ struct in_addr addr;
+ int probes;
+ int claims;
+};
+TAILQ_HEAD(arp_statehead, arp_state);
+
+void arp_announce(struct arp_state *);
+void arp_probe(struct arp_state *);
+struct arp_state *arp_new(struct interface *);
+void arp_free(struct arp_state *);
void arp_close(struct interface *);
#endif
if (state == NULL)
return;
- if (state->arp_fd != -1) {
- eloop_event_delete(ifp->ctx->eloop, state->arp_fd, 0);
- close(state->arp_fd);
- state->arp_fd = -1;
- }
+ arp_close(ifp);
if (state->raw_fd != -1) {
eloop_event_delete(ifp->ctx->eloop, state->raw_fd, 0);
close(state->raw_fd);
struct if_options *ifo = ifp->options;
time_t timeout = ifo->timeout;
- /* If we're rebooting and we're not daemonised then we need
- * to shorten the normal timeout to ensure we try correctly
- * for a fallback or IPv4LL address. */
- if (state->state == DHS_REBOOT &&
- !(ifp->ctx->options & DHCPCD_DAEMONISED))
- {
+ /* If we're rebooting then we need to shorten the normal timeout
+ * to ensure we try for a fallback or IPv4LL address. */
+ if (state->state == DHS_REBOOT) {
if (ifo->reboot >= timeout)
timeout = 2;
else
- timeout -= ifo->reboot;
+ timeout = ifo->reboot;
}
state->state = DHS_DISCOVER;
timeout, dhcp_fallback, ifp);
else if (ifo->options & DHCPCD_IPV4LL &&
!IN_LINKLOCAL(htonl(state->addr.s_addr)))
- {
- if (IN_LINKLOCAL(htonl(state->fail.s_addr)))
- timeout = RATE_LIMIT_INTERVAL;
eloop_timeout_add_sec(ifp->ctx->eloop,
timeout, ipv4ll_start, ifp);
- }
if (ifo->options & DHCPCD_REQUEST)
syslog(LOG_INFO, "%s: soliciting a DHCP lease (requesting %s)",
ifp->name, inet_ntoa(ifo->req_addr));
send_renew(ifp);
}
+static void
+dhcp_arp_announced(struct arp_state *astate)
+{
+
+ arp_close(astate->iface);
+}
+
static void
dhcp_rebind(void *arg)
{
}
void
-dhcp_bind(void *arg)
+dhcp_bind(struct interface *ifp, struct arp_state *astate)
{
- struct interface *ifp = arg;
struct dhcp_state *state = D_STATE(ifp);
struct if_options *ifo = ifp->options;
struct dhcp_lease *lease = &state->lease;
if (state->state == DHS_BOUND)
goto applyaddr;
state->reason = NULL;
- state->xid = 0;
free(state->old);
state->old = state->new;
state->new = state->offer;
applyaddr:
ipv4_applyaddr(ifp);
- if (dhcpcd_daemonise(ifp->ctx) == 0) {
- if (!ipv4ll) {
- /* We bound a non IPv4LL address so reset the
- * conflict counter */
- state->conflicts = 0;
+ if (dhcpcd_daemonise(ifp->ctx))
+ return;
+ if (ifo->options & DHCPCD_ARP) {
+ if (state->added) {
+ if (astate == NULL) {
+ /* We don't care about what happens
+ * to the ARP announcement */
+ astate = arp_new(ifp);
+ astate->announced_cb =
+ dhcp_arp_announced;
+ }
+ if (astate)
+ arp_announce(astate);
+ } else if (!ipv4ll)
arp_close(ifp);
- }
- if (ifo->options & DHCPCD_ARP) {
- state->claims = 0;
- if (state->added)
- arp_announce(ifp);
- }
}
}
struct interface *ifp = arg;
struct dhcp_state *state = D_STATE(ifp);
- dhcp_bind(ifp);
+ dhcp_bind(ifp, NULL);
state->interval = 0;
dhcp_discover(ifp);
}
state->offer = dhcp_message_new(&ifo->req_addr, &ifo->req_mask);
if (state->offer) {
eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
- dhcp_bind(ifp);
+ dhcp_bind(ifp, NULL);
}
}
dhcp_message_new(&ifo->req_addr, &ifo->req_mask);
if (state->offer) {
ifo->options |= DHCPCD_STATIC;
- dhcp_bind(ifp);
+ dhcp_bind(ifp, NULL);
ifo->options &= ~DHCPCD_STATIC;
}
}
if (state == NULL)
return;
ifo = ifp->options;
+ state->state = DHS_REBOOT;
state->interval = 0;
if (ifo->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) {
syslog(LOG_INFO, "%s: informing address of %s",
ifp->name, inet_ntoa(state->lease.addr));
} else if (state->offer->cookie == 0) {
- if (ifo->options & DHCPCD_IPV4LL) {
- state->claims = 0;
- if (state->added)
- arp_announce(ifp);
- } else
- dhcp_discover(ifp);
return;
} else {
syslog(LOG_INFO, "%s: rebinding lease of %s",
ifp->name, inet_ntoa(state->lease.addr));
}
- state->state = DHS_REBOOT;
state->xid = dhcp_xid(ifp);
state->lease.server.s_addr = 0;
eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
return;
dhcp_auth_reset(&state->auth);
dhcp_close(ifp);
- arp_close(ifp);
if (ifp->options->options & DHCPCD_RELEASE) {
unlink(state->leasefile);
if (ifp->carrier != LINK_DOWN &&
return 0;
}
+static void
+dhcp_arp_probed(struct arp_state *astate)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+
+ /* We didn't find a profile for this
+ * address or hwaddr, so move to the next
+ * arping profile */
+ state = D_STATE(astate->iface);
+ ifo = astate->iface->options;
+ if (state->arping_index < ifo->arping_len) {
+ if (++state->arping_index < ifo->arping_len) {
+ astate->addr.s_addr =
+ ifo->arping[state->arping_index - 1];
+ arp_probe(astate);
+ }
+ dhcpcd_startinterface(astate->iface);
+ return;
+ }
+ dhcp_close(astate->iface);
+ eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface);
+ dhcp_bind(astate->iface, astate);
+}
+
+static void
+dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+{
+ struct dhcp_state *state;
+ struct if_options *ifo;
+
+ state = D_STATE(astate->iface);
+ ifo = astate->iface->options;
+ if (state->arping_index &&
+ state->arping_index <= ifo->arping_len &&
+ (amsg->sip.s_addr == ifo->arping[state->arping_index - 1] ||
+ (amsg->sip.s_addr == 0 &&
+ amsg->tip.s_addr == ifo->arping[state->arping_index - 1])))
+ {
+ struct in_addr addr;
+ char buf[HWADDR_LEN * 3];
+
+ addr.s_addr = ifo->arping[state->arping_index - 1];
+ syslog(LOG_INFO,
+ "%s: found %s on hardware address %s",
+ astate->iface->name, inet_ntoa(addr),
+ hwaddr_ntoa(amsg->sha, astate->iface->hwlen,
+ buf, sizeof(buf)));
+ if (dhcpcd_selectprofile(astate->iface, buf) == -1 &&
+ dhcpcd_selectprofile(astate->iface, inet_ntoa(addr)) == -1)
+ {
+ /* We didn't find a profile for this
+ * address or hwaddr, so move to the next
+ * arping profile */
+ dhcp_arp_probed(astate);
+ return;
+ }
+ dhcp_close(astate->iface);
+ eloop_timeout_delete(astate->iface->ctx->eloop, NULL,
+ astate->iface);
+ dhcpcd_startinterface(astate->iface);
+ }
+
+ if (state->offer == NULL)
+ return;
+
+ /* RFC 2131 3.1.5, Client-server interaction */
+ if (amsg->sip.s_addr == state->offer->yiaddr ||
+ (amsg->sip.s_addr == 0 && amsg->tip.s_addr == state->offer->yiaddr))
+ {
+ struct in_addr fail;
+ char buf[HWADDR_LEN * 3];
+
+ fail.s_addr = state->offer->yiaddr;
+ syslog(LOG_ERR, "%s: hardware address %s claims %s",
+ astate->iface->name,
+ hwaddr_ntoa(amsg->sha, astate->iface->hwlen,
+ buf, sizeof(buf)),
+ inet_ntoa(fail));
+
+ arp_free(astate);
+ unlink(state->leasefile);
+ if (!state->lease.frominfo)
+ dhcp_decline(astate->iface);
+ eloop_timeout_delete(astate->iface->ctx->eloop, NULL,
+ astate->iface);
+ if (state->lease.frominfo)
+ dhcpcd_startinterface(astate->iface);
+ else
+ eloop_timeout_add_sec(astate->iface->ctx->eloop,
+ DHCP_ARP_FAIL,
+ dhcpcd_startinterface, astate->iface);
+ }
+}
+
+void
+dhcp_probe(struct interface *ifp)
+{
+ const struct dhcp_state *state;
+ struct arp_state *astate;
+
+ astate = arp_new(ifp);
+ if (astate) {
+ state = D_CSTATE(ifp);
+ astate->addr = state->addr;
+ astate->probed_cb = dhcp_arp_probed;
+ astate->conflicted_cb = dhcp_arp_conflicted;
+ astate->announced_cb = dhcp_arp_announced;
+ arp_probe(astate);
+ }
+}
+
static void
dhcp_handledhcp(struct interface *iface, struct dhcp_message **dhcpp,
const struct in_addr *from)
* then we can't ARP for duplicate detection. */
addr.s_addr = state->offer->yiaddr;
if (!ipv4_iffindaddr(iface, &addr, NULL)) {
- state->claims = 0;
- state->probes = 0;
- state->conflicts = 0;
- arp_probe(iface);
+ struct arp_state *astate;
+
+ astate = arp_new(iface);
+ if (astate) {
+ astate->addr = addr;
+ astate->probed_cb = dhcp_arp_probed;
+ astate->conflicted_cb = dhcp_arp_conflicted;
+ arp_probe(astate);
+ }
return;
}
}
- dhcp_bind(iface);
+ dhcp_bind(iface, NULL);
}
static size_t
ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state));
if (state == NULL)
goto eexit;
+ state->raw_fd = state->arp_fd = -1;
+ TAILQ_INIT(&state->arp_states);
snprintf(state->leasefile, sizeof(state->leasefile),
LEASEFILE, ifp->name);
state->new = read_lease(ifp);
struct dhcp_state *state = D_STATE(ifp);
struct dhcpcd_ctx *ctx;
+ dhcp_close(ifp);
if (state) {
free(state->old);
free(state->new);
return -1;
/* 0 is a valid fd, so init to -1 */
state->raw_fd = state->arp_fd = -1;
+ TAILQ_INIT(&state->arp_states);
}
state->state = DHS_INIT;
state->offer = NULL;
if (state->arping_index < ifo->arping_len) {
- arp_start(ifp);
+ struct arp_state *astate;
+
+ astate = arp_new(ifp);
+ if (astate) {
+ astate->probed_cb = dhcp_arp_probed;
+ astate->conflicted_cb = dhcp_arp_conflicted;
+ dhcp_arp_probed(astate);
+ }
return;
}
return;
}
- if (state->offer == NULL || state->offer->cookie == 0)
+ if (state->offer == NULL || state->offer->cookie == 0) {
+ /* If we don't have an address yet, enter the reboot
+ * state to ensure at least fallback in short order. */
+ if (state->addr.s_addr == INADDR_ANY)
+ state->state = DHS_REBOOT;
dhcp_discover(ifp);
- else
+ } else
dhcp_reboot(ifp);
}
#include <limits.h>
#include <stdint.h>
+#include "arp.h"
#include "auth.h"
#include "dhcp-common.h"
uint32_t xid;
int socket;
- /* ARP */
- int probes;
- int claims;
- struct in_addr fail;
- size_t arping_index;
-
- /* IPv4LL */
- char randomstate[128];
- int conflicts;
- time_t defend;
-
int raw_fd;
int arp_fd;
size_t buffer_size, buffer_len, buffer_pos;
unsigned char *clientid;
struct authstate auth;
+ struct arp_statehead arp_states;
+
+ size_t arping_index;
+
+ struct arp_state *arp_ipv4ll;
+ unsigned int conflicts;
+ time_t defend;
+ char randomstate[128];
+ struct in_addr failed;
};
#define D_STATE(ifp) \
void dhcp_decline(struct interface *);
void dhcp_discover(void *);
void dhcp_inform(struct interface *);
-void dhcp_bind(void *);
+void dhcp_probe(struct interface *);
+void dhcp_bind(struct interface *, struct arp_state *);
void dhcp_reboot_newopts(struct interface *, unsigned long long);
void dhcp_close(struct interface *);
void dhcp_free(struct interface *);
handle_exit_timeout(void *arg)
{
struct dhcpcd_ctx *ctx;
- int timeout;
ctx = arg;
syslog(LOG_ERR, "timed out");
- if (!(ctx->options & DHCPCD_IPV4) ||
- !(ctx->options & DHCPCD_TIMEOUT_IPV4LL))
- {
- if (ctx->options & DHCPCD_MASTER) {
- /* We've timed out, so remove the waitip requirements.
- * If the user doesn't like this they can always set
- * an infinite timeout. */
- ctx->options &=
- ~(DHCPCD_WAITIP | DHCPCD_WAITIP4 | DHCPCD_WAITIP6);
- dhcpcd_daemonise(ctx);
- } else
- eloop_exit(ctx->eloop, EXIT_FAILURE);
+ if (!ctx->options & DHCPCD_MASTER) {
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
return;
}
- ctx->options &= ~DHCPCD_TIMEOUT_IPV4LL;
- timeout = (PROBE_NUM * PROBE_MAX) + (PROBE_WAIT * 2) + DHCP_MAX_DELAY;
- syslog(LOG_WARNING, "allowing %d seconds for IPv4LL timeout", timeout);
- eloop_timeout_add_sec(ctx->eloop, timeout, handle_exit_timeout, ctx);
+ ctx->options |= DHCPCD_NOWAITIP;
+ dhcpcd_daemonise(ctx);
}
int
int sidpipe[2], fd;
if (ctx->options & DHCPCD_DAEMONISE &&
- !(ctx->options & DHCPCD_DAEMONISED))
+ !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP)))
{
if (!dhcpcd_ipwaited(ctx))
return 0;
if (dhcpcd_daemonise(&ctx))
goto exit_success;
} else if (t > 0) {
- if (ctx.options & DHCPCD_IPV4LL)
- ctx.options |= DHCPCD_TIMEOUT_IPV4LL;
eloop_timeout_add_sec(ctx.eloop, t,
handle_exit_timeout, &ctx);
}
.It Ic reboot Ar seconds
Allow
.Ar reboot
-seconds before moving to the DISCOVER phase if we have an old lease to use.
-The default is 5 seconds.
+seconds before moving to the DISCOVER phase if we have an old lease to use
+and moving from DISCOVER to IPv4LL if no reply.
+The default is 10 seconds.
A setting of 0 seconds causes
.Nm dhcpcd
to skip the REBOOT phase and go straight into DISCOVER.
#define DHCPCD_LASTLEASE (1ULL << 7)
#define DHCPCD_INFORM (1ULL << 8)
#define DHCPCD_REQUEST (1ULL << 9)
+
#define DHCPCD_IPV4LL (1ULL << 10)
#define DHCPCD_DUID (1ULL << 11)
#define DHCPCD_PERSISTENT (1ULL << 12)
#define DHCPCD_QUIET (1ULL << 21)
#define DHCPCD_BACKGROUND (1ULL << 22)
#define DHCPCD_VENDORRAW (1ULL << 23)
-#define DHCPCD_TIMEOUT_IPV4LL (1ULL << 24)
+#define DHCPCD_NOWAITIP (1ULL << 24) /* To force daemonise */
#define DHCPCD_WAITIP (1ULL << 25)
#define DHCPCD_SLAACPRIVATE (1ULL << 26)
#define DHCPCD_CSR_WARNED (1ULL << 27)
return NULL;
}
+struct ipv4_addr *
+ipv4_iffindlladdr(struct interface *ifp)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *ap;
+
+ state = IPV4_STATE(ifp);
+ if (state) {
+ TAILQ_FOREACH(ap, &state->addrs, next) {
+ if (IN_LINKLOCAL(htonl(ap->addr.s_addr)))
+ return ap;
+ }
+ }
+ return NULL;
+}
+
struct ipv4_addr *
ipv4_findaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr)
{
if (ifn->options->options &
DHCPCD_ARP)
{
- nstate->claims = 0;
- nstate->probes = 0;
- nstate->conflicts = 0;
- arp_probe(ifn);
+ dhcp_bind(ifn, NULL);
} else {
ipv4_addaddr(ifn,
&nstate->lease);
struct ipv4_addr *ipv4_iffindaddr(struct interface *,
const struct in_addr *, const struct in_addr *);
+struct ipv4_addr *ipv4_iffindlladdr(struct interface *);
struct ipv4_addr *ipv4_findaddr(struct dhcpcd_ctx *, const struct in_addr *);
void ipv4_handleifa(struct dhcpcd_ctx *, int, struct if_head *, const char *,
const struct in_addr *, const struct in_addr *, const struct in_addr *);
#include <syslog.h>
#include <unistd.h>
-#define ELOOP_QUEUE 2
+#define ELOOP_QUEUE 6
#include "config.h"
#include "arp.h"
#include "common.h"
return dhcp;
}
-static struct dhcp_message *
-ipv4ll_find_lease(const struct interface *ifp)
+static in_addr_t
+ipv4ll_pick_addr(const struct arp_state *astate)
{
- uint32_t addr;
- struct interface *ifp2;
+ in_addr_t addr;
+ struct interface *ifp;
const struct dhcp_state *state;
for (;;) {
* See ipv4ll_start for why we don't use arc4_random. */
addr = ntohl(LINKLOCAL_ADDR | ((random() % 0xFD00) + 0x0100));
- state = D_CSTATE(ifp);
+ state = D_CSTATE(astate->iface);
/* No point using a failed address */
- if (addr == state->fail.s_addr)
+ if (addr == state->failed.s_addr)
continue;
/* Ensure we don't have the address on another interface */
- TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) {
- state = D_CSTATE(ifp2);
+ TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) {
+ state = D_CSTATE(ifp);
if (state && state->addr.s_addr == addr)
break;
}
/* Yay, this should be a unique and workable IPv4LL address */
- if (ifp2 == NULL)
+ if (ifp == NULL)
break;
}
- return ipv4ll_make_lease(addr);
+ return addr;
}
-void
-ipv4ll_claimed(void *arg)
+static void
+ipv4ll_probed(struct arp_state *astate)
{
- struct interface *ifp = arg;
- struct dhcp_state *state = D_STATE(ifp);
+ struct dhcp_state *state = D_STATE(astate->iface);
+
+ free(state->offer);
+ state->offer = ipv4ll_make_lease(astate->addr.s_addr);
+ if (state->offer == NULL) {
+ syslog(LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ dhcp_bind(astate->iface, astate);
+}
+
+static void
+ipv4ll_announced(struct arp_state *astate)
+{
+ struct dhcp_state *state = D_STATE(astate->iface);
state->conflicts = 0;
+ /* Need to keep the arp state so we can defend our IP. */
+}
+
+static void
+ipv4ll_probe(void *arg)
+{
+
+ arp_probe(arg);
+}
+
+static void
+ipv4ll_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+{
+ struct dhcp_state *state = D_STATE(astate->iface);
+ uint32_t fail;
+ char buf[HWADDR_LEN * 3];
+
+ if (state->offer == NULL)
+ return;
+
+ fail = 0;
+ /* RFC 3927 2.2.1, Probe Conflict Detection */
+ if (amsg->sip.s_addr == astate->addr.s_addr ||
+ (amsg->sip.s_addr == 0 && amsg->tip.s_addr == astate->addr.s_addr))
+ fail = astate->addr.s_addr;
+
+ /* RFC 3927 2.5, Conflict Defense */
+ if (IN_LINKLOCAL(htonl(state->addr.s_addr)) &&
+ amsg->sip.s_addr == state->addr.s_addr)
+ fail = state->addr.s_addr;
+
+ if (fail == 0)
+ return;
+
+ state->failed.s_addr = fail;
+ syslog(LOG_ERR, "%s: hardware address %s claims %s",
+ astate->iface->name,
+ hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)),
+ inet_ntoa(state->failed));
+
+ if (state->failed.s_addr == state->addr.s_addr) {
+ time_t up;
+
+ /* RFC 3927 Section 2.5 */
+ up = uptime();
+ if (state->defend + DEFEND_INTERVAL > up) {
+ syslog(LOG_WARNING,
+ "%s: IPv4LL %d second defence failed for %s",
+ astate->iface->name, DEFEND_INTERVAL,
+ inet_ntoa(state->addr));
+ dhcp_drop(astate->iface, "EXPIRE");
+ } else {
+ syslog(LOG_DEBUG, "%s: defended IPv4LL address %s",
+ astate->iface->name, inet_ntoa(state->addr));
+ state->defend = up;
+ return;
+ }
+ }
+
+ if (++state->conflicts == MAX_CONFLICTS)
+ syslog(LOG_ERR, "%s: failed to acquire an IPv4LL address",
+ astate->iface->name);
+ astate->addr.s_addr = ipv4ll_pick_addr(astate);
+ eloop_timeout_add_sec(astate->iface->ctx->eloop,
+ state->conflicts > MAX_CONFLICTS ?
+ RATE_LIMIT_INTERVAL : PROBE_WAIT,
+ ipv4ll_probe, astate);
}
void
{
struct interface *ifp = arg;
struct dhcp_state *state = D_STATE(ifp);
- uint32_t addr;
+ struct arp_state *astate;
+ struct ipv4_addr *ap;
+
+ if (state->arp_ipv4ll)
+ return;
/* RFC 3927 Section 2.1 states that the random number generator
* SHOULD be seeded with a value derived from persistent information
initstate(seed, state->randomstate, sizeof(state->randomstate));
}
- eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
- state->probes = 0;
- state->claims = 0;
- if (state->addr.s_addr) {
- if (IN_LINKLOCAL(htonl(state->addr.s_addr))) {
- arp_announce(ifp);
- return;
- }
+ if ((astate = arp_new(ifp)) == NULL)
+ return;
+
+ state->arp_ipv4ll = astate;
+ astate->probed_cb = ipv4ll_probed;
+ astate->announced_cb = ipv4ll_announced;
+ astate->conflicted_cb = ipv4ll_conflicted;
+
+ if (IN_LINKLOCAL(htonl(state->addr.s_addr))) {
+ astate->addr = state->addr;
+ arp_announce(astate);
+ return;
}
- if (state->offer == NULL)
- addr = 0;
- else {
- addr = state->offer->yiaddr;
+ if (state->offer) {
+ astate->addr.s_addr = state->offer->yiaddr;
free(state->offer);
+ ap = ipv4_iffindaddr(ifp, &astate->addr, NULL);
+ } else
+ ap = ipv4_iffindlladdr(ifp);
+ if (ap) {
+ astate->addr = ap->addr;
+ ipv4ll_probed(astate);
+ return;
}
- state->state = DHS_INIT_IPV4LL;
setstate(state->randomstate);
/* We maybe rebooting an IPv4LL address. */
- if (!IN_LINKLOCAL(htonl(addr))) {
+ if (!IN_LINKLOCAL(htonl(astate->addr.s_addr))) {
syslog(LOG_INFO, "%s: probing for an IPv4LL address",
ifp->name);
- addr = 0;
- }
- if (addr == 0)
- state->offer = ipv4ll_find_lease(ifp);
- else
- state->offer = ipv4ll_make_lease(addr);
- if (state->offer == NULL) {
- syslog(LOG_ERR, "%s: %m", __func__);
- return;
- }
- state->lease.frominfo = 0;
- arp_probe(ifp);
-}
-
-void
-ipv4ll_handle_failure(void *arg)
-{
- struct interface *ifp = arg;
- struct dhcp_state *state = D_STATE(ifp);
- time_t up;
-
- if (state->fail.s_addr == state->addr.s_addr) {
- /* RFC 3927 Section 2.5 */
- up = uptime();
- if (state->defend + DEFEND_INTERVAL > up) {
- syslog(LOG_WARNING,
- "%s: IPv4LL %d second defence failed for %s",
- ifp->name, DEFEND_INTERVAL, inet_ntoa(state->addr));
- dhcp_drop(ifp, "EXPIRE");
- } else {
- syslog(LOG_DEBUG, "%s: defended IPv4LL address %s",
- ifp->name, inet_ntoa(state->addr));
- state->defend = up;
- return;
- }
- }
-
- free(state->offer);
- state->offer = NULL;
- eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
- if (++state->conflicts >= MAX_CONFLICTS) {
- syslog(LOG_ERR, "%s: failed to acquire an IPv4LL address",
- ifp->name);
- if (ifp->options->options & DHCPCD_DHCP) {
- state->interval = RATE_LIMIT_INTERVAL / 2;
- dhcp_discover(ifp);
- } else
- eloop_timeout_add_sec(ifp->ctx->eloop,
- RATE_LIMIT_INTERVAL, ipv4ll_start, ifp);
- } else {
- eloop_timeout_add_sec(ifp->ctx->eloop,
- PROBE_WAIT, ipv4ll_start, ifp);
+ astate->addr.s_addr = INADDR_ANY;
}
+ if (astate->addr.s_addr == INADDR_ANY)
+ astate->addr.s_addr = ipv4ll_pick_addr(astate);
+ arp_probe(astate);
}