]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[tcp] Gracefully close connections during shutdown
authorMichael Brown <mcb30@ipxe.org>
Sat, 4 Jul 2015 11:40:04 +0000 (12:40 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sat, 4 Jul 2015 11:51:23 +0000 (12:51 +0100)
We currently do not wait for a received FIN before exiting to boot a
loaded OS.  In the common case of booting from an HTTP server, this
means that the TCP connection is left consuming resources on the
server side: the server will retransmit the FIN several times before
giving up.

Fix by initiating a graceful close of all TCP connections and waiting
(for up to one second) for all connections to finish closing
gracefully (i.e. for the outgoing FIN to have been sent and ACKed, and
for the incoming FIN to have been received and ACKed at least once).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/tcp.h
src/net/tcp.c

index 65fee85e010cecd28cdde7e196d0a811762a39bd..063ebaa4bc2c6330abee62a42213aba48a994341 100644 (file)
@@ -420,6 +420,13 @@ static inline int tcp_in_window ( uint32_t seq, uint32_t start,
        return ( ( seq - start ) < len );
 }
 
+/** TCP finish wait time
+ *
+ * Currently set to one second, since we should not allow a slowly
+ * responding server to substantially delay a call to shutdown().
+ */
+#define TCP_FINISH_TIMEOUT ( 1 * TICKS_PER_SEC )
+
 extern struct tcpip_protocol tcp_protocol __tcpip_protocol;
 
 #endif /* _IPXE_TCP_H */
index 1a672dfaf41633284b1a89ac5e5633725c2da8d9..1ead711297e5a944f595fd82f00735feb6434356 100644 (file)
@@ -1501,13 +1501,68 @@ struct cache_discarder tcp_discarder __cache_discarder ( CACHE_NORMAL ) = {
        .discard = tcp_discard,
 };
 
+/**
+ * Find first TCP connection that has not yet been closed
+ *
+ * @ret tcp            First unclosed connection, or NULL
+ */
+static struct tcp_connection * tcp_first_unclosed ( void ) {
+       struct tcp_connection *tcp;
+
+       /* Find first connection which has not yet been closed */
+       list_for_each_entry ( tcp, &tcp_conns, list ) {
+               if ( ! ( tcp->flags & TCP_XFER_CLOSED ) )
+                       return tcp;
+       }
+       return NULL;
+}
+
+/**
+ * Find first TCP connection that has not yet finished all operations
+ *
+ * @ret tcp            First unfinished connection, or NULL
+ */
+static struct tcp_connection * tcp_first_unfinished ( void ) {
+       struct tcp_connection *tcp;
+
+       /* Find first connection which has not yet closed gracefully,
+        * or which still has a pending transmission (e.g. to ACK the
+        * received FIN).
+        */
+       list_for_each_entry ( tcp, &tcp_conns, list ) {
+               if ( ( ! TCP_CLOSED_GRACEFULLY ( tcp->tcp_state ) ) ||
+                    process_running ( &tcp->process ) ) {
+                       return tcp;
+               }
+       }
+       return NULL;
+}
+
 /**
  * Shut down all TCP connections
  *
  */
 static void tcp_shutdown ( int booting __unused ) {
        struct tcp_connection *tcp;
+       unsigned long start;
+       unsigned long elapsed;
+
+       /* Initiate a graceful close of all connections, allowing for
+        * the fact that the connection list may change as we do so.
+        */
+       while ( ( tcp = tcp_first_unclosed() ) ) {
+               DBGC ( tcp, "TCP %p closing for shutdown\n", tcp );
+               tcp_close ( tcp, -ECANCELED );
+       }
+
+       /* Wait for all connections to finish closing gracefully */
+       start = currticks();
+       while ( ( tcp = tcp_first_unfinished() ) &&
+               ( ( elapsed = ( currticks() - start ) ) < TCP_FINISH_TIMEOUT )){
+               step();
+       }
 
+       /* Forcibly close any remaining connections */
        while ( ( tcp = list_first_entry ( &tcp_conns, struct tcp_connection,
                                           list ) ) != NULL ) {
                tcp->tcp_state = TCP_CLOSED;
@@ -1517,7 +1572,7 @@ static void tcp_shutdown ( int booting __unused ) {
 }
 
 /** TCP shutdown function */
-struct startup_fn tcp_startup_fn __startup_fn ( STARTUP_EARLY ) = {
+struct startup_fn tcp_startup_fn __startup_fn ( STARTUP_LATE ) = {
        .shutdown = tcp_shutdown,
 };