arpping() no longer blocks and address verification via arp is now performed

asynchronously in the main event loop.
This commit is contained in:
Nicholas J. Kain 2010-12-01 23:33:25 -05:00
parent a154f96538
commit e37ed0e16b
4 changed files with 181 additions and 158 deletions

View File

@ -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 * Mostly stolen from: dhcpcd - DHCP client daemon
* by Yoichi Hariguchi <yoichi@fore.com> * by Yoichi Hariguchi <yoichi@fore.com>
* Licensed under GPLv2, see file LICENSE in this source tree. * Licensed under GPLv2, see file LICENSE in this source tree.
@ -9,59 +9,14 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/if_ether.h> #include <netinet/if_ether.h>
#include <net/if_arp.h>
#include <sys/time.h> #include <sys/time.h>
#include <errno.h> #include <errno.h>
#include <poll.h> #include "arpping.h"
#include "dhcpd.h" #include "dhcpd.h"
#include "log.h" #include "log.h"
#include "strl.h" #include "strl.h"
#include "io.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() static unsigned long long curms()
{ {
struct timeval tv; struct timeval tv;
@ -69,27 +24,26 @@ static unsigned long long curms()
return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; 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, int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip,
uint8_t *from_mac, const char *interface) uint8_t *from_mac, const char *interface)
{ {
int timeout_ms; int arpfd;
struct pollfd pfd[1];
int rv = 1; /* "no reply received" yet */
int opt = 1; int opt = 1;
struct sockaddr addr; /* for interface name */ struct sockaddr addr; /* for interface name */
struct arpMsg arp; struct arpMsg arp;
pfd[0].fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); arpfd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP));
if (pfd[0].fd == -1) { if (arpfd == -1) {
log_warning("arpping: failed to create socket: %s", strerror(errno)); log_warning("arpping: failed to create socket: %s", strerror(errno));
return -1; return -1;
} }
if (setsockopt(pfd[0].fd, SOL_SOCKET, SO_BROADCAST, if (setsockopt(arpfd, SOL_SOCKET, SO_BROADCAST,
&opt, sizeof opt) == -1) { &opt, sizeof opt) == -1) {
log_warning("arpping: failed to set broadcast: %s", strerror(errno)); log_warning("arpping: failed to set broadcast: %s", strerror(errno));
goto ret; close(arpfd);
return -1;
} }
/* send arp request */ /* send arp request */
@ -109,52 +63,11 @@ int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip,
memset(&addr, 0, sizeof addr); memset(&addr, 0, sizeof addr);
strlcpy(addr.sa_data, interface, sizeof addr.sa_data); 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) { 0, &addr, sizeof addr) < 0) {
log_error("arpping: sendto failed: %s", strerror(errno)); log_error("arpping: sendto failed: %s", strerror(errno));
goto ret; close(arpfd);
return -1;
} }
return arpfd;
/* 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;
} }

View File

@ -1,8 +1,33 @@
#ifndef ARPPING_H_ #ifndef ARPPING_H_
#define ARPPING_H_ #define ARPPING_H_
#include <stdint.h>
#include <net/if_arp.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
};
int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip, int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip,
uint8_t *from_mac, const char *interface); uint8_t *from_mac, const char *interface);
#endif /* ARPPING_H_ */ #endif /* ARPPING_H_ */

View File

@ -7,6 +7,7 @@ enum {
BOUND, BOUND,
RENEWING, RENEWING,
REBINDING, REBINDING,
ARP_CHECK,
INIT_REBOOT, INIT_REBOOT,
RENEW_REQUESTED, RENEW_REQUESTED,
RELEASED RELEASED

View File

@ -56,19 +56,20 @@
#include "strl.h" #include "strl.h"
#include "pidfile.h" #include "pidfile.h"
#include "malloc.h" #include "malloc.h"
#include "io.h"
#define VERSION "1.0" #define VERSION "1.0"
#define NUMPACKETS 3 /* number of packets to send before delay */ #define NUMPACKETS 3 /* number of packets to send before delay */
#define RETRY_DELAY 30 /* time in seconds to delay after sending NUMPACKETS */ #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 struct epoll_event events[3];
static char pidfile[MAX_PATH_LENGTH] = PID_FILE_DEFAULT; static char pidfile[MAX_PATH_LENGTH] = PID_FILE_DEFAULT;
static uint32_t requested_ip, server_addr, timeout; static uint32_t requested_ip, server_addr, timeout;
static uint32_t lease, t1, t2, xid, start; 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 { enum {
LISTEN_NONE, LISTEN_NONE,
@ -167,19 +168,26 @@ static void change_listen_mode(int new_mode)
static void perform_renew(void) static void perform_renew(void)
{ {
log_line("Performing a DHCP renew..."); log_line("Performing a DHCP renew...");
switch (state) { retry:
switch (dhcp_state) {
case BOUND: case BOUND:
change_listen_mode(LISTEN_KERNEL); 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 RENEWING:
case REBINDING: case REBINDING:
state = RENEW_REQUESTED; dhcp_state = RENEW_REQUESTED;
break; break;
case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ case RENEW_REQUESTED: /* impatient are we? fine, square 1 */
run_script(NULL, SCRIPT_DECONFIG); run_script(NULL, SCRIPT_DECONFIG);
case REQUESTING: case REQUESTING:
case RELEASED: case RELEASED:
change_listen_mode(LISTEN_RAW); change_listen_mode(LISTEN_RAW);
state = INIT_SELECTING; dhcp_state = INIT_SELECTING;
break; break;
case INIT_SELECTING: case INIT_SELECTING:
break; break;
@ -199,7 +207,8 @@ static void perform_release(void)
struct in_addr temp_saddr, temp_raddr; struct in_addr temp_saddr, temp_raddr;
/* send release packet */ /* 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_saddr.s_addr = server_addr;
temp_raddr.s_addr = requested_ip; temp_raddr.s_addr = requested_ip;
log_line("Unicasting a release of %s to %s.", log_line("Unicasting a release of %s to %s.",
@ -209,8 +218,12 @@ static void perform_release(void)
} }
log_line("Entering released state."); log_line("Entering released state.");
if (dhcp_state == ARP_CHECK) {
epoll_del(arpFd);
arpFd = -1;
}
change_listen_mode(LISTEN_NONE); change_listen_mode(LISTEN_NONE);
state = RELEASED; dhcp_state = RELEASED;
timeout = 0x7fffffff; timeout = 0x7fffffff;
} }
@ -229,12 +242,63 @@ static void background(void)
write_pid(pidfile); 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 */ /* Handle select timeout dropping to zero */
static void handle_timeout(void) static void handle_timeout(void)
{ {
time_t now = time(0); time_t now = time(0);
switch (state) { switch (dhcp_state) {
case INIT_SELECTING: case INIT_SELECTING:
if (packet_num < NUMPACKETS) { if (packet_num < NUMPACKETS) {
if (packet_num == 0) if (packet_num == 0)
@ -261,7 +325,7 @@ static void handle_timeout(void)
case REQUESTING: case REQUESTING:
if (packet_num < NUMPACKETS) { if (packet_num < NUMPACKETS) {
/* send request packet */ /* send request packet */
if (state == RENEW_REQUESTED) if (dhcp_state == RENEW_REQUESTED)
/* unicast */ /* unicast */
send_renew(xid, server_addr, requested_ip); send_renew(xid, server_addr, requested_ip);
else else
@ -271,9 +335,9 @@ static void handle_timeout(void)
packet_num++; packet_num++;
} else { } else {
/* timed out, go back to init state */ /* timed out, go back to init state */
if (state == RENEW_REQUESTED) if (dhcp_state == RENEW_REQUESTED)
run_script(NULL, SCRIPT_DECONFIG); run_script(NULL, SCRIPT_DECONFIG);
state = INIT_SELECTING; dhcp_state = INIT_SELECTING;
timeout = now; timeout = now;
packet_num = 0; packet_num = 0;
change_listen_mode(LISTEN_RAW); change_listen_mode(LISTEN_RAW);
@ -281,7 +345,7 @@ static void handle_timeout(void)
break; break;
case BOUND: case BOUND:
/* Lease is starting to run out, time to enter renewing state */ /* Lease is starting to run out, time to enter renewing state */
state = RENEWING; dhcp_state = RENEWING;
change_listen_mode(LISTEN_KERNEL); change_listen_mode(LISTEN_KERNEL);
log_line("Entering renew state."); log_line("Entering renew state.");
/* fall right through */ /* fall right through */
@ -289,7 +353,7 @@ static void handle_timeout(void)
/* Either set a new T1, or enter REBINDING state */ /* Either set a new T1, or enter REBINDING state */
if ((t2 - t1) <= (lease / 14400 + 1)) { if ((t2 - t1) <= (lease / 14400 + 1)) {
/* timed out, enter rebinding state */ /* timed out, enter rebinding state */
state = REBINDING; dhcp_state = REBINDING;
timeout = now + (t2 - t1); timeout = now + (t2 - t1);
log_line("Entering rebinding state."); log_line("Entering rebinding state.");
} else { } else {
@ -304,7 +368,7 @@ static void handle_timeout(void)
/* Either set a new T2, or enter INIT state */ /* Either set a new T2, or enter INIT state */
if ((lease - t2) <= (lease / 14400 + 1)) { if ((lease - t2) <= (lease / 14400 + 1)) {
/* timed out, enter init state */ /* timed out, enter init state */
state = INIT_SELECTING; dhcp_state = INIT_SELECTING;
log_line("Lease lost, entering init state."); log_line("Lease lost, entering init state.");
run_script(NULL, SCRIPT_DECONFIG); run_script(NULL, SCRIPT_DECONFIG);
timeout = now; timeout = now;
@ -322,6 +386,45 @@ static void handle_timeout(void)
/* yah, I know, *you* say it would never happen */ /* yah, I know, *you* say it would never happen */
timeout = 0x7fffffff; timeout = 0x7fffffff;
break; 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; unsigned char *temp = NULL, *message = NULL;
int len; int len;
struct in_addr temp_addr;
struct dhcpMessage packet; struct dhcpMessage packet;
if (listen_mode == LISTEN_KERNEL) if (listen_mode == LISTEN_KERNEL)
@ -359,7 +461,7 @@ static void handle_packet(void)
} }
time_t now = time(0); time_t now = time(0);
switch (state) { switch (dhcp_state) {
case INIT_SELECTING: case INIT_SELECTING:
/* Must be a DHCPOFFER to one of our xid's */ /* Must be a DHCPOFFER to one of our xid's */
if (*message == DHCPOFFER) { if (*message == DHCPOFFER) {
@ -370,7 +472,7 @@ static void handle_packet(void)
requested_ip = packet.yiaddr; requested_ip = packet.yiaddr;
/* enter requesting state */ /* enter requesting state */
state = REQUESTING; dhcp_state = REQUESTING;
timeout = now; timeout = now;
packet_num = 0; packet_num = 0;
} else { } else {
@ -378,6 +480,11 @@ static void handle_packet(void)
} }
} }
break; 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 RENEW_REQUESTED:
case REQUESTING: case REQUESTING:
case RENEWING: case RENEWING:
@ -396,54 +503,29 @@ static void handle_packet(void)
lease = RETRY_DELAY; lease = RETRY_DELAY;
} }
if (!arpping(packet.yiaddr, NULL, 0, client_config.arp, arp_prev_dhcp_state = dhcp_state;
client_config.interface)) { dhcp_state = ARP_CHECK;
log_line("Offered address is in use: declining."); memcpy(&arp_dhcp_packet, &packet, sizeof packet);
send_decline(xid, server_addr, packet.yiaddr); arpFd = arpping(arp_dhcp_packet.yiaddr, NULL, 0,
client_config.arp, client_config.interface);
if (state != REQUESTING) epoll_add(arpFd);
run_script(NULL, SCRIPT_DECONFIG); timeout = now + 2;
state = INIT_SELECTING; memset(&arpreply, 0, sizeof arpreply);
requested_ip = 0; arpreply_offset = 0;
timeout = now; // Can transition to BOUND or INIT_SELECTING.
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();
} else if (*message == DHCPNAK) { } else if (*message == DHCPNAK) {
/* return to init state */ /* return to init state */
log_line("Received DHCP NAK."); log_line("Received DHCP NAK.");
run_script(&packet, SCRIPT_NAK); run_script(&packet, SCRIPT_NAK);
if (state != REQUESTING) if (dhcp_state != REQUESTING)
run_script(NULL, SCRIPT_DECONFIG); run_script(NULL, SCRIPT_DECONFIG);
state = INIT_SELECTING; dhcp_state = INIT_SELECTING;
timeout = now; timeout = now;
requested_ip = 0; requested_ip = 0;
packet_num = 0; packet_num = 0;
change_listen_mode(LISTEN_RAW); change_listen_mode(LISTEN_RAW);
// XXX: this isn't rfc compliant: should be exp backoff
sleep(3); /* avoid excessive network traffic */ sleep(3); /* avoid excessive network traffic */
} }
break; break;
@ -528,6 +610,8 @@ static void do_work(void)
signal_dispatch(); signal_dispatch();
else if (fd == listenFd) else if (fd == listenFd)
handle_packet(); handle_packet();
else if (fd == arpFd)
handle_arp_response();
else else
suicide("epoll_wait: unknown fd"); 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"); "cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep");
drop_root(uid, gid); drop_root(uid, gid);
state = INIT_SELECTING; dhcp_state = INIT_SELECTING;
run_script(NULL, SCRIPT_DECONFIG); run_script(NULL, SCRIPT_DECONFIG);
do_work(); do_work();