diff --git a/src/include/ipxe/tcp.h b/src/include/ipxe/tcp.h index 65fee85e..063ebaa4 100644 --- a/src/include/ipxe/tcp.h +++ b/src/include/ipxe/tcp.h @@ -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 */ diff --git a/src/net/tcp.c b/src/net/tcp.c index 1a672dfa..1ead7112 100644 --- a/src/net/tcp.c +++ b/src/net/tcp.c @@ -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, };