Browse Source

[tcp] Send TCP keepalives on idle established connections

In some circumstances, intermediate devices may lose state in a way
that temporarily prevents the successful delivery of packets from a
TCP peer.  For example, a firewall may drop a NAT forwarding table
entry.

Since iPXE spends most of its time downloading files (and hence purely
receiving data, sending only TCP ACKs), this can easily happen in a
situation in which there is no reason for iPXE's TCP stack to generate
any retransmissions.  The temporary loss of connectivity can therefore
effectively become permanent.

Work around this problem by sending TCP keepalives after a period of
inactivity on an established connection.

TCP keepalives usually send a single garbage byte in sequence number
space that has already been ACKed by the peer.  Since we do not need
to elicit a response from the peer, we instead send pure ACKs (with no
garbage data) in order to keep the transmit code path simple.

Originally-implemented-by: Ladi Prosek <lprosek@redhat.com>
Debugged-by: Ladi Prosek <lprosek@redhat.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 8 years ago
parent
commit
188789eb3c
2 changed files with 46 additions and 0 deletions
  1. 8
    0
      src/include/ipxe/tcp.h
  2. 38
    0
      src/net/tcp.c

+ 8
- 0
src/include/ipxe/tcp.h View File

@@ -378,6 +378,14 @@ struct tcp_options {
378 378
  */
379 379
 #define TCP_MSL ( 2 * 60 * TICKS_PER_SEC )
380 380
 
381
+/**
382
+ * TCP keepalive period
383
+ *
384
+ * We send keepalive ACKs after this period of inactivity has elapsed
385
+ * on an established connection.
386
+ */
387
+#define TCP_KEEPALIVE_DELAY ( 15 * TICKS_PER_SEC )
388
+
381 389
 /**
382 390
  * TCP maximum header length
383 391
  *

+ 38
- 0
src/net/tcp.c View File

@@ -113,6 +113,8 @@ struct tcp_connection {
113 113
 	struct process process;
114 114
 	/** Retransmission timer */
115 115
 	struct retry_timer timer;
116
+	/** Keepalive timer */
117
+	struct retry_timer keepalive;
116 118
 	/** Shutdown (TIME_WAIT) timer */
117 119
 	struct retry_timer wait;
118 120
 
@@ -177,6 +179,7 @@ static struct profiler tcp_xfer_profiler __profiler = { .name = "tcp.xfer" };
177 179
 static struct process_descriptor tcp_process_desc;
178 180
 static struct interface_descriptor tcp_xfer_desc;
179 181
 static void tcp_expired ( struct retry_timer *timer, int over );
182
+static void tcp_keepalive_expired ( struct retry_timer *timer, int over );
180 183
 static void tcp_wait_expired ( struct retry_timer *timer, int over );
181 184
 static struct tcp_connection * tcp_demux ( unsigned int local_port );
182 185
 static int tcp_rx_ack ( struct tcp_connection *tcp, uint32_t ack,
@@ -284,6 +287,7 @@ static int tcp_open ( struct interface *xfer, struct sockaddr *peer,
284 287
 	intf_init ( &tcp->xfer, &tcp_xfer_desc, &tcp->refcnt );
285 288
 	process_init_stopped ( &tcp->process, &tcp_process_desc, &tcp->refcnt );
286 289
 	timer_init ( &tcp->timer, tcp_expired, &tcp->refcnt );
290
+	timer_init ( &tcp->keepalive, tcp_keepalive_expired, &tcp->refcnt );
287 291
 	timer_init ( &tcp->wait, tcp_wait_expired, &tcp->refcnt );
288 292
 	tcp->prev_tcp_state = TCP_CLOSED;
289 293
 	tcp->tcp_state = TCP_STATE_SENT ( TCP_SYN );
@@ -380,6 +384,7 @@ static void tcp_close ( struct tcp_connection *tcp, int rc ) {
380 384
 		/* Remove from list and drop reference */
381 385
 		process_del ( &tcp->process );
382 386
 		stop_timer ( &tcp->timer );
387
+		stop_timer ( &tcp->keepalive );
383 388
 		stop_timer ( &tcp->wait );
384 389
 		list_del ( &tcp->list );
385 390
 		ref_put ( &tcp->refcnt );
@@ -394,6 +399,9 @@ static void tcp_close ( struct tcp_connection *tcp, int rc ) {
394 399
 	if ( ! ( tcp->tcp_state & TCP_STATE_ACKED ( TCP_SYN ) ) )
395 400
 		tcp_rx_ack ( tcp, ( tcp->snd_seq + 1 ), 0 );
396 401
 
402
+	/* Stop keepalive timer */
403
+	stop_timer ( &tcp->keepalive );
404
+
397 405
 	/* If we have no data remaining to send, start sending FIN */
398 406
 	if ( list_empty ( &tcp->tx_queue ) &&
399 407
 	     ! ( tcp->tcp_state & TCP_STATE_SENT ( TCP_FIN ) ) ) {
@@ -801,6 +809,32 @@ static void tcp_expired ( struct retry_timer *timer, int over ) {
801 809
 	}
802 810
 }
803 811
 
812
+/**
813
+ * Keepalive timer expired
814
+ *
815
+ * @v timer		Keepalive timer
816
+ * @v over		Failure indicator
817
+ */
818
+static void tcp_keepalive_expired ( struct retry_timer *timer,
819
+				    int over __unused ) {
820
+	struct tcp_connection *tcp =
821
+		container_of ( timer, struct tcp_connection, keepalive );
822
+
823
+	DBGC ( tcp, "TCP %p sending keepalive\n", tcp );
824
+
825
+	/* Reset keepalive timer */
826
+	start_timer_fixed ( &tcp->keepalive, TCP_KEEPALIVE_DELAY );
827
+
828
+	/* Send keepalive.  We do this only to preserve or restore
829
+	 * state in intermediate devices (e.g. firewall NAT tables);
830
+	 * we don't actually care about eliciting a response to verify
831
+	 * that the peer is still alive.  We therefore send just a
832
+	 * pure ACK, to keep our transmit path simple.
833
+	 */
834
+	tcp->flags |= TCP_ACK_PENDING;
835
+	tcp_xmit ( tcp );
836
+}
837
+
804 838
 /**
805 839
  * Shutdown timer expired
806 840
  *
@@ -1105,6 +1139,10 @@ static int tcp_rx_ack ( struct tcp_connection *tcp, uint32_t ack,
1105 1139
 	/* Update window size */
1106 1140
 	tcp->snd_win = win;
1107 1141
 
1142
+	/* Hold off (or start) the keepalive timer, if applicable */
1143
+	if ( ! ( tcp->tcp_state & TCP_STATE_SENT ( TCP_FIN ) ) )
1144
+		start_timer_fixed ( &tcp->keepalive, TCP_KEEPALIVE_DELAY );
1145
+
1108 1146
 	/* Ignore ACKs that don't actually acknowledge any new data.
1109 1147
 	 * (In particular, do not stop the retransmission timer; this
1110 1148
 	 * avoids creating a sorceror's apprentice syndrome when a

Loading…
Cancel
Save