/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2015 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #define ELOOP_QUEUE 5 #include "config.h" #include "arp.h" #include "if.h" #include "ipv4.h" #include "common.h" #include "dhcp.h" #include "dhcpcd.h" #include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4ll.h" #define ARP_LEN \ (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, const uint8_t *dest_hw_addr) { uint8_t arp_buffer[ARP_LEN]; struct arphdr ar; size_t len; uint8_t *p; ar.ar_hrd = htons(ifp->family); ar.ar_pro = htons(ETHERTYPE_IP); ar.ar_hln = ifp->hwlen; ar.ar_pln = sizeof(sip); ar.ar_op = htons(ARPOP_REQUEST); p = arp_buffer; len = 0; #define CHECK(fun, b, l) \ do { \ if (len + (l) > sizeof(arp_buffer)) \ goto eexit; \ fun(p, (b), (l)); \ p += (l); \ len += (l); \ } while (/* CONSTCOND */ 0) #define APPEND(b, l) CHECK(memcpy, b, l) #define ZERO(l) CHECK(memset, 0, l) APPEND(&ar, sizeof(ar)); APPEND(ifp->hwaddr, ifp->hwlen); APPEND(&sip, sizeof(sip)); 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, dest_hw_addr); eexit: errno = ENOBUFS; return -1; } void arp_report_conflicted(const struct arp_state *astate, const struct arp_msg *amsg) { if (amsg) { char buf[HWADDR_LEN * 3]; logger(astate->iface->ctx, LOG_ERR, "%s: hardware address %s claims %s", astate->iface->name, hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)), inet_ntoa(astate->failed)); } else logger(astate->iface->ctx, LOG_ERR, "%s: DAD detected %s", astate->iface->name, inet_ntoa(astate->failed)); } static void arp_packet(void *arg) { struct interface *ifp = arg; const struct interface *ifn; uint8_t arp_buffer[ARP_LEN]; struct arphdr ar; struct arp_msg arm; ssize_t bytes; struct dhcp_state *state; struct arp_state *astate, *astaten; unsigned char *hw_s, *hw_t; int flags; state = D_STATE(ifp); state->failed.s_addr = 0; flags = 0; while (!(flags & RAW_EOF)) { bytes = if_readrawpacket(ifp, ETHERTYPE_ARP, arp_buffer, sizeof(arp_buffer), &flags); if (bytes == -1) { logger(ifp->ctx, LOG_ERR, "%s: arp if_readrawpacket: %m", ifp->name); dhcp_close(ifp); return; } /* We must have a full ARP header */ 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(arm.sip.s_addr)) continue; /* Only these types are recognised */ if (ar.ar_op != htons(ARPOP_REPLY) && ar.ar_op != htons(ARPOP_REQUEST)) continue; /* Get pointers to the hardware addreses */ hw_s = arp_buffer + sizeof(ar); hw_t = hw_s + ar.ar_hln + ar.ar_pln; /* Ensure we got all the data */ if ((hw_t + ar.ar_hln + ar.ar_pln) - arp_buffer > bytes) continue; /* Ignore messages from ourself */ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { if (ar.ar_hln == ifn->hwlen && memcmp(hw_s, ifn->hwaddr, ifn->hwlen) == 0) break; } if (ifn) continue; /* 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) { if (astate->conflicted_cb) astate->conflicted_cb(astate, &arm); } } } static void arp_open(struct interface *ifp) { struct dhcp_state *state; state = D_STATE(ifp); if (state->arp_fd == -1) { state->arp_fd = if_openrawsocket(ifp, ETHERTYPE_ARP); if (state->arp_fd == -1) { logger(ifp->ctx, LOG_ERR, "%s: %s: %m", __func__, ifp->name); return; } eloop_event_add(ifp->ctx->eloop, state->arp_fd, arp_packet, ifp, NULL, NULL); } } 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) logger(ifp->ctx, LOG_DEBUG, "%s: ARP announcing %s (%d of %d), " "next in %d.0 seconds", ifp->name, inet_ntoa(astate->addr), astate->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT); else logger(ifp->ctx, LOG_DEBUG, "%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, 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, astate); } void arp_announce(struct arp_state *astate) { arp_open(astate->iface); astate->claims = 0; arp_announce1(astate); } 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 timespec tv; uint8_t *dest_hwaddr = NULL; if (++astate->probes < PROBE_NUM) { tv.tv_sec = PROBE_MIN; tv.tv_nsec = (suseconds_t)arc4random_uniform( (PROBE_MAX - PROBE_MIN) * NSEC_PER_SEC); timespecnorm(&tv); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe1, astate); } else { tv.tv_sec = ANNOUNCE_WAIT; tv.tv_nsec = 0; eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probed, astate); } logger(ifp->ctx, LOG_INFO, "%s: ARP probing %s (%d of %d), next in %0.1f seconds", 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, dest_hwaddr) == -1) logger(ifp->ctx, LOG_ERR, "send_arp: %m"); } void arp_probe(struct arp_state *astate) { arp_open(astate->iface); astate->probes = 0; logger(astate->iface->ctx, LOG_DEBUG, "%s: probing for %s", astate->iface->name, inet_ntoa(astate->addr)); arp_probe1(astate); } static struct arp_state * arp_find(struct interface *ifp, const struct in_addr *addr) { struct arp_state *astate; struct dhcp_state *state; state = D_STATE(ifp); TAILQ_FOREACH(astate, &state->arp_states, next) { if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp) return astate; } errno = ESRCH; return NULL; } struct arp_state * arp_new(struct interface *ifp, const struct in_addr *addr) { struct arp_state *astate; struct dhcp_state *state; if (addr && (astate = arp_find(ifp, addr))) return astate; if ((astate = calloc(1, sizeof(*astate))) == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__); return NULL; } state = D_STATE(ifp); astate->iface = ifp; if (addr) astate->addr = *addr; TAILQ_INSERT_TAIL(&state->arp_states, astate, next); return astate; } void arp_cancel(struct arp_state *astate) { eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate); } void arp_free(struct arp_state *astate) { struct dhcp_state *state; if (astate) { eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate); state = D_STATE(astate->iface); TAILQ_REMOVE(&state->arp_states, astate, next); if (state->arp_ipv4ll == astate) { ipv4ll_stop(astate->iface); state->arp_ipv4ll = NULL; } free(astate); } } void arp_free_but(struct arp_state *astate) { struct arp_state *p, *n; struct dhcp_state *state; state = D_STATE(astate->iface); TAILQ_FOREACH_SAFE(p, &state->arp_states, next, n) { if (p != astate) arp_free(p); } } void arp_close(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); struct arp_state *astate; 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; } while ((astate = TAILQ_FIRST(&state->arp_states))) { #ifndef __clang_analyzer__ /* clang guard needed for a more compex variant on this bug: * http://llvm.org/bugs/show_bug.cgi?id=18222 */ arp_free(astate); #endif } } void arp_handleifa(int cmd, struct interface *ifp, const struct in_addr *addr, int flags) { #ifdef IN_IFF_DUPLICATED struct dhcp_state *state = D_STATE(ifp); struct arp_state *astate, *asn; if (cmd != RTM_NEWADDR || (state = D_STATE(ifp)) == NULL) return; TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, asn) { if (astate->addr.s_addr == addr->s_addr) { if (flags & IN_IFF_DUPLICATED) { if (astate->conflicted_cb) astate->conflicted_cb(astate, NULL); } else if (!(flags & IN_IFF_NOTUSEABLE)) { if (astate->probed_cb) astate->probed_cb(astate); } } } #else UNUSED(cmd); UNUSED(ifp); UNUSED(addr); UNUSED(flags); #endif }