Fetch the gateway hardware address after receiving a router option in a DHCP

lease.

Be more aggressive about closing old arp file descriptors.

Check the ARP headers to make sure that received ARP packets are addressed to
our machine.  Whatever bug may have existed before doesn't exist on modern
Linux kernels, if it ever did.

Use the stored gateway hardware address to validate a restored link in the
DS_ARP_GW_CHECK state.

If an ARP message is received while we are in a state that does not expect
ARP messages, close the ARP socket and log a message.
This commit is contained in:
Nicholas J. Kain 2011-03-30 23:17:27 -04:00
parent e1eb41e1c0
commit b5e5685d01
5 changed files with 90 additions and 29 deletions

View File

@ -1,8 +1,13 @@
/*
* Derived from busybox's udhcpc variant, which in turn was...
/* arp.c - arp ping checking
* Copyright 2010-2011 Nicholas J. Kain <njkain@gmail.com>
*
* Originally derived from busybox's udhcpc variant, which in turn was...
* Mostly stolen from: dhcpcd - DHCP client daemon
* by Yoichi Hariguchi <yoichi@fore.com>
* Licensed under GPLv2, see file LICENSE in this source tree.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation
*/
#include <unistd.h>
#include <stdlib.h>
@ -29,7 +34,7 @@ static int arpreply_offset;
static struct dhcpMessage arp_dhcp_packet;
/* Returns fd of the arp socket, or -1 on failure. */
static int arpping(uint32_t test_nip, const uint8_t *safe_mac,
static int arpping(struct client_state_t *cs, uint32_t test_ip,
uint32_t from_ip, uint8_t *from_mac, const char *interface)
{
int arpfd;
@ -37,6 +42,10 @@ static int arpping(uint32_t test_nip, const uint8_t *safe_mac,
struct sockaddr addr; /* for interface name */
struct arpMsg arp;
if (cs->arpFd != -1) {
epoll_del(cs, cs->arpFd);
close(cs->arpFd);
}
arpfd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP));
if (arpfd == -1) {
log_warning("arpping: failed to create socket: %s", strerror(errno));
@ -65,7 +74,7 @@ static int arpping(uint32_t test_nip, const uint8_t *safe_mac,
memcpy(arp.sHaddr, from_mac, 6); /* source hardware 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 */
memcpy(arp.tInaddr, &test_ip, sizeof test_ip); /* target IP address */
memset(&addr, 0, sizeof addr);
strlcpy(addr.sa_data, interface, sizeof addr.sa_data);
@ -83,8 +92,8 @@ void arp_check(struct client_state_t *cs, struct dhcpMessage *packet)
cs->arpPrevState = cs->dhcpState;
cs->dhcpState = DS_ARP_CHECK;
memcpy(&arp_dhcp_packet, packet, sizeof (struct dhcpMessage));
cs->arpFd = arpping(arp_dhcp_packet.yiaddr, NULL, 0,
client_config.arp, client_config.interface);
cs->arpFd = arpping(cs, arp_dhcp_packet.yiaddr, 0, client_config.arp,
client_config.interface);
epoll_add(cs, cs->arpFd);
cs->timeout = 2000;
memset(&arpreply, 0, sizeof arpreply);
@ -96,8 +105,8 @@ void arp_gw_check(struct client_state_t *cs)
cs->arpPrevState = cs->dhcpState;
cs->dhcpState = DS_ARP_GW_CHECK;
memset(&arp_dhcp_packet, 0, sizeof (struct dhcpMessage));
cs->arpFd = arpping(cs->routerAddr, NULL, 0,
client_config.arp, client_config.interface);
cs->arpFd = arpping(cs, cs->routerAddr, 0, client_config.arp,
client_config.interface);
epoll_add(cs, cs->arpFd);
cs->oldTimeout = cs->timeout;
cs->timeout = 2000;
@ -105,10 +114,23 @@ void arp_gw_check(struct client_state_t *cs)
arpreply_offset = 0;
}
void arp_get_gw_hwaddr(struct client_state_t *cs)
{
if (cs->dhcpState != DS_BOUND)
log_warning("arp_get_gw_hwaddr: called when state != DS_BOUND");
memset(&arp_dhcp_packet, 0, sizeof (struct dhcpMessage));
cs->arpFd = arpping(cs, cs->routerAddr, 0, client_config.arp,
client_config.interface);
epoll_add(cs, cs->arpFd);
memset(&arpreply, 0, sizeof arpreply);
arpreply_offset = 0;
}
static void arp_failed(struct client_state_t *cs)
{
log_line("Offered address is in use: declining.");
epoll_del(cs, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
send_decline(cs->xid, cs->serverAddr, arp_dhcp_packet.yiaddr);
@ -125,6 +147,7 @@ void arp_gw_failed(struct client_state_t *cs)
{
log_line("arp: gateway appears to have changed, getting new lease");
epoll_del(cs, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
// Same as packet.c: line 258
@ -142,6 +165,7 @@ void arp_success(struct client_state_t *cs)
struct in_addr temp_addr;
epoll_del(cs, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
/* enter bound state */
@ -155,12 +179,11 @@ void arp_success(struct client_state_t *cs)
log_line("Lease of %s obtained, lease time %ld.",
inet_ntoa(temp_addr), cs->lease);
cs->requestedIP = arp_dhcp_packet.yiaddr;
cs->dhcpState = DS_BOUND;
ifchange(&arp_dhcp_packet,
((cs->arpPrevState == DS_RENEWING ||
cs->arpPrevState == DS_REBINDING)
? IFCHANGE_RENEW : IFCHANGE_BOUND));
cs->dhcpState = DS_BOUND;
change_listen_mode(cs, LM_NONE);
if (client_config.quit_after_lease)
exit(EXIT_SUCCESS);
@ -172,6 +195,7 @@ void arp_gw_success(struct client_state_t *cs)
{
log_line("arp: gateway seems unchanged");
epoll_del(cs, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
cs->timeout = cs->oldTimeout;
cs->dhcpState = cs->arpPrevState;
@ -194,15 +218,14 @@ void handle_arp_response(struct client_state_t *cs)
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 (cs->dhcpState == DS_ARP_CHECK) {
if (arpreply_offset < ARP_MSG_SIZE) {
log_warning("handle_arp_response: Received short ARP message.");
return;
}
switch (cs->dhcpState) {
case DS_ARP_CHECK:
if (arpreply.operation == htons(ARPOP_REPLY)
// don't check: Linux returns invalid tHaddr (fixed in 2.6.24?)
// && !memcmp(arpreply.tHaddr, from_mac, 6)
&& !memcmp(arpreply.tHaddr, client_config.arp, 6)
&& *(aliased_uint32_t*)arpreply.sInaddr
== arp_dhcp_packet.yiaddr)
{
@ -215,20 +238,47 @@ void handle_arp_response(struct client_state_t *cs)
memset(&arpreply, 0, sizeof arpreply);
arpreply_offset = 0;
}
return;
} else {
break;
case DS_ARP_GW_CHECK:
if (arpreply.operation == htons(ARPOP_REPLY)
// don't check: Linux returns invalid tHaddr (fixed in 2.6.24?)
// && !memcmp(arpreply.tHaddr, from_mac, 6)
&& !memcmp(arpreply.tHaddr, client_config.arp, 6)
&& *(aliased_uint32_t*)arpreply.sInaddr == cs->routerAddr)
{
// XXX: would be nice to check arp gateway mac, too
arp_gw_success(cs);
// Success only if the router/gw MAC matches stored value
if (!memcmp(cs->routerArp, arpreply.sHaddr, 6))
arp_gw_success(cs);
else
arp_gw_failed(cs);
} else {
memset(&arpreply, 0, sizeof arpreply);
arpreply_offset = 0;
}
return;
}
break;
case DS_BOUND:
if (arpreply.operation == htons(ARPOP_REPLY)
&& !memcmp(arpreply.tHaddr, client_config.arp, 6)
&& *(aliased_uint32_t*)arpreply.sInaddr == cs->routerAddr)
{
memcpy(cs->routerArp, arpreply.sHaddr, 6);
epoll_del(cs, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
log_line("gateway hardware address %02x:%02x:%02x:%02x:%02x:%02x",
cs->routerArp[0], cs->routerArp[1],
cs->routerArp[2], cs->routerArp[3],
cs->routerArp[4], cs->routerArp[5]);
} else {
log_line("still looking for gateway hardware address");
memset(&arpreply, 0, sizeof arpreply);
arpreply_offset = 0;
}
break;
default:
epoll_del(cs, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
log_warning("handle_arp_response: called in invalid state 0x%02x",
cs->dhcpState);
break;
}
}

View File

@ -35,5 +35,6 @@ void arp_success(struct client_state_t *cs);
void handle_arp_response(struct client_state_t *cs);
void arp_gw_check(struct client_state_t *cs);
void arp_gw_failed(struct client_state_t *cs);
void arp_get_gw_hwaddr(struct client_state_t *cs);
#endif /* ARPPING_H_ */

View File

@ -44,6 +44,7 @@ struct client_state_t {
int timeout, oldTimeout;
uint32_t requestedIP, serverAddr, routerAddr;
uint32_t lease, t1, t2, xid;
unsigned char routerArp[6];
};
struct client_config_t {

View File

@ -1,5 +1,5 @@
/* ifchange.c - functions to call the interface change daemon
* Time-stamp: <2011-03-30 20:21:30 nk>
* Time-stamp: <2011-03-30 22:54:28 nk>
*
* (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
*
@ -34,12 +34,14 @@
#include "config.h"
#include "packet.h"
#include "options.h"
#include "arp.h"
#include "log.h"
#include "io.h"
#include "ifchange.h"
// For access to routerAddr and nothing else.
extern struct client_state_t cs;
static char router_set;
/* Fill buf with the ifchd command text of option 'option'. */
/* Returns 0 if successful, -1 if nothing was filled in. */
@ -88,6 +90,7 @@ static int ifchd_cmd(char *buf, size_t buflen, uint8_t *option, ssize_t optlen,
// for verifying gateway existence by ARP when link returns.
if (code == DHCP_ROUTER) {
memcpy(&cs.routerAddr, option, 4);
router_set = 1;
}
if (inet_ntop(AF_INET, option, buf, buflen - (buf - obuf) - 1))
buf += strlen(buf);
@ -212,6 +215,7 @@ static void bound_if(struct dhcpMessage *packet)
snprintf(buf, sizeof buf, "ip:%s:", ip);
sockwrite(sockfd, buf, strlen(buf));
router_set = 0;
send_cmd(sockfd, packet, DHCP_SUBNET);
send_cmd(sockfd, packet, DHCP_ROUTER);
send_cmd(sockfd, packet, DHCP_DNS_SERVER);
@ -222,6 +226,8 @@ static void bound_if(struct dhcpMessage *packet)
send_cmd(sockfd, packet, DHCP_WINS_SERVER);
close(sockfd);
if (router_set == 1)
arp_get_gw_hwaddr(&cs);
}
void ifchange(struct dhcpMessage *packet, int mode)

View File

@ -83,6 +83,7 @@ struct client_state_t cs = {
.listenFd = -1,
.arpFd = -1,
.nlFd = -1,
.routerArp = "\0\0\0\0\0\0",
};
struct client_config_t client_config = {
@ -95,7 +96,7 @@ struct client_config_t client_config = {
.clientid = NULL,
.hostname = NULL,
.ifindex = 0,
.arp = "\0",
.arp = "\0\0\0\0\0\0",
};
static void show_usage(void)
@ -132,6 +133,7 @@ static void perform_renew(void)
case DS_ARP_CHECK:
// Cancel arp ping in progress and treat as previous state.
epoll_del(&cs, cs.arpFd);
close(cs.arpFd);
cs.arpFd = -1;
cs.dhcpState = cs.arpPrevState;
goto retry;
@ -178,6 +180,7 @@ static void perform_release(void)
if (cs.dhcpState == DS_ARP_CHECK) {
epoll_del(&cs, cs.arpFd);
close(cs.arpFd);
cs.arpFd = -1;
}
change_listen_mode(&cs, LM_NONE);