In various circumstances we may not properly report abrupt ends to a TCP connection to peers with an RST. Fix them. Link: https://bugs.passt.top/show_bug.cgi?id=191 Link: https://bugs.passt.top/show_bug.cgi?id=193 David Gibson (3): doc: Add test program verifying socket RST behaviour tcp: Properly propagate tap-side RST to socket side tcp_splice: Force TCP RST on abnormal close conditions doc/platform-requirements/.gitignore | 1 + doc/platform-requirements/Makefile | 8 +- doc/platform-requirements/tcp-close-rst.c | 204 ++++++++++++++++++++++ tcp.c | 37 +++- tcp_splice.c | 58 ++++-- 5 files changed, 290 insertions(+), 18 deletions(-) create mode 100644 doc/platform-requirements/tcp-close-rst.c -- 2.52.0
When the guest sends a TCP RST, or on certain error conditions, we want to
signal the abnormal termination of a TCP connection to the peer with an
RST as well. We attempt to do that by close()ing the socket.
That doesn't work: a close() will usually send a FIN, rather than an RST.
The standard method of forcing an RST on a socket is to set the SO_LINGER
socket option with a 0 timeout, then close().
Update the tcp_rst() path to do this, so it forces a socket side RST.
Update the handling of a guest side RST to use the same path (minus
sending a tap side RST) so that we properly propagate guest RSTs to the
peer.
Link: https://bugs.passt.top/show_bug.cgi?id=191
Signed-off-by: David Gibson
Add a program to doc/platform-requirements testing / documenting the
RST related behaviour of TCP sockets under various conditions.
This doesn't fix bug 191 of itself, but documents the kernel side
behaviour we'll be using in order to fix it.
Link: https://bugs.passt.top/show_bug.cgi?id=191
Signed-off-by: David Gibson
When we need to prematurely close a spliced connection, we use:
conn_flag(conn, CLOSING);
This does destroy the flow, but does so in the same way as a clean close
from both ends. That's not what we want in error conditions, or when one
side of the flow has signalled an abnormal exit with an EPOLLHUP event.
Replace all places where we close the connection - except for the happy
path close - with calls to a new tcp_splice_rst() function, which forces
the sockets to emit a TCP RST on each side.
Link: https://bugs.passt.top/show_bug.cgi?id=193
Signed-off-by: David Gibson
On Tue, 27 Jan 2026 19:39:52 +1100
David Gibson
When the guest sends a TCP RST, or on certain error conditions, we want to signal the abnormal termination of a TCP connection to the peer with an RST as well. We attempt to do that by close()ing the socket.
That doesn't work: a close() will usually send a FIN, rather than an RST. The standard method of forcing an RST on a socket is to set the SO_LINGER socket option with a 0 timeout, then close().
Update the tcp_rst() path to do this, so it forces a socket side RST. Update the handling of a guest side RST to use the same path (minus sending a tap side RST) so that we properly propagate guest RSTs to the peer.
Link: https://bugs.passt.top/show_bug.cgi?id=191
Signed-off-by: David Gibson
--- tcp.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/tcp.c b/tcp.c index 45dde5a0..9da37c2f 100644 --- a/tcp.c +++ b/tcp.c @@ -1403,7 +1403,34 @@ static int tcp_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, }
/** - * tcp_rst_do() - Reset a tap connection: send RST segment to tap, close socket + * tcp_sock_rst() - Close TCP connection forcing RST on socket side + * @c: Execution context + * @conn: Connection pointer + */ +static void tcp_sock_rst(const struct ctx *c, struct tcp_tap_conn *conn) +{ + const struct linger linger0 = { + .l_onoff = 1, + .l_linger = 0, + }; + + /* Force RST on socket to inform the peer + * + * We do this by setting SO_LINGER with 0 timeout, which means that + * close() will send an RST (unless the connection is already closed in + * both directions). + */ + if (setsockopt(conn->sock, SOL_SOCKET, + SO_LINGER, &linger0, sizeof(linger0)) < 0) { + flow_dbg_perror(conn, + "SO_LINGER failed, may not send RST to peer"); + } + + conn_event(c, conn, CLOSED); +} + +/** + * tcp_rst_do() - Reset a tap connection: send RST segment on both sides, close * @c: Execution context * @conn: Connection pointer */ @@ -1412,8 +1439,10 @@ void tcp_rst_do(const struct ctx *c, struct tcp_tap_conn *conn) if (conn->events == CLOSED) return;
+ /* Send RST on tap */ tcp_send_flag(c, conn, RST); - conn_event(c, conn, CLOSED); + + tcp_sock_rst(c, conn); }
/** @@ -1884,7 +1913,7 @@ static int tcp_data_from_tap(const struct ctx *c, struct tcp_tap_conn *conn, return -1;
if (th->rst) { - conn_event(c, conn, CLOSED); + tcp_sock_rst(c, conn);
The whole series looks good to me, except for one exceedingly minor aspect: should we do this also in the getsockopt() error handling path of tcp_prepare_flags()? I would be inclined to apply it regardless of that, the fix is critical enough. I'll start the usual test run in a few hours. -- Stefano
participants (2)
-
David Gibson
-
Stefano Brivio