Commit 01f5fbda authored by Andy Ross's avatar Andy Ross Committed by Jukka Rissanen
Browse files

net: tcp: Add retry handling



Adds a simple retry mechanism on top of the existing queueing
framework.

* On the first packet transmitted with an empty queue, the timer is
  started (to an initial timeout of 200ms).

* On a received, valid ACK, the timer is restarted.

* The timer is stopped whenever the last packet is ACKed and the queue
  becomes empty.

* On timer expiration, the timeout is doubled and the first
  unacknowledged packet is retransmitted.

* When receiving an ACK when we are known to be retransmitting, we
  mark all sent packets as needing to be sent again.

Change-Id: Iee3544ed43d0a2d50c29fa82f958a34cc10f7cda
Signed-off-by: default avatarAndy Ross <andrew.j.ross@intel.com>
Signed-off-by: default avatarLeandro Pereira <leandro.pereira@intel.com>
parent 481f67e2
Loading
Loading
Loading
Loading
+68 −1
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@
#define NET_MAX_TCP_CONTEXT CONFIG_NET_MAX_CONTEXTS
static struct net_tcp tcp_context[NET_MAX_TCP_CONTEXT];

#define INIT_RETRY_MS 200

static struct k_sem tcp_lock;

struct tcp_segment {
@@ -110,6 +112,24 @@ static inline uint32_t init_isn(void)
	return sys_rand32_get();
}

static void tcp_retry_expired(struct k_timer *timer)
{
	struct net_tcp *tcp = CONTAINER_OF(timer, struct net_tcp, retry_timer);
	struct net_buf *buf;

	/* Double the retry period for exponential backoff and resent
	 * the first (only the first!) unack'd packet.
	 */
	if (!sys_slist_is_empty(&tcp->sent_list)) {
		tcp->retry_timeout_ms *= 2;
		k_timer_start(&tcp->retry_timer, tcp->retry_timeout_ms, 0);

		buf = CONTAINER_OF(sys_slist_peek_head(&tcp->sent_list),
				   struct net_buf, sent_list);
		net_tcp_send_buf(buf);
	}
}

struct net_tcp *net_tcp_alloc(struct net_context *context)
{
	int i, key;
@@ -136,6 +156,8 @@ struct net_tcp *net_tcp_alloc(struct net_context *context)
	tcp_context[i].send_seq = init_isn();
	tcp_context[i].recv_max_ack = tcp_context[i].send_seq + 1u;

	k_timer_init(&tcp_context[i].retry_timer, tcp_retry_expired, NULL);

	return &tcp_context[i];
}

@@ -601,9 +623,23 @@ int net_tcp_send_buf(struct net_buf *buf)

	ctx->tcp->sent_ack = ctx->tcp->send_ack;

	net_nbuf_set_buf_sent(buf, true);

	return net_send_data(buf);
}

static void restart_timer(struct net_tcp *tcp)
{
	if (sys_slist_is_empty(&tcp->sent_list)) {
		tcp->flags |= NET_TCP_RETRYING;
		tcp->retry_timeout_ms = INIT_RETRY_MS;
		k_timer_start(&tcp->retry_timer, INIT_RETRY_MS, 0);
	} else {
		k_timer_stop(&tcp->retry_timer);
		tcp->flags &= ~NET_TCP_RETRYING;
	}
}

int tcp_send_data(struct net_context *context)
{
	struct net_buf *buf;
@@ -614,8 +650,10 @@ int tcp_send_data(struct net_context *context)
	 */
	SYS_SLIST_FOR_EACH_NODE(&context->tcp->sent_list, node) {
		buf = CONTAINER_OF(node, struct net_buf, sent_list);
		if (!net_nbuf_buf_sent(buf)) {
			net_tcp_send_buf(buf);
		}
	}

	return 0;
}
@@ -627,6 +665,7 @@ void net_tcp_ack_received(struct net_context *ctx, uint32_t ack)
	struct net_buf *buf;
	struct net_tcp_hdr *tcphdr;
	uint32_t seq;
	bool valid_ack = false;

	while (!sys_slist_is_empty(list)) {
		head = sys_slist_peek_head(list);
@@ -639,10 +678,38 @@ void net_tcp_ack_received(struct net_context *ctx, uint32_t ack)
		if (seq_greater(ack, seq)) {
			sys_slist_remove(list, NULL, head);
			net_nbuf_unref(buf);
			valid_ack = true;
		} else {
			break;
		}
	}

	if (valid_ack) {
		/* Restart the timer on a valid inbound ACK.  This
		 * isn't quite the same behavior as per-packet retry
		 * timers, but is close in practice (it starts retries
		 * one timer period after the connection "got stuck")
		 * and avoids the need to track per-packet timers or
		 * sent times.
		 */
		restart_timer(ctx->tcp);

		/* And, if we had been retrying, mark all packets
		 * untransmitted and then resend them.  The stalled
		 * pipe is uncorked again.
		 */
		if (ctx->tcp->flags & NET_TCP_RETRYING) {
			struct net_buf *buf;
			sys_snode_t *node;

			SYS_SLIST_FOR_EACH_NODE(&ctx->tcp->sent_list, node) {
				buf = CONTAINER_OF(node, struct net_buf,
						   sent_list);
				net_nbuf_set_buf_sent(buf, false);
			}
			tcp_send_data(ctx);
		}
	}
}

void net_tcp_init(void)
+9 −0
Original line number Diff line number Diff line
@@ -48,6 +48,9 @@ extern "C" {
/** Is the socket shutdown for read/write */
#define NET_TCP_IS_SHUTDOWN BIT(3)

/** A retransmitted packet has been sent and not yet ack'd */
#define NET_TCP_RETRYING BIT(4)

/*
 * TCP connection states
 */
@@ -114,6 +117,12 @@ struct net_tcp {
	/** Active close timer */
	struct k_delayed_work fin_timer;

	/** Retransmit timer */
	struct k_timer retry_timer;

	/** Current retransmit period */
	uint32_t retry_timeout_ms;

	/** List pointer used for TCP retransmit buffering */
	sys_slist_t sent_list;