* [PATCH mptcp-next 1/2] tls: implement tls_disconnect
2026-10-14 8:59 [PATCH mptcp-next 0/2] TLS disconnect support Geliang Tang
@ 2026-10-14 8:59 ` Geliang Tang
2026-10-14 9:42 ` sashiko-bot
2026-10-14 8:59 ` [PATCH mptcp-next 2/2] selftests: tls: update disconnect tests Geliang Tang
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Geliang Tang @ 2026-10-14 8:59 UTC (permalink / raw)
To: mptcp; +Cc: Geliang Tang
From: Geliang Tang <tanggeliang@kylinos.cn>
Implement disconnect support for TLS sockets via connect(AF_UNSPEC).
The handler invokes the underlying disconnect() first, then on success
cancels TX work (for TLS_SW), calls tls_sk_proto_free() to restore
original protocol operations and clear TLS state, cleans up RX context
and frees the TLS context. RX context uses kfree(), TLS context uses
kfree_rcu() for RCU safety.
tls_sk_proto_free() restores sk_prot, sk_socket->ops, sk_write_space,
and sk_destruct to their original values, clears icsk_ulp_data and
icsk_ulp_ops when free_ctx is true, and frees the TX sub-context. The
RX context and main TLS context are freed separately by tls_disconnect().
Add sk_proto_ops field to tls_context to save the original sk_socket->ops.
Unlike close() where the socket is destroyed, disconnect leaves the
socket alive for potential reconnection. sk_prot and sk_socket->ops are
restored inside tls_sk_proto_free() to prevent NULL pointer dereference
from concurrent poll/read/splice operations that dispatch through
sk_socket->ops.
After releasing the socket lock to cancel TX work, re-check the context
pointer to handle concurrent close(). Clear icsk_ulp_ops when freeing the
context to prevent tcp_set_ulp from failing with -EEXIST on reconnection.
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
include/net/tls.h | 12 +++--
net/tls/tls_main.c | 112 +++++++++++++++++++++++++++++++++++++++------
2 files changed, 108 insertions(+), 16 deletions(-)
diff --git a/include/net/tls.h b/include/net/tls.h
index e57bef58851e..25ad76df97cb 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -193,6 +193,10 @@ enum tls_context_flags {
* tls_dev_del call in tls_device_down if it happens simultaneously.
*/
TLS_RX_DEV_CLOSED = 2,
+ /* tls_disconnect() is in progress, prevent concurrent close() from
+ * freeing resources while disconnect is using them.
+ */
+ TLS_DISCONNECTING = 3,
};
struct cipher_context {
@@ -259,6 +263,7 @@ struct tls_context {
/* cache cold stuff */
struct proto *sk_proto;
+ const struct proto_ops *sk_proto_ops;
struct sock *sk;
void (*sk_destruct)(struct sock *sk);
@@ -374,10 +379,11 @@ static inline struct tls_context *tls_get_ctx(const struct sock *sk)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
- /* Use RCU on icsk_ulp_data only for sock diag code,
- * TLS data path doesn't need rcu_dereference().
+ /* Use rcu_access_pointer() to safely read icsk_ulp_data,
+ * as it can be cleared concurrently by tls_disconnect()
+ * or tls_sk_proto_close().
*/
- return (__force void *)icsk->icsk_ulp_data;
+ return (__force void *)rcu_access_pointer(icsk->icsk_ulp_data);
}
static inline struct tls_sw_context_rx *tls_sw_ctx_rx(
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 8bdb78718a8d..d00e56af628a 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -358,32 +358,71 @@ static void tls_sk_proto_cleanup(struct sock *sk,
}
}
-static void tls_sk_proto_close(struct sock *sk, long timeout)
+static void tls_sk_proto_free(struct sock *sk,
+ struct tls_context *ctx,
+ bool free_ctx)
{
struct inet_connection_sock *icsk = inet_csk(sk);
- struct tls_context *ctx = tls_get_ctx(sk);
long timeo = sock_sndtimeo(sk, 0);
- bool free_ctx;
-
- if (ctx->tx_conf == TLS_SW)
- tls_sw_cancel_work_tx(ctx);
-
- lock_sock(sk);
- free_ctx = ctx->tx_conf != TLS_HW && ctx->rx_conf != TLS_HW;
if (ctx->tx_conf != TLS_BASE || ctx->rx_conf != TLS_BASE)
tls_sk_proto_cleanup(sk, ctx, timeo);
write_lock_bh(&sk->sk_callback_lock);
- if (free_ctx)
- rcu_assign_pointer(icsk->icsk_ulp_data, NULL);
WRITE_ONCE(sk->sk_prot, ctx->sk_proto);
+ if (sk->sk_socket)
+ WRITE_ONCE(sk->sk_socket->ops, ctx->sk_proto_ops);
if (sk->sk_write_space == tls_write_space)
sk->sk_write_space = ctx->sk_write_space;
+ if (sk->sk_destruct == tls_device_sk_destruct)
+ sk->sk_destruct = ctx->sk_destruct;
+ if (free_ctx) {
+ rcu_assign_pointer(icsk->icsk_ulp_data, NULL);
+ if (icsk->icsk_ulp_ops) {
+ /* Balance try_module_get in __tcp_ulp_find_autoload */
+ module_put(icsk->icsk_ulp_ops->owner);
+ icsk->icsk_ulp_ops = NULL;
+ }
+ }
write_unlock_bh(&sk->sk_callback_lock);
- release_sock(sk);
if (ctx->tx_conf == TLS_SW)
tls_sw_free_ctx_tx(ctx);
+}
+
+static void tls_sk_proto_close(struct sock *sk, long timeout)
+{
+ struct tls_context *ctx;
+ bool free_ctx;
+
+ ctx = tls_get_ctx(sk);
+ if (!ctx) {
+ sk->sk_prot->close(sk, timeout);
+ return;
+ }
+
+ if (ctx->tx_conf == TLS_SW)
+ tls_sw_cancel_work_tx(ctx);
+
+ lock_sock(sk);
+ /* Re-read ctx under lock, as it may have been cleared by disconnect */
+ ctx = tls_get_ctx(sk);
+ if (!ctx) {
+ release_sock(sk);
+ sk->sk_prot->close(sk, timeout);
+ return;
+ }
+
+ /* Check if disconnect is in progress after acquiring lock */
+ if (test_bit(TLS_DISCONNECTING, &ctx->flags)) {
+ release_sock(sk);
+ ctx->sk_proto->close(sk, timeout);
+ return;
+ }
+
+ free_ctx = ctx->tx_conf != TLS_HW && ctx->rx_conf != TLS_HW;
+ tls_sk_proto_free(sk, ctx, free_ctx);
+ release_sock(sk);
+
if (ctx->rx_conf == TLS_SW || ctx->rx_conf == TLS_HW)
tls_sw_strparser_done(ctx);
if (ctx->rx_conf == TLS_SW)
@@ -904,7 +943,52 @@ static int tls_setsockopt(struct sock *sk, int level, int optname,
static int tls_disconnect(struct sock *sk, int flags)
{
- return -EOPNOTSUPP;
+ struct tls_context *ctx;
+ bool free_ctx;
+ int rc;
+
+ ctx = tls_get_ctx(sk);
+ if (!ctx)
+ return 0;
+
+ if (!ctx->sk_proto->disconnect)
+ return -EOPNOTSUPP;
+
+ set_bit(TLS_DISCONNECTING, &ctx->flags);
+
+ rc = ctx->sk_proto->disconnect(sk, flags);
+ if (rc) {
+ clear_bit(TLS_DISCONNECTING, &ctx->flags);
+ return rc;
+ }
+
+ if (ctx->tx_conf == TLS_SW) {
+ struct tls_sw_context_tx *sw_ctx_tx = tls_sw_ctx_tx(ctx);
+
+ set_bit(BIT_TX_CLOSING, &sw_ctx_tx->tx_bitmask);
+ set_bit(BIT_TX_SCHEDULED, &sw_ctx_tx->tx_bitmask);
+
+ release_sock(sk);
+ disable_delayed_work_sync(&sw_ctx_tx->tx_work.work);
+ lock_sock(sk);
+ }
+
+ free_ctx = ctx->tx_conf != TLS_HW && ctx->rx_conf != TLS_HW;
+ tls_sk_proto_free(sk, ctx, free_ctx);
+
+ if (ctx->rx_conf == TLS_SW) {
+ struct tls_sw_context_rx *sw_ctx = tls_sw_ctx_rx(ctx);
+
+ release_sock(sk);
+ tls_strp_done(&sw_ctx->strp);
+ tls_sw_free_ctx_rx(ctx);
+ lock_sock(sk);
+ }
+
+ if (free_ctx)
+ tls_ctx_free(sk, ctx);
+
+ return 0;
}
struct tls_context *tls_ctx_create(struct sock *sk)
@@ -918,6 +1002,8 @@ struct tls_context *tls_ctx_create(struct sock *sk)
mutex_init(&ctx->tx_lock);
ctx->sk_proto = READ_ONCE(sk->sk_prot);
+ if (sk->sk_socket)
+ ctx->sk_proto_ops = READ_ONCE(sk->sk_socket->ops);
ctx->sk = sk;
/* Release semantic of rcu_assign_pointer() ensures that
* ctx->sk_proto is visible before changing sk->sk_prot in
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH mptcp-next 2/2] selftests: tls: update disconnect tests
2026-10-14 8:59 [PATCH mptcp-next 0/2] TLS disconnect support Geliang Tang
2026-10-14 8:59 ` [PATCH mptcp-next 1/2] tls: implement tls_disconnect Geliang Tang
@ 2026-10-14 8:59 ` Geliang Tang
2026-10-14 10:06 ` [PATCH mptcp-next 0/2] TLS disconnect support MPTCP CI
2026-10-14 13:19 ` Matthieu Baerts
3 siblings, 0 replies; 6+ messages in thread
From: Geliang Tang @ 2026-10-14 8:59 UTC (permalink / raw)
To: mptcp; +Cc: Geliang Tang
From: Geliang Tang <tanggeliang@kylinos.cn>
Update the TLS disconnect test to verify the full disconnect/reconnect
cycle.
The test first establishes a TLS connection and verifies data transfer
works. It then disconnects both ends of the connection using
connect(AF_UNSPEC) and verifies that send/recv operations fail with
appropriate error codes (EPIPE/ECONNRESET for send, ENOTCONN for recv).
The client socket is then reconnected to a new server, TLS is
re-established, and data transfer is verified to work again.
This ensures TLS sockets can be properly disconnected and reconnected
without state corruption.
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
tools/testing/selftests/net/tls.c | 57 +++++++++++++++++++++++++++++--
1 file changed, 55 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index 9d3cd4fff062..ada2f43d05d3 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -1985,8 +1985,10 @@ TEST_F(tls_basic, disconnect)
int send_len = strlen(test_str) + 1;
struct tls_crypto_info_keys key;
struct sockaddr_in addr;
+ socklen_t len;
char buf[20];
int ret;
+ int sfd;
if (self->notls)
return;
@@ -2005,14 +2007,65 @@ TEST_F(tls_basic, disconnect)
ret = setsockopt(self->cfd, SOL_TLS, TLS_RX, &key, key.len);
ASSERT_EQ(ret, 0);
+ bzero(buf, sizeof(buf));
+ EXPECT_EQ(recv(self->cfd, buf, send_len, 0), send_len);
+ EXPECT_EQ(memcmp(buf, test_str, send_len), 0);
+
addr.sin_family = AF_UNSPEC;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = 0;
ret = connect(self->cfd, &addr, sizeof(addr));
- EXPECT_EQ(ret, -1);
- EXPECT_EQ(errno, EOPNOTSUPP);
+ EXPECT_EQ(ret, 0);
+
+ /* Verify send/recv fail after disconnect. */
+ EXPECT_EQ(send(self->cfd, buf, sizeof(buf),
+ MSG_DONTWAIT | MSG_NOSIGNAL), -1);
+ EXPECT_TRUE(errno == EPIPE || errno == ECONNRESET);
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), -1);
+ EXPECT_EQ(errno, ENOTCONN);
+
+ /* Disconnect fd as well. */
+ ret = connect(self->fd, &addr, sizeof(addr));
+ ASSERT_EQ(ret, 0);
+ EXPECT_EQ(send(self->fd, buf, sizeof(buf),
+ MSG_DONTWAIT | MSG_NOSIGNAL), -1);
+ EXPECT_TRUE(errno == EPIPE || errno == ECONNRESET);
+ EXPECT_EQ(recv(self->fd, buf, sizeof(buf), MSG_DONTWAIT), -1);
+ EXPECT_EQ(errno, ENOTCONN);
+
+ /* Reconnect fd to a new server and re-setup TLS. */
+ addr.sin_family = AF_INET;
+ addr.sin_port = 0;
+ sfd = socket(AF_INET, SOCK_STREAM, 0);
+ EXPECT_GE(sfd, 0);
+ if (sfd < 0)
+ return;
+
+ /* sfd not closed on ASSERT failure, kernel cleans up on test abort */
+ ASSERT_EQ(bind(sfd, &addr, sizeof(addr)), 0);
+ ASSERT_EQ(listen(sfd, 10), 0);
+ len = sizeof(addr);
+ ASSERT_EQ(getsockname(sfd, &addr, &len), 0);
+ ASSERT_EQ(connect(self->fd, (struct sockaddr *)&addr, sizeof(addr)), 0);
+ ret = accept(sfd, &addr, &len);
+ close(sfd);
+ ASSERT_GE(ret, 0);
+ close(self->cfd);
+ self->cfd = ret;
+
+ ASSERT_EQ(setsockopt(self->fd, IPPROTO_TCP, TCP_ULP, "tls",
+ sizeof("tls")), 0);
+ ASSERT_EQ(setsockopt(self->cfd, IPPROTO_TCP, TCP_ULP, "tls",
+ sizeof("tls")), 0);
+ ASSERT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX, &key, key.len), 0);
+ ASSERT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX, &key, key.len), 0);
+
+ /* Verify data transfer works after reconnect. */
+ EXPECT_EQ(send(self->fd, test_str, send_len, 0), send_len);
+ bzero(buf, sizeof(buf));
EXPECT_EQ(recv(self->cfd, buf, send_len, 0), send_len);
+ EXPECT_EQ(memcmp(buf, test_str, send_len), 0);
}
TEST_F(tls, rekey)
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread