diff --git a/ndhc/arpping.c b/ndhc/arpping.c index c848ec8..47dd8b7 100644 --- a/ndhc/arpping.c +++ b/ndhc/arpping.c @@ -1,5 +1,5 @@ /* - * Shamelessly ripped off from busybox's udhcpc variant, which in turn was... + * Derived from busybox's udhcpc variant, which in turn was... * Mostly stolen from: dhcpcd - DHCP client daemon * by Yoichi Hariguchi * Licensed under GPLv2, see file LICENSE in this source tree. @@ -9,59 +9,14 @@ #include #include #include -#include #include #include -#include +#include "arpping.h" #include "dhcpd.h" #include "log.h" #include "strl.h" #include "io.h" -struct arpMsg { - /* Ethernet header */ - uint8_t h_dest[6]; /* 00 destination ether addr */ - uint8_t h_source[6]; /* 06 source ether addr */ - uint16_t h_proto; /* 0c packet type ID field */ - - /* ARP packet */ - uint16_t htype; /* 0e hardware type (must be ARPHRD_ETHER) */ - uint16_t ptype; /* 10 protocol type (must be ETH_P_IP) */ - uint8_t hlen; /* 12 hardware address length (must be 6) */ - uint8_t plen; /* 13 protocol address length (must be 4) */ - uint16_t operation; /* 14 ARP opcode */ - uint8_t sHaddr[6]; /* 16 sender's hardware address */ - uint8_t sInaddr[4]; /* 1c sender's IP address */ - uint8_t tHaddr[6]; /* 20 target's hardware address */ - uint8_t tInaddr[4]; /* 26 target's IP address */ - uint8_t pad[18]; /* 2a pad for min. ethernet payload (60 bytes) */ -}; - -enum { - ARP_MSG_SIZE = 0x2a -}; - -static int safe_poll(struct pollfd *ufds, nfds_t nfds, int timeout) -{ - while (1) { - int n = poll(ufds, nfds, timeout); - if (n >= 0) - return n; - /* Make sure we inch towards completion */ - if (timeout > 0) - timeout--; - /* E.g. strace causes poll to return this */ - if (errno == EINTR) - continue; - /* Kernel is very low on memory. Retry. */ - /* I doubt many callers would handle this correctly! */ - if (errno == ENOMEM) - continue; - log_warning("poll error: %s", strerror(errno)); - return n; - } -} - static unsigned long long curms() { struct timeval tv; @@ -69,27 +24,26 @@ static unsigned long long curms() return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; } -/* Returns 1 if no reply received */ +/* Returns fd of the arp socket, or -1 on failure. */ int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip, uint8_t *from_mac, const char *interface) { - int timeout_ms; - struct pollfd pfd[1]; - int rv = 1; /* "no reply received" yet */ + int arpfd; int opt = 1; struct sockaddr addr; /* for interface name */ struct arpMsg arp; - pfd[0].fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); - if (pfd[0].fd == -1) { + arpfd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + if (arpfd == -1) { log_warning("arpping: failed to create socket: %s", strerror(errno)); return -1; } - if (setsockopt(pfd[0].fd, SOL_SOCKET, SO_BROADCAST, + if (setsockopt(arpfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) { log_warning("arpping: failed to set broadcast: %s", strerror(errno)); - goto ret; + close(arpfd); + return -1; } /* send arp request */ @@ -103,58 +57,17 @@ int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip, arp.plen = 4; /* protocol address length */ arp.operation = htons(ARPOP_REQUEST); /* ARP op code */ memcpy(arp.sHaddr, from_mac, 6); /* source hardware address */ - memcpy(arp.sInaddr, &from_ip, sizeof from_ip); /* source IP address */ + memcpy(arp.sInaddr, &from_ip, sizeof from_ip); /* source IP address */ /* tHaddr is zero-filled */ /* target hardware address */ memcpy(arp.tInaddr, &test_nip, sizeof test_nip);/* target IP address */ memset(&addr, 0, sizeof addr); strlcpy(addr.sa_data, interface, sizeof addr.sa_data); - if (safe_sendto(pfd[0].fd, (const char *)&arp, sizeof arp, + if (safe_sendto(arpfd, (const char *)&arp, sizeof arp, 0, &addr, sizeof addr) < 0) { log_error("arpping: sendto failed: %s", strerror(errno)); - goto ret; + close(arpfd); + return -1; } - - /* wait for arp reply, and check it */ - timeout_ms = 2000; - do { - typedef uint32_t aliased_uint32_t __attribute__((__may_alias__)); - int r; - unsigned long long prevTime = curms(); - - pfd[0].events = POLLIN; - r = safe_poll(pfd, 1, timeout_ms); - if (r < 0) - break; - if (r) { - r = safe_read(pfd[0].fd, (char *)&arp, sizeof arp); - if (r < 0) - break; - - //log3("sHaddr %02x:%02x:%02x:%02x:%02x:%02x", - //arp.sHaddr[0], arp.sHaddr[1], arp.sHaddr[2], - //arp.sHaddr[3], arp.sHaddr[4], arp.sHaddr[5]); - - if (r >= ARP_MSG_SIZE - && arp.operation == htons(ARPOP_REPLY) - /* don't check it: Linux doesn't return proper tHaddr (fixed in 2.6.24?) */ - /* && memcmp(arp.tHaddr, from_mac, 6) == 0 */ - && *(aliased_uint32_t*)arp.sInaddr == test_nip - ) { - /* if ARP source MAC matches safe_mac - * (which is client's MAC), then it's not a conflict - * (client simply already has this IP and replies to ARPs!) - */ - if (!safe_mac || memcmp(safe_mac, arp.sHaddr, 6) != 0) - rv = 0; - break; - } - } - timeout_ms -= (int)(curms() - prevTime); - } while (timeout_ms > 0); - - ret: - close(pfd[0].fd); - log_line("%srp reply received for this address", rv ? "No a" : "A"); - return rv; + return arpfd; } diff --git a/ndhc/arpping.h b/ndhc/arpping.h index e9ae76f..4d2fa6c 100644 --- a/ndhc/arpping.h +++ b/ndhc/arpping.h @@ -1,8 +1,33 @@ #ifndef ARPPING_H_ #define ARPPING_H_ +#include +#include + +struct arpMsg { + /* Ethernet header */ + uint8_t h_dest[6]; /* 00 destination ether addr */ + uint8_t h_source[6]; /* 06 source ether addr */ + uint16_t h_proto; /* 0c packet type ID field */ + + /* ARP packet */ + uint16_t htype; /* 0e hardware type (must be ARPHRD_ETHER) */ + uint16_t ptype; /* 10 protocol type (must be ETH_P_IP) */ + uint8_t hlen; /* 12 hardware address length (must be 6) */ + uint8_t plen; /* 13 protocol address length (must be 4) */ + uint16_t operation; /* 14 ARP opcode */ + uint8_t sHaddr[6]; /* 16 sender's hardware address */ + uint8_t sInaddr[4]; /* 1c sender's IP address */ + uint8_t tHaddr[6]; /* 20 target's hardware address */ + uint8_t tInaddr[4]; /* 26 target's IP address */ + uint8_t pad[18]; /* 2a pad for min. ethernet payload (60 bytes) */ +}; + +enum { + ARP_MSG_SIZE = 0x2a +}; + int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip, uint8_t *from_mac, const char *interface); - #endif /* ARPPING_H_ */ diff --git a/ndhc/dhcpc.h b/ndhc/dhcpc.h index 0d4e9f3..d965ced 100644 --- a/ndhc/dhcpc.h +++ b/ndhc/dhcpc.h @@ -7,6 +7,7 @@ enum { BOUND, RENEWING, REBINDING, + ARP_CHECK, INIT_REBOOT, RENEW_REQUESTED, RELEASED diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c index e8c876b..7819ac3 100644 --- a/ndhc/ndhc.c +++ b/ndhc/ndhc.c @@ -56,19 +56,20 @@ #include "strl.h" #include "pidfile.h" #include "malloc.h" +#include "io.h" #define VERSION "1.0" #define NUMPACKETS 3 /* number of packets to send before delay */ #define RETRY_DELAY 30 /* time in seconds to delay after sending NUMPACKETS */ -static int epollfd, signalFd; +static int epollfd, signalFd, arpFd = -1; static struct epoll_event events[3]; static char pidfile[MAX_PATH_LENGTH] = PID_FILE_DEFAULT; static uint32_t requested_ip, server_addr, timeout; static uint32_t lease, t1, t2, xid, start; -static int state, packet_num, listenFd, listen_mode; +static int dhcp_state, arp_prev_dhcp_state, packet_num, listenFd, listen_mode; enum { LISTEN_NONE, @@ -167,19 +168,26 @@ static void change_listen_mode(int new_mode) static void perform_renew(void) { log_line("Performing a DHCP renew..."); - switch (state) { + retry: + switch (dhcp_state) { case BOUND: change_listen_mode(LISTEN_KERNEL); + case ARP_CHECK: + // Cancel arp ping in progress and treat as previous state. + epoll_del(arpFd); + arpFd = -1; + dhcp_state = arp_prev_dhcp_state; + goto retry; case RENEWING: case REBINDING: - state = RENEW_REQUESTED; + dhcp_state = RENEW_REQUESTED; break; case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ run_script(NULL, SCRIPT_DECONFIG); case REQUESTING: case RELEASED: change_listen_mode(LISTEN_RAW); - state = INIT_SELECTING; + dhcp_state = INIT_SELECTING; break; case INIT_SELECTING: break; @@ -199,7 +207,8 @@ static void perform_release(void) struct in_addr temp_saddr, temp_raddr; /* send release packet */ - if (state == BOUND || state == RENEWING || state == REBINDING) { + if (dhcp_state == BOUND || dhcp_state == RENEWING || + dhcp_state == REBINDING || dhcp_state == ARP_CHECK) { temp_saddr.s_addr = server_addr; temp_raddr.s_addr = requested_ip; log_line("Unicasting a release of %s to %s.", @@ -209,8 +218,12 @@ static void perform_release(void) } log_line("Entering released state."); + if (dhcp_state == ARP_CHECK) { + epoll_del(arpFd); + arpFd = -1; + } change_listen_mode(LISTEN_NONE); - state = RELEASED; + dhcp_state = RELEASED; timeout = 0x7fffffff; } @@ -229,12 +242,63 @@ static void background(void) write_pid(pidfile); } +static struct arpMsg arpreply; +static int arpreply_offset; +static struct dhcpMessage arp_dhcp_packet; + +static void arp_failed(void) +{ + log_line("Offered address is in use: declining."); + epoll_del(arpFd); + arpFd = -1; + send_decline(xid, server_addr, arp_dhcp_packet.yiaddr); + + if (arp_prev_dhcp_state != REQUESTING) + run_script(NULL, SCRIPT_DECONFIG); + dhcp_state = INIT_SELECTING; + requested_ip = 0; + timeout = time(0); + packet_num = 0; + change_listen_mode(LISTEN_RAW); +} + +static void arp_success(void) +{ + struct in_addr temp_addr; + + epoll_del(arpFd); + arpFd = -1; + + /* enter bound state */ + t1 = lease >> 1; + + /* little fixed point for n * .875 */ + t2 = (lease * 0x7) >> 3; + temp_addr.s_addr = arp_dhcp_packet.yiaddr; + log_line("Lease of %s obtained, lease time %ld.", + inet_ntoa(temp_addr), lease); + start = time(0); + timeout = t1 + start; + requested_ip = arp_dhcp_packet.yiaddr; + run_script(&arp_dhcp_packet, + ((arp_prev_dhcp_state == RENEWING || + arp_prev_dhcp_state == REBINDING) + ? SCRIPT_RENEW : SCRIPT_BOUND)); + + dhcp_state = BOUND; + change_listen_mode(LISTEN_NONE); + if (client_config.quit_after_lease) + exit(EXIT_SUCCESS); + if (!client_config.foreground) + background(); +} + /* Handle select timeout dropping to zero */ static void handle_timeout(void) { time_t now = time(0); - switch (state) { + switch (dhcp_state) { case INIT_SELECTING: if (packet_num < NUMPACKETS) { if (packet_num == 0) @@ -261,7 +325,7 @@ static void handle_timeout(void) case REQUESTING: if (packet_num < NUMPACKETS) { /* send request packet */ - if (state == RENEW_REQUESTED) + if (dhcp_state == RENEW_REQUESTED) /* unicast */ send_renew(xid, server_addr, requested_ip); else @@ -271,9 +335,9 @@ static void handle_timeout(void) packet_num++; } else { /* timed out, go back to init state */ - if (state == RENEW_REQUESTED) + if (dhcp_state == RENEW_REQUESTED) run_script(NULL, SCRIPT_DECONFIG); - state = INIT_SELECTING; + dhcp_state = INIT_SELECTING; timeout = now; packet_num = 0; change_listen_mode(LISTEN_RAW); @@ -281,7 +345,7 @@ static void handle_timeout(void) break; case BOUND: /* Lease is starting to run out, time to enter renewing state */ - state = RENEWING; + dhcp_state = RENEWING; change_listen_mode(LISTEN_KERNEL); log_line("Entering renew state."); /* fall right through */ @@ -289,7 +353,7 @@ static void handle_timeout(void) /* Either set a new T1, or enter REBINDING state */ if ((t2 - t1) <= (lease / 14400 + 1)) { /* timed out, enter rebinding state */ - state = REBINDING; + dhcp_state = REBINDING; timeout = now + (t2 - t1); log_line("Entering rebinding state."); } else { @@ -304,7 +368,7 @@ static void handle_timeout(void) /* Either set a new T2, or enter INIT state */ if ((lease - t2) <= (lease / 14400 + 1)) { /* timed out, enter init state */ - state = INIT_SELECTING; + dhcp_state = INIT_SELECTING; log_line("Lease lost, entering init state."); run_script(NULL, SCRIPT_DECONFIG); timeout = now; @@ -322,6 +386,45 @@ static void handle_timeout(void) /* yah, I know, *you* say it would never happen */ timeout = 0x7fffffff; break; + case ARP_CHECK: + arp_failed(); + break; + } +} + +typedef uint32_t aliased_uint32_t __attribute__((__may_alias__)); +static void handle_arp_response(void) +{ + if (arpreply_offset < sizeof arpreply) { + int r = safe_read(arpFd, (char *)&arpreply + arpreply_offset, + sizeof arpreply - arpreply_offset); + if (r < 0) { + arp_failed(); + return; + } else + arpreply_offset += r; + } + + //log3("sHaddr %02x:%02x:%02x:%02x:%02x:%02x", + //arp.sHaddr[0], arp.sHaddr[1], arp.sHaddr[2], + //arp.sHaddr[3], arp.sHaddr[4], arp.sHaddr[5]); + + if (arpreply_offset >= ARP_MSG_SIZE) { + if (arpreply.operation == htons(ARPOP_REPLY) + /* don't check: Linux returns invalid tHaddr (fixed in 2.6.24?) */ + /* && memcmp(arp.tHaddr, from_mac, 6) == 0 */ + && *(aliased_uint32_t*)arpreply.sInaddr == arp_dhcp_packet.yiaddr) + { + /* if ARP source MAC matches safe_mac + * (which is client's MAC), then it's not a conflict + * (client simply already has this IP and replies to ARPs!) + */ + /* if (memcmp(safe_mac, arp.sHaddr, 6) == 0) */ + arp_success(); + } else { + memset(&arpreply, 0, sizeof arpreply); + arpreply_offset = 0; + } } } @@ -329,7 +432,6 @@ static void handle_packet(void) { unsigned char *temp = NULL, *message = NULL; int len; - struct in_addr temp_addr; struct dhcpMessage packet; if (listen_mode == LISTEN_KERNEL) @@ -359,7 +461,7 @@ static void handle_packet(void) } time_t now = time(0); - switch (state) { + switch (dhcp_state) { case INIT_SELECTING: /* Must be a DHCPOFFER to one of our xid's */ if (*message == DHCPOFFER) { @@ -370,7 +472,7 @@ static void handle_packet(void) requested_ip = packet.yiaddr; /* enter requesting state */ - state = REQUESTING; + dhcp_state = REQUESTING; timeout = now; packet_num = 0; } else { @@ -378,6 +480,11 @@ static void handle_packet(void) } } break; + case ARP_CHECK: + /* We ignore dhcp packets for now. This state will + * be changed by the callback for arp ping. + */ + break; case RENEW_REQUESTED: case REQUESTING: case RENEWING: @@ -396,54 +503,29 @@ static void handle_packet(void) lease = RETRY_DELAY; } - if (!arpping(packet.yiaddr, NULL, 0, client_config.arp, - client_config.interface)) { - log_line("Offered address is in use: declining."); - send_decline(xid, server_addr, packet.yiaddr); - - if (state != REQUESTING) - run_script(NULL, SCRIPT_DECONFIG); - state = INIT_SELECTING; - requested_ip = 0; - timeout = now; - packet_num = 0; - change_listen_mode(LISTEN_RAW); - break; - } - - /* enter bound state */ - t1 = lease >> 1; - - /* little fixed point for n * .875 */ - t2 = (lease * 0x7) >> 3; - temp_addr.s_addr = packet.yiaddr; - log_line("Lease of %s obtained, lease time %ld.", - inet_ntoa(temp_addr), lease); - start = now; - timeout = t1 + start; - requested_ip = packet.yiaddr; - run_script(&packet, - ((state == RENEWING || state == REBINDING) - ? SCRIPT_RENEW : SCRIPT_BOUND)); - - state = BOUND; - change_listen_mode(LISTEN_NONE); - if (client_config.quit_after_lease) - exit(EXIT_SUCCESS); - if (!client_config.foreground) - background(); + arp_prev_dhcp_state = dhcp_state; + dhcp_state = ARP_CHECK; + memcpy(&arp_dhcp_packet, &packet, sizeof packet); + arpFd = arpping(arp_dhcp_packet.yiaddr, NULL, 0, + client_config.arp, client_config.interface); + epoll_add(arpFd); + timeout = now + 2; + memset(&arpreply, 0, sizeof arpreply); + arpreply_offset = 0; + // Can transition to BOUND or INIT_SELECTING. } else if (*message == DHCPNAK) { /* return to init state */ log_line("Received DHCP NAK."); run_script(&packet, SCRIPT_NAK); - if (state != REQUESTING) + if (dhcp_state != REQUESTING) run_script(NULL, SCRIPT_DECONFIG); - state = INIT_SELECTING; + dhcp_state = INIT_SELECTING; timeout = now; requested_ip = 0; packet_num = 0; change_listen_mode(LISTEN_RAW); + // XXX: this isn't rfc compliant: should be exp backoff sleep(3); /* avoid excessive network traffic */ } break; @@ -528,6 +610,8 @@ static void do_work(void) signal_dispatch(); else if (fd == listenFd) handle_packet(); + else if (fd == arpFd) + handle_arp_response(); else suicide("epoll_wait: unknown fd"); } @@ -669,7 +753,7 @@ int main(int argc, char **argv) "cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep"); drop_root(uid, gid); - state = INIT_SELECTING; + dhcp_state = INIT_SELECTING; run_script(NULL, SCRIPT_DECONFIG); do_work();