net: tcp: Clean up FIN handling

The connection close paths were a little tangle. The use of separate
callbacks for "active" and "passive" close obscured the fundamentally
symmetric operation of those modes and made it hard to check sequence
numbers for validation (they didn't).  Similarly the use of the
official TCP states missed some details we need, like the distinction
between having "queued" a FIN packet for transmission and the state
reached when it's actually transmitted.

Remove the state-specific callbacks (which actually had very little to
do) and just rely on the existing packet queuing and generic sequence
number handling in tcp_established().  A few new state bits in the
net_tcp struct help us track current state in a way that doesn't fall
over the asymmetry of the TCP state diagram.  We can also junk the
FIN-specific timer and just use the same retransmit timer we do for
data packets (though long term we should investigate choosing
different timeouts by state).

Change-Id: I09951b848c63fefefce33962ee6cff6a09b4ca50
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
Andy Ross 2017-01-26 12:17:28 -08:00 committed by Jukka Rissanen
parent f16886d419
commit fece856959
3 changed files with 38 additions and 129 deletions

View File

@ -267,34 +267,14 @@ static void queue_fin(struct net_context *ctx)
return;
}
ctx->tcp->fin_queued = 1;
ret = net_tcp_send_buf(buf);
if (ret < 0) {
net_nbuf_unref(buf);
}
}
static enum net_verdict tcp_active_close(struct net_conn *conn,
struct net_buf *buf,
void *user_data);
static bool send_fin_if_active_close(struct net_context *context)
{
NET_ASSERT(context->tcp);
switch (net_tcp_get_state(context->tcp)) {
case NET_TCP_SYN_RCVD:
case NET_TCP_ESTABLISHED:
/* Sending a packet with the FIN flag automatically
* transitions to FIN_WAIT_1
*/
queue_fin(context);
net_conn_change_callback(context->conn_handler,
tcp_active_close, context);
return true;
default:
return false;
}
}
#endif /* CONFIG_NET_TCP */
int net_context_ref(struct net_context *context)
@ -350,9 +330,10 @@ int net_context_put(struct net_context *context)
#if defined(CONFIG_NET_TCP)
if (net_context_get_ip_proto(context) == IPPROTO_TCP) {
if (send_fin_if_active_close(context)) {
if (!context->tcp->fin_rcvd) {
NET_DBG("TCP connection in active close, not "
"disposing yet");
queue_fin(context);
return 0;
}
}
@ -684,13 +665,6 @@ static inline int send_syn_ack(struct net_context *context,
"SYN_ACK");
}
static inline int send_fin_ack(struct net_context *context,
struct sockaddr *remote)
{
return send_control_segment(context, NULL, remote,
NET_TCP_FIN | NET_TCP_ACK, "FIN_ACK");
}
static inline int send_ack(struct net_context *context,
struct sockaddr *remote)
{
@ -748,33 +722,6 @@ static int tcp_hdr_len(struct net_buf *buf)
return 4 * (hdr->offset >> 4);
}
NET_CONN_CB(tcp_passive_close)
{
struct net_context *context = (struct net_context *)user_data;
NET_ASSERT(context && context->tcp);
switch (net_tcp_get_state(context->tcp)) {
case NET_TCP_CLOSE_WAIT:
case NET_TCP_LAST_ACK:
break;
default:
NET_DBG("Context %p in wrong state %d",
context, net_tcp_get_state(context->tcp));
return NET_DROP;
}
net_tcp_print_recv_info("PASSCLOSE", buf, NET_TCP_BUF(buf)->src_port);
if (net_tcp_get_state(context->tcp) == NET_TCP_LAST_ACK &&
NET_TCP_FLAGS(buf) & NET_TCP_ACK) {
NET_DBG("ACK received in LAST_ACK, disposing of connection");
net_context_unref(context);
}
return NET_DROP;
}
/* This is called when we receive data after the connection has been
* established. The core TCP logic is located here.
*/
@ -817,9 +764,8 @@ NET_CONN_CB(tcp_established)
/* Sending an ACK in the CLOSE_WAIT state will transition to
* LAST_ACK state
*/
context->tcp->fin_rcvd = 1;
net_tcp_change_state(context->tcp, NET_TCP_CLOSE_WAIT);
net_conn_change_callback(context->conn_handler,
tcp_passive_close, context);
context->tcp->send_ack += 1;
@ -831,47 +777,15 @@ NET_CONN_CB(tcp_established)
send_ack(context, &conn->remote_addr);
if (sys_slist_is_empty(&context->tcp->sent_list)
&& context->tcp->fin_rcvd
&& context->tcp->fin_sent) {
net_context_unref(context);
}
return ret;
}
NET_CONN_CB(tcp_active_close)
{
struct net_context *context = (struct net_context *)user_data;
struct net_tcp *tcp;
NET_ASSERT(context && context->tcp);
tcp = context->tcp;
if (NET_TCP_FLAGS(buf) == NET_TCP_FIN) {
if (net_tcp_get_state(tcp) == NET_TCP_FIN_WAIT_1 ||
net_tcp_get_state(tcp) == NET_TCP_FIN_WAIT_2) {
/* Sending an ACK in FIN_WAIT_1 will transition
* to CLOSING, and to TIME_WAIT if on FIN_WAIT_2
*/
send_ack(context, &context->remote);
return NET_DROP;
}
} else if (NET_TCP_FLAGS(buf) == NET_TCP_ACK) {
if (net_tcp_get_state(tcp) == NET_TCP_FIN_WAIT_1) {
net_tcp_change_state(tcp, NET_TCP_FIN_WAIT_2);
return NET_DROP;
}
if (net_tcp_get_state(tcp) == NET_TCP_CLOSING) {
net_tcp_change_state(tcp, NET_TCP_TIME_WAIT);
return NET_DROP;
}
} else if (NET_TCP_FLAGS(buf) == (NET_TCP_FIN | NET_TCP_ACK)) {
if (net_tcp_get_state(tcp) == NET_TCP_FIN_WAIT_1) {
send_fin_ack(context, &context->remote);
return NET_DROP;
}
}
NET_DBG("Context %p in wrong state %d", context, tcp->state);
return NET_DROP;
}
NET_CONN_CB(tcp_synack_received)
{

View File

@ -165,7 +165,6 @@ int net_tcp_release(struct net_tcp *tcp)
return -EINVAL;
}
k_delayed_work_cancel(&tcp->fin_timer);
k_delayed_work_cancel(&tcp->ack_timer);
k_timer_stop(&tcp->retry_timer);
k_sem_reset(&tcp->connect_wait);
@ -625,6 +624,10 @@ int net_tcp_send_buf(struct net_buf *buf)
tcphdr->flags |= NET_TCP_ACK;
}
if (tcphdr->flags & NET_TCP_FIN) {
ctx->tcp->fin_sent = 1;
}
ctx->tcp->sent_ack = ctx->tcp->send_ack;
net_nbuf_set_buf_sent(buf, true);
@ -664,6 +667,7 @@ int net_tcp_send_data(struct net_context *context)
void net_tcp_ack_received(struct net_context *ctx, uint32_t ack)
{
struct net_tcp *tcp = ctx->tcp;
sys_slist_t *list = &ctx->tcp->sent_list;
sys_snode_t *head;
struct net_buf *buf;
@ -678,13 +682,23 @@ void net_tcp_ack_received(struct net_context *ctx, uint32_t ack)
seq = sys_get_be32(tcphdr->seq) + net_nbuf_appdatalen(buf) - 1;
if (seq_greater(ack, seq)) {
sys_slist_remove(list, NULL, head);
net_nbuf_unref(buf);
valid_ack = true;
} else {
if (!seq_greater(ack, seq)) {
break;
}
if (tcphdr->flags & NET_TCP_FIN) {
enum net_tcp_state s = net_tcp_get_state(tcp);
if (s == NET_TCP_FIN_WAIT_1) {
net_tcp_change_state(tcp, NET_TCP_FIN_WAIT_2);
} else if (s == NET_TCP_CLOSING) {
net_tcp_change_state(tcp, NET_TCP_TIME_WAIT);
}
}
sys_slist_remove(list, NULL, head);
net_nbuf_unref(buf);
valid_ack = true;
}
if (valid_ack) {
@ -720,21 +734,6 @@ void net_tcp_init(void)
{
}
#define FIN_TIMEOUT (2 * NET_TCP_MAX_SEG_LIFETIME * MSEC_PER_SEC)
static void fin_timeout(struct k_work *work)
{
struct net_tcp *tcp = CONTAINER_OF(work, struct net_tcp, fin_timer);
int rc;
NET_DBG("Remote peer didn't confirm connection close");
rc = net_context_unref(tcp->context);
if (rc < 0) {
NET_DBG("Cannot close TCP context");
}
}
#if defined(CONFIG_NET_DEBUG_TCP)
static void validate_state_transition(enum net_tcp_state current,
enum net_tcp_state new)
@ -793,13 +792,6 @@ void net_tcp_change_state(struct net_tcp *tcp,
net_tcp_set_state(tcp, new_state);
if (net_tcp_get_state(tcp) == NET_TCP_FIN_WAIT_1) {
/* Wait up to 2 * MSL before destroying this socket. */
k_delayed_work_cancel(&tcp->fin_timer);
k_delayed_work_init(&tcp->fin_timer, fin_timeout);
k_delayed_work_submit(&tcp->fin_timer, FIN_TIMEOUT);
}
if (net_tcp_get_state(tcp) != NET_TCP_CLOSED) {
return;
}

View File

@ -104,9 +104,6 @@ struct net_tcp {
/** ACK message timer */
struct k_delayed_work ack_timer;
/** Active close timer */
struct k_delayed_work fin_timer;
/** Retransmit timer */
struct k_timer retry_timer;
@ -131,8 +128,14 @@ struct net_tcp {
uint32_t flags : 8;
/** Current TCP state */
uint32_t state : 4;
/* A FIN packet has been queued for transmission */
uint32_t fin_queued : 1;
/* An outbound FIN packet has been sent */
uint32_t fin_sent : 1;
/* An inbound FIN packet has been received */
uint32_t fin_rcvd : 1;
/** Remaining bits in this uint32_t */
uint32_t _padding : 15;
uint32_t _padding : 12;
/** Accept callback to be called when the connection has been
* established.