Rather than use a single cs.timeout value, track individual timeouts for every
ARP state and for the current DHCP state.
This commit is contained in:
parent
8c30c82240
commit
9b3e1872cb
107
ndhc/arp.c
107
ndhc/arp.c
@ -20,6 +20,7 @@
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/if_ether.h>
|
||||
@ -66,6 +67,8 @@ typedef enum {
|
||||
AS_MAX,
|
||||
} arp_state_t;
|
||||
|
||||
static int arp_timeout[AS_MAX] = { -1, -1, -1, -1, -1 };
|
||||
|
||||
typedef enum {
|
||||
ASEND_COLLISION_CHECK,
|
||||
ASEND_GW_PING,
|
||||
@ -83,15 +86,14 @@ static int using_arp_bpf; // Is a BPF installed on the ARP socket?
|
||||
|
||||
int arp_relentless_def; // Don't give up defense no matter what.
|
||||
static long long last_conflict_ts; // TS of the last conflicting ARP seen.
|
||||
static long long pending_def_ts; // TS of the upcoming defense ARP send.
|
||||
static int def_old_oldTimeout; // Old value of cs->oldTimeout.
|
||||
|
||||
static int gw_check_init_pingcount; // Initial count of ASEND_GW_PING when
|
||||
// AS_GW_CHECK was entered.
|
||||
|
||||
static long long collision_check_init_ts; // Initial ts when COLLISION_CHECK
|
||||
// was entered.
|
||||
static uint16_t probe_wait_time; // Time to wait for a COLLISION_CHECK reply.
|
||||
static long long arp_check_start_ts; // TS of when we started the
|
||||
// AS_COLLISION_CHECK state.
|
||||
|
||||
static unsigned int total_conflicts; // Total number of address conflicts on
|
||||
// the interface. Never decreases.
|
||||
|
||||
@ -256,7 +258,7 @@ static void arp_switch_state(struct client_state_t *cs, arp_state_t state)
|
||||
}
|
||||
}
|
||||
|
||||
int arp_close_fd(struct client_state_t *cs)
|
||||
static int arp_min_close_fd(struct client_state_t *cs)
|
||||
{
|
||||
if (cs->arpFd == -1)
|
||||
return 0;
|
||||
@ -267,10 +269,18 @@ int arp_close_fd(struct client_state_t *cs)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int arp_close_fd(struct client_state_t *cs)
|
||||
{
|
||||
arp_min_close_fd(cs);
|
||||
for (int i = 0; i < AS_MAX; ++i)
|
||||
arp_timeout[i] = -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int arp_reopen_fd(struct client_state_t *cs)
|
||||
{
|
||||
arp_state_t prev_state = arpState;
|
||||
arp_close_fd(cs);
|
||||
arp_min_close_fd(cs);
|
||||
if (arp_open_fd(cs) == -1) {
|
||||
log_warning("arp_reopen_fd: Failed to open. Something is very wrong.");
|
||||
log_warning("arp_reopen_fd: Client will still run, but functionality will be degraded.");
|
||||
@ -331,6 +341,7 @@ static int arp_ip_anon_ping(struct client_state_t *cs, uint32_t test_ip)
|
||||
{
|
||||
BASE_ARPMSG();
|
||||
memcpy(arp.dip4, &test_ip, sizeof test_ip);
|
||||
log_line("arp: Probing for hosts that may conflict with our lease...");
|
||||
if (arp_send(cs, &arp) == -1)
|
||||
return -1;
|
||||
arp_send_stats[ASEND_COLLISION_CHECK].count++;
|
||||
@ -356,13 +367,12 @@ int arp_check(struct client_state_t *cs, struct dhcpmsg *packet)
|
||||
{
|
||||
memcpy(&arp_dhcp_packet, packet, sizeof (struct dhcpmsg));
|
||||
arp_switch_state(cs, AS_COLLISION_CHECK);
|
||||
log_line("arp: Probing for hosts that may conflict with our lease...");
|
||||
if (arp_ip_anon_ping(cs, arp_dhcp_packet.yiaddr) == -1)
|
||||
return -1;
|
||||
cs->arpPrevState = cs->dhcpState;
|
||||
cs->dhcpState = DS_COLLISION_CHECK;
|
||||
collision_check_init_ts = arp_send_stats[ASEND_COLLISION_CHECK].ts;
|
||||
cs->timeout = probe_wait_time = PROBE_WAIT;
|
||||
arp_check_start_ts = arp_send_stats[ASEND_COLLISION_CHECK].ts;
|
||||
arp_timeout[AS_COLLISION_CHECK] = probe_wait_time = PROBE_WAIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -377,8 +387,7 @@ int arp_gw_check(struct client_state_t *cs)
|
||||
arp_switch_state(cs, AS_GW_CHECK);
|
||||
cs->arpPrevState = cs->dhcpState;
|
||||
cs->dhcpState = DS_BOUND_GW_CHECK;
|
||||
cs->oldTimeout = cs->timeout;
|
||||
cs->timeout = ARP_RETRANS_DELAY + 250;
|
||||
arp_timeout[AS_GW_CHECK] = ARP_RETRANS_DELAY + 250;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -390,8 +399,7 @@ static int arp_get_gw_hwaddr(struct client_state_t *cs)
|
||||
log_line("arp: Searching for gw address...");
|
||||
if (arp_ping(cs, cs->routerAddr) == -1)
|
||||
return -1;
|
||||
cs->oldTimeout = cs->timeout;
|
||||
cs->timeout = ARP_RETRANS_DELAY + 250;
|
||||
arp_timeout[AS_GW_QUERY] = ARP_RETRANS_DELAY + 250;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -399,6 +407,7 @@ static void arp_failed(struct client_state_t *cs)
|
||||
{
|
||||
log_line("arp: Offered address is in use -- declining");
|
||||
send_decline(cs, arp_dhcp_packet.yiaddr);
|
||||
arp_timeout[AS_COLLISION_CHECK] = -1;
|
||||
reinit_selecting(cs, total_conflicts < MAX_CONFLICTS ?
|
||||
0 : RATE_LIMIT_INTERVAL);
|
||||
}
|
||||
@ -406,7 +415,7 @@ static void arp_failed(struct client_state_t *cs)
|
||||
static void arp_gw_failed(struct client_state_t *cs)
|
||||
{
|
||||
log_line("arp: Gateway appears to have changed, getting new lease.");
|
||||
cs->oldTimeout = 0;
|
||||
arp_timeout[AS_GW_CHECK] = -1;
|
||||
reinit_selecting(cs, 0);
|
||||
}
|
||||
|
||||
@ -426,7 +435,7 @@ void arp_set_defense_mode(struct client_state_t *cs)
|
||||
|
||||
void arp_success(struct client_state_t *cs)
|
||||
{
|
||||
cs->timeout = (cs->renewTime * 1000) - (curms() - cs->leaseStartTime);
|
||||
cs->dhcp_timeout = (cs->renewTime * 1000) - (curms() - cs->leaseStartTime);
|
||||
|
||||
struct in_addr temp_addr = {.s_addr = arp_dhcp_packet.yiaddr};
|
||||
log_line("arp: Lease of %s obtained, lease time %ld",
|
||||
@ -435,8 +444,7 @@ void arp_success(struct client_state_t *cs)
|
||||
cs->dhcpState = DS_BOUND;
|
||||
cs->init = 0;
|
||||
last_conflict_ts = 0;
|
||||
pending_def_ts = 0;
|
||||
def_old_oldTimeout = 0;
|
||||
arp_timeout[AS_COLLISION_CHECK] = -1;
|
||||
ifchange_bind(&arp_dhcp_packet);
|
||||
if (cs->arpPrevState == DS_RENEWING || cs->arpPrevState == DS_REBINDING) {
|
||||
arp_switch_state(cs, AS_DEFENSE);
|
||||
@ -464,7 +472,7 @@ static void arp_gw_success(struct client_state_t *cs)
|
||||
arp_switch_state(cs, AS_DEFENSE);
|
||||
arp_announcement(cs);
|
||||
|
||||
cs->timeout = cs->oldTimeout;
|
||||
arp_timeout[AS_GW_CHECK] = -1;
|
||||
cs->dhcpState = cs->arpPrevState;
|
||||
}
|
||||
|
||||
@ -526,15 +534,10 @@ static int arp_gen_probe_wait(void)
|
||||
|
||||
static void arp_defense_timeout(struct client_state_t *cs, long long nowts)
|
||||
{
|
||||
if (pending_def_ts) {
|
||||
if (arp_timeout[AS_DEFENSE] != -1) {
|
||||
log_line("arp: Defending our lease IP.");
|
||||
pending_def_ts = 0;
|
||||
arp_announcement(cs);
|
||||
if (def_old_oldTimeout) {
|
||||
cs->timeout = cs->oldTimeout;
|
||||
cs->oldTimeout = def_old_oldTimeout;
|
||||
def_old_oldTimeout = 0;
|
||||
}
|
||||
arp_timeout[AS_DEFENSE] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -546,7 +549,7 @@ static void arp_check_timeout(struct client_state_t *cs, long long nowts)
|
||||
return;
|
||||
long long rtts = arp_send_stats[ASEND_GW_PING].ts + ARP_RETRANS_DELAY;
|
||||
if (nowts < rtts) {
|
||||
cs->timeout = rtts - nowts;
|
||||
arp_timeout[arpState] = rtts - nowts;
|
||||
return;
|
||||
}
|
||||
log_line(arpState == AS_GW_CHECK ?
|
||||
@ -554,14 +557,14 @@ static void arp_check_timeout(struct client_state_t *cs, long long nowts)
|
||||
"arp: Still looking for gateway hardware address...");
|
||||
if (arp_ping(cs, cs->routerAddr) == -1)
|
||||
log_warning("arp: Failed to send ARP ping in retransmission.");
|
||||
cs->timeout = ARP_RETRANS_DELAY;
|
||||
arp_timeout[arpState] = ARP_RETRANS_DELAY;
|
||||
}
|
||||
|
||||
static void arp_collision_timeout(struct client_state_t *cs, long long nowts)
|
||||
{
|
||||
arp_defense_timeout(cs, nowts);
|
||||
|
||||
if (nowts >= collision_check_init_ts + ANNOUNCE_WAIT ||
|
||||
if (nowts >= arp_check_start_ts + ANNOUNCE_WAIT ||
|
||||
arp_send_stats[ASEND_COLLISION_CHECK].count >= PROBE_NUM) {
|
||||
arp_success(cs);
|
||||
return;
|
||||
@ -569,21 +572,21 @@ static void arp_collision_timeout(struct client_state_t *cs, long long nowts)
|
||||
long long rtts = arp_send_stats[ASEND_COLLISION_CHECK].ts +
|
||||
probe_wait_time;
|
||||
if (nowts < rtts) {
|
||||
cs->timeout = rtts - nowts;
|
||||
arp_timeout[AS_COLLISION_CHECK] = rtts - nowts;
|
||||
return;
|
||||
}
|
||||
log_line("arp: Probing for hosts that may conflict with our lease...");
|
||||
if (arp_ip_anon_ping(cs, arp_dhcp_packet.yiaddr) == -1)
|
||||
log_warning("arp: Failed to send ARP ping in retransmission.");
|
||||
cs->timeout = probe_wait_time = arp_gen_probe_wait();
|
||||
arp_timeout[AS_COLLISION_CHECK] = probe_wait_time = arp_gen_probe_wait();
|
||||
}
|
||||
|
||||
static void arp_do_defense(struct client_state_t *cs)
|
||||
{
|
||||
log_line("arp: detected a peer attempting to use our IP!");
|
||||
long long cur_conflict_ts = curms();
|
||||
long long nowts = curms();
|
||||
arp_timeout[AS_DEFENSE] = -1;
|
||||
if (!last_conflict_ts ||
|
||||
cur_conflict_ts - last_conflict_ts < DEFEND_INTERVAL) {
|
||||
nowts - last_conflict_ts < DEFEND_INTERVAL) {
|
||||
log_line("arp: Defending our lease IP.");
|
||||
arp_announcement(cs);
|
||||
} else if (!arp_relentless_def) {
|
||||
@ -591,23 +594,18 @@ static void arp_do_defense(struct client_state_t *cs)
|
||||
send_release(cs);
|
||||
reinit_selecting(cs, 0);
|
||||
} else {
|
||||
pending_def_ts = arp_send_stats[ASEND_ANNOUNCE].ts + DEFEND_INTERVAL;
|
||||
long long def_xmit_ts = pending_def_ts - cur_conflict_ts;
|
||||
if (!cs->timeout || cs->timeout > def_xmit_ts) {
|
||||
def_old_oldTimeout = cs->oldTimeout;
|
||||
cs->oldTimeout = cs->timeout;
|
||||
cs->timeout = def_xmit_ts;
|
||||
}
|
||||
arp_timeout[AS_DEFENSE] =
|
||||
arp_send_stats[ASEND_ANNOUNCE].ts + DEFEND_INTERVAL - nowts;
|
||||
}
|
||||
total_conflicts++;
|
||||
last_conflict_ts = cur_conflict_ts;
|
||||
last_conflict_ts = nowts;
|
||||
}
|
||||
|
||||
static void arp_do_gw_query(struct client_state_t *cs)
|
||||
{
|
||||
if (arp_is_query_reply(&arpreply) &&
|
||||
!memcmp(arpreply.sip4, &cs->routerAddr, 4)) {
|
||||
cs->timeout = cs->oldTimeout;
|
||||
arp_timeout[AS_GW_QUERY] = -1;
|
||||
memcpy(cs->routerArp, arpreply.smac, 6);
|
||||
log_line("arp: Gateway hardware address %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
cs->routerArp[0], cs->routerArp[1],
|
||||
@ -715,3 +713,28 @@ void handle_arp_timeout(struct client_state_t *cs, long long nowts)
|
||||
if (arp_states[arpState].timeout_fn)
|
||||
arp_states[arpState].timeout_fn(cs, nowts);
|
||||
}
|
||||
|
||||
int get_arp_timeout(void)
|
||||
{
|
||||
int mt = INT_MAX;
|
||||
for (int i = 0; i < AS_MAX; ++i) {
|
||||
log_line("DEBUG: arp_timeout[%u] == %d", i, arp_timeout[i]);
|
||||
if (arp_timeout[i] < mt && arp_timeout[i] >= 0)
|
||||
mt = arp_timeout[i];
|
||||
}
|
||||
if (mt == INT_MAX)
|
||||
return -1;
|
||||
return mt;
|
||||
}
|
||||
|
||||
void arp_timeout_adj(int off)
|
||||
{
|
||||
for (int i = 0; i < AS_MAX; ++i) {
|
||||
if (arp_timeout[i] == -1)
|
||||
continue;
|
||||
arp_timeout[i] -= off;
|
||||
if (arp_timeout[i] < 0)
|
||||
arp_timeout[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,5 +55,7 @@ void arp_set_defense_mode(struct client_state_t *cs);
|
||||
void arp_success(struct client_state_t *cs);
|
||||
void handle_arp_response(struct client_state_t *cs);
|
||||
void handle_arp_timeout(struct client_state_t *cs, long long nowts);
|
||||
int get_arp_timeout(void);
|
||||
void arp_timeout_adj(int off);
|
||||
|
||||
#endif /* ARP_H_ */
|
||||
|
@ -30,7 +30,7 @@ struct client_state_t {
|
||||
int ifsPrevState;
|
||||
int listenMode;
|
||||
int epollFd, signalFd, listenFd, arpFd, nlFd;
|
||||
int timeout, oldTimeout;
|
||||
int dhcp_timeout;
|
||||
uint32_t clientAddr, serverAddr, routerAddr;
|
||||
uint32_t lease, renewTime, rebindTime, xid;
|
||||
int using_dhcp_bpf;
|
||||
|
41
ndhc/ndhc.c
41
ndhc/ndhc.c
@ -162,7 +162,7 @@ static void do_work(void)
|
||||
{
|
||||
struct epoll_event events[3];
|
||||
long long last_awake, nowts;
|
||||
int timeout_delta;
|
||||
int timeout, timeout_delta;
|
||||
|
||||
cs.epollFd = epoll_create1(0);
|
||||
if (cs.epollFd == -1)
|
||||
@ -170,17 +170,19 @@ static void do_work(void)
|
||||
setup_signals(&cs);
|
||||
epoll_add(&cs, cs.nlFd);
|
||||
set_listen_raw(&cs);
|
||||
last_awake = curms();
|
||||
timeout_action(&cs, last_awake);
|
||||
nowts = curms();
|
||||
goto jumpstart;
|
||||
|
||||
for (;;) {
|
||||
int r = epoll_wait(cs.epollFd, events, 3, cs.timeout);
|
||||
log_line("DEBUG: before epoll_wait()");
|
||||
int r = epoll_wait(cs.epollFd, events, 3, timeout);
|
||||
if (r == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
suicide("epoll_wait failed");
|
||||
}
|
||||
log_line("DEBUG: after epoll_wait()");
|
||||
for (int i = 0; i < r; ++i) {
|
||||
int fd = events[i].data.fd;
|
||||
if (fd == cs.signalFd)
|
||||
@ -197,10 +199,35 @@ static void do_work(void)
|
||||
|
||||
nowts = curms();
|
||||
timeout_delta = nowts - last_awake;
|
||||
cs.timeout -= timeout_delta;
|
||||
if (cs.timeout <= 0) {
|
||||
cs.timeout = 0;
|
||||
|
||||
cs.dhcp_timeout -= timeout_delta;
|
||||
if (cs.dhcp_timeout < 0)
|
||||
cs.dhcp_timeout = 0;
|
||||
arp_timeout_adj(timeout_delta);
|
||||
|
||||
int arp_timeout = get_arp_timeout();
|
||||
log_line("DEBUG: arp_timeout = %d, dhcp_timeout = %d",
|
||||
arp_timeout, cs.dhcp_timeout);
|
||||
if (arp_timeout == -1)
|
||||
timeout = cs.dhcp_timeout;
|
||||
else if (arp_timeout < cs.dhcp_timeout)
|
||||
timeout = arp_timeout;
|
||||
else
|
||||
timeout = cs.dhcp_timeout;
|
||||
|
||||
if (timeout <= 0) {
|
||||
jumpstart:
|
||||
timeout_action(&cs, nowts);
|
||||
|
||||
int arp_timeout = get_arp_timeout();
|
||||
log_line("DEBUG: arp_timeout = %d, dhcp_timeout = %d",
|
||||
arp_timeout, cs.dhcp_timeout);
|
||||
if (arp_timeout == -1)
|
||||
timeout = cs.dhcp_timeout;
|
||||
else if (arp_timeout < cs.dhcp_timeout)
|
||||
timeout = arp_timeout;
|
||||
else
|
||||
timeout = cs.dhcp_timeout;
|
||||
}
|
||||
last_awake = nowts;
|
||||
}
|
||||
|
24
ndhc/state.c
24
ndhc/state.c
@ -62,7 +62,7 @@ void reinit_selecting(struct client_state_t *cs, int timeout)
|
||||
ifchange_deconfig();
|
||||
arp_close_fd(cs);
|
||||
cs->dhcpState = DS_SELECTING;
|
||||
cs->timeout = timeout;
|
||||
cs->dhcp_timeout = timeout;
|
||||
cs->clientAddr = 0;
|
||||
num_dhcp_requests = 0;
|
||||
arp_reset_send_stats();
|
||||
@ -74,7 +74,7 @@ static void set_released(struct client_state_t *cs)
|
||||
ifchange_deconfig();
|
||||
arp_close_fd(cs);
|
||||
cs->dhcpState = DS_RELEASED;
|
||||
cs->timeout = -1;
|
||||
cs->dhcp_timeout = -1;
|
||||
cs->clientAddr = 0;
|
||||
num_dhcp_requests = 0;
|
||||
arp_reset_send_stats();
|
||||
@ -89,7 +89,7 @@ static void requesting_timeout(struct client_state_t *cs, long long nowts)
|
||||
{
|
||||
if (num_dhcp_requests < 5) {
|
||||
send_selecting(cs);
|
||||
cs->timeout = delay_timeout(num_dhcp_requests);
|
||||
cs->dhcp_timeout = delay_timeout(num_dhcp_requests);
|
||||
num_dhcp_requests++;
|
||||
} else
|
||||
reinit_selecting(cs, 0);
|
||||
@ -101,7 +101,7 @@ static void bound_timeout(struct client_state_t *cs, long long nowts)
|
||||
{
|
||||
long long rnt = cs->leaseStartTime + cs->renewTime * 1000;
|
||||
if (nowts < rnt) {
|
||||
cs->timeout = rnt - nowts;
|
||||
cs->dhcp_timeout = rnt - nowts;
|
||||
return;
|
||||
}
|
||||
cs->dhcpState = DS_RENEWING;
|
||||
@ -131,13 +131,13 @@ static void renewing_timeout(struct client_state_t *cs, long long nowts)
|
||||
send_renew(cs);
|
||||
else
|
||||
wt = rbt - nowts;
|
||||
cs->timeout = wt;
|
||||
cs->dhcp_timeout = wt;
|
||||
return;
|
||||
}
|
||||
long long elt = cs->leaseStartTime + cs->lease * 1000;
|
||||
if (nowts < elt) {
|
||||
cs->dhcpState = DS_REBINDING;
|
||||
cs->timeout = (elt - nowts) / 2;
|
||||
cs->dhcp_timeout = (elt - nowts) / 2;
|
||||
log_line("Entering rebinding state.");
|
||||
} else
|
||||
lease_timedout(cs);
|
||||
@ -156,14 +156,14 @@ static void rebinding_timeout(struct client_state_t *cs, long long nowts)
|
||||
send_rebind(cs);
|
||||
else
|
||||
wt = elt - nowts;
|
||||
cs->timeout = wt;
|
||||
cs->dhcp_timeout = wt;
|
||||
} else
|
||||
lease_timedout(cs);
|
||||
}
|
||||
|
||||
static void released_timeout(struct client_state_t *cs, long long nowts)
|
||||
{
|
||||
cs->timeout = -1;
|
||||
cs->dhcp_timeout = -1;
|
||||
}
|
||||
|
||||
// Can transition to DS_BOUND or DS_SELECTING.
|
||||
@ -200,7 +200,7 @@ static void an_packet(struct client_state_t *cs, struct dhcpmsg *packet,
|
||||
}
|
||||
} else {
|
||||
cs->dhcpState = DS_BOUND;
|
||||
cs->timeout = cs->renewTime * 1000;
|
||||
cs->dhcp_timeout = cs->renewTime * 1000;
|
||||
arp_set_defense_mode(cs);
|
||||
set_listen_none(cs);
|
||||
}
|
||||
@ -222,7 +222,7 @@ static void selecting_packet(struct client_state_t *cs, struct dhcpmsg *packet,
|
||||
cs->xid = packet->xid;
|
||||
cs->clientAddr = packet->yiaddr;
|
||||
cs->dhcpState = DS_REQUESTING;
|
||||
cs->timeout = 0;
|
||||
cs->dhcp_timeout = 0;
|
||||
num_dhcp_requests = 0;
|
||||
} else {
|
||||
log_line("No server ID in message");
|
||||
@ -249,7 +249,7 @@ static void selecting_timeout(struct client_state_t *cs, long long nowts)
|
||||
if (num_dhcp_requests == 0)
|
||||
cs->xid = libc_random_u32();
|
||||
send_discover(cs);
|
||||
cs->timeout = delay_timeout(num_dhcp_requests);
|
||||
cs->dhcp_timeout = delay_timeout(num_dhcp_requests);
|
||||
num_dhcp_requests++;
|
||||
}
|
||||
|
||||
@ -318,9 +318,9 @@ void packet_action(struct client_state_t *cs, struct dhcpmsg *packet,
|
||||
|
||||
void timeout_action(struct client_state_t *cs, long long nowts)
|
||||
{
|
||||
handle_arp_timeout(cs, nowts);
|
||||
if (dhcp_states[cs->dhcpState].timeout_fn)
|
||||
dhcp_states[cs->dhcpState].timeout_fn(cs, nowts);
|
||||
handle_arp_timeout(cs, nowts);
|
||||
}
|
||||
|
||||
void force_renew_action(struct client_state_t *cs)
|
||||
|
Loading…
Reference in New Issue
Block a user