Callbacks have also been improved so they are more descriptive.
/* Assert the correct structure size for on wire */
__CTASSERT(sizeof(struct arphdr) == 8);
-ssize_t
-arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip)
+static ssize_t
+arp_request(const struct interface *ifp,
+ const struct in_addr *sip, const struct in_addr *tip)
{
uint8_t arp_buffer[ARP_LEN];
struct arphdr ar;
ar.ar_hrd = htons(ifp->family);
ar.ar_pro = htons(ETHERTYPE_IP);
ar.ar_hln = ifp->hwlen;
- ar.ar_pln = sizeof(sip);
+ ar.ar_pln = sizeof(tip->s_addr);
ar.ar_op = htons(ARPOP_REQUEST);
p = arp_buffer;
APPEND(&ar, sizeof(ar));
APPEND(ifp->hwaddr, ifp->hwlen);
- APPEND(&sip, sizeof(sip));
+ if (sip != NULL)
+ APPEND(&sip->s_addr, sizeof(sip->s_addr));
+ else
+ ZERO(sizeof(tip->s_addr));
ZERO(ifp->hwlen);
- APPEND(&tip, sizeof(tip));
+ APPEND(&tip->s_addr, sizeof(tip->s_addr));
state = ARP_CSTATE(ifp);
return bpf_send(ifp, state->bpf_fd, ETHERTYPE_ARP, arp_buffer, len);
return -1;
}
+static void
+arp_report_conflicted(const struct arp_state *astate,
+ const struct arp_msg *amsg)
+{
+ char buf[HWADDR_LEN * 3];
+
+ if (amsg == NULL) {
+ logerrx("%s: DAD detected %s",
+ astate->iface->name, inet_ntoa(astate->addr));
+ return;
+ }
+
+ logerrx("%s: hardware address %s claims %s",
+ astate->iface->name,
+ hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)),
+ inet_ntoa(astate->addr));
+}
+
+
+static void
+arp_found(struct arp_state *astate, const struct arp_msg *amsg)
+{
+ struct interface *ifp;
+ struct ivp4_addr *ia;
+#ifndef KERNEL_RFC5227
+ struct timespec now, defend;
+#endif
+
+ arp_report_conflicted(astate, amsg);
+ ifp = astate->iface;
+
+#pragma GCC diagnostic push /* GCC is clearly wrong about this warning. */
+#pragma GCC diagnostic ignored "-Wincompatible-pointer-types"
+ /* If we haven't added the address we're doing a probe. */
+ ia = ipv4_iffindaddr(ifp, &astate->addr, NULL);
+#pragma GCC diagnostic pop
+ if (ia == NULL) {
+ if (astate->found_cb != NULL)
+ astate->found_cb(astate, amsg);
+ return;
+ }
+
+#ifndef KERNEL_RFC5227
+ /* RFC 3927 Section 2.5 says a defence should
+ * broadcast an ARP announcement.
+ * Because the kernel will also unicast a reply to the
+ * hardware address which requested the IP address
+ * the other IPv4LL client will receieve two ARP
+ * messages.
+ * If another conflict happens within DEFEND_INTERVAL
+ * then we must drop our address and negotiate a new one. */
+ defend.tv_sec = astate->defend.tv_sec + DEFEND_INTERVAL;
+ defend.tv_nsec = astate->defend.tv_nsec;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (timespeccmp(&defend, &now, >))
+ logwarnx("%s: %d second defence failed for %s",
+ ifp->name, DEFEND_INTERVAL, inet_ntoa(astate->addr));
+ else if (arp_request(ifp, &astate->addr, &astate->addr) == -1)
+ logerr(__func__);
+ else {
+ logdebugx("%s: defended address %s",
+ ifp->name, inet_ntoa(astate->addr));
+ astate->defend = now;
+ return;
+ }
+#endif
+
+ if (astate->defend_failed_cb != NULL)
+ astate->defend_failed_cb(astate);
+}
+
static void
arp_packet(struct interface *ifp, uint8_t *data, size_t len)
{
memcpy(&arm.tha, hw_t, ar.ar_hln);
memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln);
- /* Run the conflicts */
+ /* Match the ARP probe to our states */
state = ARP_CSTATE(ifp);
TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) {
- if (arm.sip.s_addr != astate->addr.s_addr &&
- arm.tip.s_addr != astate->addr.s_addr)
- continue;
- if (astate->conflicted_cb)
- astate->conflicted_cb(astate, &arm);
+ if (IN_ARE_ADDR_EQUAL(&arm.sip, &astate->addr) ||
+ (IN_IS_ADDR_UNSPECIFIED(&arm.sip) &&
+ IN_ARE_ADDR_EQUAL(&arm.tip, &astate->addr)))
+ arp_found(astate, &arm);
}
}
}
}
-int
+static int
arp_open(struct interface *ifp)
{
struct iarp_state *state;
{
struct arp_state *astate = arg;
- astate->probed_cb(astate);
+ timespecclear(&astate->defend);
+ astate->not_found_cb(astate);
}
static void
ifp->name, inet_ntoa(astate->addr),
astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM,
timespec_to_double(&tv));
- if (arp_request(ifp, 0, astate->addr.s_addr) == -1)
+ if (arp_request(ifp, NULL, &astate->addr) == -1)
logerr(__func__);
}
}
#endif /* ARP */
+static struct arp_state *
+arp_find(struct interface *ifp, const struct in_addr *addr)
+{
+ struct iarp_state *state;
+ struct arp_state *astate;
+
+ if ((state = ARP_STATE(ifp)) == NULL)
+ goto out;
+ TAILQ_FOREACH(astate, &state->arp_states, next) {
+ if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp)
+ return astate;
+ }
+out:
+ errno = ESRCH;
+ return NULL;
+}
+
static void
arp_announced(void *arg)
{
logdebugx("%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, &astate->addr) == -1)
logerr(__func__);
eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT,
astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced,
arp_announce1(astate);
}
+void
+arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia)
+{
+ struct arp_state *astate;
+
+ astate = arp_find(ifp, ia);
+ if (astate == NULL) {
+ astate = arp_new(ifp, ia);
+ if (astate == NULL)
+ return;
+ astate->announced_cb = arp_free;
+ }
+ arp_announce(astate);
+}
+
void
arp_announceaddr(struct dhcpcd_ctx *ctx, const struct in_addr *ia)
{
struct interface *ifp;
struct ipv4_addr *iaf;
- struct arp_state *astate;
TAILQ_FOREACH(ifp, ctx->ifaces, next) {
iaf = ipv4_iffindaddr(ifp, ia, NULL);
if (ifp == NULL)
return;
- astate = arp_find(ifp, ia);
- if (astate != NULL)
- arp_announce(astate);
-}
-
-void
-arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia)
-{
- struct arp_state *astate;
-
- astate = arp_new(ifp, ia);
- if (astate != NULL)
- arp_announce(astate);
-}
-
-void
-arp_report_conflicted(const struct arp_state *astate,
- const struct arp_msg *amsg)
-{
-
- if (amsg != NULL) {
- char buf[HWADDR_LEN * 3];
-
- logerrx("%s: hardware address %s claims %s",
- astate->iface->name,
- hwaddr_ntoa(amsg->sha, astate->iface->hwlen,
- buf, sizeof(buf)),
- inet_ntoa(astate->failed));
- } else
- logerrx("%s: DAD detected %s",
- astate->iface->name, inet_ntoa(astate->failed));
-}
-
-struct arp_state *
-arp_find(struct interface *ifp, const struct in_addr *addr)
-{
- struct iarp_state *state;
- struct arp_state *astate;
-
- if ((state = ARP_STATE(ifp)) == NULL)
- goto out;
- TAILQ_FOREACH(astate, &state->arp_states, next) {
- if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp)
- return astate;
- }
-out:
- errno = ESRCH;
- return NULL;
+ arp_ifannounceaddr(ifp, ia);
}
struct arp_state *
arp_tryfree(ifp);
}
-static void
-arp_free_but1(struct interface *ifp, struct arp_state *astate)
-{
- struct iarp_state *state;
-
- if ((state = ARP_STATE(ifp)) != NULL) {
- struct arp_state *p, *n;
-
- TAILQ_FOREACH_SAFE(p, &state->arp_states, next, n) {
- if (p != astate)
- arp_free(p);
- }
- }
-}
-
void
-arp_free_but(struct arp_state *astate)
+arp_freeaddr(struct interface *ifp, const struct in_addr *ia)
{
+ struct arp_state *astate;
- arp_free_but1(astate->iface, astate);
+ astate = arp_find(ifp, ia);
+ arp_free(astate);
}
void
arp_drop(struct interface *ifp)
-{
-
- arp_free_but1(ifp, NULL);
- arp_close(ifp);
-}
-
-void
-arp_handleifa(int cmd, struct ipv4_addr *addr)
{
struct iarp_state *state;
- struct arp_state *astate, *asn;
+ struct arp_state *astate;
- state = ARP_STATE(addr->iface);
+ state = ARP_STATE(ifp);
if (state == NULL)
return;
- TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, asn) {
- if (astate->addr.s_addr != addr->addr.s_addr)
- continue;
- if (cmd == RTM_DELADDR)
- arp_free(astate);
-#ifdef IN_IFF_DUPLICATED
- if (cmd != RTM_NEWADDR)
- continue;
- if (addr->addr_flags & IN_IFF_DUPLICATED) {
- if (astate->conflicted_cb)
- astate->conflicted_cb(astate, NULL);
- } else if (!(addr->addr_flags & IN_IFF_NOTUSEABLE)) {
- if (astate->probed_cb)
- astate->probed_cb(astate);
- }
-#endif
+ while ((astate = TAILQ_FIRST(&state->arp_states)) != NULL) {
+ arp_free(astate);
}
+
+ /* No need to close because the last free will close */
}
TAILQ_ENTRY(arp_state) next;
struct interface *iface;
- void (*probed_cb)(struct arp_state *);
+ void (*found_cb)(struct arp_state *, const struct arp_msg *);
+ void (*not_found_cb)(struct arp_state *);
void (*announced_cb)(struct arp_state *);
- void (*conflicted_cb)(struct arp_state *, const struct arp_msg *);
+ void (*defend_failed_cb)(struct arp_state *);
void (*free_cb)(struct arp_state *);
struct in_addr addr;
int probes;
int claims;
- struct in_addr failed;
+ struct timespec defend;
};
TAILQ_HEAD(arp_statehead, arp_state);
((const struct iarp_state *)(ifp)->if_data[IF_DATA_ARP])
#ifdef ARP
-int arp_open(struct interface *);
-ssize_t arp_request(const struct interface *, in_addr_t, in_addr_t);
-void arp_probe(struct arp_state *);
-void arp_report_conflicted(const struct arp_state *, const struct arp_msg *);
struct arp_state *arp_new(struct interface *, const struct in_addr *);
-struct arp_state *arp_find(struct interface *, const struct in_addr *);
+void arp_probe(struct arp_state *);
void arp_announce(struct arp_state *);
void arp_announceaddr(struct dhcpcd_ctx *, const struct in_addr *);
void arp_ifannounceaddr(struct interface *, const struct in_addr *);
void arp_cancel(struct arp_state *);
void arp_free(struct arp_state *);
-void arp_free_but(struct arp_state *);
+void arp_freeaddr(struct interface *, const struct in_addr *);
void arp_drop(struct interface *);
-
-void arp_handleifa(int, struct ipv4_addr *);
#endif /* ARP */
#endif /* ARP_H */
};
static int dhcp_openbpf(struct interface *);
+static void dhcp_start1(void *);
#ifdef ARP
-static void dhcp_arp_conflicted(struct arp_state *, const struct arp_msg *);
+static void dhcp_arp_found(struct arp_state *, const struct arp_msg *);
#endif
static void dhcp_handledhcp(struct interface *, struct bootp *, size_t,
const struct in_addr *);
send_request(ifp);
}
-static int
-dhcp_leaseextend(struct interface *ifp)
-{
-
-#ifdef ARP
- if (ifp->options->options & DHCPCD_ARP) {
- const struct dhcp_state *state;
- struct arp_state *astate;
-
- state = D_CSTATE(ifp);
- if ((astate = arp_new(ifp, &state->lease.addr)) == NULL)
- return -1;
- astate->conflicted_cb = dhcp_arp_conflicted;
-
-#ifndef KERNEL_RFC5227
- if (arp_open(ifp) == -1)
- return -1;
-#endif
-
- logwarnx("%s: extending lease until DaD failure or DHCP",
- ifp->name);
- return 0;
- }
-#endif
-
- logwarnx("%s: extending lease", ifp->name);
- return 0;
-}
-
static void
dhcp_expire1(struct interface *ifp)
{
{
struct interface *ifp = arg;
- logerrx("%s: DHCP lease expired", ifp->name);
if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) {
- if (dhcp_leaseextend(ifp) == 0)
- return;
- logerr(__func__);
+ logwarnx("%s: DHCP lease expired, extending lease", ifp->name);
+ return;
}
+
+ logerrx("%s: DHCP lease expired", ifp->name);
dhcp_expire1(ifp);
}
send_rebind(ifp);
}
-#ifdef ARP
static void
-dhcp_arp_probed(struct arp_state *astate)
+dhcp_finish_dad(struct interface *ifp, struct in_addr *ia)
{
- struct interface *ifp;
- struct dhcp_state *state;
- struct if_options *ifo;
+ struct dhcp_state *state = D_STATE(ifp);
- ifp = astate->iface;
- state = D_STATE(ifp);
- ifo = ifp->options;
-#ifdef ARPING
- if (ifo->arping_len && state->arping_index < ifo->arping_len) {
- /* We didn't find a profile for this
- * address or hwaddr, so move to the next
- * arping profile */
- if (++state->arping_index < ifo->arping_len) {
- astate->addr.s_addr =
- ifo->arping[state->arping_index];
- arp_probe(astate);
- return;
- }
- arp_free(astate);
- dhcpcd_startinterface(ifp);
+ if (state->state != DHS_PROBE)
return;
- }
-#endif
-
- /* Already bound so DAD has worked */
- if (state->state == DHS_BOUND)
+ if (state->offer == NULL || state->offer->yiaddr != ia->s_addr)
return;
- logdebugx("%s: DAD completed for %s",
- ifp->name, inet_ntoa(astate->addr));
- if (!(ifo->options & DHCPCD_INFORM))
+ logdebugx("%s: DAD completed for %s", ifp->name, inet_ntoa(*ia));
+ if (!(ifp->options->options & DHCPCD_INFORM))
dhcp_bind(ifp);
#ifndef IN_IFF_TENTATIVE
else {
ipv4ll_drop(ifp);
#endif
- if (ifo->options & DHCPCD_INFORM)
+ if (ifp->options->options & DHCPCD_INFORM)
dhcp_inform(ifp);
}
+
+static void
+dhcp_addr_duplicated(struct interface *ifp, struct in_addr *ia)
+{
+ struct dhcp_state *state = D_STATE(ifp);
+#ifdef IN_IFF_DUPLICATED
+ struct ipv4_addr *iap;
+#endif
+
+ if ((state->offer == NULL || state->offer->yiaddr != ia->s_addr) &&
+ !IN_ARE_ADDR_EQUAL(ia, &state->lease.addr))
+ return;
+
+ /* RFC 2131 3.1.5, Client-server interaction */
+ logerrx("%s: DAD detected %s", ifp->name, inet_ntoa(*ia));
+ unlink(state->leasefile);
+ if (!(ifp->options->options & DHCPCD_STATIC) && !state->lease.frominfo)
+ dhcp_decline(ifp);
+#ifdef IN_IFF_DUPLICATED
+ if ((iap = ipv4_iffindaddr(ifp, ia, NULL)) != NULL)
+ ipv4_deladdr(iap, 0);
+#endif
+ eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ DHCP_RAND_MAX, dhcp_discover, ifp);
+}
+
+#ifdef ARP
static void
-dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+dhcp_arp_not_found(struct arp_state *astate)
{
struct interface *ifp;
struct dhcp_state *state;
-#ifdef ARPING
struct if_options *ifo;
-#endif
ifp = astate->iface;
state = D_STATE(ifp);
+ ifo = ifp->options;
+#ifdef ARPING
+ if (ifo->arping_len && state->arping_index < ifo->arping_len) {
+ /* We didn't find a profile for this
+ * address or hwaddr, so move to the next
+ * arping profile */
+ if (++state->arping_index < ifo->arping_len) {
+ astate->addr.s_addr =
+ ifo->arping[state->arping_index];
+ arp_probe(astate);
+ return;
+ }
+ arp_free(astate);
+ dhcpcd_startinterface(ifp);
+ return;
+ }
+#endif
+ dhcp_finish_dad(ifp, &astate->addr);
+}
+
+static void
+dhcp_arp_found(struct arp_state *astate, const struct arp_msg *amsg)
+{
#ifdef ARPING
+ struct interface *ifp;
+ struct dhcp_state *state;
+ struct if_options *ifo;
+
+ ifp = astate->iface;
+ state = D_STATE(ifp);
+
ifo = ifp->options;
if (state->arping_index != -1 &&
state->arping_index < ifo->arping_len &&
{
char buf[HWADDR_LEN * 3];
- astate->failed.s_addr = ifo->arping[state->arping_index];
- arp_report_conflicted(astate, amsg);
hwaddr_ntoa(amsg->sha, ifp->hwlen, buf, sizeof(buf));
if (dhcpcd_selectprofile(ifp, buf) == -1 &&
- dhcpcd_selectprofile(ifp,
- inet_ntoa(astate->failed)) == -1)
+ dhcpcd_selectprofile(ifp, inet_ntoa(amsg->sip)) == -1)
{
/* We didn't find a profile for this
* address or hwaddr, so move to the next
* arping profile */
- dhcp_arp_probed(astate);
+ dhcp_arp_not_found(astate);
return;
}
arp_free(astate);
}
#endif
- /* RFC 2131 3.1.5, Client-server interaction
- * NULL amsg means IN_IFF_DUPLICATED */
- if (amsg == NULL || (state->offer &&
- (amsg->sip.s_addr == state->offer->yiaddr ||
- (amsg->sip.s_addr == 0 &&
- amsg->tip.s_addr == state->offer->yiaddr))))
- {
-#ifdef IN_IFF_DUPLICATED
- struct ipv4_addr *ia;
-#endif
-
- if (amsg)
- astate->failed.s_addr = state->offer->yiaddr;
- else
- astate->failed = astate->addr;
- arp_report_conflicted(astate, amsg);
- unlink(state->leasefile);
-#ifdef ARP
- if (!(ifp->options->options & DHCPCD_STATIC) &&
- !state->lease.frominfo)
- dhcp_decline(ifp);
-#endif
-#ifdef IN_IFF_DUPLICATED
- if ((ia = ipv4_iffindaddr(ifp, &astate->addr, NULL)) != NULL)
- ipv4_deladdr(ia, 1);
-#endif
- arp_free(astate);
- eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
- eloop_timeout_add_sec(ifp->ctx->eloop,
- DHCP_RAND_MAX, dhcp_discover, ifp);
- return;
- }
-
- /* Bound address */
- if (amsg && state->addr &&
- amsg->sip.s_addr == state->addr->addr.s_addr)
- {
- astate->failed = state->addr->addr;
- arp_report_conflicted(astate, amsg);
- if (state->state == DHS_BOUND) {
- /* For now, just report the duplicated address */
- } else {
- arp_free(astate);
- dhcp_expire1(ifp);
- }
- return;
- }
+ dhcp_addr_duplicated(astate->iface, &astate->addr);
}
+#ifdef KERNEL_RFC5227
static void
dhcp_arp_announced(struct arp_state *state)
{
-// TODO: DHCP addresses handle ACD?
-//#ifdef KERNEL_RFC5227
arp_free(state);
-//#endif
}
-#endif
+#endif /* KERNEL_RFC5227 */
+#endif /* ARP */
void
dhcp_bind(struct interface *ifp)
if (ifp->ctx->options & DHCPCD_FORKED)
return;
state->interval = 0;
- if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND &&
- dhcp_leaseextend(ifp) == -1)
- {
- logerr("%s: %s", ifp->name, __func__);
- dhcp_expire(ifp);
- }
dhcp_discover(ifp);
}
}
#ifdef ARP
+#ifndef KERNEL_RFC5227
+static void
+dhcp_arp_defend_failed(struct arp_state *astate)
+{
+
+ dhcp_drop(astate->iface, "EXPIRED");
+ dhcp_start1(astate->iface);
+}
+#endif
+
static struct arp_state *
dhcp_arp_new(struct interface *ifp, struct in_addr *addr)
{
struct arp_state *astate;
+
astate = arp_new(ifp, addr);
if (astate == NULL)
return NULL;
- astate->probed_cb = dhcp_arp_probed;
- astate->conflicted_cb = dhcp_arp_conflicted;
+ astate->found_cb = dhcp_arp_found;
+ astate->not_found_cb = dhcp_arp_not_found;
+#ifdef KERNEL_RFC5227
astate->announced_cb = dhcp_arp_announced;
+#else
+ astate->defend_failed_cb = dhcp_arp_defend_failed;
+#endif
return astate;
}
struct dhcp_state *state;
struct in_addr addr;
struct ipv4_addr *ia;
- struct arp_state *astate;
eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
/* If the interface already has the address configured
* then we can't ARP for duplicate detection. */
ia = ipv4_iffindaddr(ifp, &addr, NULL);
- astate = dhcp_arp_new(ifp, &addr);
- if (astate == NULL)
- return -1;
-
#ifdef IN_IFF_TENTATIVE
if (ia == NULL || ia->addr_flags & IN_IFF_NOTUSEABLE) {
state->state = DHS_PROBE;
}
#else
if (ifp->options->options & DHCPCD_ARP && ia == NULL) {
+ struct arp_state *astate;
struct dhcp_lease l;
+ astate = dhcp_arp_new(ifp, &addr);
+ if (astate == NULL)
+ return -1;
+
state->state = DHS_PROBE;
get_lease(ifp, &l, state->offer, state->offer_len);
loginfox("%s: probing address %s/%d",
return;
}
+#ifdef ARP
+ if (state->addr != NULL)
+ arp_freeaddr(ifp, &state->addr->addr);
+#endif
#ifdef ARPING
state->arping_index = -1;
#endif
+
if (ifp->options->options & DHCPCD_RELEASE &&
!(ifp->options->options & DHCPCD_INFORM))
{
astate = dhcp_arp_new(ifp, NULL);
if (astate)
- dhcp_arp_probed(astate);
+ dhcp_arp_not_found(astate);
return;
}
#endif
return;
#ifdef IN_IFF_NOTUSEABLE
- if (ia->addr_flags & IN_IFF_NOTUSEABLE)
- return;
+ if (!(ia->addr_flags & IN_IFF_NOTUSEABLE))
+ dhcp_finish_dad(ifp, &ia->addr);
+ else if (ia->addr_flags & IN_IFF_DUPLICATED)
+ dhcp_addr_duplicated(ifp, &ia->addr);
#endif
ifo = ifp->options;
int r;
struct ipv4_state *state;
struct ipv4_addr *ap;
-#ifdef ARP
- struct arp_state *astate;
-#else
- UNUSED(keeparp);
-#endif
logdebugx("%s: deleting IP address %s",
addr->iface->name, addr->saddr);
logerr("%s: %s", addr->iface->name, __func__);
#ifdef ARP
- if (!keeparp && (astate = arp_find(addr->iface, &addr->addr)) != NULL)
- arp_free(astate);
+ if (!keeparp)
+ arp_freeaddr(addr->iface, &addr->addr);
#endif
state = IPV4_STATE(addr->iface);
ifo->options & DHCPCD_INFORM ||
(ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0))
return 0;
+ arp_freeaddr(ifp, &state->addr->addr);
r = ipv4_deladdr(state->addr, 0);
return r;
}
}
if (addr->s_addr != INADDR_ANY && addr->s_addr != INADDR_BROADCAST) {
-#ifdef ARP
- arp_handleifa(cmd, ia);
-#endif
dhcp_handleifa(cmd, ia, pid);
+#ifdef IPV4LL
+ ipv4ll_handleifa(cmd, ia, pid);
+#endif
}
if (cmd == RTM_DELADDR)
(IN_IFF_TENTATIVE | IN_IFF_DUPLICATED | IN_IFF_DETACHED)
#endif
+#define IN_ARE_ADDR_EQUAL(a, b) ((a)->s_addr == (b)->s_addr)
+#define IN_IS_ADDR_UNSPECIFIED(a) ((a)->s_addr == INADDR_ANY)
+
struct ipv4_addr {
TAILQ_ENTRY(ipv4_addr) next;
struct in_addr addr;
#include "sa.h"
#include "script.h"
-#ifdef IPV4LL
static const struct in_addr inaddr_llmask = {
.s_addr = HTONL(LINKLOCAL_MASK)
};
.s_addr = HTONL(LINKLOCAL_BCAST)
};
+static void ipv4ll_start1(struct interface *, struct arp_state *);
+
static in_addr_t
-ipv4ll_pickaddr(struct arp_state *astate)
+ipv4ll_pickaddr(struct interface *ifp)
{
struct in_addr addr;
- struct ipv4ll_state *istate;
+ struct ipv4ll_state *state;
- istate = IPV4LL_STATE(astate->iface);
- setstate(istate->randomstate);
+ state = IPV4LL_STATE(ifp);
+ setstate(state->randomstate);
do {
long r;
+again:
/* RFC 3927 Section 2.1 states that the first 256 and
* last 256 addresses are reserved for future use.
* See ipv4ll_start for why we don't use arc4random. */
((uint32_t)(r % 0xFD00) + 0x0100));
/* No point using a failed address */
- if (addr.s_addr == astate->failed.s_addr)
- continue;
+ if (IN_ARE_ADDR_EQUAL(&addr, &state->pickedaddr))
+ goto again;
/* Ensure we don't have the address on another interface */
- } while (ipv4_findaddr(astate->iface->ctx, &addr) != NULL);
+ } while (ipv4_findaddr(ifp->ctx, &addr) != NULL);
/* Restore the original random state */
- setstate(istate->arp->iface->ctx->randomstate);
+ setstate(ifp->ctx->randomstate);
return addr.s_addr;
}
}
static void
-ipv4ll_probed(struct arp_state *astate)
+ipv4ll_announced(struct arp_state *astate)
+{
+ struct ipv4ll_state *state = IPV4LL_STATE(astate->iface);
+
+ state->conflicts = 0;
+#ifdef KERNEL_RFC5227
+ arp_free(astate);
+#endif
+}
+
+static void
+ipv4ll_arpfree(struct arp_state *astate)
{
- struct interface *ifp;
struct ipv4ll_state *state;
- struct ipv4_addr *ia;
- assert(astate != NULL);
- assert(astate->iface != NULL);
+ state = IPV4LL_STATE(astate->iface);
+ if (state != NULL && state->arp == astate)
+ state->arp = NULL;
+}
+
+static void
+ipv4ll_not_found(struct interface *ifp)
+{
+ struct ipv4ll_state *state;
+ struct ipv4_addr *ia;
+ struct arp_state *astate;
- ifp = astate->iface;
state = IPV4LL_STATE(ifp);
assert(state != NULL);
- ia = ipv4_iffindaddr(ifp, &astate->addr, &inaddr_llmask);
+ ia = ipv4_iffindaddr(ifp, &state->pickedaddr, &inaddr_llmask);
#ifdef IN_IFF_NOTREADY
if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY)
#endif
loginfox("%s: using IPv4LL address %s",
- ifp->name, inet_ntoa(astate->addr));
+ ifp->name, inet_ntoa(state->pickedaddr));
if (ia == NULL) {
if (ifp->ctx->options & DHCPCD_TEST)
goto test;
- ia = ipv4_addaddr(ifp, &astate->addr,
+ ia = ipv4_addaddr(ifp, &state->pickedaddr,
&inaddr_llmask, &inaddr_llbcast);
}
if (ia == NULL)
#ifdef IN_IFF_NOTREADY
if (ia->addr_flags & IN_IFF_NOTREADY)
return;
- logdebugx("%s: DAD completed for %s",
- ifp->name, inet_ntoa(astate->addr));
+ logdebugx("%s: DAD completed for %s", ifp->name, ia->saddr);
#endif
test:
state->addr = ia;
+ state->down = false;
if (ifp->ctx->options & DHCPCD_TEST) {
script_runreason(ifp, "TEST");
eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS);
return;
}
- timespecclear(&state->defend);
if_initrt(ifp->ctx, AF_INET);
rt_build(ifp->ctx, AF_INET);
- arp_announce(astate);
+#ifdef KERNEL_RFC5227
+ astate = arp_new(ifp, &ia->addr);
+ if (astate != NULL) {
+ astate->announced_cb = ipv4ll_announced;
+ astate->free_cb = ipv4ll_arpfree;
+ arp_announce(astate);
+ }
+#else
+ arp_annnounce(state->arp);
+#endif
script_runreason(ifp, "IPV4LL");
dhcpcd_daemonise(ifp->ctx);
}
static void
-ipv4ll_announced(struct arp_state *astate)
+ipv4ll_startifp(void *arg)
{
- struct ipv4ll_state *state = IPV4LL_STATE(astate->iface);
+ struct interface *ifp = arg;
+ struct ipv4ll_state *state;
- state->conflicts = 0;
- /* Need to keep the arp state so we can defend our IP. */
+ state = IPV4LL_STATE(ifp);
+ ipv4ll_start1(ifp, state->arp);
}
static void
-ipv4ll_probe(void *arg)
+ipv4ll_found(struct interface *ifp)
{
+ struct ipv4ll_state *state = IPV4LL_STATE(ifp);
-#ifdef IN_IFF_TENTATIVE
- ipv4ll_probed(arg);
-#else
- arp_probe(arg);
-#endif
+ arp_cancel(state->arp);
+ if (++state->conflicts == MAX_CONFLICTS)
+ logerr("%s: failed to acquire an IPv4LL address",
+ ifp->name);
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ state->conflicts >= MAX_CONFLICTS ?
+ RATE_LIMIT_INTERVAL : PROBE_WAIT,
+ ipv4ll_startifp, ifp);
+}
+
+static void
+ipv4ll_defend_failed(struct interface *ifp)
+{
+ struct ipv4ll_state *state = IPV4LL_STATE(ifp);
+
+ ipv4_deladdr(state->addr, 1);
+ state->down = true;
+ state->addr = NULL;
+ if_initrt(ifp->ctx, AF_INET);
+ rt_build(ifp->ctx, AF_INET);
+ script_runreason(ifp, "IPV4LL");
+ ipv4ll_start1(ifp, state->arp);
}
+#ifndef KERNEL_RFC5227
static void
-ipv4ll_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+ipv4ll_not_found_arp(struct arp_state *astate)
{
struct interface *ifp;
struct ipv4ll_state *state;
-#ifdef IN_IFF_DUPLICATED
struct ipv4_addr *ia;
-#endif
assert(astate != NULL);
assert(astate->iface != NULL);
+
ifp = astate->iface;
state = IPV4LL_STATE(ifp);
assert(state != NULL);
+ assert(state->arp == astate);
+ ipv4ll_not_found_arp(state);
+}
- /*
- * NULL amsg means kernel detected DAD.
- * We always fail on matching sip.
- * We only fail on matching tip and we haven't added that address yet.
- */
- if (amsg == NULL ||
- amsg->sip.s_addr == astate->addr.s_addr ||
- (amsg->sip.s_addr == 0 && amsg->tip.s_addr == astate->addr.s_addr
- && ipv4_iffindaddr(ifp, &amsg->tip, NULL) == NULL))
- astate->failed = astate->addr;
- else
- return;
-
- arp_report_conflicted(astate, amsg);
-
- if (state->addr != NULL &&
- astate->failed.s_addr == state->addr->addr.s_addr)
- {
-#ifdef KERNEL_RFC5227
- logwarnx("%s: IPv4LL defence failed for %s",
- ifp->name, state->addr->saddr);
-#else
- struct timespec now, defend;
-
- /* RFC 3927 Section 2.5 says a defence should
- * broadcast an ARP announcement.
- * Because the kernel will also unicast a reply to the
- * hardware address which requested the IP address
- * the other IPv4LL client will receieve two ARP
- * messages.
- * If another conflict happens within DEFEND_INTERVAL
- * then we must drop our address and negotiate a new one. */
- defend.tv_sec = state->defend.tv_sec + DEFEND_INTERVAL;
- defend.tv_nsec = state->defend.tv_nsec;
- clock_gettime(CLOCK_MONOTONIC, &now);
- if (timespeccmp(&defend, &now, >))
- logwarnx("%s: IPv4LL %d second defence failed for %s",
- ifp->name, DEFEND_INTERVAL, state->addr->saddr);
- else if (arp_request(ifp,
- state->addr->addr.s_addr, state->addr->addr.s_addr) == -1)
- logerr(__func__);
- else {
- logdebugx("%s: defended IPv4LL address %s",
- ifp->name, state->addr->saddr);
- state->defend = now;
- return;
- }
-#endif
- ipv4_deladdr(state->addr, 1);
- state->down = 1;
- state->addr = NULL;
- if_initrt(ifp->ctx, AF_INET);
- rt_build(ifp->ctx, AF_INET);
- script_runreason(ifp, "IPV4LL");
- }
-
-#ifdef IN_IFF_DUPLICATED
- ia = ipv4_iffindaddr(ifp, &astate->addr, NULL);
- if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED)
- ipv4_deladdr(ia, 1);
-#endif
+static void
+ipv4ll_found_arp(struct arp_state *astate, __unused const struct arp_msg *amsg)
+{
+ struct interface *ifp = astate->iface;
+ struct ipv4ll_state *state = IPV4LL_STATE(ifp);
- arp_cancel(astate);
- if (++state->conflicts == MAX_CONFLICTS)
- logerr("%s: failed to acquire an IPv4LL address",
- ifp->name);
- state->pickedaddr.s_addr = ipv4ll_pickaddr(astate);
- astate->addr = state->pickedaddr;
- eloop_timeout_add_sec(ifp->ctx->eloop,
- state->conflicts >= MAX_CONFLICTS ?
- RATE_LIMIT_INTERVAL : PROBE_WAIT,
- ipv4ll_probe, astate);
+ assert(state->arp == astate);
+ ipv4ll_found(ifp);
}
static void
-ipv4ll_arpfree(struct arp_state *astate)
+ipv4ll_defend_failed_arp(struct arp_state *astate)
{
- struct ipv4ll_state *state;
+ struct ipv4ll_state *state = IPV4LL_STATE(astate->ifp);
- state = IPV4LL_STATE(astate->iface);
- if (state != NULL && state->arp == astate)
- state->arp = NULL;
+ assert(state->arp == astate);
+ ipv4ll_defend_failed1(astate->iface);
}
+#endif
-void
-ipv4ll_start(void *arg)
+static void
+ipv4ll_start1(struct interface *ifp, struct arp_state *astate)
{
- struct interface *ifp;
struct ipv4ll_state *state;
- struct arp_state *astate;
struct ipv4_addr *ia;
+ bool repick;
- assert(arg != NULL);
- ifp = arg;
+ assert(ifp != NULL);
if ((state = IPV4LL_STATE(ifp)) == NULL) {
ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state));
if ((state = IPV4LL_STATE(ifp)) == NULL) {
state->seeded = true;
}
- if (state->arp != NULL)
- return;
- if ((astate = arp_new(ifp, NULL)) == NULL)
- return;
+#ifndef KERNEL_RFC5227
+ if (astate == NULL) {
+ if (state->arp != NULL)
+ return;
+ if ((astate = arp_new(ifp, NULL)) == NULL)
+ return;
+ astate->found_cb = ipv4ll_found_arp;
+ astate->not_found_cb = ipv4ll_not_found_arp;
+ astate->announced_cb = ipv4ll_announced_arp;
+ astate->defend_failed_cb = ipv4ll_defend_failed_arp;
+ astate->free_cb = ipv4ll_arpfree;
+ state->arp = astate;
+ } else
+ assert(state->arp == astate);
+#else
+ UNUSED(astate);
+#endif
- state->arp = astate;
- astate->probed_cb = ipv4ll_probed;
- astate->announced_cb = ipv4ll_announced;
- astate->conflicted_cb = ipv4ll_conflicted;
- astate->free_cb = ipv4ll_arpfree;
+ state->down = true;
/* Find the previosuly used address. */
if (state->pickedaddr.s_addr != INADDR_ANY)
if (ia == NULL)
ia = ipv4_iffindlladdr(ifp);
+ repick = false;
#ifdef IN_IFF_TENTATIVE
if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) {
+ state->pickedaddr = ia->addr; /* So it's not picked again. */
+ repick = true;
ipv4_deladdr(ia, 0);
ia = NULL;
}
#endif
+ state->addr = ia;
if (ia != NULL) {
- state->pickedaddr = astate->addr = ia->addr;
+ state->pickedaddr = ia->addr;
+#ifndef KERNEL_RFC5227
+ astate->addr = ia->addr;
+#endif
#ifdef IN_IFF_TENTATIVE
if (ia->addr_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED)) {
loginfox("%s: waiting for DAD to complete on %s",
}
loginfox("%s: using IPv4LL address %s", ifp->name, ia->saddr);
#endif
- ipv4ll_probed(astate);
+ ipv4ll_not_found(ifp);
return;
}
loginfox("%s: probing for an IPv4LL address", ifp->name);
- if (state->pickedaddr.s_addr == INADDR_ANY)
- state->pickedaddr.s_addr = ipv4ll_pickaddr(astate);
+ if (repick || state->pickedaddr.s_addr == INADDR_ANY)
+ state->pickedaddr.s_addr = ipv4ll_pickaddr(ifp);
+#ifndef KERNEL_RFC5227
astate->addr = state->pickedaddr;
+#endif
#ifdef IN_IFF_TENTATIVE
- ipv4ll_probed(astate);
+ ipv4ll_not_found(ifp);
#else
arp_probe(astate);
#endif
}
+void
+ipv4ll_start(void *arg)
+{
+
+ ipv4ll_start1(arg, NULL);
+}
+
static void
ipv4ll_freearp(struct interface *ifp)
{
eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp);
arp_free(state->arp);
- state->arp = NULL;
}
void
ipv4ll_freearp(ifp);
-#ifndef IN_IFF_TENATIVE
if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP)
-#endif
return;
state = IPV4LL_STATE(ifp);
return 0;
}
#endif
+
+void
+ipv4ll_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid)
+{
+ struct interface *ifp;
+ struct ipv4ll_state *state;
+
+ ifp = ia->iface;
+ state = IPV4LL_STATE(ifp);
+ if (state == NULL || state->addr == NULL ||
+ !IN_ARE_ADDR_EQUAL(&state->addr->addr, &ia->addr))
+ return;
+
+ if (cmd == RTM_DELADDR) {
+ loginfox("%s: pid %d deleted IP address %s",
+ ifp->name, pid, ia->saddr);
+ ipv4ll_defend_failed(ifp);
+ }
+
+#ifdef IN_IFF_DUPLICATED
+ if (cmd != RTM_NEWADDR)
+ return;
+ if (!(ia->addr_flags & IN_IFF_NOTUSEABLE))
+ ipv4ll_not_found(ifp);
+ else if (ia->addr_flags & IN_IFF_DUPLICATED) {
+ logerrx("%s: DAD detected %s", ifp->name, ia->saddr);
+ ipv4_deladdr(state->addr, 1);
+ ipv4ll_found(ifp);
+ }
#endif
+}
struct in_addr pickedaddr;
struct ipv4_addr *addr;
struct arp_state *arp;
- unsigned int conflicts;
- struct timespec defend;
char randomstate[128];
bool seeded;
- uint8_t down;
+ bool down;
+ size_t conflicts;
};
#define IPV4LL_STATE(ifp) \
void ipv4ll_start(void *);
void ipv4ll_claimed(void *);
void ipv4ll_handle_failure(void *);
+void ipv4ll_handleifa(int, struct ipv4_addr *, pid_t pid);
#ifdef HAVE_ROUTE_METRIC
int ipv4ll_recvrt(int, const struct rt *);
#endif