* [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests
@ 2025-10-03 4:31 alistair23
2025-10-03 4:31 ` [PATCH v3 1/8] net/handshake: Store the key serial number on completion alistair23
` (7 more replies)
0 siblings, 8 replies; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
The TLS 1.3 specification allows the TLS client or server to send a
KeyUpdate. This is generally used when the sequence is about to
overflow or after a certain amount of bytes have been encrypted.
The TLS spec doesn't mandate the conditions though, so a KeyUpdate
can be sent by the TLS client or server at any time. This includes
when running NVMe-OF over a TLS 1.3 connection.
As such Linux should be able to handle a KeyUpdate event, as the
other NVMe side could initiate a KeyUpdate.
Upcoming WD NVMe-TCP hardware controllers implement TLS support
and send KeyUpdate requests.
This series builds on top of the existing TLS EKEYEXPIRED work,
which already detects a KeyUpdate request. We can now pass that
information up to the NVMe layer (target and host) and then pass
it up to userspace.
Userspace (ktls-utils) will need to save the connection state
in the keyring during the initial handshake. The kernel then
provides the key serial back to userspace when handling a
KeyUpdate. Userspace can use this to restore the connection
information and then update the keys, this final process
is similar to the initial handshake.
This series depends on the recvmsg() kernel patch:
https://lore.kernel.org/linux-nvme/2cbe1350-0bf5-4487-be33-1d317cb73acf@suse.de/T/#mf56283228ae6c93e37dfbf1c0f6263910217cd80
ktls-utils (tlshd) userspace patches are available at:
https://lore.kernel.org/kernel-tls-handshake/CAKmqyKNpFhPtM8HAkgRMKQA8_N7AgoeqaSTe2=0spPnb+Oz2ng@mail.gmail.com/T/#mb277f5c998282666d0f41cc02f4abf516fcc4e9c
Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
Based-on: 2cbe1350-0bf5-4487-be33-1d317cb73acf@suse.de
v3:
- Rebase on the recvmsg() workflow patch
- Add debugfs support for the host
- Don't cancel an ongoing request
- Ensure a request is destructed on completion
v2:
- Change "key-serial" to "session-id"
- Fix reported build failures
- Drop tls_clear_err() function
- Stop keep alive timer during KeyUpdate
- Drop handshake message decoding in the NVMe layer
Alistair Francis (8):
net/handshake: Store the key serial number on completion
net/handshake: Define handshake_sk_destruct_req
net/handshake: Ensure the request is destructed on completion
nvmet: Expose nvmet_stop_keep_alive_timer publically
net/handshake: Support KeyUpdate message types
nvme-tcp: Support KeyUpdate
nvmet-tcp: Support KeyUpdate
nvme-tcp: Allow userspace to trigger a KeyUpdate with debugfs
Documentation/netlink/specs/handshake.yaml | 20 ++-
Documentation/networking/tls-handshake.rst | 6 +-
drivers/nvme/host/tcp.c | 147 +++++++++++++++++++--
drivers/nvme/target/core.c | 1 +
drivers/nvme/target/tcp.c | 104 +++++++++++++--
include/net/handshake.h | 14 +-
include/uapi/linux/handshake.h | 14 ++
net/handshake/genl.c | 5 +-
net/handshake/request.c | 18 +++
net/handshake/tlshd.c | 47 ++++++-
net/sunrpc/svcsock.c | 4 +-
net/sunrpc/xprtsock.c | 4 +-
12 files changed, 349 insertions(+), 35 deletions(-)
--
2.51.0
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v3 1/8] net/handshake: Store the key serial number on completion
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
@ 2025-10-03 4:31 ` alistair23
2025-10-06 6:15 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req alistair23
` (6 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Allow userspace to include a key serial number when completing a
handshake with the HANDSHAKE_CMD_DONE command.
We then store this serial number and will provide it back to userspace
in the future. This allows userspace to save data to the keyring and
then restore that data later.
This will be used to support the TLS KeyUpdate operation, as now
userspace can resume information about a established session.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
v3:
- No change
v2:
- Change "key-serial" to "session-id"
Documentation/netlink/specs/handshake.yaml | 4 ++++
Documentation/networking/tls-handshake.rst | 2 ++
drivers/nvme/host/tcp.c | 3 ++-
drivers/nvme/target/tcp.c | 3 ++-
include/net/handshake.h | 4 +++-
include/uapi/linux/handshake.h | 1 +
net/handshake/genl.c | 5 +++--
net/handshake/tlshd.c | 15 +++++++++++++--
net/sunrpc/svcsock.c | 4 +++-
net/sunrpc/xprtsock.c | 4 +++-
10 files changed, 36 insertions(+), 9 deletions(-)
diff --git a/Documentation/netlink/specs/handshake.yaml b/Documentation/netlink/specs/handshake.yaml
index 95c3fade7a8d..a273bc74d26f 100644
--- a/Documentation/netlink/specs/handshake.yaml
+++ b/Documentation/netlink/specs/handshake.yaml
@@ -87,6 +87,9 @@ attribute-sets:
name: remote-auth
type: u32
multi-attr: true
+ -
+ name: session-id
+ type: u32
operations:
list:
@@ -123,6 +126,7 @@ operations:
- status
- sockfd
- remote-auth
+ - session-id
mcast-groups:
list:
diff --git a/Documentation/networking/tls-handshake.rst b/Documentation/networking/tls-handshake.rst
index 6f5ea1646a47..d7287890056a 100644
--- a/Documentation/networking/tls-handshake.rst
+++ b/Documentation/networking/tls-handshake.rst
@@ -60,6 +60,8 @@ fills in a structure that contains the parameters of the request:
key_serial_t ta_my_privkey;
unsigned int ta_num_peerids;
key_serial_t ta_my_peerids[5];
+ key_serial_t user_session_id;
+
};
The @ta_sock field references an open and connected socket. The consumer
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index 9ef1d4aea838..700c37af52ba 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -1691,7 +1691,8 @@ static void nvme_tcp_set_queue_io_cpu(struct nvme_tcp_queue *queue)
qid, queue->io_cpu);
}
-static void nvme_tcp_tls_done(void *data, int status, key_serial_t pskid)
+static void nvme_tcp_tls_done(void *data, int status, key_serial_t pskid,
+ key_serial_t user_session_id)
{
struct nvme_tcp_queue *queue = data;
struct nvme_tcp_ctrl *ctrl = queue->ctrl;
diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
index 470bf37e5a63..4ef4dd140ada 100644
--- a/drivers/nvme/target/tcp.c
+++ b/drivers/nvme/target/tcp.c
@@ -1780,7 +1780,8 @@ static int nvmet_tcp_tls_key_lookup(struct nvmet_tcp_queue *queue,
}
static void nvmet_tcp_tls_handshake_done(void *data, int status,
- key_serial_t peerid)
+ key_serial_t peerid,
+ key_serial_t user_session_id)
{
struct nvmet_tcp_queue *queue = data;
diff --git a/include/net/handshake.h b/include/net/handshake.h
index 8ebd4f9ed26e..dc2222fd6d99 100644
--- a/include/net/handshake.h
+++ b/include/net/handshake.h
@@ -18,7 +18,8 @@ enum {
};
typedef void (*tls_done_func_t)(void *data, int status,
- key_serial_t peerid);
+ key_serial_t peerid,
+ key_serial_t user_session_id);
struct tls_handshake_args {
struct socket *ta_sock;
@@ -31,6 +32,7 @@ struct tls_handshake_args {
key_serial_t ta_my_privkey;
unsigned int ta_num_peerids;
key_serial_t ta_my_peerids[5];
+ key_serial_t user_session_id;
};
int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags);
diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h
index 662e7de46c54..b68ffbaa5f31 100644
--- a/include/uapi/linux/handshake.h
+++ b/include/uapi/linux/handshake.h
@@ -55,6 +55,7 @@ enum {
HANDSHAKE_A_DONE_STATUS = 1,
HANDSHAKE_A_DONE_SOCKFD,
HANDSHAKE_A_DONE_REMOTE_AUTH,
+ HANDSHAKE_A_DONE_SESSION_ID,
__HANDSHAKE_A_DONE_MAX,
HANDSHAKE_A_DONE_MAX = (__HANDSHAKE_A_DONE_MAX - 1)
diff --git a/net/handshake/genl.c b/net/handshake/genl.c
index f55d14d7b726..6cdce7e5dbc0 100644
--- a/net/handshake/genl.c
+++ b/net/handshake/genl.c
@@ -16,10 +16,11 @@ static const struct nla_policy handshake_accept_nl_policy[HANDSHAKE_A_ACCEPT_HAN
};
/* HANDSHAKE_CMD_DONE - do */
-static const struct nla_policy handshake_done_nl_policy[HANDSHAKE_A_DONE_REMOTE_AUTH + 1] = {
+static const struct nla_policy handshake_done_nl_policy[HANDSHAKE_A_DONE_SESSION_ID + 1] = {
[HANDSHAKE_A_DONE_STATUS] = { .type = NLA_U32, },
[HANDSHAKE_A_DONE_SOCKFD] = { .type = NLA_S32, },
[HANDSHAKE_A_DONE_REMOTE_AUTH] = { .type = NLA_U32, },
+ [HANDSHAKE_A_DONE_SESSION_ID] = { .type = NLA_U32, },
};
/* Ops table for handshake */
@@ -35,7 +36,7 @@ static const struct genl_split_ops handshake_nl_ops[] = {
.cmd = HANDSHAKE_CMD_DONE,
.doit = handshake_nl_done_doit,
.policy = handshake_done_nl_policy,
- .maxattr = HANDSHAKE_A_DONE_REMOTE_AUTH,
+ .maxattr = HANDSHAKE_A_DONE_SESSION_ID,
.flags = GENL_CMD_CAP_DO,
},
};
diff --git a/net/handshake/tlshd.c b/net/handshake/tlshd.c
index 081093dfd553..2549c5dbccd8 100644
--- a/net/handshake/tlshd.c
+++ b/net/handshake/tlshd.c
@@ -26,7 +26,8 @@
struct tls_handshake_req {
void (*th_consumer_done)(void *data, int status,
- key_serial_t peerid);
+ key_serial_t peerid,
+ key_serial_t user_session_id);
void *th_consumer_data;
int th_type;
@@ -39,6 +40,8 @@ struct tls_handshake_req {
unsigned int th_num_peerids;
key_serial_t th_peerid[5];
+
+ key_serial_t user_session_id;
};
static struct tls_handshake_req *
@@ -55,6 +58,7 @@ tls_handshake_req_init(struct handshake_req *req,
treq->th_num_peerids = 0;
treq->th_certificate = TLS_NO_CERT;
treq->th_privkey = TLS_NO_PRIVKEY;
+ treq->user_session_id = TLS_NO_PRIVKEY;
return treq;
}
@@ -83,6 +87,13 @@ static void tls_handshake_remote_peerids(struct tls_handshake_req *treq,
if (i >= treq->th_num_peerids)
break;
}
+
+ nla_for_each_attr(nla, head, len, rem) {
+ if (nla_type(nla) == HANDSHAKE_A_DONE_SESSION_ID) {
+ treq->user_session_id = nla_get_u32(nla);
+ break;
+ }
+ }
}
/**
@@ -105,7 +116,7 @@ static void tls_handshake_done(struct handshake_req *req,
set_bit(HANDSHAKE_F_REQ_SESSION, &req->hr_flags);
treq->th_consumer_done(treq->th_consumer_data, -status,
- treq->th_peerid[0]);
+ treq->th_peerid[0], treq->user_session_id);
}
#if IS_ENABLED(CONFIG_KEYS)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index e2c5e0e626f9..4ec3119bd113 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -444,13 +444,15 @@ static void svc_tcp_kill_temp_xprt(struct svc_xprt *xprt)
* @data: address of xprt to wake
* @status: status of handshake
* @peerid: serial number of key containing the remote peer's identity
+ * @user_session_id: serial number of the userspace session ID
*
* If a security policy is specified as an export option, we don't
* have a specific export here to check. So we set a "TLS session
* is present" flag on the xprt and let an upper layer enforce local
* security policy.
*/
-static void svc_tcp_handshake_done(void *data, int status, key_serial_t peerid)
+static void svc_tcp_handshake_done(void *data, int status, key_serial_t peerid,
+ key_serial_t user_session_id)
{
struct svc_xprt *xprt = data;
struct svc_sock *svsk = container_of(xprt, struct svc_sock, sk_xprt);
diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
index 3aa987e7f072..bce0f43bef65 100644
--- a/net/sunrpc/xprtsock.c
+++ b/net/sunrpc/xprtsock.c
@@ -2589,9 +2589,11 @@ static int xs_tcp_tls_finish_connecting(struct rpc_xprt *lower_xprt,
* @data: address of xprt to wake
* @status: status of handshake
* @peerid: serial number of key containing the remote's identity
+ * @user_session_id: serial number of the userspace session ID
*
*/
-static void xs_tls_handshake_done(void *data, int status, key_serial_t peerid)
+static void xs_tls_handshake_done(void *data, int status, key_serial_t peerid,
+ key_serial_t user_session_id)
{
struct rpc_xprt *lower_xprt = data;
struct sock_xprt *lower_transport =
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
2025-10-03 4:31 ` [PATCH v3 1/8] net/handshake: Store the key serial number on completion alistair23
@ 2025-10-03 4:31 ` alistair23
2025-10-03 9:51 ` Simon Horman
2025-10-06 6:24 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion alistair23
` (5 subsequent siblings)
7 siblings, 2 replies; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Define a `handshake_sk_destruct_req()` function to allow the destruction
of the handshake req.
This is required to avoid hash conflicts when handshake_req_hash_add()
is called as part of submitting the KeyUpdate request.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
v3:
- New patch
net/handshake/request.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/net/handshake/request.c b/net/handshake/request.c
index 274d2c89b6b2..0d1c91c80478 100644
--- a/net/handshake/request.c
+++ b/net/handshake/request.c
@@ -98,6 +98,22 @@ static void handshake_sk_destruct(struct sock *sk)
sk_destruct(sk);
}
+/**
+ * handshake_sk_destruct_req - destroy an existing request
+ * @sk: socket on which there is an existing request
+ */
+static void handshake_sk_destruct_req(struct sock *sk)
+{
+ struct handshake_req *req;
+
+ req = handshake_req_hash_lookup(sk);
+ if (!req)
+ return;
+
+ trace_handshake_destruct(sock_net(sk), req, sk);
+ handshake_req_destroy(req);
+}
+
/**
* handshake_req_alloc - Allocate a handshake request
* @proto: security protocol
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
2025-10-03 4:31 ` [PATCH v3 1/8] net/handshake: Store the key serial number on completion alistair23
2025-10-03 4:31 ` [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req alistair23
@ 2025-10-03 4:31 ` alistair23
2025-10-06 6:16 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically alistair23
` (4 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
To avoid future handshake_req_hash_add() calls failing with EEXIST when
performing a KeyUpdate let's make sure the old request is destructed
as part of the completion.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
v3:
- New patch
net/handshake/request.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/handshake/request.c b/net/handshake/request.c
index 0d1c91c80478..194725a8aaca 100644
--- a/net/handshake/request.c
+++ b/net/handshake/request.c
@@ -311,6 +311,8 @@ void handshake_complete(struct handshake_req *req, unsigned int status,
/* Handshake request is no longer pending */
sock_put(sk);
}
+
+ handshake_sk_destruct_req(sk);
}
EXPORT_SYMBOL_IF_KUNIT(handshake_complete);
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
` (2 preceding siblings ...)
2025-10-03 4:31 ` [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion alistair23
@ 2025-10-03 4:31 ` alistair23
2025-10-03 9:51 ` Christoph Hellwig
2025-10-06 6:36 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 5/8] net/handshake: Support KeyUpdate message types alistair23
` (3 subsequent siblings)
7 siblings, 2 replies; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
drivers/nvme/target/core.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 0dd7bd99afa3..bed1c6ebe83a 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -430,6 +430,7 @@ void nvmet_stop_keep_alive_timer(struct nvmet_ctrl *ctrl)
cancel_delayed_work_sync(&ctrl->ka_work);
}
+EXPORT_SYMBOL_GPL(nvmet_stop_keep_alive_timer);
u16 nvmet_req_find_ns(struct nvmet_req *req)
{
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 5/8] net/handshake: Support KeyUpdate message types
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
` (3 preceding siblings ...)
2025-10-03 4:31 ` [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically alistair23
@ 2025-10-03 4:31 ` alistair23
2025-10-06 6:20 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 6/8] nvme-tcp: Support KeyUpdate alistair23
` (2 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
When reporting the msg-type to userspace let's also support reporting
KeyUpdate events. This supports reporting a client/server event and if
the other side requested a KeyUpdateRequest.
Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
v3:
- Fixup yamllint and kernel-doc failures
Documentation/netlink/specs/handshake.yaml | 16 +++++++++-
Documentation/networking/tls-handshake.rst | 4 +--
drivers/nvme/host/tcp.c | 12 ++++++--
drivers/nvme/target/tcp.c | 11 +++++--
include/net/handshake.h | 10 +++++--
include/uapi/linux/handshake.h | 13 +++++++++
net/handshake/tlshd.c | 34 ++++++++++++++++++----
7 files changed, 84 insertions(+), 16 deletions(-)
diff --git a/Documentation/netlink/specs/handshake.yaml b/Documentation/netlink/specs/handshake.yaml
index a273bc74d26f..c72ec8fa7d7a 100644
--- a/Documentation/netlink/specs/handshake.yaml
+++ b/Documentation/netlink/specs/handshake.yaml
@@ -21,12 +21,18 @@ definitions:
type: enum
name: msg-type
value-start: 0
- entries: [unspec, clienthello, serverhello]
+ entries: [unspec, clienthello, serverhello, clientkeyupdate,
+ clientkeyupdaterequest, serverkeyupdate, serverkeyupdaterequest]
-
type: enum
name: auth
value-start: 0
entries: [unspec, unauth, psk, x509]
+ -
+ type: enum
+ name: key-update-type
+ value-start: 0
+ entries: [unspec, send, received, received_request_update]
attribute-sets:
-
@@ -74,6 +80,13 @@ attribute-sets:
-
name: keyring
type: u32
+ -
+ name: key-update-request
+ type: u32
+ enum: key-update-type
+ -
+ name: key-serial
+ type: u32
-
name: done
attributes:
@@ -116,6 +129,7 @@ operations:
- certificate
- peername
- keyring
+ - key-serial
-
name: done
doc: Handler reports handshake completion
diff --git a/Documentation/networking/tls-handshake.rst b/Documentation/networking/tls-handshake.rst
index d7287890056a..f858011e5bfb 100644
--- a/Documentation/networking/tls-handshake.rst
+++ b/Documentation/networking/tls-handshake.rst
@@ -110,7 +110,7 @@ To initiate a client-side TLS handshake with a pre-shared key, use:
.. code-block:: c
- ret = tls_client_hello_psk(args, gfp_flags);
+ ret = tls_client_hello_psk(args, gfp_flags, handshake_key_update_type);
However, in this case, the consumer fills in the @ta_my_peerids array
with serial numbers of keys containing the peer identities it wishes
@@ -140,7 +140,7 @@ or
.. code-block:: c
- ret = tls_server_hello_psk(args, gfp_flags);
+ ret = tls_server_hello_psk(args, gfp_flags, handshake_key_update_type);
The argument structure is filled in as above.
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index 700c37af52ba..b07401ad68eb 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -20,6 +20,7 @@
#include <linux/iov_iter.h>
#include <net/busy_poll.h>
#include <trace/events/sock.h>
+#include <uapi/linux/handshake.h>
#include "nvme.h"
#include "fabrics.h"
@@ -206,6 +207,10 @@ static struct workqueue_struct *nvme_tcp_wq;
static const struct blk_mq_ops nvme_tcp_mq_ops;
static const struct blk_mq_ops nvme_tcp_admin_mq_ops;
static int nvme_tcp_try_send(struct nvme_tcp_queue *queue);
+static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl,
+ struct nvme_tcp_queue *queue,
+ key_serial_t pskid,
+ handshake_key_update_type keyupdate);
static inline struct nvme_tcp_ctrl *to_tcp_ctrl(struct nvme_ctrl *ctrl)
{
@@ -1726,7 +1731,8 @@ static void nvme_tcp_tls_done(void *data, int status, key_serial_t pskid,
static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl,
struct nvme_tcp_queue *queue,
- key_serial_t pskid)
+ key_serial_t pskid,
+ handshake_key_update_type keyupdate)
{
int qid = nvme_tcp_queue_id(queue);
int ret;
@@ -1748,7 +1754,7 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl,
args.ta_timeout_ms = tls_handshake_timeout * 1000;
queue->tls_err = -EOPNOTSUPP;
init_completion(&queue->tls_complete);
- ret = tls_client_hello_psk(&args, GFP_KERNEL);
+ ret = tls_client_hello_psk(&args, GFP_KERNEL, keyupdate);
if (ret) {
dev_err(nctrl->device, "queue %d: failed to start TLS: %d\n",
qid, ret);
@@ -1898,7 +1904,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid,
/* If PSKs are configured try to start TLS */
if (nvme_tcp_tls_configured(nctrl) && pskid) {
- ret = nvme_tcp_start_tls(nctrl, queue, pskid);
+ ret = nvme_tcp_start_tls(nctrl, queue, pskid, HANDSHAKE_KEY_UPDATE_TYPE_UNSPEC);
if (ret)
goto err_init_connect;
}
diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
index 4ef4dd140ada..bee0355195f5 100644
--- a/drivers/nvme/target/tcp.c
+++ b/drivers/nvme/target/tcp.c
@@ -214,6 +214,10 @@ static struct workqueue_struct *nvmet_tcp_wq;
static const struct nvmet_fabrics_ops nvmet_tcp_ops;
static void nvmet_tcp_free_cmd(struct nvmet_tcp_cmd *c);
static void nvmet_tcp_free_cmd_buffers(struct nvmet_tcp_cmd *cmd);
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue,
+ handshake_key_update_type keyupdate);
+#endif
static inline u16 nvmet_tcp_cmd_tag(struct nvmet_tcp_queue *queue,
struct nvmet_tcp_cmd *cmd)
@@ -1833,7 +1837,8 @@ static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w)
kref_put(&queue->kref, nvmet_tcp_release_queue);
}
-static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue)
+static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue,
+ handshake_key_update_type keyupdate)
{
int ret = -EOPNOTSUPP;
struct tls_handshake_args args;
@@ -1852,7 +1857,7 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue)
args.ta_keyring = key_serial(queue->port->nport->keyring);
args.ta_timeout_ms = tls_handshake_timeout * 1000;
- ret = tls_server_hello_psk(&args, GFP_KERNEL);
+ ret = tls_server_hello_psk(&args, GFP_KERNEL, keyupdate);
if (ret) {
kref_put(&queue->kref, nvmet_tcp_release_queue);
pr_err("failed to start TLS, err=%d\n", ret);
@@ -1934,7 +1939,7 @@ static void nvmet_tcp_alloc_queue(struct nvmet_tcp_port *port,
sk->sk_data_ready = port->data_ready;
write_unlock_bh(&sk->sk_callback_lock);
if (!nvmet_tcp_try_peek_pdu(queue)) {
- if (!nvmet_tcp_tls_handshake(queue))
+ if (!nvmet_tcp_tls_handshake(queue, HANDSHAKE_KEY_UPDATE_TYPE_UNSPEC))
return;
/* TLS handshake failed, terminate the connection */
goto out_destroy_sq;
diff --git a/include/net/handshake.h b/include/net/handshake.h
index dc2222fd6d99..7da5d09b9bad 100644
--- a/include/net/handshake.h
+++ b/include/net/handshake.h
@@ -10,6 +10,10 @@
#ifndef _NET_HANDSHAKE_H
#define _NET_HANDSHAKE_H
+#include <uapi/linux/handshake.h>
+
+#define handshake_key_update_type u32
+
enum {
TLS_NO_KEYRING = 0,
TLS_NO_PEERID = 0,
@@ -37,9 +41,11 @@ struct tls_handshake_args {
int tls_client_hello_anon(const struct tls_handshake_args *args, gfp_t flags);
int tls_client_hello_x509(const struct tls_handshake_args *args, gfp_t flags);
-int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags);
+int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags,
+ handshake_key_update_type keyupdate);
int tls_server_hello_x509(const struct tls_handshake_args *args, gfp_t flags);
-int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags);
+int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags,
+ handshake_key_update_type keyupdate);
bool tls_handshake_cancel(struct sock *sk);
void tls_handshake_close(struct socket *sock);
diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h
index b68ffbaa5f31..b691530073c6 100644
--- a/include/uapi/linux/handshake.h
+++ b/include/uapi/linux/handshake.h
@@ -19,6 +19,10 @@ enum handshake_msg_type {
HANDSHAKE_MSG_TYPE_UNSPEC,
HANDSHAKE_MSG_TYPE_CLIENTHELLO,
HANDSHAKE_MSG_TYPE_SERVERHELLO,
+ HANDSHAKE_MSG_TYPE_CLIENTKEYUPDATE,
+ HANDSHAKE_MSG_TYPE_CLIENTKEYUPDATEREQUEST,
+ HANDSHAKE_MSG_TYPE_SERVERKEYUPDATE,
+ HANDSHAKE_MSG_TYPE_SERVERKEYUPDATEREQUEST,
};
enum handshake_auth {
@@ -28,6 +32,13 @@ enum handshake_auth {
HANDSHAKE_AUTH_X509,
};
+enum handshake_key_update_type {
+ HANDSHAKE_KEY_UPDATE_TYPE_UNSPEC,
+ HANDSHAKE_KEY_UPDATE_TYPE_SEND,
+ HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED,
+ HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED_REQUEST_UPDATE,
+};
+
enum {
HANDSHAKE_A_X509_CERT = 1,
HANDSHAKE_A_X509_PRIVKEY,
@@ -46,6 +57,8 @@ enum {
HANDSHAKE_A_ACCEPT_CERTIFICATE,
HANDSHAKE_A_ACCEPT_PEERNAME,
HANDSHAKE_A_ACCEPT_KEYRING,
+ HANDSHAKE_A_ACCEPT_KEY_UPDATE_REQUEST,
+ HANDSHAKE_A_ACCEPT_KEY_SERIAL,
__HANDSHAKE_A_ACCEPT_MAX,
HANDSHAKE_A_ACCEPT_MAX = (__HANDSHAKE_A_ACCEPT_MAX - 1)
diff --git a/net/handshake/tlshd.c b/net/handshake/tlshd.c
index 2549c5dbccd8..05126f8943f1 100644
--- a/net/handshake/tlshd.c
+++ b/net/handshake/tlshd.c
@@ -41,6 +41,7 @@ struct tls_handshake_req {
unsigned int th_num_peerids;
key_serial_t th_peerid[5];
+ int th_key_update_request;
key_serial_t user_session_id;
};
@@ -58,7 +59,8 @@ tls_handshake_req_init(struct handshake_req *req,
treq->th_num_peerids = 0;
treq->th_certificate = TLS_NO_CERT;
treq->th_privkey = TLS_NO_PRIVKEY;
- treq->user_session_id = TLS_NO_PRIVKEY;
+ treq->user_session_id = args->user_session_id;
+
return treq;
}
@@ -265,6 +267,16 @@ static int tls_handshake_accept(struct handshake_req *req,
break;
}
+ ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_KEY_SERIAL,
+ treq->user_session_id);
+ if (ret < 0)
+ goto out_cancel;
+
+ ret = nla_put_u32(msg, HANDSHAKE_A_ACCEPT_KEY_UPDATE_REQUEST,
+ treq->th_key_update_request);
+ if (ret < 0)
+ goto out_cancel;
+
genlmsg_end(msg, hdr);
return genlmsg_reply(msg, info);
@@ -341,6 +353,7 @@ EXPORT_SYMBOL(tls_client_hello_x509);
* tls_client_hello_psk - request a PSK-based TLS handshake on a socket
* @args: socket and handshake parameters for this request
* @flags: memory allocation control flags
+ * @keyupdate: specifies if and what type of KeyUpdate operation
*
* Return values:
* %0: Handshake request enqueue; ->done will be called when complete
@@ -348,7 +361,8 @@ EXPORT_SYMBOL(tls_client_hello_x509);
* %-ESRCH: No user agent is available
* %-ENOMEM: Memory allocation failed
*/
-int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
+int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags,
+ handshake_key_update_type keyupdate)
{
struct tls_handshake_req *treq;
struct handshake_req *req;
@@ -362,7 +376,11 @@ int tls_client_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
if (!req)
return -ENOMEM;
treq = tls_handshake_req_init(req, args);
- treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
+ if (keyupdate != HANDSHAKE_KEY_UPDATE_TYPE_UNSPEC)
+ treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTKEYUPDATE;
+ else
+ treq->th_type = HANDSHAKE_MSG_TYPE_CLIENTHELLO;
+ treq->th_key_update_request = keyupdate;
treq->th_auth_mode = HANDSHAKE_AUTH_PSK;
treq->th_num_peerids = args->ta_num_peerids;
for (i = 0; i < args->ta_num_peerids; i++)
@@ -404,13 +422,15 @@ EXPORT_SYMBOL(tls_server_hello_x509);
* tls_server_hello_psk - request a server TLS handshake on a socket
* @args: socket and handshake parameters for this request
* @flags: memory allocation control flags
+ * @keyupdate: specifies if and what type of KeyUpdate operation
*
* Return values:
* %0: Handshake request enqueue; ->done will be called when complete
* %-ESRCH: No user agent is available
* %-ENOMEM: Memory allocation failed
*/
-int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
+int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags,
+ handshake_key_update_type keyupdate)
{
struct tls_handshake_req *treq;
struct handshake_req *req;
@@ -419,7 +439,11 @@ int tls_server_hello_psk(const struct tls_handshake_args *args, gfp_t flags)
if (!req)
return -ENOMEM;
treq = tls_handshake_req_init(req, args);
- treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO;
+ if (keyupdate != HANDSHAKE_KEY_UPDATE_TYPE_UNSPEC)
+ treq->th_type = HANDSHAKE_MSG_TYPE_SERVERKEYUPDATE;
+ else
+ treq->th_type = HANDSHAKE_MSG_TYPE_SERVERHELLO;
+ treq->th_key_update_request = keyupdate;
treq->th_auth_mode = HANDSHAKE_AUTH_PSK;
treq->th_num_peerids = 1;
treq->th_peerid[0] = args->ta_my_peerids[0];
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 6/8] nvme-tcp: Support KeyUpdate
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
` (4 preceding siblings ...)
2025-10-03 4:31 ` [PATCH v3 5/8] net/handshake: Support KeyUpdate message types alistair23
@ 2025-10-03 4:31 ` alistair23
2025-10-06 6:34 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 7/8] nvmet-tcp: " alistair23
2025-10-03 4:31 ` [PATCH v3 8/8] nvme-tcp: Allow userspace to trigger a KeyUpdate with debugfs alistair23
7 siblings, 1 reply; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
If the nvme_tcp_try_send() or nvme_tcp_try_recv() functions return
EKEYEXPIRED then the underlying TLS keys need to be updated. This occurs
on an KeyUpdate event.
If the NVMe Target (TLS server) initiates a KeyUpdate this patch will
allow the NVMe layer to process the KeyUpdate request and forward the
request to userspace. Userspace must then update the key to keep the
connection alive.
This patch allows us to handle the NVMe target sending a KeyUpdate
request without aborting the connection. At this time we don't support
initiating a KeyUpdate.
Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
v3:
- Don't cancel existing handshake requests
v2:
- Don't change the state
- Use a helper function for KeyUpdates
- Continue sending in nvme_tcp_send_all() after a KeyUpdate
- Remove command message using recvmsg
drivers/nvme/host/tcp.c | 60 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 54 insertions(+), 6 deletions(-)
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index b07401ad68eb..4f27319f0078 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -172,6 +172,7 @@ struct nvme_tcp_queue {
bool tls_enabled;
u32 rcv_crc;
u32 snd_crc;
+ key_serial_t user_session_id;
__le32 exp_ddgst;
__le32 recv_ddgst;
struct completion tls_complete;
@@ -211,6 +212,7 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl,
struct nvme_tcp_queue *queue,
key_serial_t pskid,
handshake_key_update_type keyupdate);
+static void update_tls_keys(struct nvme_tcp_queue *queue);
static inline struct nvme_tcp_ctrl *to_tcp_ctrl(struct nvme_ctrl *ctrl)
{
@@ -394,6 +396,14 @@ static inline void nvme_tcp_send_all(struct nvme_tcp_queue *queue)
do {
ret = nvme_tcp_try_send(queue);
} while (ret > 0);
+
+ if (ret == -EKEYEXPIRED) {
+ update_tls_keys(queue);
+
+ do {
+ ret = nvme_tcp_try_send(queue);
+ } while (ret > 0);
+ }
}
static inline bool nvme_tcp_queue_has_pending(struct nvme_tcp_queue *queue)
@@ -1346,6 +1356,8 @@ static int nvme_tcp_try_send(struct nvme_tcp_queue *queue)
done:
if (ret == -EAGAIN) {
ret = 0;
+ } else if (ret == -EKEYEXPIRED) {
+ goto out;
} else if (ret < 0) {
dev_err(queue->ctrl->ctrl.device,
"failed to send request %d\n", ret);
@@ -1381,17 +1393,45 @@ static int nvme_tcp_try_recvmsg(struct nvme_tcp_queue *queue)
}
} while (result >= 0);
- if (result < 0 && result != -EAGAIN) {
+ if (result == -EKEYEXPIRED) {
+ return -EKEYEXPIRED;
+ } else if (result == -EAGAIN) {
+ result = 0;
+ } else if (result < 0) {
dev_err(queue->ctrl->ctrl.device,
"receive failed: %d\n", result);
queue->rd_enabled = false;
nvme_tcp_error_recovery(&queue->ctrl->ctrl);
- } else if (result == -EAGAIN)
- result = 0;
+ }
return result < 0 ? result : (queue->nr_cqe = nr_cqe);
}
+static void update_tls_keys(struct nvme_tcp_queue *queue)
+{
+ int qid = nvme_tcp_queue_id(queue);
+ int ret;
+
+ dev_dbg(queue->ctrl->ctrl.device,
+ "updating key for queue %d\n", qid);
+
+ cancel_work(&queue->io_work);
+
+ nvme_stop_keep_alive(&(queue->ctrl->ctrl));
+ flush_work(&(queue->ctrl->ctrl).async_event_work);
+
+ ret = nvme_tcp_start_tls(&(queue->ctrl->ctrl),
+ queue, queue->ctrl->ctrl.tls_pskid,
+ HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED);
+
+ if (ret < 0) {
+ dev_err(queue->ctrl->ctrl.device,
+ "failed to update the keys %d\n", ret);
+ nvme_tcp_fail_request(queue->request);
+ nvme_tcp_done_send_req(queue);
+ }
+}
+
static void nvme_tcp_io_work(struct work_struct *w)
{
struct nvme_tcp_queue *queue =
@@ -1407,15 +1447,21 @@ static void nvme_tcp_io_work(struct work_struct *w)
mutex_unlock(&queue->send_mutex);
if (result > 0)
pending = true;
- else if (unlikely(result < 0))
+ else if (unlikely(result < 0)) {
+ if (result == -EKEYEXPIRED)
+ update_tls_keys(queue);
break;
+ }
}
result = nvme_tcp_try_recvmsg(queue);
if (result > 0)
pending = true;
- else if (unlikely(result < 0))
- return;
+ else if (unlikely(result < 0)) {
+ if (result == -EKEYEXPIRED)
+ update_tls_keys(queue);
+ break;
+ }
/* did we get some space after spending time in recv? */
if (nvme_tcp_queue_has_pending(queue) &&
@@ -1723,6 +1769,7 @@ static void nvme_tcp_tls_done(void *data, int status, key_serial_t pskid,
ctrl->ctrl.tls_pskid = key_serial(tls_key);
key_put(tls_key);
queue->tls_err = 0;
+ queue->user_session_id = user_session_id;
}
out_complete:
@@ -1752,6 +1799,7 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl,
keyring = key_serial(nctrl->opts->keyring);
args.ta_keyring = keyring;
args.ta_timeout_ms = tls_handshake_timeout * 1000;
+ args.user_session_id = queue->user_session_id;
queue->tls_err = -EOPNOTSUPP;
init_completion(&queue->tls_complete);
ret = tls_client_hello_psk(&args, GFP_KERNEL, keyupdate);
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 7/8] nvmet-tcp: Support KeyUpdate
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
` (5 preceding siblings ...)
2025-10-03 4:31 ` [PATCH v3 6/8] nvme-tcp: Support KeyUpdate alistair23
@ 2025-10-03 4:31 ` alistair23
2025-10-03 9:54 ` Christoph Hellwig
2025-10-06 6:48 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 8/8] nvme-tcp: Allow userspace to trigger a KeyUpdate with debugfs alistair23
7 siblings, 2 replies; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
If the nvmet_tcp_try_recv() function return EKEYEXPIRED or if we receive
a KeyUpdate handshake type then the underlying TLS keys need to be
updated.
If the NVMe Host (TLS client) initiates a KeyUpdate this patch will
allow the NVMe layer to process the KeyUpdate request and forward the
request to userspace. Userspace must then update the key to keep the
connection alive.
This patch allows us to handle the NVMe host sending a KeyUpdate
request without aborting the connection. At this time we don't support
initiating a KeyUpdate.
Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
v3:
- Use a write lock for sk_user_data
- Fix build with CONFIG_NVME_TARGET_TCP_TLS disabled
- Remove unused variable
v2:
- Use a helper function for KeyUpdates
- Ensure keep alive timer is stopped
- Wait for TLS KeyUpdate to complete
drivers/nvme/target/tcp.c | 90 ++++++++++++++++++++++++++++++++++++---
1 file changed, 85 insertions(+), 5 deletions(-)
diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
index bee0355195f5..fd59dd3ca632 100644
--- a/drivers/nvme/target/tcp.c
+++ b/drivers/nvme/target/tcp.c
@@ -175,6 +175,7 @@ struct nvmet_tcp_queue {
/* TLS state */
key_serial_t tls_pskid;
+ key_serial_t user_session_id;
struct delayed_work tls_handshake_tmo_work;
unsigned long poll_end;
@@ -186,6 +187,8 @@ struct nvmet_tcp_queue {
struct sockaddr_storage sockaddr_peer;
struct work_struct release_work;
+ struct completion tls_complete;
+
int idx;
struct list_head queue_list;
@@ -836,6 +839,11 @@ static int nvmet_tcp_try_send_one(struct nvmet_tcp_queue *queue,
return 1;
}
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+static int nvmet_tcp_try_peek_pdu(struct nvmet_tcp_queue *queue);
+static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w);
+#endif
+
static int nvmet_tcp_try_send(struct nvmet_tcp_queue *queue,
int budget, int *sends)
{
@@ -844,6 +852,13 @@ static int nvmet_tcp_try_send(struct nvmet_tcp_queue *queue,
for (i = 0; i < budget; i++) {
ret = nvmet_tcp_try_send_one(queue, i == budget - 1);
if (unlikely(ret < 0)) {
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+ if (ret == -EKEYEXPIRED &&
+ queue->state != NVMET_TCP_Q_DISCONNECTING &&
+ queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
+ goto done;
+ }
+#endif
nvmet_tcp_socket_error(queue, ret);
goto done;
} else if (ret == 0) {
@@ -1110,6 +1125,45 @@ static inline bool nvmet_tcp_pdu_valid(u8 type)
return false;
}
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+static int update_tls_keys(struct nvmet_tcp_queue *queue)
+{
+ int ret;
+
+ cancel_work(&queue->io_work);
+ queue->state = NVMET_TCP_Q_TLS_HANDSHAKE;
+
+ /* Restore the default callbacks before starting upcall */
+ write_lock_bh(&queue->sock->sk->sk_callback_lock);
+ queue->sock->sk->sk_data_ready = queue->data_ready;
+ queue->sock->sk->sk_state_change = queue->state_change;
+ queue->sock->sk->sk_write_space = queue->write_space;
+ queue->sock->sk->sk_user_data = NULL;
+ write_unlock_bh(&queue->sock->sk->sk_callback_lock);
+
+ nvmet_stop_keep_alive_timer(queue->nvme_sq.ctrl);
+
+ INIT_DELAYED_WORK(&queue->tls_handshake_tmo_work,
+ nvmet_tcp_tls_handshake_timeout);
+
+ ret = nvmet_tcp_tls_handshake(queue, HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED);
+
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&queue->tls_complete, 10 * HZ);
+
+ if (ret <= 0) {
+ tls_handshake_cancel(queue->sock->sk);
+ return ret;
+ }
+
+ queue->state = NVMET_TCP_Q_LIVE;
+
+ return ret;
+}
+#endif
+
static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_queue *queue,
struct msghdr *msg, char *cbuf)
{
@@ -1135,6 +1189,9 @@ static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_queue *queue,
ret = -EAGAIN;
}
break;
+ case TLS_RECORD_TYPE_HANDSHAKE:
+ ret = -EAGAIN;
+ break;
default:
/* discard this record type */
pr_err("queue %d: TLS record %d unhandled\n",
@@ -1344,6 +1401,13 @@ static int nvmet_tcp_try_recv(struct nvmet_tcp_queue *queue,
for (i = 0; i < budget; i++) {
ret = nvmet_tcp_try_recv_one(queue);
if (unlikely(ret < 0)) {
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+ if (ret == -EKEYEXPIRED &&
+ queue->state != NVMET_TCP_Q_DISCONNECTING &&
+ queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
+ goto done;
+ }
+#endif
nvmet_tcp_socket_error(queue, ret);
goto done;
} else if (ret == 0) {
@@ -1408,14 +1472,26 @@ static void nvmet_tcp_io_work(struct work_struct *w)
ret = nvmet_tcp_try_recv(queue, NVMET_TCP_RECV_BUDGET, &ops);
if (ret > 0)
pending = true;
- else if (ret < 0)
- return;
+ else if (ret < 0) {
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+ if (ret == -EKEYEXPIRED)
+ update_tls_keys(queue);
+ else
+#endif
+ return;
+ }
ret = nvmet_tcp_try_send(queue, NVMET_TCP_SEND_BUDGET, &ops);
if (ret > 0)
pending = true;
- else if (ret < 0)
- return;
+ else if (ret < 0) {
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+ if (ret == -EKEYEXPIRED)
+ update_tls_keys(queue);
+ else
+#endif
+ return;
+ }
} while (pending && ops < NVMET_TCP_IO_WORK_BUDGET);
@@ -1798,6 +1874,7 @@ static void nvmet_tcp_tls_handshake_done(void *data, int status,
}
if (!status) {
queue->tls_pskid = peerid;
+ queue->user_session_id = user_session_id;
queue->state = NVMET_TCP_Q_CONNECTING;
} else
queue->state = NVMET_TCP_Q_FAILED;
@@ -1813,6 +1890,7 @@ static void nvmet_tcp_tls_handshake_done(void *data, int status,
else
nvmet_tcp_set_queue_sock(queue);
kref_put(&queue->kref, nvmet_tcp_release_queue);
+ complete(&queue->tls_complete);
}
static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w)
@@ -1843,7 +1921,7 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue,
int ret = -EOPNOTSUPP;
struct tls_handshake_args args;
- if (queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
+ if (queue->state != NVMET_TCP_Q_TLS_HANDSHAKE && !keyupdate) {
pr_warn("cannot start TLS in state %d\n", queue->state);
return -EINVAL;
}
@@ -1856,7 +1934,9 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue,
args.ta_data = queue;
args.ta_keyring = key_serial(queue->port->nport->keyring);
args.ta_timeout_ms = tls_handshake_timeout * 1000;
+ args.user_session_id = queue->user_session_id;
+ init_completion(&queue->tls_complete);
ret = tls_server_hello_psk(&args, GFP_KERNEL, keyupdate);
if (ret) {
kref_put(&queue->kref, nvmet_tcp_release_queue);
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 8/8] nvme-tcp: Allow userspace to trigger a KeyUpdate with debugfs
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
` (6 preceding siblings ...)
2025-10-03 4:31 ` [PATCH v3 7/8] nvmet-tcp: " alistair23
@ 2025-10-03 4:31 ` alistair23
7 siblings, 0 replies; 22+ messages in thread
From: alistair23 @ 2025-10-03 4:31 UTC (permalink / raw)
To: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, hare, alistair23, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Allow userspace to trigger a KeyUpdate via debugfs. This patch exposes a
key_update file that can be written to with the queue number to trigger
a KeyUpdate on that queue.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
v3:
- New patch
drivers/nvme/host/tcp.c | 72 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index 4f27319f0078..8c6d18727e90 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -11,6 +11,7 @@
#include <linux/crc32.h>
#include <linux/nvme-tcp.h>
#include <linux/nvme-keyring.h>
+#include <linux/debugfs.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <net/tls.h>
@@ -1432,6 +1433,75 @@ static void update_tls_keys(struct nvme_tcp_queue *queue)
}
}
+#ifdef CONFIG_NVME_TCP_TLS
+#define NVME_DEBUGFS_RW_ATTR(field) \
+ static int field##_open(struct inode *inode, struct file *file) \
+ { return single_open(file, field##_show, inode->i_private); } \
+ \
+ static const struct file_operations field##_fops = { \
+ .open = field##_open, \
+ .read = seq_read, \
+ .write = field##_write, \
+ .release = single_release, \
+ }
+
+static int nvme_ctrl_key_update_show(struct seq_file *m, void *p)
+{
+ seq_printf(m, "0\n");
+
+ return 0;
+}
+
+static ssize_t nvme_ctrl_key_update_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *m = file->private_data;
+ struct nvme_ctrl *nctrl = m->private;
+ struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl);
+ char kbuf[16] = {0};
+ int queue_nr, rc;
+ struct nvme_tcp_queue *queue;
+
+ if (count > sizeof(kbuf) - 1)
+ return -EINVAL;
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+ kbuf[count] = 0;
+
+ rc = kstrtouint(kbuf, 10, &queue_nr);
+ if (rc)
+ return rc;
+
+ if (queue_nr >= nctrl->queue_count)
+ return -EINVAL;
+
+ queue = &ctrl->queues[queue_nr];
+
+ update_tls_keys(queue);
+
+ return count;
+}
+NVME_DEBUGFS_RW_ATTR(nvme_ctrl_key_update);
+#endif
+
+static void nvme_tcp_debugfs_init(struct nvme_ctrl *ctrl,
+ const char *dev_name)
+{
+ struct dentry *parent;
+
+ /* create debugfs directory and attribute */
+ parent = debugfs_create_dir(dev_name, NULL);
+ if (IS_ERR(parent)) {
+ pr_warn("%s: failed to create debugfs directory\n", dev_name);
+ return;
+ }
+
+#ifdef CONFIG_NVME_TCP_TLS
+ debugfs_create_file("key_update", S_IWUSR, parent, ctrl,
+ &nvme_ctrl_key_update_fops);
+#endif
+}
+
static void nvme_tcp_io_work(struct work_struct *w)
{
struct nvme_tcp_queue *queue =
@@ -3065,6 +3135,8 @@ static struct nvme_ctrl *nvme_tcp_create_ctrl(struct device *dev,
list_add_tail(&ctrl->list, &nvme_tcp_ctrl_list);
mutex_unlock(&nvme_tcp_ctrl_mutex);
+ nvme_tcp_debugfs_init(&ctrl->ctrl, dev_name(dev));
+
return &ctrl->ctrl;
out_uninit_ctrl:
--
2.51.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically
2025-10-03 4:31 ` [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically alistair23
@ 2025-10-03 9:51 ` Christoph Hellwig
2025-10-06 6:36 ` Hannes Reinecke
1 sibling, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2025-10-03 9:51 UTC (permalink / raw)
To: alistair23
Cc: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs, kbusch, axboe, hch, sagi, kch,
hare, Alistair Francis
On Fri, Oct 03, 2025 at 02:31:35PM +1000, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Please explain why making it available is a good idea. Because it
feels a bit sketchy as-is.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req
2025-10-03 4:31 ` [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req alistair23
@ 2025-10-03 9:51 ` Simon Horman
2025-10-06 6:24 ` Hannes Reinecke
1 sibling, 0 replies; 22+ messages in thread
From: Simon Horman @ 2025-10-03 9:51 UTC (permalink / raw)
To: alistair23
Cc: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs, kbusch, axboe, hch, sagi, kch,
hare, Alistair Francis
On Fri, Oct 03, 2025 at 02:31:33PM +1000, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Define a `handshake_sk_destruct_req()` function to allow the destruction
> of the handshake req.
>
> This is required to avoid hash conflicts when handshake_req_hash_add()
> is called as part of submitting the KeyUpdate request.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> v3:
> - New patch
Hi Alistair,
This is a not a proper review: I'll leave that to others.
But I notice that both Clang 21.1.1 and GCC 15.2.0, when run with
-Wunused-function, flag handshake_sk_descruct_req() as unused.
Which is the case until the following patch.
As both this and the following patch are small, and touch the same file,
I'm wondering if a simple approach is to squash the two patches into one.
Or perhaps no one cares. If so, sorry for the noise.
...
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 7/8] nvmet-tcp: Support KeyUpdate
2025-10-03 4:31 ` [PATCH v3 7/8] nvmet-tcp: " alistair23
@ 2025-10-03 9:54 ` Christoph Hellwig
2025-10-06 6:48 ` Hannes Reinecke
1 sibling, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2025-10-03 9:54 UTC (permalink / raw)
To: alistair23
Cc: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs, kbusch, axboe, hch, sagi, kch,
hare, Alistair Francis
On Fri, Oct 03, 2025 at 02:31:38PM +1000, alistair23@gmail.com wrote:
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +static int nvmet_tcp_try_peek_pdu(struct nvmet_tcp_queue *queue);
> +static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w);
> +#endif
Can we find a way to structure the code to do without forward declarations
and either without ifdefs or with stubs when you need them?
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED &&
> + queue->state != NVMET_TCP_Q_DISCONNECTING &&
> + queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
> + goto done;
> + }
Wrong indentation and superflous braces here.
> + ret = nvmet_tcp_tls_handshake(queue, HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED);
> +
> + if (ret < 0)
> + return ret;
> +
> + ret = wait_for_completion_interruptible_timeout(&queue->tls_complete, 10 * HZ);
Please avoid the overly long lines.
> +
> + if (ret <= 0) {
> + tls_handshake_cancel(queue->sock->sk);
> + return ret;
> + }
> +
> + queue->state = NVMET_TCP_Q_LIVE;
> +
> + return ret;
This should be a unconditional ret 0, or am I missing something?
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED &&
> + queue->state != NVMET_TCP_Q_DISCONNECTING &&
> + queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
> + goto done;
> + }
Same as above. And given that we have multiple instances of this
check I suspect we want a little helper for it.
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED)
> + update_tls_keys(queue);
> + else
> +#endif
> + return;
> + }
If you provide a proper stub for update_tls_keys this becomes much
saner:
if (ret != -EKEYEXPIRED)
return;
update_tls_keys(queue);
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED)
> + update_tls_keys(queue);
> + else
> +#endif
> + return;
Same here.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 1/8] net/handshake: Store the key serial number on completion
2025-10-03 4:31 ` [PATCH v3 1/8] net/handshake: Store the key serial number on completion alistair23
@ 2025-10-06 6:15 ` Hannes Reinecke
0 siblings, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-06 6:15 UTC (permalink / raw)
To: alistair23, chuck.lever, hare, kernel-tls-handshake, netdev,
linux-kernel, linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, Alistair Francis
On 10/3/25 06:31, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Allow userspace to include a key serial number when completing a
> handshake with the HANDSHAKE_CMD_DONE command.
>
> We then store this serial number and will provide it back to userspace
> in the future. This allows userspace to save data to the keyring and
> then restore that data later.
>
> This will be used to support the TLS KeyUpdate operation, as now
> userspace can resume information about a established session.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> v3:
> - No change
> v2:
> - Change "key-serial" to "session-id"
>
Reviewed-by: Hannes Reincke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion
2025-10-03 4:31 ` [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion alistair23
@ 2025-10-06 6:16 ` Hannes Reinecke
2025-10-07 1:22 ` Alistair Francis
0 siblings, 1 reply; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-06 6:16 UTC (permalink / raw)
To: alistair23, chuck.lever, hare, kernel-tls-handshake, netdev,
linux-kernel, linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, Alistair Francis
On 10/3/25 06:31, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> To avoid future handshake_req_hash_add() calls failing with EEXIST when
> performing a KeyUpdate let's make sure the old request is destructed
> as part of the completion.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> v3:
> - New patch
>
> net/handshake/request.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/net/handshake/request.c b/net/handshake/request.c
> index 0d1c91c80478..194725a8aaca 100644
> --- a/net/handshake/request.c
> +++ b/net/handshake/request.c
> @@ -311,6 +311,8 @@ void handshake_complete(struct handshake_req *req, unsigned int status,
> /* Handshake request is no longer pending */
> sock_put(sk);
> }
> +
> + handshake_sk_destruct_req(sk);
> }
> EXPORT_SYMBOL_IF_KUNIT(handshake_complete);
>
Curious.
Why do we need it now? We had been happily using the handshake mechanism
for quite some time now, so who had been destroying the request without
this patch?
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 5/8] net/handshake: Support KeyUpdate message types
2025-10-03 4:31 ` [PATCH v3 5/8] net/handshake: Support KeyUpdate message types alistair23
@ 2025-10-06 6:20 ` Hannes Reinecke
0 siblings, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-06 6:20 UTC (permalink / raw)
To: alistair23, chuck.lever, hare, kernel-tls-handshake, netdev,
linux-kernel, linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, Alistair Francis
On 10/3/25 06:31, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> When reporting the msg-type to userspace let's also support reporting
> KeyUpdate events. This supports reporting a client/server event and if
> the other side requested a KeyUpdateRequest.
>
> Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> v3:
> - Fixup yamllint and kernel-doc failures
>
> Documentation/netlink/specs/handshake.yaml | 16 +++++++++-
> Documentation/networking/tls-handshake.rst | 4 +--
> drivers/nvme/host/tcp.c | 12 ++++++--
> drivers/nvme/target/tcp.c | 11 +++++--
> include/net/handshake.h | 10 +++++--
> include/uapi/linux/handshake.h | 13 +++++++++
> net/handshake/tlshd.c | 34 ++++++++++++++++++----
> 7 files changed, 84 insertions(+), 16 deletions(-)
>
> diff --git a/Documentation/netlink/specs/handshake.yaml b/Documentation/netlink/specs/handshake.yaml
> index a273bc74d26f..c72ec8fa7d7a 100644
> --- a/Documentation/netlink/specs/handshake.yaml
> +++ b/Documentation/netlink/specs/handshake.yaml
> @@ -21,12 +21,18 @@ definitions:
> type: enum
> name: msg-type
> value-start: 0
> - entries: [unspec, clienthello, serverhello]
> + entries: [unspec, clienthello, serverhello, clientkeyupdate,
> + clientkeyupdaterequest, serverkeyupdate, serverkeyupdaterequest]
> -
> type: enum
> name: auth
> value-start: 0
> entries: [unspec, unauth, psk, x509]
> + -
> + type: enum
> + name: key-update-type
> + value-start: 0
> + entries: [unspec, send, received, received_request_update]
>
> attribute-sets:
> -
> @@ -74,6 +80,13 @@ attribute-sets:
> -
> name: keyring
> type: u32
> + -
> + name: key-update-request
> + type: u32
> + enum: key-update-type
> + -
> + name: key-serial
> + type: u32
> -
> name: done
> attributes:
> @@ -116,6 +129,7 @@ operations:
> - certificate
> - peername
> - keyring
> + - key-serial
> -
> name: done
> doc: Handler reports handshake completion
> diff --git a/Documentation/networking/tls-handshake.rst b/Documentation/networking/tls-handshake.rst
> index d7287890056a..f858011e5bfb 100644
> --- a/Documentation/networking/tls-handshake.rst
> +++ b/Documentation/networking/tls-handshake.rst
> @@ -110,7 +110,7 @@ To initiate a client-side TLS handshake with a pre-shared key, use:
>
> .. code-block:: c
>
> - ret = tls_client_hello_psk(args, gfp_flags);
> + ret = tls_client_hello_psk(args, gfp_flags, handshake_key_update_type);
>
> However, in this case, the consumer fills in the @ta_my_peerids array
> with serial numbers of keys containing the peer identities it wishes
> @@ -140,7 +140,7 @@ or
>
> .. code-block:: c
>
> - ret = tls_server_hello_psk(args, gfp_flags);
> + ret = tls_server_hello_psk(args, gfp_flags, handshake_key_update_type);
>
> The argument structure is filled in as above.
>
I would very much prefer to have our own function here; currently the
'tls_server_hello_psk' is issuing a 'ServerHello' command, the
'tls_client_hello_psk' is issuing a 'ClientHello' command.
Consequently I'd rather have 'tls_client_keyupdate_psk()' /
'tls_server_keyupdate_psk()' functions to indicate that we're sending
a KeyUpdate command instead of overloading the existing
commands.
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req
2025-10-03 4:31 ` [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req alistair23
2025-10-03 9:51 ` Simon Horman
@ 2025-10-06 6:24 ` Hannes Reinecke
1 sibling, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-06 6:24 UTC (permalink / raw)
To: alistair23, chuck.lever, hare, kernel-tls-handshake, netdev,
linux-kernel, linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, Alistair Francis
On 10/3/25 06:31, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Define a `handshake_sk_destruct_req()` function to allow the destruction
> of the handshake req.
>
> This is required to avoid hash conflicts when handshake_req_hash_add()
> is called as part of submitting the KeyUpdate request.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> v3:
> - New patch
>
> net/handshake/request.c | 16 ++++++++++++++++
> 1 file changed, 16 insertions(+)
>
> diff --git a/net/handshake/request.c b/net/handshake/request.c
> index 274d2c89b6b2..0d1c91c80478 100644
> --- a/net/handshake/request.c
> +++ b/net/handshake/request.c
> @@ -98,6 +98,22 @@ static void handshake_sk_destruct(struct sock *sk)
> sk_destruct(sk);
> }
>
> +/**
> + * handshake_sk_destruct_req - destroy an existing request
> + * @sk: socket on which there is an existing request
> + */
> +static void handshake_sk_destruct_req(struct sock *sk)
> +{
> + struct handshake_req *req;
> +
> + req = handshake_req_hash_lookup(sk);
> + if (!req)
> + return;
> +
> + trace_handshake_destruct(sock_net(sk), req, sk);
> + handshake_req_destroy(req);
> +}
> +
> /**
> * handshake_req_alloc - Allocate a handshake request
> * @proto: security protocol
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 6/8] nvme-tcp: Support KeyUpdate
2025-10-03 4:31 ` [PATCH v3 6/8] nvme-tcp: Support KeyUpdate alistair23
@ 2025-10-06 6:34 ` Hannes Reinecke
0 siblings, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-06 6:34 UTC (permalink / raw)
To: alistair23, chuck.lever, hare, kernel-tls-handshake, netdev,
linux-kernel, linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, Alistair Francis
On 10/3/25 06:31, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> If the nvme_tcp_try_send() or nvme_tcp_try_recv() functions return
> EKEYEXPIRED then the underlying TLS keys need to be updated. This occurs
> on an KeyUpdate event.
>
> If the NVMe Target (TLS server) initiates a KeyUpdate this patch will
> allow the NVMe layer to process the KeyUpdate request and forward the
> request to userspace. Userspace must then update the key to keep the
> connection alive.
>
> This patch allows us to handle the NVMe target sending a KeyUpdate
> request without aborting the connection. At this time we don't support
> initiating a KeyUpdate.
>
> Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> v3:
> - Don't cancel existing handshake requests
> v2:
> - Don't change the state
> - Use a helper function for KeyUpdates
> - Continue sending in nvme_tcp_send_all() after a KeyUpdate
> - Remove command message using recvmsg
>
> drivers/nvme/host/tcp.c | 60 ++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 54 insertions(+), 6 deletions(-)
>
Weelll ... Checking the code the network stack will only return
-EKEYEXPIRED on recvmsg() (as it parses the TLS message on receive).
So really this is handling an incoming KeyUpdate request only.
And I would keep it that way, as initiating a KeyUpdate request
is quite different (conceptually) than handling an incoming one.
> diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
> index b07401ad68eb..4f27319f0078 100644
> --- a/drivers/nvme/host/tcp.c
> +++ b/drivers/nvme/host/tcp.c
> @@ -172,6 +172,7 @@ struct nvme_tcp_queue {
> bool tls_enabled;
> u32 rcv_crc;
> u32 snd_crc;
> + key_serial_t user_session_id;
> __le32 exp_ddgst;
> __le32 recv_ddgst;
> struct completion tls_complete;
> @@ -211,6 +212,7 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl,
> struct nvme_tcp_queue *queue,
> key_serial_t pskid,
> handshake_key_update_type keyupdate);
> +static void update_tls_keys(struct nvme_tcp_queue *queue);
>
> static inline struct nvme_tcp_ctrl *to_tcp_ctrl(struct nvme_ctrl *ctrl)
> {
> @@ -394,6 +396,14 @@ static inline void nvme_tcp_send_all(struct nvme_tcp_queue *queue)
> do {
> ret = nvme_tcp_try_send(queue);
> } while (ret > 0);
> +
> + if (ret == -EKEYEXPIRED) {
> + update_tls_keys(queue);
> +
> + do {
> + ret = nvme_tcp_try_send(queue);
> + } while (ret > 0);
> + }
> }
>
See above. I'd rather have two patches, one for handling incoming
KeyUpdate message (where we'd see an EKEYEXPIRED on receive), and
another one for initiating KeyUpdates.
> static inline bool nvme_tcp_queue_has_pending(struct nvme_tcp_queue *queue)
> @@ -1346,6 +1356,8 @@ static int nvme_tcp_try_send(struct nvme_tcp_queue *queue)
> done:
> if (ret == -EAGAIN) {
> ret = 0;
> + } else if (ret == -EKEYEXPIRED) {
> + goto out;
> } else if (ret < 0) {
> dev_err(queue->ctrl->ctrl.device,
> "failed to send request %d\n", ret);
See above.
> @@ -1381,17 +1393,45 @@ static int nvme_tcp_try_recvmsg(struct nvme_tcp_queue *queue)
> }
> } while (result >= 0);
>
> - if (result < 0 && result != -EAGAIN) {
> + if (result == -EKEYEXPIRED) {
> + return -EKEYEXPIRED;
> + } else if (result == -EAGAIN) {
> + result = 0;
> + } else if (result < 0) {
> dev_err(queue->ctrl->ctrl.device,
> "receive failed: %d\n", result);
> queue->rd_enabled = false;
> nvme_tcp_error_recovery(&queue->ctrl->ctrl);
> - } else if (result == -EAGAIN)
> - result = 0;
> + }
>
> return result < 0 ? result : (queue->nr_cqe = nr_cqe);
> }
>
Remind me again to resend my tcp_recvmsg patch. The blanking out
of -EAGAIN is actually wrong.
> +static void update_tls_keys(struct nvme_tcp_queue *queue)
> +{
> + int qid = nvme_tcp_queue_id(queue);
> + int ret;
> +
> + dev_dbg(queue->ctrl->ctrl.device,
> + "updating key for queue %d\n", qid);
> +
> + cancel_work(&queue->io_work);
> +
Hmm.
You issue a 'cancel_work()', but later on you call this
function from within the workqueue context. Not a great
idea.
I'd rather calling 'update_tls_keys()' from io_work() only,
and drop the 'cancel_work()' invocation as that would be
pointless now.
> + nvme_stop_keep_alive(&(queue->ctrl->ctrl));
> + flush_work(&(queue->ctrl->ctrl).async_event_work);
> +
> + ret = nvme_tcp_start_tls(&(queue->ctrl->ctrl),
> + queue, queue->ctrl->ctrl.tls_pskid,
> + HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED);
> +
> + if (ret < 0) {
> + dev_err(queue->ctrl->ctrl.device,
> + "failed to update the keys %d\n", ret);
> + nvme_tcp_fail_request(queue->request);
> + nvme_tcp_done_send_req(queue);
> + }
> +}
> +
> static void nvme_tcp_io_work(struct work_struct *w)
> {
> struct nvme_tcp_queue *queue =
> @@ -1407,15 +1447,21 @@ static void nvme_tcp_io_work(struct work_struct *w)
> mutex_unlock(&queue->send_mutex);
> if (result > 0)
> pending = true;
> - else if (unlikely(result < 0))
> + else if (unlikely(result < 0)) {
> + if (result == -EKEYEXPIRED)
> + update_tls_keys(queue);
> break;
> + }
> }
>
> result = nvme_tcp_try_recvmsg(queue);
> if (result > 0)
> pending = true;
> - else if (unlikely(result < 0))
> - return;
> + else if (unlikely(result < 0)) {
> + if (result == -EKEYEXPIRED)
> + update_tls_keys(queue);
> + break;
> + }
>
> /* did we get some space after spending time in recv? */
> if (nvme_tcp_queue_has_pending(queue) &&
> @@ -1723,6 +1769,7 @@ static void nvme_tcp_tls_done(void *data, int status, key_serial_t pskid,
> ctrl->ctrl.tls_pskid = key_serial(tls_key);
> key_put(tls_key);
> queue->tls_err = 0;
> + queue->user_session_id = user_session_id;
> }
>
> out_complete:
> @@ -1752,6 +1799,7 @@ static int nvme_tcp_start_tls(struct nvme_ctrl *nctrl,
> keyring = key_serial(nctrl->opts->keyring);
> args.ta_keyring = keyring;
> args.ta_timeout_ms = tls_handshake_timeout * 1000;
> + args.user_session_id = queue->user_session_id;
> queue->tls_err = -EOPNOTSUPP;
> init_completion(&queue->tls_complete);
> ret = tls_client_hello_psk(&args, GFP_KERNEL, keyupdate);
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically
2025-10-03 4:31 ` [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically alistair23
2025-10-03 9:51 ` Christoph Hellwig
@ 2025-10-06 6:36 ` Hannes Reinecke
1 sibling, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-06 6:36 UTC (permalink / raw)
To: alistair23, chuck.lever, hare, kernel-tls-handshake, netdev,
linux-kernel, linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, Alistair Francis
On 10/3/25 06:31, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> drivers/nvme/target/core.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
> index 0dd7bd99afa3..bed1c6ebe83a 100644
> --- a/drivers/nvme/target/core.c
> +++ b/drivers/nvme/target/core.c
> @@ -430,6 +430,7 @@ void nvmet_stop_keep_alive_timer(struct nvmet_ctrl *ctrl)
>
> cancel_delayed_work_sync(&ctrl->ka_work);
> }
> +EXPORT_SYMBOL_GPL(nvmet_stop_keep_alive_timer);
>
> u16 nvmet_req_find_ns(struct nvmet_req *req)
> {
Please reshuffle the patchset to keep the host and target side patches
together. And explain why this one is needed.
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 7/8] nvmet-tcp: Support KeyUpdate
2025-10-03 4:31 ` [PATCH v3 7/8] nvmet-tcp: " alistair23
2025-10-03 9:54 ` Christoph Hellwig
@ 2025-10-06 6:48 ` Hannes Reinecke
2025-10-17 1:53 ` Alistair Francis
1 sibling, 1 reply; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-06 6:48 UTC (permalink / raw)
To: alistair23, chuck.lever, hare, kernel-tls-handshake, netdev,
linux-kernel, linux-doc, linux-nvme, linux-nfs
Cc: kbusch, axboe, hch, sagi, kch, Alistair Francis
On 10/3/25 06:31, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> If the nvmet_tcp_try_recv() function return EKEYEXPIRED or if we receive
> a KeyUpdate handshake type then the underlying TLS keys need to be
> updated.
>
> If the NVMe Host (TLS client) initiates a KeyUpdate this patch will
> allow the NVMe layer to process the KeyUpdate request and forward the
> request to userspace. Userspace must then update the key to keep the
> connection alive.
>
> This patch allows us to handle the NVMe host sending a KeyUpdate
> request without aborting the connection. At this time we don't support
> initiating a KeyUpdate.
>
> Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> v3:
> - Use a write lock for sk_user_data
> - Fix build with CONFIG_NVME_TARGET_TCP_TLS disabled
> - Remove unused variable
> v2:
> - Use a helper function for KeyUpdates
> - Ensure keep alive timer is stopped
> - Wait for TLS KeyUpdate to complete
>
> drivers/nvme/target/tcp.c | 90 ++++++++++++++++++++++++++++++++++++---
> 1 file changed, 85 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
> index bee0355195f5..fd59dd3ca632 100644
> --- a/drivers/nvme/target/tcp.c
> +++ b/drivers/nvme/target/tcp.c
> @@ -175,6 +175,7 @@ struct nvmet_tcp_queue {
>
> /* TLS state */
> key_serial_t tls_pskid;
> + key_serial_t user_session_id;
> struct delayed_work tls_handshake_tmo_work;
>
> unsigned long poll_end;
> @@ -186,6 +187,8 @@ struct nvmet_tcp_queue {
> struct sockaddr_storage sockaddr_peer;
> struct work_struct release_work;
>
> + struct completion tls_complete;
> +
> int idx;
> struct list_head queue_list;
>
> @@ -836,6 +839,11 @@ static int nvmet_tcp_try_send_one(struct nvmet_tcp_queue *queue,
> return 1;
> }
>
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +static int nvmet_tcp_try_peek_pdu(struct nvmet_tcp_queue *queue);
> +static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w);
> +#endif
> +
> static int nvmet_tcp_try_send(struct nvmet_tcp_queue *queue,
> int budget, int *sends)
> {
And we need this why?
> @@ -844,6 +852,13 @@ static int nvmet_tcp_try_send(struct nvmet_tcp_queue *queue,
> for (i = 0; i < budget; i++) {
> ret = nvmet_tcp_try_send_one(queue, i == budget - 1);
> if (unlikely(ret < 0)) {
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED &&
> + queue->state != NVMET_TCP_Q_DISCONNECTING &&
> + queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
> + goto done;
> + }
> +#endif
> nvmet_tcp_socket_error(queue, ret);
> goto done;
> } else if (ret == 0) {
See my comment to the host patches. Handling an incoming KeyUpdate is
vastly different than initiating a KeyUpdate. _and_ the network stack
will only ever return -EKEYEXPIRED on receive.
So please split the patches in handling an incoming KeyUpdate and
initiating a KeyUpdate.
> @@ -1110,6 +1125,45 @@ static inline bool nvmet_tcp_pdu_valid(u8 type)
> return false;
> }
>
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +static int update_tls_keys(struct nvmet_tcp_queue *queue)
> +{
> + int ret;
> +
> + cancel_work(&queue->io_work);
> + queue->state = NVMET_TCP_Q_TLS_HANDSHAKE;
> +
> + /* Restore the default callbacks before starting upcall */
> + write_lock_bh(&queue->sock->sk->sk_callback_lock);
> + queue->sock->sk->sk_data_ready = queue->data_ready;
> + queue->sock->sk->sk_state_change = queue->state_change;
> + queue->sock->sk->sk_write_space = queue->write_space;
> + queue->sock->sk->sk_user_data = NULL;
> + write_unlock_bh(&queue->sock->sk->sk_callback_lock);
> +
We do have a function for this ...
> + nvmet_stop_keep_alive_timer(queue->nvme_sq.ctrl);
> +
> + INIT_DELAYED_WORK(&queue->tls_handshake_tmo_work,
> + nvmet_tcp_tls_handshake_timeout);
> +
> + ret = nvmet_tcp_tls_handshake(queue, HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED);
> +
> + if (ret < 0)
> + return ret;
> +
> + ret = wait_for_completion_interruptible_timeout(&queue->tls_complete, 10 * HZ);
> +
> + if (ret <= 0) {
> + tls_handshake_cancel(queue->sock->sk);
> + return ret;
> + }
> +
> + queue->state = NVMET_TCP_Q_LIVE;
> +
> + return ret;
> +}
> +#endif
> +
> static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_queue *queue,
> struct msghdr *msg, char *cbuf)
> {
> @@ -1135,6 +1189,9 @@ static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_queue *queue,
> ret = -EAGAIN;
> }
> break;
> + case TLS_RECORD_TYPE_HANDSHAKE:
> + ret = -EAGAIN;
> + break;
Shouldn't this be rather -EKEYEXPIRED?
> default:
> /* discard this record type */
> pr_err("queue %d: TLS record %d unhandled\n",
> @@ -1344,6 +1401,13 @@ static int nvmet_tcp_try_recv(struct nvmet_tcp_queue *queue,
> for (i = 0; i < budget; i++) {
> ret = nvmet_tcp_try_recv_one(queue);
> if (unlikely(ret < 0)) {
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED &&
> + queue->state != NVMET_TCP_Q_DISCONNECTING &&
> + queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
> + goto done;
> + }
> +#endif
> nvmet_tcp_socket_error(queue, ret);
> goto done;
> } else if (ret == 0) {
> @@ -1408,14 +1472,26 @@ static void nvmet_tcp_io_work(struct work_struct *w)
> ret = nvmet_tcp_try_recv(queue, NVMET_TCP_RECV_BUDGET, &ops);
> if (ret > 0)
> pending = true;
> - else if (ret < 0)
> - return;
> + else if (ret < 0) {
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED)
> + update_tls_keys(queue);
> + else
> +#endif
> + return;
> + }
>
> ret = nvmet_tcp_try_send(queue, NVMET_TCP_SEND_BUDGET, &ops);
> if (ret > 0)
> pending = true;
> - else if (ret < 0)
> - return;
> + else if (ret < 0) {
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> + if (ret == -EKEYEXPIRED)
> + update_tls_keys(queue);
> + else
> +#endif
> + return;
> + }
>
> } while (pending && ops < NVMET_TCP_IO_WORK_BUDGET);
>
Wouldn't it be better to move the call to 'update_tls_keys()' out of
the loop and just requeue io_work afterwards?
> @@ -1798,6 +1874,7 @@ static void nvmet_tcp_tls_handshake_done(void *data, int status,
> }
> if (!status) {
> queue->tls_pskid = peerid;
> + queue->user_session_id = user_session_id;
> queue->state = NVMET_TCP_Q_CONNECTING;
> } else
> queue->state = NVMET_TCP_Q_FAILED;
> @@ -1813,6 +1890,7 @@ static void nvmet_tcp_tls_handshake_done(void *data, int status,
> else
> nvmet_tcp_set_queue_sock(queue);
> kref_put(&queue->kref, nvmet_tcp_release_queue);
> + complete(&queue->tls_complete);
> }
>
> static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w)
> @@ -1843,7 +1921,7 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue,
> int ret = -EOPNOTSUPP;
> struct tls_handshake_args args;
>
> - if (queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
> + if (queue->state != NVMET_TCP_Q_TLS_HANDSHAKE && !keyupdate) {
> pr_warn("cannot start TLS in state %d\n", queue->state);
> return -EINVAL;
>
Why? Shouldn't we always set the HANDSHAKE state?
> @@ -1856,7 +1934,9 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue,
> args.ta_data = queue;
> args.ta_keyring = key_serial(queue->port->nport->keyring);
> args.ta_timeout_ms = tls_handshake_timeout * 1000;
> + args.user_session_id = queue->user_session_id;
>
> + init_completion(&queue->tls_complete);
> ret = tls_server_hello_psk(&args, GFP_KERNEL, keyupdate);
> if (ret) {
> kref_put(&queue->kref, nvmet_tcp_release_queue)
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion
2025-10-06 6:16 ` Hannes Reinecke
@ 2025-10-07 1:22 ` Alistair Francis
2025-10-07 5:20 ` Hannes Reinecke
0 siblings, 1 reply; 22+ messages in thread
From: Alistair Francis @ 2025-10-07 1:22 UTC (permalink / raw)
To: Hannes Reinecke
Cc: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs, kbusch, axboe, hch, sagi, kch,
Alistair Francis
On Mon, Oct 6, 2025 at 4:16 PM Hannes Reinecke <hare@suse.de> wrote:
>
> On 10/3/25 06:31, alistair23@gmail.com wrote:
> > From: Alistair Francis <alistair.francis@wdc.com>
> >
> > To avoid future handshake_req_hash_add() calls failing with EEXIST when
> > performing a KeyUpdate let's make sure the old request is destructed
> > as part of the completion.
> >
> > Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> > ---
> > v3:
> > - New patch
> >
> > net/handshake/request.c | 2 ++
> > 1 file changed, 2 insertions(+)
> >
> > diff --git a/net/handshake/request.c b/net/handshake/request.c
> > index 0d1c91c80478..194725a8aaca 100644
> > --- a/net/handshake/request.c
> > +++ b/net/handshake/request.c
> > @@ -311,6 +311,8 @@ void handshake_complete(struct handshake_req *req, unsigned int status,
> > /* Handshake request is no longer pending */
> > sock_put(sk);
> > }
> > +
> > + handshake_sk_destruct_req(sk);
> > }
> > EXPORT_SYMBOL_IF_KUNIT(handshake_complete);
> >
> Curious.
> Why do we need it now? We had been happily using the handshake mechanism
> for quite some time now, so who had been destroying the request without
> this patch?
Until now a handshake would only be destroyed on a failure or when a
sock is freed (via the sk_destruct function pointer).
handshake_complete() is only called on errors, not a successful
handshake so it doesn't remove the request.
Note that destroying is mostly just removing the entry from the hash
table with rhashtable_remove_fast(). Which is what we need to be able
to submit it again.
Alistair
>
> Cheers,
>
> Hannes
> --
> Dr. Hannes Reinecke Kernel Storage Architect
> hare@suse.de +49 911 74053 688
> SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
> HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion
2025-10-07 1:22 ` Alistair Francis
@ 2025-10-07 5:20 ` Hannes Reinecke
0 siblings, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2025-10-07 5:20 UTC (permalink / raw)
To: Alistair Francis
Cc: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs, kbusch, axboe, hch, sagi, kch,
Alistair Francis
On 10/7/25 03:22, Alistair Francis wrote:
> On Mon, Oct 6, 2025 at 4:16 PM Hannes Reinecke <hare@suse.de> wrote:
>>
>> On 10/3/25 06:31, alistair23@gmail.com wrote:
>>> From: Alistair Francis <alistair.francis@wdc.com>
>>>
>>> To avoid future handshake_req_hash_add() calls failing with EEXIST when
>>> performing a KeyUpdate let's make sure the old request is destructed
>>> as part of the completion.
>>>
>>> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
>>> ---
>>> v3:
>>> - New patch
>>>
>>> net/handshake/request.c | 2 ++
>>> 1 file changed, 2 insertions(+)
>>>
>>> diff --git a/net/handshake/request.c b/net/handshake/request.c
>>> index 0d1c91c80478..194725a8aaca 100644
>>> --- a/net/handshake/request.c
>>> +++ b/net/handshake/request.c
>>> @@ -311,6 +311,8 @@ void handshake_complete(struct handshake_req *req, unsigned int status,
>>> /* Handshake request is no longer pending */
>>> sock_put(sk);
>>> }
>>> +
>>> + handshake_sk_destruct_req(sk);
>>> }
>>> EXPORT_SYMBOL_IF_KUNIT(handshake_complete);
>>>
>> Curious.
>> Why do we need it now? We had been happily using the handshake mechanism
>> for quite some time now, so who had been destroying the request without
>> this patch?
>
> Until now a handshake would only be destroyed on a failure or when a
> sock is freed (via the sk_destruct function pointer).
> handshake_complete() is only called on errors, not a successful
> handshake so it doesn't remove the request.
>
> Note that destroying is mostly just removing the entry from the hash
> table with rhashtable_remove_fast(). Which is what we need to be able
> to submit it again.
>
And we really should've done that in the first place.
Thanks for the explanation.
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 7/8] nvmet-tcp: Support KeyUpdate
2025-10-06 6:48 ` Hannes Reinecke
@ 2025-10-17 1:53 ` Alistair Francis
0 siblings, 0 replies; 22+ messages in thread
From: Alistair Francis @ 2025-10-17 1:53 UTC (permalink / raw)
To: Hannes Reinecke
Cc: chuck.lever, hare, kernel-tls-handshake, netdev, linux-kernel,
linux-doc, linux-nvme, linux-nfs, kbusch, axboe, hch, sagi, kch,
Alistair Francis
On Mon, Oct 6, 2025 at 4:48 PM Hannes Reinecke <hare@suse.de> wrote:
>
> On 10/3/25 06:31, alistair23@gmail.com wrote:
> > From: Alistair Francis <alistair.francis@wdc.com>
> >
> > If the nvmet_tcp_try_recv() function return EKEYEXPIRED or if we receive
> > a KeyUpdate handshake type then the underlying TLS keys need to be
> > updated.
> >
> > If the NVMe Host (TLS client) initiates a KeyUpdate this patch will
> > allow the NVMe layer to process the KeyUpdate request and forward the
> > request to userspace. Userspace must then update the key to keep the
> > connection alive.
> >
> > This patch allows us to handle the NVMe host sending a KeyUpdate
> > request without aborting the connection. At this time we don't support
> > initiating a KeyUpdate.
> >
> > Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3
> > Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> > ---
> > v3:
> > - Use a write lock for sk_user_data
> > - Fix build with CONFIG_NVME_TARGET_TCP_TLS disabled
> > - Remove unused variable
> > v2:
> > - Use a helper function for KeyUpdates
> > - Ensure keep alive timer is stopped
> > - Wait for TLS KeyUpdate to complete
> >
> > drivers/nvme/target/tcp.c | 90 ++++++++++++++++++++++++++++++++++++---
> > 1 file changed, 85 insertions(+), 5 deletions(-)
> >
> > diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
> > index bee0355195f5..fd59dd3ca632 100644
> > --- a/drivers/nvme/target/tcp.c
> > +++ b/drivers/nvme/target/tcp.c
> > @@ -175,6 +175,7 @@ struct nvmet_tcp_queue {
> >
> > /* TLS state */
> > key_serial_t tls_pskid;
> > + key_serial_t user_session_id;
> > struct delayed_work tls_handshake_tmo_work;
> >
> > unsigned long poll_end;
> > @@ -186,6 +187,8 @@ struct nvmet_tcp_queue {
> > struct sockaddr_storage sockaddr_peer;
> > struct work_struct release_work;
> >
> > + struct completion tls_complete;
> > +
> > int idx;
> > struct list_head queue_list;
> >
> > @@ -836,6 +839,11 @@ static int nvmet_tcp_try_send_one(struct nvmet_tcp_queue *queue,
> > return 1;
> > }
> >
> > +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> > +static int nvmet_tcp_try_peek_pdu(struct nvmet_tcp_queue *queue);
> > +static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w);
> > +#endif
> > +
> > static int nvmet_tcp_try_send(struct nvmet_tcp_queue *queue,
> > int budget, int *sends)
> > {
>
> And we need this why?
>
> > @@ -844,6 +852,13 @@ static int nvmet_tcp_try_send(struct nvmet_tcp_queue *queue,
> > for (i = 0; i < budget; i++) {
> > ret = nvmet_tcp_try_send_one(queue, i == budget - 1);
> > if (unlikely(ret < 0)) {
> > +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> > + if (ret == -EKEYEXPIRED &&
> > + queue->state != NVMET_TCP_Q_DISCONNECTING &&
> > + queue->state != NVMET_TCP_Q_TLS_HANDSHAKE) {
> > + goto done;
> > + }
> > +#endif
> > nvmet_tcp_socket_error(queue, ret);
> > goto done;
> > } else if (ret == 0) {
>
> See my comment to the host patches. Handling an incoming KeyUpdate is
> vastly different than initiating a KeyUpdate. _and_ the network stack
> will only ever return -EKEYEXPIRED on receive.
> So please split the patches in handling an incoming KeyUpdate and
> initiating a KeyUpdate.
Ok, removed from both.
>
> > @@ -1110,6 +1125,45 @@ static inline bool nvmet_tcp_pdu_valid(u8 type)
> > return false;
> > }
> >
> > +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> > +static int update_tls_keys(struct nvmet_tcp_queue *queue)
> > +{
> > + int ret;
> > +
> > + cancel_work(&queue->io_work);
> > + queue->state = NVMET_TCP_Q_TLS_HANDSHAKE;
> > +
> > + /* Restore the default callbacks before starting upcall */
> > + write_lock_bh(&queue->sock->sk->sk_callback_lock);
> > + queue->sock->sk->sk_data_ready = queue->data_ready;
> > + queue->sock->sk->sk_state_change = queue->state_change;
> > + queue->sock->sk->sk_write_space = queue->write_space;
> > + queue->sock->sk->sk_user_data = NULL;
> > + write_unlock_bh(&queue->sock->sk->sk_callback_lock);
> > +
> We do have a function for this ...
>
> > + nvmet_stop_keep_alive_timer(queue->nvme_sq.ctrl);
> > +
> > + INIT_DELAYED_WORK(&queue->tls_handshake_tmo_work,
> > + nvmet_tcp_tls_handshake_timeout);
> > +
> > + ret = nvmet_tcp_tls_handshake(queue, HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED);
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = wait_for_completion_interruptible_timeout(&queue->tls_complete, 10 * HZ);
> > +
> > + if (ret <= 0) {
> > + tls_handshake_cancel(queue->sock->sk);
> > + return ret;
> > + }
> > +
> > + queue->state = NVMET_TCP_Q_LIVE;
> > +
> > + return ret;
> > +}
> > +#endif
> > +
> > static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_queue *queue,
> > struct msghdr *msg, char *cbuf)
> > {
> > @@ -1135,6 +1189,9 @@ static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_queue *queue,
> > ret = -EAGAIN;
> > }
> > break;
> > + case TLS_RECORD_TYPE_HANDSHAKE:
> > + ret = -EAGAIN;
> > + break;
>
> Shouldn't this be rather -EKEYEXPIRED?
It shouldn't be. The TLS layer returns -EKEYEXPIRED and we update the
keys. TLS_RECORD_TYPE_HANDSHAKE occurs after the KeyUpdate when the
NVMe layer reads the KeyUpdate message, but we have already acted on
the KeyUpdate from the -EKEYEXPIRED returned by the TLS layer.
Basically the TLS layer handles decoding the KeyUpdate (already in
mainline) and returning -EKEYEXPIRED which kicks off the KeyUpdate.
This is just us clearing the message from the TLS buffer.
Alistair
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2025-10-17 1:53 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-03 4:31 [PATCH v3 0/8] nvme-tcp: Support receiving KeyUpdate requests alistair23
2025-10-03 4:31 ` [PATCH v3 1/8] net/handshake: Store the key serial number on completion alistair23
2025-10-06 6:15 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 2/8] net/handshake: Define handshake_sk_destruct_req alistair23
2025-10-03 9:51 ` Simon Horman
2025-10-06 6:24 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 3/8] net/handshake: Ensure the request is destructed on completion alistair23
2025-10-06 6:16 ` Hannes Reinecke
2025-10-07 1:22 ` Alistair Francis
2025-10-07 5:20 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 4/8] nvmet: Expose nvmet_stop_keep_alive_timer publically alistair23
2025-10-03 9:51 ` Christoph Hellwig
2025-10-06 6:36 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 5/8] net/handshake: Support KeyUpdate message types alistair23
2025-10-06 6:20 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 6/8] nvme-tcp: Support KeyUpdate alistair23
2025-10-06 6:34 ` Hannes Reinecke
2025-10-03 4:31 ` [PATCH v3 7/8] nvmet-tcp: " alistair23
2025-10-03 9:54 ` Christoph Hellwig
2025-10-06 6:48 ` Hannes Reinecke
2025-10-17 1:53 ` Alistair Francis
2025-10-03 4:31 ` [PATCH v3 8/8] nvme-tcp: Allow userspace to trigger a KeyUpdate with debugfs alistair23
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).