diff --git a/src/arp.c b/src/arp.c index 1f321ad..9f351c9 100644 --- a/src/arp.c +++ b/src/arp.c @@ -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; diff --git a/src/arp.h b/src/arp.h index 9e474e9..ba2c477 100644 --- a/src/arp.h +++ b/src/arp.h @@ -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_ */ diff --git a/src/ndhc.c b/src/ndhc.c index 6b8f0ca..83819b0 100644 --- a/src/ndhc.c +++ b/src/ndhc.c @@ -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; } } } diff --git a/src/ndhc.h b/src/ndhc.h index a669a89..6815441 100644 --- a/src/ndhc.h +++ b/src/ndhc.h @@ -29,24 +29,26 @@ #define NJK_NDHC_NDHC_H_ #include +#include #include #include #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 { diff --git a/src/netlink.c b/src/netlink.c index 8c9ded4..3244997 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -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; diff --git a/src/state.c b/src/state.c index 64c6143..d3f3cff 100644 --- a/src/state.c +++ b/src/state.c @@ -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; -} diff --git a/src/state.h b/src/state.h index 129219f..ea0c5d1 100644 --- a/src/state.h +++ b/src/state.h @@ -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