| /* |
| * dhcpcd - DHCP client daemon |
| * Copyright (c) 2006-2008 Roy Marples <roy@marples.name> |
| * 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 <errno.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| |
| #include "arp.h" |
| #include "bind.h" |
| #include "common.h" |
| #include "configure.h" |
| #include "dhcpcd.h" |
| #include "eloop.h" |
| #include "if-options.h" |
| #include "ipv4ll.h" |
| #include "net.h" |
| |
| #define ARP_LEN \ |
| (sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN)) |
| |
| static int |
| send_arp(const struct interface *iface, int op, in_addr_t sip, in_addr_t tip, |
| unsigned char *dest_hw_addr) |
| { |
| uint8_t arp_buffer[ARP_LEN]; |
| struct arphdr ar; |
| size_t len; |
| uint8_t *p; |
| int retval; |
| |
| ar.ar_hrd = htons(iface->family); |
| ar.ar_pro = htons(ETHERTYPE_IP); |
| ar.ar_hln = iface->hwlen; |
| ar.ar_pln = sizeof(sip); |
| ar.ar_op = htons(op); |
| memcpy(arp_buffer, &ar, sizeof(ar)); |
| p = arp_buffer + sizeof(ar); |
| memcpy(p, iface->hwaddr, iface->hwlen); |
| p += iface->hwlen; |
| memcpy(p, &sip, sizeof(sip)); |
| p += sizeof(sip); |
| if (dest_hw_addr != NULL) |
| memcpy(p, dest_hw_addr, iface->hwlen); |
| else |
| memset(p, '\0', iface->hwlen); |
| p += iface->hwlen; |
| memcpy(p, &tip, sizeof(tip)); |
| p += sizeof(tip); |
| len = p - arp_buffer; |
| retval = send_raw_packet(iface, ETHERTYPE_ARP, arp_buffer, len, |
| dest_hw_addr); |
| return retval; |
| } |
| |
| static void |
| handle_arp_failure(struct interface *iface) |
| { |
| if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) { |
| handle_ipv4ll_failure(iface); |
| return; |
| } |
| unlink(iface->leasefile); |
| if (!iface->state->lease.frominfo) |
| send_decline(iface); |
| close_sockets(iface); |
| delete_timeout(NULL, iface); |
| if (iface->state->lease.frominfo) |
| start_interface(iface); |
| else |
| add_timeout_sec(DHCP_ARP_FAIL, start_interface, iface); |
| } |
| |
| static void |
| save_gateway_addr(struct interface *iface, uint8_t *gw_hwaddr, uint8_t gw_hwlen) |
| { |
| memset(&iface->server_info, 0, sizeof(iface->server_info)); |
| if (gw_hwlen > sizeof(iface->server_info.gw_hwaddr)) |
| return; |
| memcpy(iface->server_info.gw_hwaddr, gw_hwaddr, gw_hwlen); |
| iface->server_info.gw_hwlen = gw_hwlen; |
| } |
| |
| static void |
| handle_arp_packet(void *arg) |
| { |
| struct interface *iface = arg; |
| uint8_t arp_buffer[ARP_LEN]; |
| struct arphdr ar; |
| uint32_t reply_s; |
| uint32_t reply_t; |
| uint8_t *hw_s, *hw_t; |
| ssize_t bytes; |
| struct if_state *state = iface->state; |
| struct if_options *opts = state->options; |
| const char *hwaddr; |
| struct in_addr ina; |
| |
| state->fail.s_addr = 0; |
| for(;;) { |
| bytes = get_raw_packet(iface, ETHERTYPE_ARP, |
| arp_buffer, sizeof(arp_buffer)); |
| if (bytes == 0 || bytes == -1) |
| return; |
| /* We must have a full ARP header */ |
| if ((size_t)bytes < sizeof(ar)) |
| continue; |
| memcpy(&ar, arp_buffer, sizeof(ar)); |
| /* Protocol must be IP. */ |
| if (ar.ar_pro != htons(ETHERTYPE_IP)) |
| continue; |
| if (ar.ar_pln != sizeof(reply_s)) |
| 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 with strange hardware address lengths */ |
| if (ar.ar_hln != iface->hwlen) |
| continue; |
| /* Ignore messages from ourself */ |
| if (memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0) |
| 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); |
| syslog(LOG_INFO, |
| "%s: found %s on hardware address %s", |
| iface->name, inet_ntoa(ina), hwaddr); |
| if (select_profile(iface, hwaddr) == -1 && |
| errno == ENOENT) |
| select_profile(iface, inet_ntoa(ina)); |
| close_sockets(iface); |
| delete_timeout(NULL, iface); |
| start_interface(iface); |
| return; |
| } |
| |
| if (state->offer) { |
| if (state->state == DHS_PROBEGW || |
| state->state == DHS_REBOOT) { |
| /* Check for successful gateway probe */ |
| if (ar.ar_op == htons(ARPOP_REPLY) && |
| reply_s == state->offer->giaddr) { |
| ina.s_addr = reply_s; |
| syslog(LOG_INFO, |
| "%s: gateway %s found at %s", |
| iface->name, inet_ntoa(ina), |
| hwaddr_ntoa((unsigned char *) |
| hw_s, |
| (size_t)ar.ar_hln)); |
| if (state->state == DHS_PROBEGW) { |
| delete_timeout(NULL, iface); |
| save_gateway_addr(iface, hw_s, |
| ar.ar_hln); |
| bind_interface(iface); |
| } else if (memcmp(iface->server_info. |
| gw_hwaddr, hw_s, |
| ar.ar_hln) == 0) |
| notify_unicast_arp(iface); |
| delete_event(iface->arp_fd); |
| close(iface->arp_fd); |
| iface->arp_fd = -1; |
| } |
| return; |
| } else { |
| /* Check for conflict */ |
| if (reply_s == state->offer->yiaddr || |
| (reply_s == 0 && |
| reply_t == state->offer->yiaddr)) |
| state->fail.s_addr = |
| state->offer->yiaddr; |
| } |
| } |
| |
| /* Handle IPv4LL conflicts */ |
| if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) && |
| (reply_s == iface->addr.s_addr || |
| (reply_s == 0 && reply_t == iface->addr.s_addr))) |
| state->fail.s_addr = iface->addr.s_addr; |
| |
| if (state->fail.s_addr) { |
| syslog(LOG_ERR, "%s: hardware address %s claims %s", |
| iface->name, |
| hwaddr_ntoa((unsigned char *)hw_s, |
| (size_t)ar.ar_hln), |
| inet_ntoa(state->fail)); |
| errno = EEXIST; |
| handle_arp_failure(iface); |
| return; |
| } |
| } |
| } |
| |
| void |
| send_arp_announce(void *arg) |
| { |
| struct interface *iface = arg; |
| struct if_state *state = iface->state; |
| struct timeval tv; |
| |
| if (iface->arp_fd == -1) { |
| open_socket(iface, ETHERTYPE_ARP); |
| add_event(iface->arp_fd, handle_arp_packet, iface); |
| } |
| if (++state->claims < ANNOUNCE_NUM) |
| syslog(LOG_DEBUG, |
| "%s: sending ARP announce (%d of %d), " |
| "next in %d.00 seconds", |
| iface->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT); |
| else |
| syslog(LOG_DEBUG, |
| "%s: sending ARP announce (%d of %d)", |
| iface->name, state->claims, ANNOUNCE_NUM); |
| if (send_arp(iface, ARPOP_REQUEST, |
| state->new->yiaddr, state->new->yiaddr, NULL) == -1) |
| syslog(LOG_ERR, "send_arp: %m"); |
| if (state->claims < ANNOUNCE_NUM) { |
| add_timeout_sec(ANNOUNCE_WAIT, send_arp_announce, iface); |
| return; |
| } |
| if (IN_LINKLOCAL(htonl(state->new->yiaddr))) { |
| /* We should pretend to be at the end |
| * of the DHCP negotation cycle unless we rebooted */ |
| if (state->interval != 0) |
| state->interval = 64; |
| state->probes = 0; |
| state->claims = 0; |
| tv.tv_sec = state->interval - DHCP_RAND_MIN; |
| tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); |
| timernorm(&tv); |
| add_timeout_tv(&tv, start_discover, iface); |
| } else { |
| delete_event(iface->arp_fd); |
| close(iface->arp_fd); |
| iface->arp_fd = -1; |
| } |
| } |
| |
| void |
| send_arp_probe(void *arg) |
| { |
| struct interface *iface = arg; |
| struct if_state *state = iface->state; |
| struct in_addr addr; |
| struct timeval tv; |
| int arping = 0; |
| in_addr_t src_addr = 0; |
| |
| if (state->arping_index < state->options->arping_len) { |
| addr.s_addr = state->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; |
| if (state->state == DHS_PROBEGW) { |
| /* ARP for the gateway using our leased IP address */ |
| src_addr = addr.s_addr; |
| addr.s_addr = state->offer->giaddr; |
| } |
| } else |
| addr.s_addr = iface->addr.s_addr; |
| |
| if (iface->arp_fd == -1) { |
| open_socket(iface, ETHERTYPE_ARP); |
| add_event(iface->arp_fd, handle_arp_packet, iface); |
| } |
| if (state->probes == 0) { |
| if (arping) |
| syslog(LOG_INFO, "%s: searching for %s", |
| iface->name, inet_ntoa(addr)); |
| else |
| syslog(LOG_INFO, "%s: checking for %s", |
| iface->name, inet_ntoa(addr)); |
| } |
| if (++state->probes < PROBE_NUM) { |
| tv.tv_sec = PROBE_MIN; |
| tv.tv_usec = arc4random() % (PROBE_MAX_U - PROBE_MIN_U); |
| timernorm(&tv); |
| add_timeout_tv(&tv, send_arp_probe, iface); |
| } else { |
| tv.tv_sec = ANNOUNCE_WAIT; |
| tv.tv_usec = 0; |
| if (arping) { |
| state->probes = 0; |
| if (++state->arping_index < state->options->arping_len) |
| add_timeout_tv(&tv, send_arp_probe, iface); |
| else |
| add_timeout_tv(&tv, start_interface, iface); |
| } else if (state->state == DHS_PROBEGW) { |
| /* Allow ourselves to fail only once this way */ |
| syslog(LOG_ERR, "arpgw: ARP timed out"); |
| state->options->options &= ~DHCPCD_ARPGW; |
| errno = ENOENT; |
| handle_arp_failure(iface); |
| return; |
| } else if ((state->options->options & DHCPCD_ARPGW) != 0 && |
| start_arpgw(iface) != 0) { |
| return; |
| } else |
| add_timeout_tv(&tv, bind_interface, iface); |
| } |
| syslog(LOG_DEBUG, |
| "%s: sending ARP probe (%d of %d), next in %0.2f seconds", |
| iface->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM, |
| timeval_to_double(&tv)); |
| if (send_arp(iface, ARPOP_REQUEST, src_addr, addr.s_addr, NULL) == -1) |
| syslog(LOG_ERR, "send_arp: %m"); |
| } |
| |
| int |
| start_arpself(struct interface *iface) |
| { |
| struct if_state *state = iface->state; |
| struct in_addr addr; |
| |
| if (iface->addr.s_addr != state->offer->yiaddr) { |
| /* If the interface already has the address configured |
| * then we can't ARP for duplicate detection. */ |
| addr.s_addr = state->offer->yiaddr; |
| if (has_address(iface->name, &addr, NULL) != 1) { |
| state->claims = 0; |
| state->probes = 0; |
| state->conflicts = 0; |
| state->state = DHS_PROBE; |
| send_arp_probe(iface); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| start_arpgw(struct interface *iface) |
| { |
| struct if_state *state = iface->state; |
| struct in_addr ina; |
| |
| if (get_option_addr(&ina, state->offer, DHO_ROUTER)) |
| return 0; |
| |
| /* Abuse the "giaddr" struct entry to store the first router |
| * IP address */ |
| state->offer->giaddr = ina.s_addr; |
| state->probes = 0; |
| state->state = DHS_PROBEGW; |
| send_arp_probe(iface); |
| return 1; |
| } |
| |
| |
| void |
| start_arping(struct interface *iface) |
| { |
| iface->state->probes = 0; |
| iface->state->arping_index = 0; |
| send_arp_probe(iface); |
| } |
| |
| int |
| start_unicast_arp(struct interface *iface) |
| { |
| int i; |
| struct in_addr gwa; |
| in_addr_t src_addr = 0; |
| struct if_state *state = iface->state; |
| |
| if (!state->offer) |
| return 0; |
| |
| if (!state->lease.frominfo) |
| return 0; |
| |
| if (iface->server_info.gw_hwlen != iface->hwlen) |
| return 0; |
| |
| if (get_option_addr(&gwa, state->offer, DHO_ROUTER)) |
| return 0; |
| |
| /* Invalidate our gateway address until the next successful PROBEGW */ |
| iface->server_info.gw_hwlen = 0; |
| |
| if (state->offer->yiaddr) |
| src_addr = state->offer->yiaddr; |
| else |
| src_addr = state->offer->ciaddr; |
| |
| if (iface->arp_fd == -1) { |
| open_socket(iface, ETHERTYPE_ARP); |
| add_event(iface->arp_fd, handle_arp_packet, iface); |
| } |
| |
| /* Send a unicast ARP for the gateway IP / MAC sourced from leased IP */ |
| if (send_arp(iface, ARPOP_REQUEST, src_addr, gwa.s_addr, |
| iface->server_info.gw_hwaddr) == -1) { |
| syslog(LOG_ERR, "send_arp: %m"); |
| return 0; |
| } |
| |
| return 1; |
| } |