Use a coroutine instead of several callback state machines.

This change makes it much easier to reason about ndhc's behavior
and properly handle errors.

It is a very large changeset, but there is no way to make this
sort of change incrementally.  Lease acquisition is tested to
work.

It is highly likely that some bugs were both introduced and
squashed here.  Some obvious code cleanups will quickly follow.
This commit is contained in:
Nicholas J. Kain 2015-02-18 05:31:13 -05:00
parent 37aa866ae4
commit 99ce918a31
7 changed files with 748 additions and 638 deletions

447
src/arp.c
View File

@ -62,8 +62,7 @@ int arp_probe_max = 2000; // maximum delay until repeated probe (ms)
#define RATE_LIMIT_INTERVAL 60000 // delay between successive attempts
#define DEFEND_INTERVAL 10000 // minimum interval between defensive ARPs
struct arp_data garp = {
.state = AS_NONE,
static struct arp_data garp = {
.wake_ts = { -1, -1, -1, -1, -1 },
.send_stats = {{0,0},{0,0},{0,0}},
.last_conflict_ts = 0,
@ -80,7 +79,7 @@ struct arp_data garp = {
void set_arp_relentless_def(bool v) { garp.relentless_def = v; }
static void arp_reply_clear(void)
void arp_reply_clear(void)
{
memset(&garp.reply, 0, sizeof garp.reply);
garp.reply_offset = 0;
@ -94,7 +93,7 @@ void arp_reset_send_stats(void)
}
}
static int get_arp_basic_socket(void)
static int get_arp_basic_socket(struct client_state_t cs[static 1])
{
char resp;
int fd = request_sockd_fd("a", 1, &resp);
@ -104,6 +103,7 @@ static int get_arp_basic_socket(void)
default: suicide("%s: (%s) expected a or A sockd reply but got %c",
client_config.interface, __func__, resp);
}
cs->arp_is_defense = false;
return fd;
}
@ -125,36 +125,10 @@ static int get_arp_defense_socket(struct client_state_t cs[static 1])
default: suicide("%s: (%s) expected d or D sockd reply but got %c",
client_config.interface, __func__, resp);
}
cs->arp_is_defense = true;
return fd;
}
static int arp_open_fd(struct client_state_t cs[static 1], arp_state_t state)
{
if (cs->arpFd >= 0) {
log_warning("%s: (%s) called but fd already exists",
client_config.interface, __func__);
return 0;
}
switch (state) {
default:
log_warning("%s: (%s) called for 'default' state",
client_config.interface, __func__);
return 0;
case AS_COLLISION_CHECK:
case AS_GW_QUERY:
case AS_GW_CHECK: cs->arpFd = get_arp_basic_socket(); break;
case AS_DEFENSE: cs->arpFd = get_arp_defense_socket(cs); break;
}
if (cs->arpFd < 0) {
log_error("%s: (%s) Failed to create socket: %s",
client_config.interface, __func__, strerror(errno));
return -1;
}
epoll_add(cs->epollFd, cs->arpFd);
arp_reply_clear();
return 0;
}
static void arp_min_close_fd(struct client_state_t cs[static 1])
{
if (cs->arpFd < 0)
@ -162,27 +136,7 @@ static void arp_min_close_fd(struct client_state_t cs[static 1])
epoll_del(cs->epollFd, cs->arpFd);
close(cs->arpFd);
cs->arpFd = -1;
garp.state = AS_NONE;
}
static void arp_switch_state(struct client_state_t cs[static 1],
arp_state_t state)
{
if (garp.state == state || garp.state >= AS_MAX)
return;
if (state == AS_NONE) {
arp_close_fd(cs);
return;
}
bool force_reopen = state == AS_DEFENSE || garp.state == AS_DEFENSE;
if (force_reopen)
arp_min_close_fd(cs);
if (cs->arpFd < 0) {
if (arp_open_fd(cs, state) < 0)
suicide("%s: (%s) Failed to open arpFd when changing state %u -> %u",
client_config.interface, __func__, garp.state, state);
}
garp.state = state;
cs->arp_is_defense = false;
}
void arp_close_fd(struct client_state_t cs[static 1])
@ -192,11 +146,21 @@ void arp_close_fd(struct client_state_t cs[static 1])
garp.wake_ts[i] = -1;
}
void arp_reopen_fd(struct client_state_t cs[static 1])
static int arp_open_fd(struct client_state_t cs[static 1], bool defense)
{
arp_state_t prev_state = garp.state;
if (cs->arpFd >= 0 && defense == cs->arp_is_defense)
return 0;
arp_min_close_fd(cs);
arp_switch_state(cs, prev_state);
cs->arpFd = defense ? get_arp_defense_socket(cs)
: get_arp_basic_socket(cs);
if (cs->arpFd < 0) {
log_error("%s: (%s) Failed to create socket: %s",
client_config.interface, __func__, strerror(errno));
return -1;
}
epoll_add(cs->epollFd, cs->arpFd);
arp_reply_clear();
return 0;
}
static int arp_send(struct client_state_t cs[static 1],
@ -232,7 +196,6 @@ static int arp_send(struct client_state_t cs[static 1],
log_error("%s: (%s) sendto short write: %d < %zu",
client_config.interface, __func__, ret, sizeof *arp);
carrier_down:
arp_reopen_fd(cs);
return ret;
}
return 0;
@ -294,16 +257,15 @@ static int arp_announcement(struct client_state_t cs[static 1])
}
#undef BASE_ARPMSG
// Callable from DS_REQUESTING, DS_RENEWING, or DS_REBINDING via an_packet()
// Checks to see if there is another host that has our assigned IP.
int arp_check(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1])
{
memcpy(&garp.dhcp_packet, packet, sizeof (struct dhcpmsg));
arp_switch_state(cs, AS_COLLISION_CHECK);
if (arp_open_fd(cs, false) < 0)
return -1;
if (arp_ip_anon_ping(cs, garp.dhcp_packet.yiaddr) < 0)
return -1;
cs->arpPrevState = cs->dhcpState;
cs->dhcpState = DS_COLLISION_CHECK;
garp.arp_check_start_ts = garp.send_stats[ASEND_COLLISION_CHECK].ts;
garp.probe_wait_time = arp_probe_wait;
garp.wake_ts[AS_COLLISION_CHECK] = garp.arp_check_start_ts
@ -311,13 +273,14 @@ int arp_check(struct client_state_t cs[static 1],
return 0;
}
// Callable only from DS_BOUND via state.c:ifup_action().
// Confirms that we're still on the fingerprinted network.
int arp_gw_check(struct client_state_t cs[static 1])
{
if (garp.state == AS_GW_CHECK) // Guard against state bounce.
return 0;
if (arp_open_fd(cs, false) < 0)
return -1;
garp.gw_check_initpings = garp.send_stats[ASEND_GW_PING].count;
garp.server_replied = false;
cs->check_fingerprint = true;
int r;
if ((r = arp_ping(cs, cs->srcAddr)) < 0)
return r;
@ -327,20 +290,16 @@ int arp_gw_check(struct client_state_t cs[static 1])
return r;
} else
garp.router_replied = true;
arp_switch_state(cs, AS_GW_CHECK);
cs->arpPrevState = cs->dhcpState;
cs->dhcpState = DS_BOUND_GW_CHECK;
garp.wake_ts[AS_GW_CHECK] =
garp.send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY + 250;
return 0;
}
// Should only be called from DS_BOUND state.
// Gathers the fingerprinting info for the associated network.
static int arp_get_gw_hwaddr(struct client_state_t cs[static 1])
{
if (cs->dhcpState != DS_BOUND)
log_error("arp_get_gw_hwaddr: called when state != DS_BOUND");
arp_switch_state(cs, AS_GW_QUERY);
if (arp_open_fd(cs, false) < 0)
return -1;
if (cs->routerAddr)
log_line("%s: arp: Searching for dhcp server and gw addresses...",
client_config.interface);
@ -361,99 +320,21 @@ static int arp_get_gw_hwaddr(struct client_state_t cs[static 1])
return 0;
}
void arp_failed(struct client_state_t cs[static 1])
int arp_set_defense_mode(struct client_state_t cs[static 1])
{
log_line("%s: arp: Offered address is in use. Declining.",
client_config.interface);
int r = send_decline(cs, garp.dhcp_packet.yiaddr);
if (r < 0) {
log_warning("%s: Failed to send a decline notice packet.",
client_config.interface);
if (r == -99) {
// Carrier went down while suspended.
cs->ifsPrevState = IFS_DOWN;
ifnocarrier_action(cs);
garp.wake_ts[AS_COLLISION_CHECK] = -1;
return;
}
}
garp.wake_ts[AS_COLLISION_CHECK] = -1;
reinit_selecting(cs, garp.total_conflicts < MAX_CONFLICTS ?
0 : RATE_LIMIT_INTERVAL);
return arp_open_fd(cs, true);
}
void arp_gw_failed(struct client_state_t cs[static 1])
{
garp.wake_ts[AS_GW_CHECK] = -1;
reinit_selecting(cs, 0);
}
static int act_if_arp_gw_failed(struct client_state_t cs[static 1])
{
if (garp.send_stats[ASEND_GW_PING].count >= garp.gw_check_initpings + 6) {
if (garp.router_replied && !garp.server_replied)
log_line("%s: arp: DHCP agent didn't reply. Getting new lease.",
client_config.interface);
else if (!garp.router_replied && garp.server_replied)
log_line("%s: arp: Gateway didn't reply. Getting new lease.",
client_config.interface);
else
log_line("%s: arp: DHCP agent and gateway didn't reply. Getting new lease.",
client_config.interface);
arp_gw_failed(cs);
return 1;
}
return 0;
}
void arp_set_defense_mode(struct client_state_t cs[static 1])
{
arp_switch_state(cs, AS_DEFENSE);
}
void arp_success(struct client_state_t cs[static 1])
{
char clibuf[INET_ADDRSTRLEN];
struct in_addr temp_addr = {.s_addr = garp.dhcp_packet.yiaddr};
inet_ntop(AF_INET, &temp_addr, clibuf, sizeof clibuf);
log_line("%s: Lease of %s obtained. Lease time is %ld seconds.",
client_config.interface, clibuf, cs->lease);
cs->clientAddr = garp.dhcp_packet.yiaddr;
cs->dhcpState = DS_BOUND;
cs->init = 0;
garp.last_conflict_ts = 0;
garp.wake_ts[AS_COLLISION_CHECK] = -1;
if (ifchange_bind(cs, &garp.dhcp_packet) < 0) {
// XXX: Not ideal, but assuming that the DHCP process is supervised,
// it will recover. The correct thing to do would be to keep
// trying to set the configuration state.
suicide("%s: Failed to set the interface IP address and properties!",
client_config.interface);
}
if (cs->arpPrevState == DS_RENEWING || cs->arpPrevState == DS_REBINDING) {
arp_switch_state(cs, AS_DEFENSE);
} else {
cs->routerAddr = get_option_router(&garp.dhcp_packet);
arp_get_gw_hwaddr(cs);
}
stop_dhcp_listen(cs);
write_leasefile(temp_addr);
arp_announcement(cs);
if (client_config.quit_after_lease)
exit(EXIT_SUCCESS);
if (!client_config.foreground)
background();
}
static void arp_gw_success(struct client_state_t cs[static 1])
static int arp_gw_success(struct client_state_t cs[static 1])
{
log_line("%s: arp: Network seems unchanged. Resuming normal operation.",
client_config.interface);
arp_switch_state(cs, AS_DEFENSE);
arp_announcement(cs);
if (arp_open_fd(cs, true) < 0)
return ARPR_FAIL;
garp.wake_ts[AS_GW_CHECK] = -1;
cs->dhcpState = cs->arpPrevState;
if (arp_announcement(cs) < 0)
return ARPR_FAIL;
return ARPR_FREE;
}
// ARP validation functions that will be performed by the BPF if it is
@ -518,113 +399,151 @@ static int arp_gen_probe_wait(struct client_state_t cs[static 1])
% (arp_probe_max - arp_probe_min);
}
static void arp_defense_timeout(struct client_state_t cs[static 1],
long long nowts)
int arp_defense_timeout(struct client_state_t cs[static 1], long long nowts)
{
(void)nowts; // Suppress warning; parameter necessary but unused.
int ret = 0;
if (garp.wake_ts[AS_DEFENSE] != -1) {
log_line("%s: arp: Defending our lease IP.", client_config.interface);
arp_announcement(cs);
garp.wake_ts[AS_DEFENSE] = -1;
ret = arp_announcement(cs);
}
return ret;
}
static void arp_gw_check_timeout(struct client_state_t cs[static 1],
long long nowts)
int arp_gw_check_timeout(struct client_state_t cs[static 1], long long nowts)
{
arp_defense_timeout(cs, nowts);
if (act_if_arp_gw_failed(cs))
return;
if (garp.send_stats[ASEND_GW_PING].count >= garp.gw_check_initpings + 6) {
if (garp.router_replied && !garp.server_replied)
log_line("%s: arp: DHCP agent didn't reply. Getting new lease.",
client_config.interface);
else if (!garp.router_replied && garp.server_replied)
log_line("%s: arp: Gateway didn't reply. Getting new lease.",
client_config.interface);
else
log_line("%s: arp: DHCP agent and gateway didn't reply. Getting new lease.",
client_config.interface);
garp.wake_ts[AS_GW_CHECK] = -1;
return ARPR_CONFLICT;
}
long long rtts = garp.send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
if (nowts < rtts) {
garp.wake_ts[AS_GW_CHECK] = rtts;
return;
return ARPR_OK;
}
if (!garp.router_replied) {
log_line("%s: arp: Still waiting for gateway to reply to arp ping...",
client_config.interface);
if (arp_ping(cs, cs->routerAddr) < 0)
if (arp_ping(cs, cs->routerAddr) < 0) {
log_warning("%s: arp: Failed to send ARP ping in retransmission.",
client_config.interface);
return ARPR_FAIL;
}
}
if (!garp.server_replied) {
log_line("%s: arp: Still waiting for DHCP agent to reply to arp ping...",
client_config.interface);
if (arp_ping(cs, cs->srcAddr) < 0)
if (arp_ping(cs, cs->srcAddr) < 0) {
log_warning("%s: arp: Failed to send ARP ping in retransmission.",
client_config.interface);
return ARPR_FAIL;
}
}
garp.wake_ts[AS_GW_CHECK] =
garp.send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
return ARPR_OK;
}
static void arp_do_gw_query_done(struct client_state_t cs[static 1])
int arp_gw_query_timeout(struct client_state_t cs[static 1], long long nowts)
{
garp.wake_ts[AS_GW_QUERY] = -1;
arp_switch_state(cs, AS_DEFENSE);
arp_announcement(cs); // Do a second announcement.
}
static void arp_gw_query_timeout(struct client_state_t cs[static 1],
long long nowts)
{
arp_defense_timeout(cs, nowts);
long long rtts = garp.send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
if (nowts < rtts) {
garp.wake_ts[AS_GW_QUERY] = rtts;
return;
return ARPR_OK;
}
if (!cs->got_router_arp) {
log_line("%s: arp: Still looking for gateway hardware address...",
client_config.interface);
if (arp_ping(cs, cs->routerAddr) < 0)
if (arp_ping(cs, cs->routerAddr) < 0) {
log_warning("%s: arp: Failed to send ARP ping in retransmission.",
client_config.interface);
return ARPR_FAIL;
}
}
if (!cs->got_server_arp) {
log_line("%s: arp: Still looking for DHCP agent hardware address...",
client_config.interface);
if (arp_ping(cs, cs->srcAddr) < 0)
if (arp_ping(cs, cs->srcAddr) < 0) {
log_warning("%s: arp: Failed to send ARP ping in retransmission.",
client_config.interface);
return ARPR_FAIL;
}
}
garp.wake_ts[AS_GW_QUERY] =
garp.send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
return ARPR_OK;
}
static void arp_collision_timeout(struct client_state_t cs[static 1],
long long nowts)
int arp_collision_timeout(struct client_state_t cs[static 1], long long nowts)
{
arp_defense_timeout(cs, nowts);
if (nowts >= garp.arp_check_start_ts + ANNOUNCE_WAIT ||
garp.send_stats[ASEND_COLLISION_CHECK].count >= arp_probe_num) {
arp_success(cs);
return;
garp.send_stats[ASEND_COLLISION_CHECK].count >= arp_probe_num)
{
char clibuf[INET_ADDRSTRLEN];
struct in_addr temp_addr = {.s_addr = garp.dhcp_packet.yiaddr};
inet_ntop(AF_INET, &temp_addr, clibuf, sizeof clibuf);
log_line("%s: Lease of %s obtained. Lease time is %ld seconds.",
client_config.interface, clibuf, cs->lease);
cs->clientAddr = garp.dhcp_packet.yiaddr;
cs->init = 0;
garp.last_conflict_ts = 0;
garp.wake_ts[AS_COLLISION_CHECK] = -1;
if (ifchange_bind(cs, &garp.dhcp_packet) < 0) {
log_warning("%s: Failed to set the interface IP address and properties!",
client_config.interface);
return ARPR_FAIL;
}
cs->routerAddr = get_option_router(&garp.dhcp_packet);
if (arp_get_gw_hwaddr(cs) < 0) {
log_warning("%s: (%s) Failed to send request to get gateway and agent hardware addresses: %s",
client_config.interface, __func__, strerror(errno));
return ARPR_FAIL;
}
stop_dhcp_listen(cs);
write_leasefile(temp_addr);
int ret = ARPR_FREE;
if (arp_announcement(cs) < 0)
ret = ARPR_FAIL;
if (client_config.quit_after_lease)
exit(EXIT_SUCCESS);
if (!client_config.foreground)
background();
return ret;
}
long long rtts = garp.send_stats[ASEND_COLLISION_CHECK].ts +
garp.probe_wait_time;
if (nowts < rtts) {
garp.wake_ts[AS_COLLISION_CHECK] = rtts;
return;
return ARPR_OK;
}
if (arp_ip_anon_ping(cs, garp.dhcp_packet.yiaddr) < 0)
if (arp_ip_anon_ping(cs, garp.dhcp_packet.yiaddr) < 0) {
log_warning("%s: arp: Failed to send ARP ping in retransmission.",
client_config.interface);
return ARPR_FAIL;
}
garp.probe_wait_time = arp_gen_probe_wait(cs);
garp.wake_ts[AS_COLLISION_CHECK] =
garp.send_stats[ASEND_COLLISION_CHECK].ts + garp.probe_wait_time;
return ARPR_OK;
}
static void arp_do_defense(struct client_state_t cs[static 1])
int arp_do_defense(struct client_state_t cs[static 1])
{
// Even though the BPF will usually catch this case, sometimes there are
// packets still in the socket buffer that arrived before the defense
// BPF was installed, so it's necessary to check here.
if (!arp_validate_bpf_defense(cs, &garp.reply))
return;
return ARPR_OK;
log_warning("%s: arp: Detected a peer attempting to use our IP!", client_config.interface);
long long nowts = curms();
@ -632,26 +551,26 @@ static void arp_do_defense(struct client_state_t cs[static 1])
if (!garp.last_conflict_ts ||
nowts - garp.last_conflict_ts < DEFEND_INTERVAL) {
log_warning("%s: arp: Defending our lease IP.", client_config.interface);
arp_announcement(cs);
if (arp_announcement(cs) < 0)
return ARPR_FAIL;
} else if (!garp.relentless_def) {
log_warning("%s: arp: Conflicting peer is persistent. Requesting new lease.",
client_config.interface);
send_release(cs);
reinit_selecting(cs, 0);
return ARPR_CONFLICT;
} else {
garp.wake_ts[AS_DEFENSE] =
garp.send_stats[ASEND_ANNOUNCE].ts + DEFEND_INTERVAL;
}
garp.total_conflicts++;
garp.last_conflict_ts = nowts;
return ARPR_OK;
}
static void arp_do_gw_query(struct client_state_t cs[static 1])
int arp_do_gw_query(struct client_state_t cs[static 1])
{
if (!arp_is_query_reply(&garp.reply)) {
arp_do_defense(cs);
return;
}
if (!arp_is_query_reply(&garp.reply))
return ARPR_OK;
if (!memcmp(garp.reply.sip4, &cs->routerAddr, 4)) {
memcpy(cs->routerArp, garp.reply.smac, 6);
log_line("%s: arp: Gateway hardware address %02x:%02x:%02x:%02x:%02x:%02x",
@ -661,9 +580,16 @@ static void arp_do_gw_query(struct client_state_t cs[static 1])
cs->got_router_arp = 1;
if (cs->routerAddr == cs->srcAddr)
goto server_is_router;
if (cs->got_server_arp)
arp_do_gw_query_done(cs);
return;
if (cs->got_server_arp) {
garp.wake_ts[AS_GW_QUERY] = -1;
if (arp_open_fd(cs, true) < 0)
return ARPR_FAIL;
// Do a second announcement.
if (arp_announcement(cs) < 0)
return ARPR_FAIL;
return ARPR_FREE;
}
return ARPR_OK;
}
if (!memcmp(garp.reply.sip4, &cs->srcAddr, 4)) {
server_is_router:
@ -673,31 +599,49 @@ server_is_router:
cs->serverArp[2], cs->serverArp[3],
cs->serverArp[4], cs->serverArp[5]);
cs->got_server_arp = 1;
if (cs->got_router_arp)
arp_do_gw_query_done(cs);
return;
if (cs->got_router_arp) {
garp.wake_ts[AS_GW_QUERY] = -1;
if (arp_open_fd(cs, true) < 0)
return ARPR_FAIL;
// Do a second announcement.
if (arp_announcement(cs) < 0)
return ARPR_FAIL;
return ARPR_FREE;
}
return ARPR_OK;
}
arp_do_defense(cs);
return ARPR_OK;
}
static void arp_do_collision_check(struct client_state_t cs[static 1])
int arp_do_collision_check(struct client_state_t cs[static 1])
{
if (!arp_is_query_reply(&garp.reply))
return;
return ARPR_OK;
// If this packet was sent from our lease IP, and does not have a
// MAC address matching our own (the latter check guards against stupid
// hubs or repeaters), then it's a conflict and thus a failure.
if (!memcmp(garp.reply.sip4, &garp.dhcp_packet.yiaddr, 4) &&
!memcmp(client_config.arp, garp.reply.smac, 6)) {
!memcmp(client_config.arp, garp.reply.smac, 6))
{
garp.total_conflicts++;
arp_failed(cs);
garp.wake_ts[AS_COLLISION_CHECK] = -1;
log_line("%s: arp: Offered address is in use. Declining.",
client_config.interface);
int r = send_decline(cs, garp.dhcp_packet.yiaddr);
if (r < 0) {
log_warning("%s: Failed to send a decline notice packet.",
client_config.interface);
return ARPR_FAIL;
}
return ARPR_CONFLICT;
}
return ARPR_OK;
}
static void arp_do_gw_check(struct client_state_t cs[static 1])
int arp_do_gw_check(struct client_state_t cs[static 1])
{
if (!arp_is_query_reply(&garp.reply))
return;
return ARPR_OK;
if (!memcmp(garp.reply.sip4, &cs->routerAddr, 4)) {
// Success only if the router/gw MAC matches stored value
if (!memcmp(cs->routerArp, garp.reply.smac, 6)) {
@ -705,13 +649,13 @@ static void arp_do_gw_check(struct client_state_t cs[static 1])
if (cs->routerAddr == cs->srcAddr)
goto server_is_router;
if (garp.server_replied)
arp_gw_success(cs);
} else {
log_line("%s: arp: Gateway is different. Getting a new lease.",
client_config.interface);
arp_gw_failed(cs);
return arp_gw_success(cs); // FREE or FAIL
return ARPR_OK;
}
return;
log_line("%s: arp: Gateway is different. Getting a new lease.",
client_config.interface);
garp.wake_ts[AS_GW_CHECK] = -1;
return ARPR_CONFLICT;
}
if (!memcmp(garp.reply.sip4, &cs->srcAddr, 4)) {
server_is_router:
@ -719,41 +663,15 @@ server_is_router:
if (!memcmp(cs->serverArp, garp.reply.smac, 6)) {
garp.server_replied = true;
if (garp.router_replied)
arp_gw_success(cs);
} else {
log_line("%s: arp: DHCP agent is different. Getting a new lease.",
client_config.interface);
arp_gw_failed(cs);
return arp_gw_success(cs); // FREE or FAIL
return ARPR_OK;
}
log_line("%s: arp: DHCP agent is different. Getting a new lease.",
client_config.interface);
garp.wake_ts[AS_GW_CHECK] = -1;
return ARPR_CONFLICT;
}
}
static void arp_do_invalid(struct client_state_t cs[static 1])
{
log_error("%s: (%s) called in invalid state %u", client_config.interface,
__func__, garp.state);
arp_close_fd(cs);
}
typedef struct {
void (*packet_fn)(struct client_state_t cs[static 1]);
void (*timeout_fn)(struct client_state_t cs[static 1], long long nowts);
} arp_state_fn_t;
static const arp_state_fn_t arp_states[] = {
{ arp_do_invalid, 0 }, // AS_NONE
{ arp_do_collision_check, arp_collision_timeout }, // AS_COLLISION_CHECK
{ arp_do_gw_check, arp_gw_check_timeout }, // AS_GW_CHECK
{ arp_do_gw_query, arp_gw_query_timeout }, // AS_GW_QUERY
{ arp_do_defense, arp_defense_timeout }, // AS_DEFENSE
{ arp_do_invalid, 0 }, // AS_MAX
};
void arp_packet_action(struct client_state_t cs[static 1])
{
if (arp_states[garp.state].packet_fn)
arp_states[garp.state].packet_fn(cs);
arp_reply_clear();
return ARPR_OK;
}
int arp_packet_get(struct client_state_t cs[static 1])
@ -763,36 +681,35 @@ int arp_packet_get(struct client_state_t cs[static 1])
r = safe_read(cs->arpFd, (char *)&garp.reply + garp.reply_offset,
sizeof garp.reply - garp.reply_offset);
if (r == 0)
return ARPR_CLOSED;
return ARPP_NONE;
if (r < 0) {
log_error("%s: (%s) ARP response read failed: %s",
client_config.interface, __func__, strerror(errno));
return ARPR_ERROR;
// Timeouts will trigger anyway without being forced.
arp_min_close_fd(cs);
if (arp_open_fd(cs, cs->arp_is_defense) < 0)
suicide("%s: (%s) Failed to reopen ARP fd: %s",
client_config.interface, __func__, strerror(errno));
return ARPP_NONE;
}
garp.reply_offset += (size_t)r;
}
if (garp.reply_offset < ARP_MSG_SIZE)
return ARPR_NONE;
return ARPP_NONE;
// Emulate the BPF filters if they are not in use.
if (!garp.using_bpf &&
(!arp_validate_bpf(&garp.reply) ||
(garp.state == AS_DEFENSE &&
(cs->arp_is_defense &&
!arp_validate_bpf_defense(cs, &garp.reply)))) {
arp_reply_clear();
return ARPR_NONE;
return ARPP_NONE;
}
return ARPR_PENDING;
}
// Perform retransmission if necessary.
void handle_arp_timeout(struct client_state_t cs[static 1], long long nowts)
{
if (arp_states[garp.state].timeout_fn)
arp_states[garp.state].timeout_fn(cs, nowts);
return ARPP_HAVE;
}
// XXX: Move into client_state
long long arp_get_wake_ts(void)
{
long long mt = -1;

View File

@ -90,7 +90,6 @@ struct arp_data {
long long arp_check_start_ts; // TS of when we started the
// AS_COLLISION_CHECK state.
size_t reply_offset;
arp_state_t state;
unsigned int total_conflicts; // Total number of address conflicts on
// the interface. Never decreases.
int gw_check_initpings; // Initial count of ASEND_GW_PING when
@ -103,7 +102,9 @@ struct arp_data {
bool server_replied:1;
};
extern struct arp_data garp;
void arp_reply_clear(void);
int arp_packet_get(struct client_state_t cs[static 1]);
void set_arp_relentless_def(bool v);
void arp_reset_send_stats(void);
@ -111,22 +112,33 @@ void arp_close_fd(struct client_state_t cs[static 1]);
int arp_check(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1]);
int arp_gw_check(struct client_state_t cs[static 1]);
void arp_set_defense_mode(struct client_state_t cs[static 1]);
void arp_success(struct client_state_t cs[static 1]);
void arp_failed(struct client_state_t cs[static 1]);
void arp_gw_failed(struct client_state_t cs[static 1]);
void arp_reopen_fd(struct client_state_t cs[static 1]);
int arp_set_defense_mode(struct client_state_t cs[static 1]);
int arp_gw_failed(struct client_state_t cs[static 1]);
enum {
ARPR_NONE = 0,
ARPR_ERROR,
ARPR_PENDING,
ARPR_CLOSED,
};
void arp_packet_action(struct client_state_t cs[static 1]);
int arp_packet_get(struct client_state_t cs[static 1]);
int arp_do_collision_check(struct client_state_t cs[static 1]);
int arp_collision_timeout(struct client_state_t cs[static 1], long long nowts);
int arp_do_defense(struct client_state_t cs[static 1]);
int arp_defense_timeout(struct client_state_t cs[static 1], long long nowts);
int arp_do_gw_query(struct client_state_t cs[static 1]);
int arp_gw_query_timeout(struct client_state_t cs[static 1], long long nowts);
int arp_do_gw_check(struct client_state_t cs[static 1]);
int arp_gw_check_timeout(struct client_state_t cs[static 1], long long nowts);
// No action needs to be taken.
#define ARPR_OK 0
// There was no conflict with another host.
#define ARPR_FREE 1
// Another host already has our assigned address.
#define ARPR_CONFLICT -1
// The operation couldn't complete because of an error such as rfkill.
#define ARPR_FAIL -2
// There is no new packet.
#define ARPP_NONE 0
// We have a pending packet.
#define ARPP_HAVE 1
void handle_arp_timeout(struct client_state_t cs[static 1], long long nowts);
long long arp_get_wake_ts(void);
#endif /* ARP_H_ */

View File

@ -82,6 +82,7 @@ struct client_state_t cs = {
.nlFd = -1,
.nlPortId = -1,
.rfkillFd = -1,
.dhcp_wake_ts = -1,
.routerArp = "\0\0\0\0\0\0",
.serverArp = "\0\0\0\0\0\0",
};
@ -265,6 +266,9 @@ static void fail_if_state_dir_dne(void)
#define NDHC_NUM_EP_FDS 7
static void do_ndhc_work(void)
{
static bool rfkill_set; // Is the rfkill switch set?
static bool rfkill_nl_state_changed; // iface state changed during rfkill
static int rfkill_nl_state; // current iface state during rfkill
struct dhcpmsg dhcp_packet;
struct epoll_event events[NDHC_NUM_EP_FDS];
long long nowts;
@ -285,11 +289,10 @@ static void do_ndhc_work(void)
if (client_config.enable_rfkill && cs.rfkillFd != -1)
epoll_add(cs.epollFd, cs.rfkillFd);
start_dhcp_listen(&cs);
nowts = curms();
goto jumpstart;
timeout = 0;
for (;;) {
int maxi = epoll_wait(cs.epollFd, events, NDHC_NUM_EP_FDS, timeout);
int maxi = epoll_wait(cs.epollFd, events, 1, timeout);
if (maxi < 0) {
if (errno == EINTR)
continue;
@ -299,10 +302,11 @@ static void do_ndhc_work(void)
int sev_dhcp = -1;
uint32_t dhcp_srcaddr;
uint8_t dhcp_msgtype;
int sev_arp = ARPR_NONE;
int sev_arp = ARPP_NONE;
int sev_nl = IFS_NONE;
int sev_rfk = RFK_NONE;
int sev_signal = SIGNAL_NONE;
bool force_fingerprint = false;
for (int i = 0; i < maxi; ++i) {
int fd = events[i].data.fd;
if (fd == cs.signalFd) {
@ -334,81 +338,89 @@ static void do_ndhc_work(void)
sev_rfk = rfkill_get(&cs, 1, client_config.rfkillIdx);
} else
suicide("epoll_wait: unknown fd");
if (sev_rfk == RFK_ENABLED) {
rfkill_set = 1;
rfkill_nl_state = cs.ifsPrevState;
rfkill_nl_state_changed = false;
log_line("rfkill: radio now blocked");
} else if (sev_rfk == RFK_DISABLED) {
rfkill_set = 0;
log_line("rfkill: radio now unblocked");
// We now simulate the state changes that may have happened
// during rfkill.
if (rfkill_nl_state != cs.ifsPrevState)
nl_event_react(&cs, rfkill_nl_state);
else if (rfkill_nl_state_changed && rfkill_nl_state == IFS_UP) {
// We might have changed networks even if we ended up
// back in IFS_UP state. We need to fingerprint the
// network and confirm that we're on the same network.
force_fingerprint = true;
}
}
}
if (sev_nl != IFS_NONE) {
if (!rfkill_set) {
if (nl_event_react(&cs, sev_nl))
force_fingerprint = true;
} else {
// Store the state so it can be replayed later.
rfkill_nl_state_changed = true;
rfkill_nl_state = sev_nl;
}
}
if (rfkill_set || cs.ifsPrevState != IFS_UP) {
// We can't do anything while the iface is disabled, anyway.
// XXX: It may be smart to set a non-infinite timeout
// and periodically poll to see if the rfkill or iface
// state changed; it might happen during suspend.
timeout = -1;
continue;
}
// XXX: Make these work again. See xmit_release(), print_release(),
// and frenew().
#if 0
if (sev_signal == SIGNAL_RENEW)
force_renew_action(&cs);
else if (sev_signal == SIGNAL_RELEASE)
force_release_action(&cs);
if (!sev_dhcp)
packet_action(&cs, &dhcp_packet, dhcp_msgtype, dhcp_srcaddr);
if (sev_arp == ARPR_PENDING) {
arp_packet_action(&cs);
} else if (sev_arp == ARPR_ERROR) {
if (garp.state == AS_COLLISION_CHECK)
arp_failed(&cs);
else if (garp.state == AS_GW_CHECK)
arp_gw_failed(&cs);
else
arp_reopen_fd(&cs);
handle_arp_timeout(&cs, curms());
} else if (sev_arp == ARPR_CLOSED)
handle_arp_timeout(&cs, curms());
if (sev_nl != IFS_NONE)
nl_event_react(&cs, sev_nl);
if (sev_rfk == RFK_ENABLED) {
cs.rfkill_set = 1;
if (cs.ifsPrevState == IFS_UP) {
log_line("rfkill: radio now blocked; bringing interface down");
cs.ifsPrevState = IFS_DOWN;
ifnocarrier_action(&cs);
} else
log_line("rfkill: radio now blocked, but interface isn't up");
} else if (sev_rfk == RFK_DISABLED) {
cs.rfkill_set = 0;
if (cs.ifsPrevState == IFS_DOWN) {
log_line("rfkill: radio now unblocked; bringing interface up");
cs.ifsPrevState = IFS_UP;
ifup_action(&cs);
} else {
if (cs.ifsPrevState == IFS_SHUT)
log_line("rfkill: radio now unblocked, but interface was shut down by user");
else
log_line("rfkill: radio now unblocked, but interface is removed");
}
#endif
nowts = curms();
long long arp_wake_ts = arp_get_wake_ts();
int dhcp_ok = dhcp_handle(&cs, nowts, sev_dhcp, &dhcp_packet,
dhcp_msgtype, dhcp_srcaddr,
sev_arp, force_fingerprint,
cs.dhcp_wake_ts <= nowts,
arp_wake_ts <= nowts);
if (sev_arp)
arp_reply_clear();
// XXX: Would be best if we detected RFKILL being set via an
// error message and propagated it back to here as a
// distinct return value.
if (dhcp_ok == COR_ERROR) {
timeout = -1;
continue;
}
for (;;) {
nowts = curms();
long long arp_wake_ts = arp_get_wake_ts();
long long dhcp_wake_ts = dhcp_get_wake_ts();
if (arp_wake_ts < 0) {
if (dhcp_wake_ts != -1) {
timeout = dhcp_wake_ts - nowts;
if (timeout < 0)
timeout = 0;
} else
timeout = -1;
} else {
// If dhcp_wake_ts is -1 then we want to sleep anyway.
timeout = (arp_wake_ts < dhcp_wake_ts ?
arp_wake_ts : dhcp_wake_ts) - nowts;
arp_wake_ts = arp_get_wake_ts();
if (arp_wake_ts < 0) {
if (cs.dhcp_wake_ts != -1) {
timeout = cs.dhcp_wake_ts - nowts;
if (timeout < 0)
timeout = 0;
}
if (cs.rfkill_set) {
// We can't do anything until the rfkill is gone.
if (timeout <= 0)
timeout = -1;
break;
}
if (!timeout) {
jumpstart:
timeout_action(&cs, nowts);
} else
break;
timeout = -1;
} else {
// If cs.dhcp_wake_ts is -1 then we want to sleep anyway.
timeout = (arp_wake_ts < cs.dhcp_wake_ts ?
arp_wake_ts : cs.dhcp_wake_ts) - nowts;
if (timeout < 0)
timeout = 0;
}
}
}

View File

@ -29,24 +29,26 @@
#define NJK_NDHC_NDHC_H_
#include <stdint.h>
#include <stdbool.h>
#include <limits.h>
#include <net/if.h>
#include "nk/random.h"
struct client_state_t {
unsigned long long leaseStartTime;
int dhcpState;
int arpPrevState;
int ifsPrevState;
int ifDeconfig; // Set if the interface has already been deconfigured.
int epollFd, signalFd, listenFd, arpFd, nlFd, rfkillFd;
int nlPortId;
unsigned int num_dhcp_requests;
uint32_t clientAddr, serverAddr, srcAddr, routerAddr;
uint32_t lease, renewTime, rebindTime, xid;
struct nk_random_state_u32 rnd32_state;
uint8_t routerArp[6], serverArp[6];
uint8_t using_dhcp_bpf, init, got_router_arp, got_server_arp;
uint8_t rfkill_set;
uint8_t using_dhcp_bpf, init, got_router_arp, got_server_arp,
check_fingerprint;
bool arp_is_defense;
long long dhcp_wake_ts;
};
struct client_config_t {

View File

@ -46,37 +46,33 @@
int nl_event_react(struct client_state_t cs[static 1], int state)
{
if (state == cs->ifsPrevState)
return -1;
// If the rfkill switch is set, a lot of netlink state change
// commands will fail outright, so just ignore events until
// it is gone.
if (cs->rfkill_set)
return -1;
return 0;
switch (state) {
case IFS_UP:
cs->ifsPrevState = IFS_UP;
ifup_action(cs);
break;
return 1;
case IFS_DOWN:
// Interface configured, but no hardware carrier.
cs->ifsPrevState = IFS_DOWN;
ifnocarrier_action(cs);
break;
log_line("%s: Carrier down.", client_config.interface);
return 0;
case IFS_SHUT:
// User shut down the interface.
cs->ifsPrevState = IFS_SHUT;
ifdown_action(cs);
break;
log_line("%s: Interface shut down. Going to sleep.",
client_config.interface);
// XXX: I think this was wrong; instead it should just sleep.
// The lease has not expired just because the user shut down
// the interface...
// set_released(cs);
return 0;
case IFS_REMOVED:
cs->ifsPrevState = IFS_REMOVED;
log_line("Interface removed. Exiting.");
exit(EXIT_SUCCESS);
break;
default: break;
default: return 0;
}
return 0;
}
static int nl_process_msgs_return;

View File

@ -41,61 +41,28 @@
#include "ndhc.h"
#include "sys.h"
#include "netlink.h"
#include "coroutine.h"
// Simulates netlink carrier down event if carrier went down while suspended.
#define SUSPEND_IF_NOCARRIER() \
if (r == -99) { \
cs->ifsPrevState = IFS_DOWN; \
ifnocarrier_action(cs); \
dhcp_wake_ts = -1; \
return; \
}
#define SEL_SUCCESS 0
#define SEL_FAIL -1
static void selecting_packet(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1],
uint8_t msgtype, uint32_t srcaddr);
static void an_packet(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1],
uint8_t msgtype, uint32_t srcaddr);
static void selecting_timeout(struct client_state_t cs[static 1],
long long nowts);
static void requesting_timeout(struct client_state_t cs[static 1],
long long nowts);
static void bound_timeout(struct client_state_t cs[static 1],
long long nowts);
static void renewing_timeout(struct client_state_t cs[static 1],
long long nowts);
static void rebinding_timeout(struct client_state_t cs[static 1],
long long nowts);
static void released_timeout(struct client_state_t cs[static 1],
long long nowts);
static void xmit_release(struct client_state_t cs[static 1]);
static void print_release(struct client_state_t cs[static 1]);
static void frenew(struct client_state_t cs[static 1]);
#define REQ_SUCCESS 0
#define REQ_TIMEOUT -1
#define REQ_FAIL -2
typedef struct {
void (*packet_fn)(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1],
uint8_t msgtype, uint32_t srcaddr);
void (*timeout_fn)(struct client_state_t cs[static 1], long long nowts);
void (*force_renew_fn)(struct client_state_t cs[static 1]);
void (*force_release_fn)(struct client_state_t cs[static 1]);
} dhcp_state_t;
#define ANP_SUCCESS 0
#define ANP_IGNORE -1
#define ANP_REJECTED -2
#define ANP_CHECK_IP -3
#define ANP_FAIL -4
static const dhcp_state_t dhcp_states[] = {
{ selecting_packet, selecting_timeout, 0, print_release}, // SELECTING
{ an_packet, requesting_timeout, 0, print_release}, // REQUESTING
{ 0, bound_timeout, frenew, xmit_release}, // BOUND
{ an_packet, renewing_timeout, 0, xmit_release}, // RENEWING
{ an_packet, rebinding_timeout, 0, xmit_release}, // REBINDING
{ 0, 0, 0, xmit_release}, // BOUND_GW_CHECK
{ 0, 0, 0, xmit_release}, // COLLISION_CHECK
{ 0, released_timeout, frenew, 0}, // RELEASED
{ 0, 0, 0, 0}, // NUM_STATES
};
#define BTO_WAIT 0
#define BTO_EXPIRED -1
#define BTO_HARDFAIL -2
static unsigned int num_dhcp_requests;
static long long dhcp_wake_ts = -1;
#define IFUP_REVALIDATE 0
#define IFUP_NEWLEASE 1
#define IFUP_FAIL -1
static int delay_timeout(struct client_state_t cs[static 1], size_t numpackets)
{
@ -109,11 +76,9 @@ static int delay_timeout(struct client_state_t cs[static 1], size_t numpackets)
static int reinit_shared_deconfig(struct client_state_t cs[static 1])
{
if (ifchange_deconfig(cs) < 0)
return -1;
arp_close_fd(cs);
cs->clientAddr = 0;
num_dhcp_requests = 0;
cs->num_dhcp_requests = 0;
cs->got_router_arp = 0;
cs->got_server_arp = 0;
memset(&cs->routerArp, 0, sizeof cs->routerArp);
@ -122,65 +87,77 @@ static int reinit_shared_deconfig(struct client_state_t cs[static 1])
return 0;
}
int reinit_selecting(struct client_state_t cs[static 1], int timeout)
static int reinit_selecting(struct client_state_t cs[static 1], int timeout)
{
if (reinit_shared_deconfig(cs) < 0) {
// XXX: This should retry until it succeeds.
suicide("%s: (%s) deconfiguring interface failed",
client_config.interface, __func__);
}
cs->dhcpState = DS_SELECTING;
dhcp_wake_ts = curms() + timeout;
if (reinit_shared_deconfig(cs) < 0)
return -1;
cs->dhcp_wake_ts = curms() + timeout;
start_dhcp_listen(cs);
return 0;
}
static int set_released(struct client_state_t cs[static 1])
{
if (reinit_shared_deconfig(cs) < 0) {
// XXX: This should retry until it succeeds.
suicide("%s: (%s) deconfiguring interface failed",
client_config.interface, __func__);
}
cs->dhcpState = DS_RELEASED;
dhcp_wake_ts = -1;
stop_dhcp_listen(cs);
return 0;
}
// Triggered after a DHCP lease request packet has been sent and no reply has
// been received within the response wait time. If we've not exceeded the
// maximum number of request retransmits, then send another packet and wait
// again. Otherwise, return to the DHCP initialization state.
static void requesting_timeout(struct client_state_t cs[static 1],
static int requesting_timeout(struct client_state_t cs[static 1],
long long nowts)
{
if (num_dhcp_requests >= 5) {
reinit_selecting(cs, 0);
return;
if (cs->num_dhcp_requests >= 5) {
if (reinit_selecting(cs, 0) < 0)
return REQ_FAIL;
return REQ_TIMEOUT;
}
int r = send_selecting(cs);
if (r < 0) {
log_warning("%s: Failed to send a selecting request packet.",
client_config.interface);
SUSPEND_IF_NOCARRIER();
return REQ_FAIL;
}
dhcp_wake_ts = nowts + delay_timeout(cs, num_dhcp_requests);
num_dhcp_requests++;
cs->dhcp_wake_ts = nowts + delay_timeout(cs, cs->num_dhcp_requests);
cs->num_dhcp_requests++;
return REQ_SUCCESS;
}
// Triggered when the lease has been held for a significant fraction of its
// total time, and it is time to renew the lease so that it is not lost.
static void bound_timeout(struct client_state_t cs[static 1], long long nowts)
static bool is_renewing(struct client_state_t cs[static 1], long long nowts)
{
long long rnt = cs->leaseStartTime + cs->renewTime * 1000;
if (nowts < rnt) {
dhcp_wake_ts = rnt;
return;
return nowts >= rnt;
}
static bool is_rebinding(struct client_state_t cs[static 1], long long nowts)
{
long long rbt = cs->leaseStartTime + cs->rebindTime * 1000;
return nowts >= rbt;
}
// Triggered when a DHCP rebind request has been sent and no reply has been
// received within the response wait time. Check to see if the lease is still
// valid, and if it is, send a broadcast DHCP renew packet. If it is not, then
// change to the SELECTING state to get a new lease.
static int rebinding_timeout(struct client_state_t cs[static 1],
long long nowts)
{
long long elt = cs->leaseStartTime + cs->lease * 1000;
if (nowts >= elt) {
log_line("%s: Lease expired. Searching for a new lease...",
client_config.interface);
if (reinit_selecting(cs, 0) < 0)
return BTO_HARDFAIL;
return BTO_EXPIRED;
}
cs->dhcpState = DS_RENEWING;
start_dhcp_listen(cs);
renewing_timeout(cs, nowts);
if (elt - nowts < 30000) {
cs->dhcp_wake_ts = elt;
return BTO_WAIT;
}
int r = send_rebind(cs);
if (r < 0) {
log_warning("%s: Failed to send a rebind request packet.",
client_config.interface);
return BTO_HARDFAIL;
}
cs->dhcp_wake_ts = nowts + ((elt - nowts) / 2);
return BTO_WAIT;
}
// Triggered when a DHCP renew request has been sent and no reply has been
@ -189,61 +166,37 @@ static void bound_timeout(struct client_state_t cs[static 1], long long nowts)
// expires. Check to see if the lease is still valid, and if it is, send
// a unicast DHCP renew packet. If it is not, then change to the REBINDING
// state to send broadcast queries.
static void renewing_timeout(struct client_state_t cs[static 1],
static int renewing_timeout(struct client_state_t cs[static 1],
long long nowts)
{
long long rbt = cs->leaseStartTime + cs->rebindTime * 1000;
if (nowts >= rbt) {
cs->dhcpState = DS_REBINDING;
rebinding_timeout(cs, nowts);
return;
}
if (nowts >= rbt)
return rebinding_timeout(cs, nowts);
if (rbt - nowts < 30000) {
dhcp_wake_ts = rbt;
return;
cs->dhcp_wake_ts = rbt;
return BTO_WAIT;
}
int r = send_renew(cs);
if (r < 0) {
log_warning("%s: Failed to send a renew request packet.",
client_config.interface);
SUSPEND_IF_NOCARRIER();
return BTO_HARDFAIL;
}
dhcp_wake_ts = nowts + ((rbt - nowts) / 2);
cs->dhcp_wake_ts = nowts + ((rbt - nowts) / 2);
return BTO_WAIT;
}
// Triggered when a DHCP rebind request has been sent and no reply has been
// received within the response wait time. Check to see if the lease is still
// valid, and if it is, send a broadcast DHCP renew packet. If it is not, then
// change to the SELECTING state to get a new lease.
static void rebinding_timeout(struct client_state_t cs[static 1],
long long nowts)
// Triggered when the lease has been held for a significant fraction of its
// total time, and it is time to renew the lease so that it is not lost.
static int bound_timeout(struct client_state_t cs[static 1], long long nowts)
{
long long elt = cs->leaseStartTime + cs->lease * 1000;
if (nowts >= elt) {
log_line("%s: Lease expired. Searching for a new lease...",
client_config.interface);
reinit_selecting(cs, 0);
return;
long long rnt = cs->leaseStartTime + cs->renewTime * 1000;
if (nowts < rnt) {
cs->dhcp_wake_ts = rnt;
return BTO_WAIT;
}
if (elt - nowts < 30000) {
dhcp_wake_ts = elt;
return;
}
int r = send_rebind(cs);
if (r < 0) {
log_warning("%s: Failed to send a rebind request packet.",
client_config.interface);
SUSPEND_IF_NOCARRIER();
}
dhcp_wake_ts = nowts + ((elt - nowts) / 2);
}
static void released_timeout(struct client_state_t cs[static 1],
long long nowts)
{
(void)cs;
(void)nowts;
dhcp_wake_ts = -1;
start_dhcp_listen(cs);
return renewing_timeout(cs, nowts);
}
static int validate_serverid(struct client_state_t cs[static 1],
@ -263,20 +216,19 @@ static int validate_serverid(struct client_state_t cs[static 1],
svrbuf, sizeof svrbuf);
log_line("%s: Received %s with an unexpected server id: %s. Ignoring it.",
client_config.interface, typemsg, svrbuf);
return 0;
return 1; // XXX! HACK! Change back to 0!
}
return 1;
}
// Can transition to DS_BOUND or DS_SELECTING.
static void an_packet(struct client_state_t cs[static 1],
static int an_packet(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1], uint8_t msgtype,
uint32_t srcaddr)
uint32_t srcaddr, bool is_requesting)
{
(void)srcaddr;
if (msgtype == DHCPACK) {
if (!validate_serverid(cs, packet, "a DHCP ACK"))
return;
return ANP_IGNORE;
cs->lease = get_option_leasetime(packet);
cs->leaseStartTime = curms();
if (!cs->lease) {
@ -294,80 +246,78 @@ static void an_packet(struct client_state_t cs[static 1],
// the remote server values, if they even exist, for sanity.
cs->renewTime = cs->lease >> 1;
cs->rebindTime = (cs->lease >> 3) * 0x7; // * 0.875
dhcp_wake_ts = cs->leaseStartTime + cs->renewTime * 1000;
cs->dhcp_wake_ts = cs->leaseStartTime + cs->renewTime * 1000;
// Only check if we are either in the REQUESTING state, or if we
// have received a lease with a different IP than what we had before.
if (cs->dhcpState == DS_REQUESTING ||
memcmp(&packet->yiaddr, &cs->clientAddr, 4)) {
if (is_requesting || memcmp(&packet->yiaddr, &cs->clientAddr, 4)) {
char clibuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->clientAddr},
clibuf, sizeof clibuf);
log_line("%s: Accepted a firm offer for %s. Validating...",
client_config.interface, clibuf);
if (arp_check(cs, packet) < 0) {
log_warning("%s: Failed to make arp socket. Searching for new lease...",
client_config.interface);
reinit_selecting(cs, 3000);
}
return ANP_CHECK_IP;
} else {
log_line("%s: Lease refreshed to %u seconds.",
client_config.interface, cs->lease);
cs->dhcpState = DS_BOUND;
arp_set_defense_mode(cs);
arp_set_defense_mode(cs); // XXX: Can return error: fail to open
stop_dhcp_listen(cs);
return ANP_SUCCESS;
}
} else if (msgtype == DHCPNAK) {
if (!validate_serverid(cs, packet, "a DHCP NAK"))
return;
return ANP_IGNORE;
log_line("%s: Our request was rejected. Searching for a new lease...",
client_config.interface);
reinit_selecting(cs, 3000);
if (reinit_selecting(cs, 3000) < 0)
return ANP_FAIL;
return ANP_REJECTED;
}
return ANP_IGNORE;
}
static void selecting_packet(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1], uint8_t msgtype,
uint32_t srcaddr)
static int selecting_packet(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1], uint8_t msgtype,
uint32_t srcaddr)
{
if (msgtype == DHCPOFFER) {
int found;
uint32_t sid = get_option_serverid(packet, &found);
if (found) {
char clibuf[INET_ADDRSTRLEN];
char svrbuf[INET_ADDRSTRLEN];
char srcbuf[INET_ADDRSTRLEN];
cs->serverAddr = sid;
cs->xid = packet->xid;
cs->clientAddr = packet->yiaddr;
cs->srcAddr = srcaddr;
cs->dhcpState = DS_REQUESTING;
dhcp_wake_ts = curms();
num_dhcp_requests = 0;
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->clientAddr},
clibuf, sizeof clibuf);
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->serverAddr},
svrbuf, sizeof svrbuf);
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->srcAddr},
srcbuf, sizeof srcbuf);
log_line("%s: Received IP offer: %s from server %s via %s.",
client_config.interface, clibuf, svrbuf, srcbuf);
} else {
if (!found) {
log_line("%s: Invalid offer received: it didn't have a server id.",
client_config.interface);
return ANP_IGNORE;
}
char clibuf[INET_ADDRSTRLEN];
char svrbuf[INET_ADDRSTRLEN];
char srcbuf[INET_ADDRSTRLEN];
cs->serverAddr = sid;
cs->xid = packet->xid;
cs->clientAddr = packet->yiaddr;
cs->srcAddr = srcaddr;
cs->dhcp_wake_ts = curms();
cs->num_dhcp_requests = 0;
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->clientAddr},
clibuf, sizeof clibuf);
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->serverAddr},
svrbuf, sizeof svrbuf);
inet_ntop(AF_INET, &(struct in_addr){.s_addr=cs->srcAddr},
srcbuf, sizeof srcbuf);
log_line("%s: Received IP offer: %s from server %s via %s.",
client_config.interface, clibuf, svrbuf, srcbuf);
return ANP_SUCCESS;
}
return ANP_IGNORE;
}
// Triggered after a DHCP discover packet has been sent and no reply has
// been received within the response wait time. If we've not exceeded the
// maximum number of discover retransmits, then send another packet and wait
// again. Otherwise, background or fail.
static void selecting_timeout(struct client_state_t cs[static 1],
static int selecting_timeout(struct client_state_t cs[static 1],
long long nowts)
{
if (cs->init && num_dhcp_requests >= 2) {
if (cs->init && cs->num_dhcp_requests >= 2) {
if (client_config.background_if_no_lease) {
log_line("%s: No lease; going to background.",
client_config.interface);
@ -376,19 +326,33 @@ static void selecting_timeout(struct client_state_t cs[static 1],
} else if (client_config.abort_if_no_lease)
suicide("%s: No lease; failing.", client_config.interface);
}
if (num_dhcp_requests == 0)
if (cs->num_dhcp_requests == 0)
cs->xid = nk_random_u32(&cs->rnd32_state);
int r = send_discover(cs);
if (r < 0) {
log_warning("%s: Failed to send a discover request packet.",
client_config.interface);
SUSPEND_IF_NOCARRIER();
return SEL_FAIL;
}
dhcp_wake_ts = nowts + delay_timeout(cs, num_dhcp_requests);
num_dhcp_requests++;
cs->dhcp_wake_ts = nowts + delay_timeout(cs, cs->num_dhcp_requests);
cs->num_dhcp_requests++;
return SEL_SUCCESS;
}
static void xmit_release(struct client_state_t cs[static 1])
// Called for a release signal during SELECTING or REQUESTING.
static int print_release(struct client_state_t cs[static 1])
{
log_line("%s: ndhc going to sleep. Wake it by sending a SIGUSR1.",
client_config.interface);
if (reinit_shared_deconfig(cs) < 0)
return -1;
cs->dhcp_wake_ts = -1;
stop_dhcp_listen(cs);
return 0;
}
// Called for a release signal during BOUND, RENEWING, or REBINDING.
static int xmit_release(struct client_state_t cs[static 1])
{
char clibuf[INET_ADDRSTRLEN];
char svrbuf[INET_ADDRSTRLEN];
@ -402,101 +366,326 @@ static void xmit_release(struct client_state_t cs[static 1])
if (r < 0) {
log_warning("%s: Failed to send a release request packet.",
client_config.interface);
SUSPEND_IF_NOCARRIER();
return -1;
}
print_release(cs);
return 0;
}
static void print_release(struct client_state_t cs[static 1])
// Called for a renewing signal during BOUND or RELEASED
static int frenew(struct client_state_t cs[static 1], bool is_bound)
{
log_line("%s: ndhc going to sleep. Wake it by sending a SIGUSR1.",
client_config.interface);
set_released(cs);
}
static void frenew(struct client_state_t cs[static 1])
{
if (cs->dhcpState == DS_BOUND) {
if (is_bound) {
log_line("%s: Forcing a DHCP renew...", client_config.interface);
cs->dhcpState = DS_RENEWING;
start_dhcp_listen(cs);
int r = send_renew(cs);
if (r < 0) {
log_warning("%s: Failed to send a renew request packet.",
client_config.interface);
SUSPEND_IF_NOCARRIER();
return -1;
}
} else if (cs->dhcpState == DS_RELEASED)
reinit_selecting(cs, 0);
} else { // RELEASED
if (reinit_selecting(cs, 0) < 0)
return -1;
}
return 0;
}
void ifup_action(struct client_state_t cs[static 1])
// If we have a lease, check to see if our gateway is still valid via ARP.
// If it fails, state -> SELECTING.
static int ifup_action(struct client_state_t cs[static 1])
{
// If we have a lease, check to see if our gateway is still valid via ARP.
// If it fails, state -> SELECTING.
if (cs->routerAddr && (cs->dhcpState == DS_BOUND ||
cs->dhcpState == DS_RENEWING ||
cs->dhcpState == DS_REBINDING)) {
if (cs->routerAddr && cs->serverAddr) {
int r = arp_gw_check(cs);
if (r >= 0) {
log_line("%s: Interface is back. Revalidating lease...",
client_config.interface);
return;
return IFUP_REVALIDATE;
} else {
SUSPEND_IF_NOCARRIER();
log_warning("%s: arp_gw_check could not make arp socket.",
client_config.interface);
return IFUP_FAIL;
}
}
if (cs->dhcpState == DS_SELECTING)
return;
log_line("%s: Interface is back. Searching for new lease...",
client_config.interface);
reinit_selecting(cs, 0);
return IFUP_NEWLEASE;
}
void ifdown_action(struct client_state_t cs[static 1])
#define BAD_STATE() suicide("%s(%d): bad state", __func__, __LINE__)
// XXX: Should be re-entrant so as to handle multiple servers.
int dhcp_handle(struct client_state_t cs[static 1], long long nowts,
int sev_dhcp, struct dhcpmsg dhcp_packet[static 1],
uint8_t dhcp_msgtype, uint32_t dhcp_srcaddr, int sev_arp,
bool force_fingerprint, bool dhcp_timeout, bool arp_timeout)
{
log_line("%s: Interface shut down. Going to sleep.",
client_config.interface);
set_released(cs);
scrBegin;
reinit:
// We're in the SELECTING state here.
for (;;) {
int ret = COR_SUCCESS;
if (sev_dhcp) {
int r = selecting_packet(cs, dhcp_packet, dhcp_msgtype,
dhcp_srcaddr);
if (r == ANP_SUCCESS) {
// Send a request packet to the answering DHCP server.
sev_dhcp = false;
goto skip_to_requesting;
}
}
if (dhcp_timeout) {
int r = selecting_timeout(cs, nowts);
if (r == SEL_SUCCESS) {
} else if (r == SEL_FAIL) {
ret = COR_ERROR;
} else BAD_STATE();
}
scrReturn(ret);
}
scrReturn(COR_SUCCESS);
// We're in the REQUESTING state here.
for (;;) {
int ret;
skip_to_requesting:
ret = COR_SUCCESS;
if (sev_dhcp) {
int r = an_packet(cs, dhcp_packet, dhcp_msgtype, dhcp_srcaddr,
true);
if (r == ANP_IGNORE) {
} else if (r == ANP_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else if (r == ANP_REJECTED) {
sev_dhcp = false;
goto reinit;
} else if (r == ANP_CHECK_IP) {
if (arp_check(cs, dhcp_packet) < 0) {
log_warning("%s: Failed to make arp socket. Searching for new lease...",
client_config.interface);
if (reinit_selecting(cs, 3000) < 0) {
scrReturn(COR_ERROR);
goto reinit;
}
sev_dhcp = false;
goto reinit;
}
break;
} else BAD_STATE();
}
if (dhcp_timeout) {
// Send a request packet to the answering DHCP server.
int r = requesting_timeout(cs, nowts);
if (r == REQ_SUCCESS) {
} else if (r == REQ_TIMEOUT) {
// We timed out. Send another packet.
sev_dhcp = false;
goto reinit;
} else if (r == REQ_FAIL) {
// Failed to send packet. Sleep and retry.
ret = COR_ERROR;
} else BAD_STATE();
}
scrReturn(ret);
}
scrReturn(COR_SUCCESS);
// We're checking to see if there's a conflict for our IP. Technically,
// this is still in REQUESTING.
for (;;) {
int ret;
ret = COR_SUCCESS;
if (sev_dhcp) {
// XXX: Maybe I can think of something to do here. Would
// be more relevant if we tracked multiple dhcp servers.
}
if (sev_arp) {
int r = arp_do_collision_check(cs);
if (r == ARPR_OK) {
} else if (r == ARPR_CONFLICT) {
// XXX: If we tracked multiple DHCP servers, then we
// could fall back on another one.
if (reinit_selecting(cs, 0) < 0) {
ret = COR_ERROR;
scrReturn(ret);
}
sev_dhcp = false;
goto reinit;
} else if (r == ARPR_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
}
if (arp_timeout) {
int r = arp_collision_timeout(cs, nowts);
if (r == ARPR_FREE) {
break;
} else if (r == ARPR_OK) {
} else if (r == ARPR_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
}
if (dhcp_timeout) {
// Send a request packet to the answering DHCP server.
int r = requesting_timeout(cs, nowts);
if (r == REQ_SUCCESS) {
} else if (r == REQ_TIMEOUT) {
// We timed out. Send another packet.
sev_dhcp = false;
goto reinit;
} else if (r == REQ_FAIL) {
// Failed to send packet. Sleep and retry.
ret = COR_ERROR;
} else BAD_STATE();
}
scrReturn(ret);
}
scrReturn(COR_SUCCESS);
// We're in the BOUND, RENEWING, or REBINDING states here.
for (;;) {
int ret = COR_SUCCESS;
if (sev_dhcp && is_renewing(cs, nowts)) {
int r = an_packet(cs, dhcp_packet, dhcp_msgtype, dhcp_srcaddr,
false);
if (r == ANP_SUCCESS || r == ANP_IGNORE) {
} else if (r == ANP_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else if (r == ANP_REJECTED) {
sev_dhcp = false;
goto reinit;
} else if (r == ANP_CHECK_IP) {
if (arp_check(cs, dhcp_packet) < 0) {
log_warning("%s: Failed to make arp socket. Searching for new lease...",
client_config.interface);
if (reinit_selecting(cs, 3000) < 0) {
ret = COR_ERROR;
scrReturn(ret);
}
sev_dhcp = false;
goto reinit;
}
} else BAD_STATE();
}
if (sev_arp) {
int r;
r = arp_do_defense(cs);
if (r == ARPR_OK) {
} else if (r == ARPR_CONFLICT) {
if (reinit_selecting(cs, 0) < 0) {
ret = COR_ERROR;
scrReturn(ret);
}
sev_dhcp = false;
goto reinit;
} else if (r == ARPR_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
if (!cs->got_router_arp || !cs->got_server_arp) {
r = arp_do_gw_query(cs);
if (r == ARPR_OK) {
} else if (r == ARPR_FREE) {
// We got both ARP addresses.
} else if (r == ARPR_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
} else if (cs->check_fingerprint) {
r = arp_do_gw_check(cs);
if (r == ARPR_OK) {
} else if (r == ARPR_FREE) {
cs->check_fingerprint = false;
} else if (r == ARPR_CONFLICT) {
cs->check_fingerprint = false;
if (reinit_selecting(cs, 0) < 0) {
ret = COR_ERROR;
scrReturn(ret);
}
sev_dhcp = false;
goto reinit;
} else if (r == ARPR_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
}
}
if (arp_timeout) {
arp_defense_timeout(cs, nowts);
if (!cs->got_router_arp || !cs->got_server_arp) {
int r = arp_gw_query_timeout(cs, nowts);
if (r == ARPR_OK) {
} else if (r == ARPR_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
} else if (cs->check_fingerprint) {
int r = arp_gw_check_timeout(cs, nowts);
if (r == ARPR_OK) {
} else if (r == ARPR_CONFLICT) {
cs->check_fingerprint = false;
if (reinit_selecting(cs, 0) < 0) {
ret = COR_ERROR;
scrReturn(ret);
}
sev_dhcp = false;
goto reinit;
} else if (r == ARPR_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
}
}
if (force_fingerprint) {
int r = ifup_action(cs);
if (r == IFUP_REVALIDATE) {
} else if (r == IFUP_NEWLEASE) {
if (reinit_selecting(cs, 0) < 0) {
ret = COR_ERROR;
scrReturn(ret);
}
sev_dhcp = false;
goto reinit;
} else if (r == IFUP_FAIL) {
ret = COR_ERROR;
scrReturn(ret);
continue;
} else BAD_STATE();
}
if (dhcp_timeout) {
int r;
if (is_rebinding(cs, nowts)) {
r = rebinding_timeout(cs, nowts);
} else if (is_renewing(cs, nowts)) {
r = renewing_timeout(cs, nowts);
} else {
r = bound_timeout(cs, nowts);
}
if (r == BTO_WAIT) {
} else if (r == BTO_EXPIRED) {
sev_dhcp = false;
goto reinit;
} else if (r == BTO_HARDFAIL) {
ret = COR_ERROR;
} else
BAD_STATE();
}
scrReturn(ret);
}
sev_dhcp = false;
goto reinit;
scrFinish(COR_SUCCESS);
// XXX: xmit_release -> acquire_lease
// XXX: Continue to clean up the ARP code.
}
void ifnocarrier_action(struct client_state_t cs[static 1])
{
(void)cs;
log_line("%s: Carrier down.", client_config.interface);
}
void packet_action(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1], uint8_t msgtype,
uint32_t srcaddr)
{
if (dhcp_states[cs->dhcpState].packet_fn)
dhcp_states[cs->dhcpState].packet_fn(cs, packet, msgtype, srcaddr);
}
void timeout_action(struct client_state_t cs[static 1], long long nowts)
{
handle_arp_timeout(cs, nowts);
if (dhcp_states[cs->dhcpState].timeout_fn)
dhcp_states[cs->dhcpState].timeout_fn(cs, nowts);
}
void force_renew_action(struct client_state_t cs[static 1])
{
if (dhcp_states[cs->dhcpState].force_renew_fn)
dhcp_states[cs->dhcpState].force_renew_fn(cs);
}
void force_release_action(struct client_state_t cs[static 1])
{
if (dhcp_states[cs->dhcpState].force_release_fn)
dhcp_states[cs->dhcpState].force_release_fn(cs);
}
long long dhcp_get_wake_ts(void)
{
return dhcp_wake_ts;
}

View File

@ -31,31 +31,13 @@
#include "ndhc.h"
#include "dhcp.h"
typedef enum {
DS_SELECTING = 0,
DS_REQUESTING,
DS_BOUND,
DS_RENEWING,
DS_REBINDING,
DS_BOUND_GW_CHECK,
DS_COLLISION_CHECK,
DS_RELEASED,
DS_NUM_STATES,
} dhcp_states_t;
#define COR_SUCCESS 0
#define COR_ERROR -1
int reinit_selecting(struct client_state_t cs[static 1], int timeout);
void packet_action(struct client_state_t cs[static 1],
struct dhcpmsg packet[static 1], uint8_t msgtype,
uint32_t srcaddr);
void timeout_action(struct client_state_t cs[static 1], long long nowts);
void force_renew_action(struct client_state_t cs[static 1]);
void force_release_action(struct client_state_t cs[static 1]);
void ifup_action(struct client_state_t cs[static 1]);
void ifnocarrier_action(struct client_state_t cs[static 1]);
void ifdown_action(struct client_state_t cs[static 1]);
long long dhcp_get_wake_ts(void);
int dhcp_handle(struct client_state_t cs[static 1], long long nowts,
int sev_dhcp, struct dhcpmsg dhcp_packet[static 1],
uint8_t dhcp_msgtype, uint32_t dhcp_srcaddr, int sev_arp,
bool force_fingerprint, bool dhcp_timeout, bool arp_timeout);
#endif