Skip to content

Commit

Permalink
distinguish selection and operational phase by state; decrease some t…
Browse files Browse the repository at this point in the history
…imeouts
  • Loading branch information
RalfJung committed Dec 4, 2017
1 parent 1e8ffe1 commit 9c85ff2
Showing 1 changed file with 100 additions and 71 deletions.
171 changes: 100 additions & 71 deletions client/l2tp_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,40 @@ enum l2tp_limit_type {
LIMIT_TYPE_BANDWIDTH_DOWN = 0x01
};

/* The state machine looks as follows:
STATE_REINIT (initial state)
When the FD is successfully initialized:
-> STATE_RESOLVING
When DNS resolving succeeds:
-> STATE_GET_USAGE (sending a usage and a cookie request every 2s)
when we receive usage information or a cookie
-> STATE_IDLE
Now broker selection is performed; for the selected broker the main loop changes the state so
that we go on:
-> STATE_GET_COOKIE (sending a cookie request every 2s)
when we receive the cookie
-> STATE_GET_TUNNEL
when we receive the tunnel information
-> STATE_KEEPALIVE
when the connection fails
-> STATE_FAILED
In case of an error, we transition to STATE_REINIT (if it happens early) or STATE_FAILED
(if it happens when we are already >= STATE_GET_COOKIE). The main loop restarts everything
once the selected broker enters STATE_FAILED.
For broken brokers, the main loop sets the state to STATE_FAILED to make sure that
they do not do anything.
*/
enum l2tp_ctrl_state {
STATE_REINIT,
STATE_RESOLVING,
STATE_GET_USAGE,
STATE_IDLE,
STATE_USAGE_SEND,
STATE_USAGE_WAIT,
STATE_GET_COOKIE,
STATE_GET_TUNNEL,
STATE_KEEPALIVE,
STATE_REINIT,
STATE_RESOLVING,
STATE_FAILED,
};

enum l2tp_session_features {
Expand Down Expand Up @@ -183,8 +208,7 @@ typedef struct {
// Tunnel uptime.
time_t tunnel_up_since;

// Should the context only be used as a standby context.
int standby_only;
// Whether we received usage information from the broker
int standby_available;

// PMTU probing.
Expand Down Expand Up @@ -426,7 +450,6 @@ int context_reinitialize(l2tp_context *ctx)
if (setsockopt(ctx->fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)) < 0)
return -1;

ctx->standby_only = 1;
ctx->standby_available = 0;
ctx->keepalive_seqno = 0;
ctx->reliable_seqno = 0;
Expand All @@ -443,11 +466,11 @@ int context_reinitialize(l2tp_context *ctx)

// Reset relevant timers.
time_t now = timer_now();
ctx->timer_usage = now;
ctx->timer_cookie = now;
ctx->timer_tunnel = now;
ctx->timer_usage = 0;
ctx->timer_cookie = 0;
ctx->timer_tunnel = 0;
ctx->timer_reinit = 0;
ctx->timer_keepalive = now;
ctx->timer_reinit = now;
ctx->timer_resolving = -1;

// PMTU discovery.
Expand Down Expand Up @@ -555,32 +578,32 @@ void context_process_control_packet(l2tp_context *ctx)
// Check packet type.
switch (type) {
case CONTROL_TYPE_USAGE: {
if (ctx->state == STATE_USAGE_WAIT) {
if (ctx->state == STATE_GET_USAGE) {
// Broker usage information.
ctx->usage = parse_u16(&buf);
syslog(LOG_DEBUG, "Broker usage of %s:%s: %u\n", ctx->broker_hostname, ctx->broker_port, ctx->usage);

// Mark the connection as being available for later establishment.
ctx->standby_available = 1;
ctx->timer_cookie = timer_now();
ctx->state = STATE_IDLE;
}
break;
}
case CONTROL_TYPE_COOKIE: {
if (ctx->state == STATE_GET_COOKIE) {
if (ctx->state == STATE_GET_USAGE || ctx->state == STATE_GET_COOKIE) {
if (payload_length != 8)
break;

memcpy(&ctx->cookie, buf, 8);

// Mark the connection as being available for later establishment.
ctx->standby_available = 1;

if (ctx->standby_only) // Inactive broker.
ctx->state = STATE_IDLE;
else // This broker is now active.
ctx->state = STATE_GET_TUNNEL;
if (ctx->state == STATE_GET_COOKIE) {
// Proceed building a tunnel.
ctx->state = STATE_GET_TUNNEL;
} else {
// State STATE_GET_USAGE. We are ready. Keep default usage (0xFFFF).
ctx->standby_available = 1;
ctx->state = STATE_IDLE;
}
}
break;
}
Expand All @@ -593,8 +616,7 @@ void context_process_control_packet(l2tp_context *ctx)
syslog(LOG_WARNING, "Received error response from broker with errorcode %d!", error_code);
else
syslog(LOG_WARNING, "Received error response from broker!");
// FIXME: is this really a good idea, to go into get cookie?
ctx->state = STATE_GET_COOKIE;
ctx->state = STATE_FAILED; // let the main loop restart everything
} else if (ctx->state == STATE_KEEPALIVE) {
if (payload_length > 0)
syslog(LOG_ERR, "Broker sent us a teardown request, closing tunnel with errorcode %d!", error_code);
Expand All @@ -616,8 +638,7 @@ void context_process_control_packet(l2tp_context *ctx)

if (context_setup_tunnel(ctx, remote_tunnel_id, server_features) < 0) {
syslog(LOG_ERR, "Unable to create local L2TP tunnel!");
// FIXME: is this really a good idea, to go into get cookie?
ctx->state = STATE_GET_COOKIE;
ctx->state = STATE_FAILED; // let the main loop restart everything
} else {
syslog(LOG_INFO, "Tunnel successfully established.");
context_call_hook(ctx, "session.up");
Expand Down Expand Up @@ -759,7 +780,7 @@ void context_send_raw_packet(l2tp_context *ctx, char *packet, uint8_t len)
// need to bind the socket again and re-initialize the context.
syslog(LOG_WARNING, "Failed to send() control packet, interface disappeared?");
syslog(LOG_WARNING, "Forcing tunnel reinitialization.");
ctx->state = STATE_REINIT;
ctx->state = STATE_FAILED;
break;
}
default: {
Expand Down Expand Up @@ -947,8 +968,20 @@ int context_setup_tunnel(l2tp_context *ctx, uint32_t peer_tunnel_id, uint32_t se
nlmsg_free(msg);

result = nl_wait_for_ack(ctx->nl_sock);
if (result < 0)
if (result < 0) {
// Make sure we delete the tunnel again
msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->nl_family, 0, NLM_F_REQUEST,
L2TP_CMD_TUNNEL_DELETE, L2TP_GENL_VERSION);

nla_put_u32(msg, L2TP_ATTR_CONN_ID, ctx->tunnel_id);

nl_send_auto_complete(ctx->nl_sock, msg);
nlmsg_free(msg);
nl_wait_for_ack(ctx->nl_sock);

return -1;
}

return 0;
}
Expand Down Expand Up @@ -998,10 +1031,10 @@ void context_close_tunnel(l2tp_context *ctx, uint8_t reason)
// Notify the broker that the tunnel has been closed.
context_send_packet(ctx, CONTROL_TYPE_ERROR, (char *) &reason, 1);

// Call down hook, delete the tunnel and set state to reinit.
// Call down hook, delete the tunnel and let the main loop restart everything.
context_call_hook(ctx, "session.down");
context_delete_tunnel(ctx);
ctx->state = STATE_REINIT;
ctx->state = STATE_FAILED;
}

void broker_select_one(broker_cfg one_broker)
Expand Down Expand Up @@ -1051,6 +1084,17 @@ void context_process(l2tp_context *ctx)
{
// Transmit packets if needed.
switch (ctx->state) {
case STATE_REINIT: {
if (is_timeout(&ctx->timer_reinit, 2)) {
syslog(LOG_INFO, "Reinitializing tunnel context.");
if (context_reinitialize(ctx) < 0) {
syslog(LOG_ERR, "Unable to reinitialize the context!");
}
}
if (ctx->state != STATE_RESOLVING)
break;
// Deliberate fall-through to STATE_RESOLVING
}
case STATE_RESOLVING: {
if (ctx->broker_resq == NULL)
context_start_connect(ctx);
Expand All @@ -1073,13 +1117,12 @@ void context_process(l2tp_context *ctx)
syslog(LOG_ERR, "Failed to connect to remote endpoint - check WAN connectivity!");
ctx->state = STATE_REINIT;
} else {
ctx->timer_usage = timer_now();
ctx->state = STATE_USAGE_SEND;
ctx->state = STATE_GET_USAGE;
}
asyncns_freeaddrinfo(result);
ctx->broker_resq = NULL;
}
} else if (is_timeout(&ctx->timer_resolving, 5)) {
} else if (is_timeout(&ctx->timer_resolving, 2)) {
syslog(LOG_ERR, "Hostname resolution timed out.");

if (ctx->broker_resq)
Expand All @@ -1088,18 +1131,19 @@ void context_process(l2tp_context *ctx)
ctx->state = STATE_REINIT;
return;
}
break;
}
case STATE_USAGE_SEND: {
context_send_usage_request(ctx);
ctx->timer_usage = timer_now();
ctx->state = STATE_USAGE_WAIT;
if (ctx->state == STATE_GET_USAGE) {
// Deliberate fall-through, let's get the usage ASAP.
} else {
break;
}
}
case STATE_USAGE_WAIT: {
// Get usage information if available.
if (is_timeout(&ctx->timer_usage, 2))
ctx->state = STATE_GET_COOKIE;
case STATE_GET_USAGE: {
if (is_timeout(&ctx->timer_usage, 2)) {
// Get usage information.
context_send_usage_request(ctx);
// Also ask for cookie, to provide compatibility with old brokers.
context_send_packet(ctx, CONTROL_TYPE_COOKIE, "XXXXXXXX", 8);
}
break;
}
case STATE_GET_COOKIE: {
Expand Down Expand Up @@ -1190,18 +1234,8 @@ void context_process(l2tp_context *ctx)
}
break;
}
case STATE_REINIT: {
if (is_timeout(&ctx->timer_reinit, 15)) {
syslog(LOG_INFO, "Reinitializing tunnel context.");
if (context_reinitialize(ctx) < 0) {
syslog(LOG_ERR, "Unable to reinitialize the context!");
} else {
context_start_connect(ctx);
}
}
break;
}
case STATE_IDLE: {
case STATE_IDLE:
case STATE_FAILED: {
break;
}
}
Expand Down Expand Up @@ -1402,13 +1436,11 @@ int main(int argc, char **argv)
if (working_brokers == 0) {
brokers[i].broken = 0;
}
// Start hostname resolution and connect process.
if (!brokers[i].broken) {
context_start_connect(brokers[i].ctx);
} else {
if (brokers[i].broken) {
// Inhibit hostname resolution and connect process.
syslog(LOG_INFO, "Not trying %s:%s again as it broke last time we tried.",
brokers[i].address, brokers[i].port);
brokers[i].ctx->state = STATE_IDLE;
brokers[i].ctx->state = STATE_FAILED;
}
}
// Adapt working_brokers, needs updating if we re-enabled brokers
Expand All @@ -1417,7 +1449,7 @@ int main(int argc, char **argv)
working_brokers = broker_cnt;
}

// Perform broker processing for 20 seconds or until all brokers are ready
// Perform broker processing for 10 seconds or until all brokers are ready
// (whichever is shorter); since all contexts are in standby mode, all
// available connections will be stuck in GET_COOKIE state.
time_t timer_collect = timer_now();
Expand All @@ -1433,7 +1465,7 @@ int main(int argc, char **argv)
ready_cnt += brokers[i].ctx->standby_available ? 1 : 0;
}

if (ready_cnt == working_brokers || is_timeout(&timer_collect, 20))
if (ready_cnt == working_brokers || is_timeout(&timer_collect, 10))
break;

// First available broker just use the first one available.
Expand All @@ -1453,7 +1485,6 @@ int main(int argc, char **argv)
brokers[i].port);

// Activate the broker.
main_context->standby_only = 0;
main_context->state = STATE_GET_COOKIE;

// Initially, we mark this broker as broken. We will remove this mark after establishing
Expand All @@ -1462,37 +1493,35 @@ int main(int argc, char **argv)
brokers[i].broken = timer_now();

// Perform processing on the main context; if the connection fails and does
// not recover after 30 seconds, restart the broker selection process.
int restart_timer = 0;
// not recover after 15 seconds, restart the broker selection process.
time_t timer_establish = timer_now();
for (;;) {
broker_select_one(brokers[i]);
context_process(main_context);

if (main_context->state == STATE_REINIT) {
if (main_context->state == STATE_FAILED) {
syslog(LOG_ERR, "Connection to %s lost.", main_context->broker_hostname);
break;
}

// If the connection is lost, we start the reconnection timer.
if (restart_timer && main_context->state != STATE_KEEPALIVE) {
// Hitting this code path should not be possible, but lets play safe.
if (timer_establish < 0 && main_context->state != STATE_KEEPALIVE) {
timer_establish = timer_now();
restart_timer = 0;
}

// After 30 seconds, we check if the tunnel has been established.
if (is_timeout(&timer_establish, 30)) {
// After 15 seconds, we check if the tunnel has been established.
if (is_timeout(&timer_establish, 15)) {
if (main_context->state != STATE_KEEPALIVE) {
// Tunnel is not established yet, skip to the next broker.
syslog(LOG_ERR, "Connection with broker not established after 30 seconds, restarting broker selection...");
syslog(LOG_ERR, "Connection with broker not established after 15 seconds, restarting broker selection...");
break;
}

// We successfully established a connection, this broker is fine.
brokers[i].broken = 0;

timer_establish = -1;
restart_timer = 1;
}
}

Expand Down

0 comments on commit 9c85ff2

Please sign in to comment.