* [RFC PATCH v7 1/4] tls: add TLS 1.3 hardware offload support
2026-02-05 23:15 [RFC PATCH v7 0/4] tls: Add TLS 1.3 hardware offload support Rishikesh Jethwani
@ 2026-02-05 23:15 ` Rishikesh Jethwani
2026-02-23 14:34 ` Sabrina Dubroca
2026-02-05 23:15 ` [RFC PATCH v7 2/4] tls: add hardware offload key update support Rishikesh Jethwani
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Rishikesh Jethwani @ 2026-02-05 23:15 UTC (permalink / raw)
To: netdev
Cc: saeedm, tariqt, mbloch, borisp, john.fastabend, kuba, sd, davem,
pabeni, edumazet, leon, Rishikesh Jethwani
Add TLS 1.3 support to the kernel TLS hardware offload infrastructure,
enabling hardware acceleration for TLS 1.3 connections on capable NICs.
TLS 1.3 differs from TLS 1.2 in several key ways that affect hardware
offload:
1. Content type handling: TLS 1.3 encrypts the content type as part of
the ciphertext (inner content type), with the outer header always
showing application_data. Added content type byte appending in
tls_device_record_close() before the authentication tag.
2. Version validation: Extended tls_set_device_offload() and
tls_set_device_offload_rx() to accept TLS_1_3_VERSION in addition
to TLS_1_2_VERSION.
3. Software fallback: Updated tls_device_fallback.c to handle TLS 1.3
IV construction (XOR with sequence number instead of explicit IV)
and version-specific AAD sizes (5 bytes for TLS 1.3 vs 13 bytes for
TLS 1.2).
4. Reencrypt path: Modified tls_device_reencrypt() to use
prot->prepend_size and prot->tag_size instead of hardcoded
TLS 1.2 values.
5. Memory fallback: Pre-populate dummy_page with identity mapping for
all 256 byte values, enabling safe fallback when memory allocation
fails during TLS 1.3 content type appending.
6. Rekey handling: HW offload key update (rekey) is not yet supported.
Added upfront checks to reject rekey on HW offload connections with
-EOPNOTSUPP. For SW connections, rekey skips device offload attempts
and goes directly to software path.
Tested on Mellanox ConnectX-6 Dx (Crypto Enabled) with TLS 1.3 AES-GCM-128
and AES-GCM-256 cipher suites.
Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
---
net/tls/tls_device.c | 57 ++++++++++++++++++----
net/tls/tls_device_fallback.c | 36 ++++++++++----
net/tls/tls_main.c | 89 +++++++++++++++++++++--------------
3 files changed, 129 insertions(+), 53 deletions(-)
diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
index 82ea407e520a..459963c254f4 100644
--- a/net/tls/tls_device.c
+++ b/net/tls/tls_device.c
@@ -319,6 +319,33 @@ static void tls_device_record_close(struct sock *sk,
struct tls_prot_info *prot = &ctx->prot_info;
struct page_frag dummy_tag_frag;
+ /* TLS 1.3: append content type byte before tag.
+ * Record structure: [Header (5)] + [Ciphertext + ContentType (1)]
+ * + [Tag (16)]
+ * The content type is encrypted with the ciphertext for authentication.
+ */
+ if (prot->version == TLS_1_3_VERSION) {
+ struct page_frag dummy_content_type_frag;
+ struct page_frag *content_type_pfrag = pfrag;
+
+ if (unlikely(pfrag->size - pfrag->offset < prot->tail_size) &&
+ !skb_page_frag_refill(prot->tail_size, pfrag,
+ sk->sk_allocation)) {
+ /* Out of memory: use pre-populated dummy_page */
+ dummy_content_type_frag.page = dummy_page;
+ dummy_content_type_frag.offset = record_type;
+ content_type_pfrag = &dummy_content_type_frag;
+ } else {
+ /* Write content type to current pfrag */
+ unsigned char *content_type_addr;
+
+ content_type_addr = page_address(pfrag->page) +
+ pfrag->offset;
+ *content_type_addr = record_type;
+ }
+ tls_append_frag(record, content_type_pfrag, prot->tail_size);
+ }
+
/* append tag
* device will fill in the tag, we just need to append a placeholder
* use socket memory to improve coalescing (re-using a single buffer
@@ -335,7 +362,7 @@ static void tls_device_record_close(struct sock *sk,
/* fill prepend */
tls_fill_prepend(ctx, skb_frag_address(&record->frags[0]),
- record->len - prot->overhead_size,
+ (record->len - prot->overhead_size) + prot->tail_size,
record_type);
}
@@ -883,6 +910,7 @@ static int
tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
{
struct tls_sw_context_rx *sw_ctx = tls_sw_ctx_rx(tls_ctx);
+ struct tls_prot_info *prot = &tls_ctx->prot_info;
const struct tls_cipher_desc *cipher_desc;
int err, offset, copy, data_len, pos;
struct sk_buff *skb, *skb_iter;
@@ -894,7 +922,7 @@ tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
DEBUG_NET_WARN_ON_ONCE(!cipher_desc || !cipher_desc->offloadable);
rxm = strp_msg(tls_strp_msg(sw_ctx));
- orig_buf = kmalloc(rxm->full_len + TLS_HEADER_SIZE + cipher_desc->iv,
+ orig_buf = kmalloc(rxm->full_len + prot->prepend_size,
sk->sk_allocation);
if (!orig_buf)
return -ENOMEM;
@@ -909,9 +937,8 @@ tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
offset = rxm->offset;
sg_init_table(sg, 1);
- sg_set_buf(&sg[0], buf,
- rxm->full_len + TLS_HEADER_SIZE + cipher_desc->iv);
- err = skb_copy_bits(skb, offset, buf, TLS_HEADER_SIZE + cipher_desc->iv);
+ sg_set_buf(&sg[0], buf, rxm->full_len + prot->prepend_size);
+ err = skb_copy_bits(skb, offset, buf, prot->prepend_size);
if (err)
goto free_buf;
@@ -922,7 +949,7 @@ tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
else
err = 0;
- data_len = rxm->full_len - cipher_desc->tag;
+ data_len = rxm->full_len - prot->tag_size;
if (skb_pagelen(skb) > offset) {
copy = min_t(int, skb_pagelen(skb) - offset, data_len);
@@ -1089,7 +1116,8 @@ int tls_set_device_offload(struct sock *sk)
}
crypto_info = &ctx->crypto_send.info;
- if (crypto_info->version != TLS_1_2_VERSION) {
+ if (crypto_info->version != TLS_1_2_VERSION &&
+ crypto_info->version != TLS_1_3_VERSION) {
rc = -EOPNOTSUPP;
goto release_netdev;
}
@@ -1196,7 +1224,8 @@ int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx)
struct net_device *netdev;
int rc = 0;
- if (ctx->crypto_recv.info.version != TLS_1_2_VERSION)
+ if (ctx->crypto_recv.info.version != TLS_1_2_VERSION &&
+ ctx->crypto_recv.info.version != TLS_1_3_VERSION)
return -EOPNOTSUPP;
netdev = get_netdev_for_sock(sk);
@@ -1409,12 +1438,22 @@ static struct notifier_block tls_dev_notifier = {
int __init tls_device_init(void)
{
- int err;
+ unsigned char *page_addr;
+ int err, i;
dummy_page = alloc_page(GFP_KERNEL);
if (!dummy_page)
return -ENOMEM;
+ /* Pre-populate dummy_page with identity mapping for all byte values.
+ * This is used as fallback for TLS 1.3 content type when memory
+ * allocation fails. By populating all 256 values, we avoid needing
+ * to validate record_type at runtime.
+ */
+ page_addr = page_address(dummy_page);
+ for (i = 0; i < 256; i++)
+ page_addr[i] = (unsigned char)i;
+
destruct_wq = alloc_workqueue("ktls_device_destruct", WQ_PERCPU, 0);
if (!destruct_wq) {
err = -ENOMEM;
diff --git a/net/tls/tls_device_fallback.c b/net/tls/tls_device_fallback.c
index 03d508a45aae..c8d18cce80ed 100644
--- a/net/tls/tls_device_fallback.c
+++ b/net/tls/tls_device_fallback.c
@@ -55,7 +55,7 @@ static int tls_enc_record(struct aead_request *aead_req,
cipher_desc = get_cipher_desc(prot->cipher_type);
DEBUG_NET_WARN_ON_ONCE(!cipher_desc || !cipher_desc->offloadable);
- buf_size = TLS_HEADER_SIZE + cipher_desc->iv;
+ buf_size = prot->prepend_size;
len = min_t(int, *in_len, buf_size);
memcpy_from_scatterwalk(buf, in, len);
@@ -66,16 +66,24 @@ static int tls_enc_record(struct aead_request *aead_req,
return 0;
len = buf[4] | (buf[3] << 8);
- len -= cipher_desc->iv;
+ if (prot->version != TLS_1_3_VERSION)
+ len -= cipher_desc->iv;
tls_make_aad(aad, len - cipher_desc->tag, (char *)&rcd_sn, buf[0], prot);
- memcpy(iv + cipher_desc->salt, buf + TLS_HEADER_SIZE, cipher_desc->iv);
+ /* For TLS 1.2, copy explicit IV from record header.
+ * For TLS 1.3, IV was already set up and we XOR with sequence number.
+ */
+ if (prot->version == TLS_1_3_VERSION)
+ tls_xor_iv_with_seq(prot, iv, (char *)&rcd_sn);
+ else
+ memcpy(iv + cipher_desc->salt, buf + TLS_HEADER_SIZE,
+ cipher_desc->iv);
sg_init_table(sg_in, ARRAY_SIZE(sg_in));
sg_init_table(sg_out, ARRAY_SIZE(sg_out));
- sg_set_buf(sg_in, aad, TLS_AAD_SPACE_SIZE);
- sg_set_buf(sg_out, aad, TLS_AAD_SPACE_SIZE);
+ sg_set_buf(sg_in, aad, prot->aad_size);
+ sg_set_buf(sg_out, aad, prot->aad_size);
scatterwalk_get_sglist(in, sg_in + 1);
scatterwalk_get_sglist(out, sg_out + 1);
@@ -112,7 +120,6 @@ static void tls_init_aead_request(struct aead_request *aead_req,
struct crypto_aead *aead)
{
aead_request_set_tfm(aead_req, aead);
- aead_request_set_ad(aead_req, TLS_AAD_SPACE_SIZE);
}
static struct aead_request *tls_alloc_aead_request(struct crypto_aead *aead,
@@ -303,9 +310,9 @@ static struct sk_buff *tls_enc_skb(struct tls_context *tls_ctx,
{
struct tls_offload_context_tx *ctx = tls_offload_ctx_tx(tls_ctx);
int tcp_payload_offset = skb_tcp_all_headers(skb);
+ void *buf, *iv, *aad, *dummy_buf, *salt, *iv_src;
int payload_len = skb->len - tcp_payload_offset;
const struct tls_cipher_desc *cipher_desc;
- void *buf, *iv, *aad, *dummy_buf, *salt;
struct aead_request *aead_req;
struct sk_buff *nskb = NULL;
int buf_len;
@@ -317,7 +324,11 @@ static struct sk_buff *tls_enc_skb(struct tls_context *tls_ctx,
cipher_desc = get_cipher_desc(tls_ctx->crypto_send.info.cipher_type);
DEBUG_NET_WARN_ON_ONCE(!cipher_desc || !cipher_desc->offloadable);
- buf_len = cipher_desc->salt + cipher_desc->iv + TLS_AAD_SPACE_SIZE +
+ /* Set AAD size based on TLS version */
+ aead_request_set_ad(aead_req, tls_ctx->prot_info.aad_size);
+
+ buf_len = cipher_desc->salt + cipher_desc->iv +
+ tls_ctx->prot_info.aad_size +
sync_size + cipher_desc->tag;
buf = kmalloc(buf_len, GFP_ATOMIC);
if (!buf)
@@ -325,9 +336,16 @@ static struct sk_buff *tls_enc_skb(struct tls_context *tls_ctx,
iv = buf;
salt = crypto_info_salt(&tls_ctx->crypto_send.info, cipher_desc);
+ iv_src = crypto_info_iv(&tls_ctx->crypto_send.info, cipher_desc);
memcpy(iv, salt, cipher_desc->salt);
aad = buf + cipher_desc->salt + cipher_desc->iv;
- dummy_buf = aad + TLS_AAD_SPACE_SIZE;
+ dummy_buf = aad + tls_ctx->prot_info.aad_size;
+
+ /* For TLS 1.3, copy the full fixed IV (salt + iv portion).
+ * For TLS 1.2, iv portion will be filled from record in tls_enc_record.
+ */
+ if (tls_ctx->prot_info.version == TLS_1_3_VERSION)
+ memcpy(iv + cipher_desc->salt, iv_src, cipher_desc->iv);
nskb = alloc_skb(skb_headroom(skb) + skb->len, GFP_ATOMIC);
if (!nskb)
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 56ce0bc8317b..f7c369714b85 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -711,49 +711,68 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
}
if (tx) {
- rc = tls_set_device_offload(sk);
- conf = TLS_HW;
- if (!rc) {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXDEVICE);
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRTXDEVICE);
- } else {
- rc = tls_set_sw_offload(sk, 1,
- update ? crypto_info : NULL);
- if (rc)
- goto err_crypto_info;
-
- if (update) {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
- } else {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXSW);
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRTXSW);
+ /* HW rekey not yet supported */
+ if (update && ctx->tx_conf == TLS_HW) {
+ rc = -EOPNOTSUPP;
+ goto err_crypto_info;
+ }
+
+ /* Only try HW offload on initial setup, not rekey */
+ if (!update) {
+ rc = tls_set_device_offload(sk);
+ conf = TLS_HW;
+ if (!rc) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXDEVICE);
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRTXDEVICE);
+ goto out;
}
- conf = TLS_SW;
}
- } else {
- rc = tls_set_device_offload_rx(sk, ctx);
- conf = TLS_HW;
- if (!rc) {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXDEVICE);
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRRXDEVICE);
+
+ rc = tls_set_sw_offload(sk, 1, update ? crypto_info : NULL);
+ if (rc)
+ goto err_crypto_info;
+
+ if (update) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
} else {
- rc = tls_set_sw_offload(sk, 0,
- update ? crypto_info : NULL);
- if (rc)
- goto err_crypto_info;
-
- if (update) {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
- } else {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXSW);
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRRXSW);
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXSW);
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRTXSW);
+ }
+ conf = TLS_SW;
+ } else {
+ /* HW rekey not yet supported */
+ if (update && ctx->rx_conf == TLS_HW) {
+ rc = -EOPNOTSUPP;
+ goto err_crypto_info;
+ }
+
+ /* Only try HW offload on initial setup, not rekey */
+ if (!update) {
+ rc = tls_set_device_offload_rx(sk, ctx);
+ conf = TLS_HW;
+ if (!rc) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXDEVICE);
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRRXDEVICE);
+ tls_sw_strparser_arm(sk, ctx);
+ goto out;
}
- conf = TLS_SW;
}
- if (!update)
+
+ rc = tls_set_sw_offload(sk, 0, update ? crypto_info : NULL);
+ if (rc)
+ goto err_crypto_info;
+
+ if (update) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
+ } else {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXSW);
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRRXSW);
tls_sw_strparser_arm(sk, ctx);
+ }
+ conf = TLS_SW;
}
+out:
if (tx)
ctx->tx_conf = conf;
else
--
2.25.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [RFC PATCH v7 1/4] tls: add TLS 1.3 hardware offload support
2026-02-05 23:15 ` [RFC PATCH v7 1/4] tls: add " Rishikesh Jethwani
@ 2026-02-23 14:34 ` Sabrina Dubroca
2026-02-23 21:33 ` Rishikesh Jethwani
0 siblings, 1 reply; 8+ messages in thread
From: Sabrina Dubroca @ 2026-02-23 14:34 UTC (permalink / raw)
To: Rishikesh Jethwani
Cc: netdev, saeedm, tariqt, mbloch, borisp, john.fastabend, kuba,
davem, pabeni, edumazet, leon
The subject prefix needs to contain "PATCH net-next" to tell which
tree this applies to (net-next since it's a new feature).
See https://docs.kernel.org/process/maintainer-netdev.html
2026-02-05, 16:15:55 -0700, Rishikesh Jethwani wrote:
> Add TLS 1.3 support to the kernel TLS hardware offload infrastructure,
> enabling hardware acceleration for TLS 1.3 connections on capable NICs.
>
> TLS 1.3 differs from TLS 1.2 in several key ways that affect hardware
> offload:
>
> 1. Content type handling: TLS 1.3 encrypts the content type as part of
> the ciphertext (inner content type), with the outer header always
> showing application_data. Added content type byte appending in
> tls_device_record_close() before the authentication tag.
>
> 2. Version validation: Extended tls_set_device_offload() and
> tls_set_device_offload_rx() to accept TLS_1_3_VERSION in addition
> to TLS_1_2_VERSION.
>
> 3. Software fallback: Updated tls_device_fallback.c to handle TLS 1.3
> IV construction (XOR with sequence number instead of explicit IV)
> and version-specific AAD sizes (5 bytes for TLS 1.3 vs 13 bytes for
> TLS 1.2).
>
> 4. Reencrypt path: Modified tls_device_reencrypt() to use
> prot->prepend_size and prot->tag_size instead of hardcoded
> TLS 1.2 values.
>
> 5. Memory fallback: Pre-populate dummy_page with identity mapping for
> all 256 byte values, enabling safe fallback when memory allocation
> fails during TLS 1.3 content type appending.
>
> 6. Rekey handling: HW offload key update (rekey) is not yet supported.
> Added upfront checks to reject rekey on HW offload connections with
> -EOPNOTSUPP. For SW connections, rekey skips device offload attempts
> and goes directly to software path.
Really not a fan of the LLM output. I don't think most of this needs
to be in the commit message, or at least it should be much less
verbose.
Most of the comments in this series are also LLM verbosity, and
entirely unnecessary.
> Tested on Mellanox ConnectX-6 Dx (Crypto Enabled) with TLS 1.3 AES-GCM-128
> and AES-GCM-256 cipher suites.
>
> Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
> ---
> net/tls/tls_device.c | 57 ++++++++++++++++++----
> net/tls/tls_device_fallback.c | 36 ++++++++++----
> net/tls/tls_main.c | 89 +++++++++++++++++++++--------------
> 3 files changed, 129 insertions(+), 53 deletions(-)
>
> diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
> index 82ea407e520a..459963c254f4 100644
> --- a/net/tls/tls_device.c
> +++ b/net/tls/tls_device.c
> @@ -319,6 +319,33 @@ static void tls_device_record_close(struct sock *sk,
> struct tls_prot_info *prot = &ctx->prot_info;
> struct page_frag dummy_tag_frag;
>
> + /* TLS 1.3: append content type byte before tag.
> + * Record structure: [Header (5)] + [Ciphertext + ContentType (1)]
> + * + [Tag (16)]
> + * The content type is encrypted with the ciphertext for authentication.
> + */
> + if (prot->version == TLS_1_3_VERSION) {
> + struct page_frag dummy_content_type_frag;
> + struct page_frag *content_type_pfrag = pfrag;
> +
> + if (unlikely(pfrag->size - pfrag->offset < prot->tail_size) &&
> + !skb_page_frag_refill(prot->tail_size, pfrag,
> + sk->sk_allocation)) {
> + /* Out of memory: use pre-populated dummy_page */
nit: unnecessary comment
Maybe a silly question but if we have this fallback to the dummy_page,
why even bother trying to allocate?
> + dummy_content_type_frag.page = dummy_page;
> + dummy_content_type_frag.offset = record_type;
> + content_type_pfrag = &dummy_content_type_frag;
> + } else {
> + /* Write content type to current pfrag */
nit: unnecessary comment
> + unsigned char *content_type_addr;
> +
> + content_type_addr = page_address(pfrag->page) +
> + pfrag->offset;
> + *content_type_addr = record_type;
> + }
> + tls_append_frag(record, content_type_pfrag, prot->tail_size);
> + }
> +
> /* append tag
> * device will fill in the tag, we just need to append a placeholder
> * use socket memory to improve coalescing (re-using a single buffer
> @@ -335,7 +362,7 @@ static void tls_device_record_close(struct sock *sk,
>
> /* fill prepend */
> tls_fill_prepend(ctx, skb_frag_address(&record->frags[0]),
> - record->len - prot->overhead_size,
> + (record->len - prot->overhead_size) + prot->tail_size,
nit: why the parens?
> record_type);
> }
>
> @@ -883,6 +910,7 @@ static int
> tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
> {
> struct tls_sw_context_rx *sw_ctx = tls_sw_ctx_rx(tls_ctx);
> + struct tls_prot_info *prot = &tls_ctx->prot_info;
> const struct tls_cipher_desc *cipher_desc;
> int err, offset, copy, data_len, pos;
> struct sk_buff *skb, *skb_iter;
> @@ -894,7 +922,7 @@ tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
> DEBUG_NET_WARN_ON_ONCE(!cipher_desc || !cipher_desc->offloadable);
>
> rxm = strp_msg(tls_strp_msg(sw_ctx));
> - orig_buf = kmalloc(rxm->full_len + TLS_HEADER_SIZE + cipher_desc->iv,
> + orig_buf = kmalloc(rxm->full_len + prot->prepend_size,
> sk->sk_allocation);
> if (!orig_buf)
> return -ENOMEM;
> @@ -909,9 +937,8 @@ tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
> offset = rxm->offset;
>
> sg_init_table(sg, 1);
> - sg_set_buf(&sg[0], buf,
> - rxm->full_len + TLS_HEADER_SIZE + cipher_desc->iv);
> - err = skb_copy_bits(skb, offset, buf, TLS_HEADER_SIZE + cipher_desc->iv);
> + sg_set_buf(&sg[0], buf, rxm->full_len + prot->prepend_size);
> + err = skb_copy_bits(skb, offset, buf, prot->prepend_size);
> if (err)
> goto free_buf;
>
> @@ -922,7 +949,7 @@ tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)
> else
> err = 0;
>
> - data_len = rxm->full_len - cipher_desc->tag;
> + data_len = rxm->full_len - prot->tag_size;
Does this actually need to be changed? prot->tag_size doesn't vary
based on TLS version.
>
> if (skb_pagelen(skb) > offset) {
> copy = min_t(int, skb_pagelen(skb) - offset, data_len);
> @@ -1089,7 +1116,8 @@ int tls_set_device_offload(struct sock *sk)
> }
>
> crypto_info = &ctx->crypto_send.info;
> - if (crypto_info->version != TLS_1_2_VERSION) {
> + if (crypto_info->version != TLS_1_2_VERSION &&
> + crypto_info->version != TLS_1_3_VERSION) {
You can get rid of this whole version check, it was only needed
because HW offload supported less than SW. tls_main already ensures
that the version is 1.2 or 1.3.
> rc = -EOPNOTSUPP;
> goto release_netdev;
> }
> @@ -1196,7 +1224,8 @@ int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx)
> struct net_device *netdev;
> int rc = 0;
>
> - if (ctx->crypto_recv.info.version != TLS_1_2_VERSION)
> + if (ctx->crypto_recv.info.version != TLS_1_2_VERSION &&
> + ctx->crypto_recv.info.version != TLS_1_3_VERSION)
And same here.
But on the topic of version checks, Jakub asked you previously [1] to
make sure all drivers reject 1.3, and your reply only mentioned 3
drivers. We have 5 in tree (and the 2 your reply didn't mention don't
seem to have a version check, based on a quick look).
[1] https://lore.kernel.org/netdev/20260105172745.5cc67e79@kernel.org/
[...]
> diff --git a/net/tls/tls_device_fallback.c b/net/tls/tls_device_fallback.c
> index 03d508a45aae..c8d18cce80ed 100644
> --- a/net/tls/tls_device_fallback.c
> +++ b/net/tls/tls_device_fallback.c
> @@ -55,7 +55,7 @@ static int tls_enc_record(struct aead_request *aead_req,
> cipher_desc = get_cipher_desc(prot->cipher_type);
> DEBUG_NET_WARN_ON_ONCE(!cipher_desc || !cipher_desc->offloadable);
>
> - buf_size = TLS_HEADER_SIZE + cipher_desc->iv;
> + buf_size = prot->prepend_size;
> len = min_t(int, *in_len, buf_size);
>
> memcpy_from_scatterwalk(buf, in, len);
> @@ -66,16 +66,24 @@ static int tls_enc_record(struct aead_request *aead_req,
> return 0;
>
> len = buf[4] | (buf[3] << 8);
> - len -= cipher_desc->iv;
> + if (prot->version != TLS_1_3_VERSION)
> + len -= cipher_desc->iv;
>
> tls_make_aad(aad, len - cipher_desc->tag, (char *)&rcd_sn, buf[0], prot);
>
> - memcpy(iv + cipher_desc->salt, buf + TLS_HEADER_SIZE, cipher_desc->iv);
> + /* For TLS 1.2, copy explicit IV from record header.
> + * For TLS 1.3, IV was already set up and we XOR with sequence number.
> + */
> + if (prot->version == TLS_1_3_VERSION)
> + tls_xor_iv_with_seq(prot, iv, (char *)&rcd_sn);
tls_sw calls this unconditionally. I think it would make sense to be
consistent for the whole iv generation.
> + else
> + memcpy(iv + cipher_desc->salt, buf + TLS_HEADER_SIZE,
> + cipher_desc->iv);
>
> sg_init_table(sg_in, ARRAY_SIZE(sg_in));
> sg_init_table(sg_out, ARRAY_SIZE(sg_out));
> - sg_set_buf(sg_in, aad, TLS_AAD_SPACE_SIZE);
> - sg_set_buf(sg_out, aad, TLS_AAD_SPACE_SIZE);
> + sg_set_buf(sg_in, aad, prot->aad_size);
> + sg_set_buf(sg_out, aad, prot->aad_size);
> scatterwalk_get_sglist(in, sg_in + 1);
> scatterwalk_get_sglist(out, sg_out + 1);
>
> @@ -112,7 +120,6 @@ static void tls_init_aead_request(struct aead_request *aead_req,
> struct crypto_aead *aead)
> {
> aead_request_set_tfm(aead_req, aead);
> - aead_request_set_ad(aead_req, TLS_AAD_SPACE_SIZE);
Then please get rid of this function entirely. We don't need a wrapper
for a single call to the crypto API.
> }
>
> static struct aead_request *tls_alloc_aead_request(struct crypto_aead *aead,
> @@ -303,9 +310,9 @@ static struct sk_buff *tls_enc_skb(struct tls_context *tls_ctx,
> {
> struct tls_offload_context_tx *ctx = tls_offload_ctx_tx(tls_ctx);
> int tcp_payload_offset = skb_tcp_all_headers(skb);
> + void *buf, *iv, *aad, *dummy_buf, *salt, *iv_src;
> int payload_len = skb->len - tcp_payload_offset;
> const struct tls_cipher_desc *cipher_desc;
> - void *buf, *iv, *aad, *dummy_buf, *salt;
> struct aead_request *aead_req;
> struct sk_buff *nskb = NULL;
> int buf_len;
> @@ -317,7 +324,11 @@ static struct sk_buff *tls_enc_skb(struct tls_context *tls_ctx,
> cipher_desc = get_cipher_desc(tls_ctx->crypto_send.info.cipher_type);
> DEBUG_NET_WARN_ON_ONCE(!cipher_desc || !cipher_desc->offloadable);
>
> - buf_len = cipher_desc->salt + cipher_desc->iv + TLS_AAD_SPACE_SIZE +
> + /* Set AAD size based on TLS version */
another unnecessary comment.
[...]
> + /* HW rekey not yet supported */
(here too)
[...]
> + /* Only try HW offload on initial setup, not rekey */
(here too)
--
Sabrina
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [RFC PATCH v7 1/4] tls: add TLS 1.3 hardware offload support
2026-02-23 14:34 ` Sabrina Dubroca
@ 2026-02-23 21:33 ` Rishikesh Jethwani
0 siblings, 0 replies; 8+ messages in thread
From: Rishikesh Jethwani @ 2026-02-23 21:33 UTC (permalink / raw)
To: Sabrina Dubroca
Cc: netdev, saeedm, tariqt, mbloch, borisp, john.fastabend, kuba,
davem, pabeni, edumazet, leon
> Maybe a silly question but if we have this fallback to the dummy_page,
> why even bother trying to allocate?
Existing append tag code comment explains this:
* use socket memory to improve coalescing (re-using a single buffer
* increases frag count)
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC PATCH v7 2/4] tls: add hardware offload key update support
2026-02-05 23:15 [RFC PATCH v7 0/4] tls: Add TLS 1.3 hardware offload support Rishikesh Jethwani
2026-02-05 23:15 ` [RFC PATCH v7 1/4] tls: add " Rishikesh Jethwani
@ 2026-02-05 23:15 ` Rishikesh Jethwani
2026-02-23 14:40 ` Sabrina Dubroca
2026-02-05 23:15 ` [RFC PATCH v7 3/4] mlx5: TLS 1.3 hardware offload support Rishikesh Jethwani
2026-02-05 23:15 ` [RFC PATCH v7 4/4] selftests: drivers: net: hw: add TLS hardware offload test Rishikesh Jethwani
3 siblings, 1 reply; 8+ messages in thread
From: Rishikesh Jethwani @ 2026-02-05 23:15 UTC (permalink / raw)
To: netdev
Cc: saeedm, tariqt, mbloch, borisp, john.fastabend, kuba, sd, davem,
pabeni, edumazet, leon, Rishikesh Jethwani
Add TLS KeyUpdate (rekey) support for hardware offload connections,
enabling key rotation on established TLS 1.3 connections without
tearing down the hardware offload.
Key changes:
1. Rekey API: Extended tls_set_device_offload() and
tls_set_device_offload_rx() with new_crypto_info parameter to
distinguish initial setup from key updates. During rekey, the old
HW context is deleted (tls_dev_del) and a new one is added
(tls_dev_add) with the updated key material.
2. Graceful degradation: If hardware key update fails, the connection
gracefully degrades to software. For TX, TLS_TX_DEV_CLOSED is set
and sk_validate_xmit_skb switches to tls_validate_xmit_skb_sw for
software encryption. For RX, TLS_RX_DEV_DEGRADED and TLS_RX_DEV_CLOSED
are set for software decryption. tx_conf/rx_conf remains TLS_HW.
3. Record sequence management: During TX rekey, old pending records
are deleted and unacked_record_sn is reset to the new rec_seq.
4. SW context refactoring: Split tls_set_sw_offload() into
tls_sw_ctx_init() and tls_sw_ctx_finalize() to allow the HW offload
RX path to initialize SW context first, attempt HW setup, then
finalize (memzero new_crypto_info, call tls_finish_key_update).
5. Added TLS_TX_DEV_CLOSED flag to track TX hardware context state,
to avoid double tls_dev_del call, symmetric with existing
TLS_RX_DEV_CLOSED.
This removes the rekey rejection checks added in the previous patch,
replacing them with full rekey support including graceful degradation.
Tested on Mellanox ConnectX-6 Dx (Crypto Enabled) with multiple
TLS 1.3 key update cycles.
Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
---
include/net/tls.h | 4 +
net/tls/tls.h | 15 ++-
net/tls/tls_device.c | 274 +++++++++++++++++++++++++++++++------------
net/tls/tls_main.c | 94 +++++++--------
net/tls/tls_sw.c | 77 ++++++++----
5 files changed, 319 insertions(+), 145 deletions(-)
diff --git a/include/net/tls.h b/include/net/tls.h
index ebd2550280ae..9a203394763b 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -189,6 +189,10 @@ enum tls_context_flags {
* tls_dev_del call in tls_device_down if it happens simultaneously.
*/
TLS_RX_DEV_CLOSED = 2,
+ /* Flag for TX HW context deleted during failed rekey.
+ * Prevents double tls_dev_del in cleanup paths.
+ */
+ TLS_TX_DEV_CLOSED = 3,
};
struct cipher_context {
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 2f86baeb71fc..2b93ab13a429 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -147,6 +147,10 @@ void tls_strp_abort_strp(struct tls_strparser *strp, int err);
int init_prot_info(struct tls_prot_info *prot,
const struct tls_crypto_info *crypto_info,
const struct tls_cipher_desc *cipher_desc);
+int tls_sw_ctx_init(struct sock *sk, int tx,
+ struct tls_crypto_info *new_crypto_info);
+void tls_sw_ctx_finalize(struct sock *sk, int tx,
+ struct tls_crypto_info *new_crypto_info);
int tls_set_sw_offload(struct sock *sk, int tx,
struct tls_crypto_info *new_crypto_info);
void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
@@ -229,9 +233,11 @@ static inline bool tls_strp_msg_mixed_decrypted(struct tls_sw_context_rx *ctx)
#ifdef CONFIG_TLS_DEVICE
int tls_device_init(void);
void tls_device_cleanup(void);
-int tls_set_device_offload(struct sock *sk);
+int tls_set_device_offload(struct sock *sk,
+ struct tls_crypto_info *crypto_info);
void tls_device_free_resources_tx(struct sock *sk);
-int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx);
+int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx,
+ struct tls_crypto_info *crypto_info);
void tls_device_offload_cleanup_rx(struct sock *sk);
void tls_device_rx_resync_new_rec(struct sock *sk, u32 rcd_len, u32 seq);
int tls_device_decrypted(struct sock *sk, struct tls_context *tls_ctx);
@@ -240,7 +246,7 @@ static inline int tls_device_init(void) { return 0; }
static inline void tls_device_cleanup(void) {}
static inline int
-tls_set_device_offload(struct sock *sk)
+tls_set_device_offload(struct sock *sk, struct tls_crypto_info *crypto_info)
{
return -EOPNOTSUPP;
}
@@ -248,7 +254,8 @@ tls_set_device_offload(struct sock *sk)
static inline void tls_device_free_resources_tx(struct sock *sk) {}
static inline int
-tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx)
+tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx,
+ struct tls_crypto_info *crypto_info)
{
return -EOPNOTSUPP;
}
diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
index 459963c254f4..4aaa51a35f1f 100644
--- a/net/tls/tls_device.c
+++ b/net/tls/tls_device.c
@@ -79,7 +79,9 @@ static void tls_device_tx_del_task(struct work_struct *work)
netdev = rcu_dereference_protected(ctx->netdev,
!refcount_read(&ctx->refcount));
- netdev->tlsdev_ops->tls_dev_del(netdev, ctx, TLS_OFFLOAD_CTX_DIR_TX);
+ if (!test_bit(TLS_TX_DEV_CLOSED, &ctx->flags))
+ netdev->tlsdev_ops->tls_dev_del(netdev, ctx,
+ TLS_OFFLOAD_CTX_DIR_TX);
dev_put(netdev);
ctx->netdev = NULL;
tls_device_free_ctx(ctx);
@@ -1086,12 +1088,13 @@ static struct tls_offload_context_tx *alloc_offload_ctx_tx(struct tls_context *c
return offload_ctx;
}
-int tls_set_device_offload(struct sock *sk)
+int tls_set_device_offload(struct sock *sk,
+ struct tls_crypto_info *new_crypto_info)
{
+ struct tls_crypto_info *crypto_info, *src_crypto_info;
struct tls_record_info *start_marker_record;
struct tls_offload_context_tx *offload_ctx;
const struct tls_cipher_desc *cipher_desc;
- struct tls_crypto_info *crypto_info;
struct tls_prot_info *prot;
struct net_device *netdev;
struct tls_context *ctx;
@@ -1101,8 +1104,12 @@ int tls_set_device_offload(struct sock *sk)
ctx = tls_get_ctx(sk);
prot = &ctx->prot_info;
- if (ctx->priv_ctx_tx)
- return -EEXIST;
+ /* Rekey is only supported for connections that are already
+ * using HW offload. For SW offload connections, the caller
+ * should fall back to tls_set_sw_offload() for rekey.
+ */
+ if (new_crypto_info && ctx->tx_conf != TLS_HW)
+ return -EINVAL;
netdev = get_netdev_for_sock(sk);
if (!netdev) {
@@ -1116,57 +1123,62 @@ int tls_set_device_offload(struct sock *sk)
}
crypto_info = &ctx->crypto_send.info;
- if (crypto_info->version != TLS_1_2_VERSION &&
- crypto_info->version != TLS_1_3_VERSION) {
+ src_crypto_info = new_crypto_info ?: crypto_info;
+ if (src_crypto_info->version != TLS_1_2_VERSION &&
+ src_crypto_info->version != TLS_1_3_VERSION) {
rc = -EOPNOTSUPP;
goto release_netdev;
}
- cipher_desc = get_cipher_desc(crypto_info->cipher_type);
+ cipher_desc = get_cipher_desc(src_crypto_info->cipher_type);
if (!cipher_desc || !cipher_desc->offloadable) {
rc = -EINVAL;
goto release_netdev;
}
- rc = init_prot_info(prot, crypto_info, cipher_desc);
- if (rc)
- goto release_netdev;
+ iv = crypto_info_iv(src_crypto_info, cipher_desc);
+ rec_seq = crypto_info_rec_seq(src_crypto_info, cipher_desc);
- iv = crypto_info_iv(crypto_info, cipher_desc);
- rec_seq = crypto_info_rec_seq(crypto_info, cipher_desc);
+ if (!new_crypto_info) {
+ rc = init_prot_info(prot, src_crypto_info, cipher_desc);
+ if (rc)
+ goto release_netdev;
- memcpy(ctx->tx.iv + cipher_desc->salt, iv, cipher_desc->iv);
- memcpy(ctx->tx.rec_seq, rec_seq, cipher_desc->rec_seq);
+ memcpy(ctx->tx.iv + cipher_desc->salt, iv, cipher_desc->iv);
+ memcpy(ctx->tx.rec_seq, rec_seq, cipher_desc->rec_seq);
- start_marker_record = kmalloc(sizeof(*start_marker_record), GFP_KERNEL);
- if (!start_marker_record) {
- rc = -ENOMEM;
- goto release_netdev;
- }
+ start_marker_record = kmalloc(sizeof(*start_marker_record),
+ GFP_KERNEL);
+ if (!start_marker_record) {
+ rc = -ENOMEM;
+ goto release_netdev;
+ }
- offload_ctx = alloc_offload_ctx_tx(ctx);
- if (!offload_ctx) {
- rc = -ENOMEM;
- goto free_marker_record;
- }
+ offload_ctx = alloc_offload_ctx_tx(ctx);
+ if (!offload_ctx) {
+ rc = -ENOMEM;
+ goto free_marker_record;
+ }
- rc = tls_sw_fallback_init(sk, offload_ctx, crypto_info);
- if (rc)
- goto free_offload_ctx;
+ rc = tls_sw_fallback_init(sk, offload_ctx, src_crypto_info);
+ if (rc)
+ goto free_offload_ctx;
- start_marker_record->end_seq = tcp_sk(sk)->write_seq;
- start_marker_record->len = 0;
- start_marker_record->num_frags = 0;
- list_add_tail(&start_marker_record->list, &offload_ctx->records_list);
+ start_marker_record->end_seq = tcp_sk(sk)->write_seq;
+ start_marker_record->len = 0;
+ start_marker_record->num_frags = 0;
+ list_add_tail(&start_marker_record->list,
+ &offload_ctx->records_list);
- clean_acked_data_enable(tcp_sk(sk), &tls_tcp_clean_acked);
- ctx->push_pending_record = tls_device_push_pending_record;
+ clean_acked_data_enable(tcp_sk(sk), &tls_tcp_clean_acked);
+ ctx->push_pending_record = tls_device_push_pending_record;
- /* TLS offload is greatly simplified if we don't send
- * SKBs where only part of the payload needs to be encrypted.
- * So mark the last skb in the write queue as end of record.
- */
- tcp_write_collapse_fence(sk);
+ /* TLS offload is greatly simplified if we don't send
+ * SKBs where only part of the payload needs to be encrypted.
+ * So mark the last skb in the write queue as end of record.
+ */
+ tcp_write_collapse_fence(sk);
+ }
/* Avoid offloading if the device is down
* We don't want to offload new flows after
@@ -1182,29 +1194,91 @@ int tls_set_device_offload(struct sock *sk)
goto release_lock;
}
- ctx->priv_ctx_tx = offload_ctx;
+ if (!new_crypto_info) {
+ ctx->priv_ctx_tx = offload_ctx;
+ } else {
+ char *key = crypto_info_key(src_crypto_info, cipher_desc);
+
+ offload_ctx = tls_offload_ctx_tx(ctx);
+
+ rc = crypto_aead_setkey(offload_ctx->aead_send, key,
+ cipher_desc->key);
+ if (rc)
+ goto release_lock;
+
+ /* For rekey, delete old HW context before adding new one. */
+ if (!test_bit(TLS_TX_DEV_CLOSED, &ctx->flags))
+ netdev->tlsdev_ops->tls_dev_del(netdev, ctx,
+ TLS_OFFLOAD_CTX_DIR_TX);
+ }
+
rc = netdev->tlsdev_ops->tls_dev_add(netdev, sk, TLS_OFFLOAD_CTX_DIR_TX,
- &ctx->crypto_send.info,
+ src_crypto_info,
tcp_sk(sk)->write_seq);
trace_tls_device_offload_set(sk, TLS_OFFLOAD_CTX_DIR_TX,
tcp_sk(sk)->write_seq, rec_seq, rc);
- if (rc)
- goto release_lock;
- tls_device_attach(ctx, sk, netdev);
+ if (new_crypto_info) {
+ unsigned long flags;
+ __be64 rcd_sn;
+
+ memcpy(ctx->tx.iv + cipher_desc->salt, iv, cipher_desc->iv);
+ memcpy(ctx->tx.rec_seq, rec_seq, cipher_desc->rec_seq);
+
+ spin_lock_irqsave(&offload_ctx->lock, flags);
+ /* Delete old records, can't be retransmitted with new key */
+ delete_all_records(offload_ctx);
+
+ /* Update unacked_record_sn for the new key's rec_seq.
+ * This is critical for SW fallback encryption to use
+ * the correct record sequence number after rekey.
+ */
+ memcpy(&rcd_sn, rec_seq, sizeof(rcd_sn));
+ offload_ctx->unacked_record_sn = be64_to_cpu(rcd_sn);
+ spin_unlock_irqrestore(&offload_ctx->lock, flags);
+
+ unsafe_memcpy(crypto_info, new_crypto_info,
+ cipher_desc->crypto_info,
+ /* size was checked in do_tls_setsockopt_conf */);
+ memzero_explicit(new_crypto_info, cipher_desc->crypto_info);
+ }
+
+ if (rc) {
+ if (new_crypto_info) {
+ set_bit(TLS_TX_DEV_CLOSED, &ctx->flags);
+ /* HW rekey failed, gracefully degrade to SW encryption.
+ * SW fallback already has new key, IV, and rec_seq.
+ * Old HW ctx was deleted, continue with SW encryption.
+ */
+ smp_store_release(&sk->sk_validate_xmit_skb,
+ tls_validate_xmit_skb_sw);
+ } else {
+ goto release_lock;
+ }
+ } else {
+ if (new_crypto_info)
+ clear_bit(TLS_TX_DEV_CLOSED, &ctx->flags);
+
+ tls_device_attach(ctx, sk, netdev);
+
+ /* following this assignment tls_is_skb_tx_device_offloaded
+ * will return true and the context might be accessed
+ * by the netdev's xmit function.
+ */
+ smp_store_release(&sk->sk_validate_xmit_skb,
+ tls_validate_xmit_skb);
+ }
+
up_read(&device_offload_lock);
- /* following this assignment tls_is_skb_tx_device_offloaded
- * will return true and the context might be accessed
- * by the netdev's xmit function.
- */
- smp_store_release(&sk->sk_validate_xmit_skb, tls_validate_xmit_skb);
dev_put(netdev);
return 0;
release_lock:
up_read(&device_offload_lock);
+ if (new_crypto_info)
+ goto release_netdev;
clean_acked_data_disable(tcp_sk(sk));
crypto_free_aead(offload_ctx->aead_send);
free_offload_ctx:
@@ -1217,17 +1291,33 @@ int tls_set_device_offload(struct sock *sk)
return rc;
}
-int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx)
+int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx,
+ struct tls_crypto_info *new_crypto_info)
{
- struct tls12_crypto_info_aes_gcm_128 *info;
+ struct tls_crypto_info *crypto_info, *src_crypto_info;
+ const struct tls_cipher_desc *cipher_desc;
struct tls_offload_context_rx *context;
struct net_device *netdev;
+ char *rec_seq;
int rc = 0;
- if (ctx->crypto_recv.info.version != TLS_1_2_VERSION &&
- ctx->crypto_recv.info.version != TLS_1_3_VERSION)
+ /* Rekey is only supported for connections that are already
+ * using HW offload. For SW offload connections, the caller
+ * should fall back to tls_set_sw_offload() for rekey.
+ */
+ if (new_crypto_info && ctx->rx_conf != TLS_HW)
+ return -EINVAL;
+
+ crypto_info = &ctx->crypto_recv.info;
+ src_crypto_info = new_crypto_info ?: crypto_info;
+ if (src_crypto_info->version != TLS_1_2_VERSION &&
+ src_crypto_info->version != TLS_1_3_VERSION)
return -EOPNOTSUPP;
+ cipher_desc = get_cipher_desc(src_crypto_info->cipher_type);
+ if (!cipher_desc || !cipher_desc->offloadable)
+ return -EINVAL;
+
netdev = get_netdev_for_sock(sk);
if (!netdev) {
pr_err_ratelimited("%s: netdev not found\n", __func__);
@@ -1253,28 +1343,57 @@ int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx)
goto release_lock;
}
- context = kzalloc(sizeof(*context), GFP_KERNEL);
- if (!context) {
- rc = -ENOMEM;
- goto release_lock;
+ if (!new_crypto_info) {
+ context = kzalloc(sizeof(*context), GFP_KERNEL);
+ if (!context) {
+ rc = -ENOMEM;
+ goto release_lock;
+ }
+ context->resync_nh_reset = 1;
+ ctx->priv_ctx_rx = context;
}
- context->resync_nh_reset = 1;
- ctx->priv_ctx_rx = context;
- rc = tls_set_sw_offload(sk, 0, NULL);
+ rc = tls_sw_ctx_init(sk, 0, new_crypto_info);
if (rc)
goto release_ctx;
+ /* For rekey, delete old HW context before adding new one. */
+ if (new_crypto_info && !test_bit(TLS_RX_DEV_CLOSED, &ctx->flags))
+ netdev->tlsdev_ops->tls_dev_del(netdev, ctx,
+ TLS_OFFLOAD_CTX_DIR_RX);
+
rc = netdev->tlsdev_ops->tls_dev_add(netdev, sk, TLS_OFFLOAD_CTX_DIR_RX,
- &ctx->crypto_recv.info,
+ src_crypto_info,
tcp_sk(sk)->copied_seq);
- info = (void *)&ctx->crypto_recv.info;
+ rec_seq = crypto_info_rec_seq(src_crypto_info, cipher_desc);
trace_tls_device_offload_set(sk, TLS_OFFLOAD_CTX_DIR_RX,
- tcp_sk(sk)->copied_seq, info->rec_seq, rc);
- if (rc)
- goto free_sw_resources;
+ tcp_sk(sk)->copied_seq, rec_seq, rc);
+ if (rc) {
+ if (new_crypto_info) {
+ /* HW rekey failed, gracefully degrade to SW decryption.
+ * SW context already set up via tls_sw_ctx_init.
+ * Old HW ctx was deleted, set degraded flag for
+ * SW fallback.
+ */
+ set_bit(TLS_RX_DEV_DEGRADED, &ctx->flags);
+ set_bit(TLS_RX_DEV_CLOSED, &ctx->flags);
+ } else {
+ goto free_sw_resources;
+ }
+ } else {
+ if (new_crypto_info) {
+ /* HW rekey succeeded, clear degraded state
+ * if previously set
+ */
+ clear_bit(TLS_RX_DEV_DEGRADED, &ctx->flags);
+ clear_bit(TLS_RX_DEV_CLOSED, &ctx->flags);
+ }
+
+ tls_device_attach(ctx, sk, netdev);
+ }
+
+ tls_sw_ctx_finalize(sk, 0, new_crypto_info);
- tls_device_attach(ctx, sk, netdev);
up_read(&device_offload_lock);
dev_put(netdev);
@@ -1283,10 +1402,15 @@ int tls_set_device_offload_rx(struct sock *sk, struct tls_context *ctx)
free_sw_resources:
up_read(&device_offload_lock);
- tls_sw_free_resources_rx(sk);
+ if (new_crypto_info)
+ goto release_netdev;
+ tls_sw_release_resources_rx(sk);
down_read(&device_offload_lock);
release_ctx:
- ctx->priv_ctx_rx = NULL;
+ if (!new_crypto_info) {
+ kfree(ctx->priv_ctx_rx);
+ ctx->priv_ctx_rx = NULL;
+ }
release_lock:
up_read(&device_offload_lock);
release_netdev:
@@ -1305,8 +1429,9 @@ void tls_device_offload_cleanup_rx(struct sock *sk)
if (!netdev)
goto out;
- netdev->tlsdev_ops->tls_dev_del(netdev, tls_ctx,
- TLS_OFFLOAD_CTX_DIR_RX);
+ if (!test_bit(TLS_RX_DEV_CLOSED, &tls_ctx->flags))
+ netdev->tlsdev_ops->tls_dev_del(netdev, tls_ctx,
+ TLS_OFFLOAD_CTX_DIR_RX);
if (tls_ctx->tx_conf != TLS_HW) {
dev_put(netdev);
@@ -1363,13 +1488,18 @@ static int tls_device_down(struct net_device *netdev)
synchronize_net();
/* Release the offload context on the driver side. */
- if (ctx->tx_conf == TLS_HW)
+ if (ctx->tx_conf == TLS_HW &&
+ !test_bit(TLS_TX_DEV_CLOSED, &ctx->flags)) {
netdev->tlsdev_ops->tls_dev_del(netdev, ctx,
TLS_OFFLOAD_CTX_DIR_TX);
+ set_bit(TLS_TX_DEV_CLOSED, &ctx->flags);
+ }
if (ctx->rx_conf == TLS_HW &&
- !test_bit(TLS_RX_DEV_CLOSED, &ctx->flags))
+ !test_bit(TLS_RX_DEV_CLOSED, &ctx->flags)) {
netdev->tlsdev_ops->tls_dev_del(netdev, ctx,
TLS_OFFLOAD_CTX_DIR_RX);
+ set_bit(TLS_RX_DEV_CLOSED, &ctx->flags);
+ }
dev_put(netdev);
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index f7c369714b85..f7fe6676cc4c 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -711,68 +711,68 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
}
if (tx) {
- /* HW rekey not yet supported */
- if (update && ctx->tx_conf == TLS_HW) {
- rc = -EOPNOTSUPP;
- goto err_crypto_info;
- }
-
- /* Only try HW offload on initial setup, not rekey */
- if (!update) {
- rc = tls_set_device_offload(sk);
- conf = TLS_HW;
- if (!rc) {
+ rc = tls_set_device_offload(sk, update ? crypto_info : NULL);
+ conf = TLS_HW;
+ if (!rc) {
+ if (update) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
+ } else {
TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXDEVICE);
TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRTXDEVICE);
- goto out;
}
- }
-
- rc = tls_set_sw_offload(sk, 1, update ? crypto_info : NULL);
- if (rc)
+ } else if (update && ctx->tx_conf == TLS_HW) {
+ /* HW rekey failed - return the actual error.
+ * Cannot fall back to SW for an existing HW connection.
+ */
goto err_crypto_info;
-
- if (update) {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
} else {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXSW);
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRTXSW);
+ rc = tls_set_sw_offload(sk, 1,
+ update ? crypto_info : NULL);
+ if (rc)
+ goto err_crypto_info;
+
+ if (update) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
+ } else {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXSW);
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRTXSW);
+ }
+ conf = TLS_SW;
}
- conf = TLS_SW;
} else {
- /* HW rekey not yet supported */
- if (update && ctx->rx_conf == TLS_HW) {
- rc = -EOPNOTSUPP;
- goto err_crypto_info;
- }
-
- /* Only try HW offload on initial setup, not rekey */
- if (!update) {
- rc = tls_set_device_offload_rx(sk, ctx);
- conf = TLS_HW;
- if (!rc) {
+ rc = tls_set_device_offload_rx(sk, ctx,
+ update ? crypto_info : NULL);
+ conf = TLS_HW;
+ if (!rc) {
+ if (update) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
+ } else {
TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXDEVICE);
TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRRXDEVICE);
- tls_sw_strparser_arm(sk, ctx);
- goto out;
}
- }
-
- rc = tls_set_sw_offload(sk, 0, update ? crypto_info : NULL);
- if (rc)
+ } else if (update && ctx->rx_conf == TLS_HW) {
+ /* HW rekey failed - return the actual error.
+ * Cannot fall back to SW for an existing HW connection.
+ */
goto err_crypto_info;
-
- if (update) {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
} else {
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXSW);
- TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRRXSW);
- tls_sw_strparser_arm(sk, ctx);
+ rc = tls_set_sw_offload(sk, 0,
+ update ? crypto_info : NULL);
+ if (rc)
+ goto err_crypto_info;
+
+ if (update) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
+ } else {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXSW);
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSCURRRXSW);
+ }
+ conf = TLS_SW;
}
- conf = TLS_SW;
+ if (!update)
+ tls_sw_strparser_arm(sk, ctx);
}
-out:
if (tx)
ctx->tx_conf = conf;
else
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 9937d4c810f2..2fcc0178490d 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -2775,20 +2775,19 @@ static void tls_finish_key_update(struct sock *sk, struct tls_context *tls_ctx)
ctx->saved_data_ready(sk);
}
-int tls_set_sw_offload(struct sock *sk, int tx,
- struct tls_crypto_info *new_crypto_info)
+int tls_sw_ctx_init(struct sock *sk, int tx,
+ struct tls_crypto_info *new_crypto_info)
{
struct tls_crypto_info *crypto_info, *src_crypto_info;
struct tls_sw_context_tx *sw_ctx_tx = NULL;
struct tls_sw_context_rx *sw_ctx_rx = NULL;
const struct tls_cipher_desc *cipher_desc;
- char *iv, *rec_seq, *key, *salt;
- struct cipher_context *cctx;
struct tls_prot_info *prot;
struct crypto_aead **aead;
struct tls_context *ctx;
struct crypto_tfm *tfm;
int rc = 0;
+ char *key;
ctx = tls_get_ctx(sk);
prot = &ctx->prot_info;
@@ -2809,12 +2808,10 @@ int tls_set_sw_offload(struct sock *sk, int tx,
if (tx) {
sw_ctx_tx = ctx->priv_ctx_tx;
crypto_info = &ctx->crypto_send.info;
- cctx = &ctx->tx;
aead = &sw_ctx_tx->aead_send;
} else {
sw_ctx_rx = ctx->priv_ctx_rx;
crypto_info = &ctx->crypto_recv.info;
- cctx = &ctx->rx;
aead = &sw_ctx_rx->aead_recv;
}
@@ -2830,10 +2827,7 @@ int tls_set_sw_offload(struct sock *sk, int tx,
if (rc)
goto free_priv;
- iv = crypto_info_iv(src_crypto_info, cipher_desc);
key = crypto_info_key(src_crypto_info, cipher_desc);
- salt = crypto_info_salt(src_crypto_info, cipher_desc);
- rec_seq = crypto_info_rec_seq(src_crypto_info, cipher_desc);
if (!*aead) {
*aead = crypto_alloc_aead(cipher_desc->cipher_name, 0, 0);
@@ -2877,19 +2871,6 @@ int tls_set_sw_offload(struct sock *sk, int tx,
goto free_aead;
}
- memcpy(cctx->iv, salt, cipher_desc->salt);
- memcpy(cctx->iv + cipher_desc->salt, iv, cipher_desc->iv);
- memcpy(cctx->rec_seq, rec_seq, cipher_desc->rec_seq);
-
- if (new_crypto_info) {
- unsafe_memcpy(crypto_info, new_crypto_info,
- cipher_desc->crypto_info,
- /* size was checked in do_tls_setsockopt_conf */);
- memzero_explicit(new_crypto_info, cipher_desc->crypto_info);
- if (!tx)
- tls_finish_key_update(sk, ctx);
- }
-
goto out;
free_aead:
@@ -2908,3 +2889,55 @@ int tls_set_sw_offload(struct sock *sk, int tx,
out:
return rc;
}
+
+void tls_sw_ctx_finalize(struct sock *sk, int tx,
+ struct tls_crypto_info *new_crypto_info)
+{
+ struct tls_crypto_info *crypto_info, *src_crypto_info;
+ const struct tls_cipher_desc *cipher_desc;
+ struct tls_context *ctx = tls_get_ctx(sk);
+ struct cipher_context *cctx;
+ char *iv, *salt, *rec_seq;
+
+ if (tx) {
+ crypto_info = &ctx->crypto_send.info;
+ cctx = &ctx->tx;
+ } else {
+ crypto_info = &ctx->crypto_recv.info;
+ cctx = &ctx->rx;
+ }
+
+ src_crypto_info = new_crypto_info ?: crypto_info;
+ cipher_desc = get_cipher_desc(src_crypto_info->cipher_type);
+
+ iv = crypto_info_iv(src_crypto_info, cipher_desc);
+ salt = crypto_info_salt(src_crypto_info, cipher_desc);
+ rec_seq = crypto_info_rec_seq(src_crypto_info, cipher_desc);
+
+ memcpy(cctx->iv, salt, cipher_desc->salt);
+ memcpy(cctx->iv + cipher_desc->salt, iv, cipher_desc->iv);
+ memcpy(cctx->rec_seq, rec_seq, cipher_desc->rec_seq);
+
+ if (new_crypto_info) {
+ unsafe_memcpy(crypto_info, new_crypto_info,
+ cipher_desc->crypto_info,
+ /* size was checked in do_tls_setsockopt_conf */);
+ memzero_explicit(new_crypto_info, cipher_desc->crypto_info);
+
+ if (!tx)
+ tls_finish_key_update(sk, ctx);
+ }
+}
+
+int tls_set_sw_offload(struct sock *sk, int tx,
+ struct tls_crypto_info *new_crypto_info)
+{
+ int rc;
+
+ rc = tls_sw_ctx_init(sk, tx, new_crypto_info);
+ if (rc)
+ return rc;
+
+ tls_sw_ctx_finalize(sk, tx, new_crypto_info);
+ return 0;
+}
--
2.25.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [RFC PATCH v7 2/4] tls: add hardware offload key update support
2026-02-05 23:15 ` [RFC PATCH v7 2/4] tls: add hardware offload key update support Rishikesh Jethwani
@ 2026-02-23 14:40 ` Sabrina Dubroca
0 siblings, 0 replies; 8+ messages in thread
From: Sabrina Dubroca @ 2026-02-23 14:40 UTC (permalink / raw)
To: Rishikesh Jethwani
Cc: netdev, saeedm, tariqt, mbloch, borisp, john.fastabend, kuba,
davem, pabeni, edumazet, leon
2026-02-05, 16:15:56 -0700, Rishikesh Jethwani wrote:
> Add TLS KeyUpdate (rekey) support for hardware offload connections,
> enabling key rotation on established TLS 1.3 connections without
> tearing down the hardware offload.
>
> Key changes:
>
> 1. Rekey API: Extended tls_set_device_offload() and
> tls_set_device_offload_rx() with new_crypto_info parameter to
> distinguish initial setup from key updates. During rekey, the old
> HW context is deleted (tls_dev_del) and a new one is added
> (tls_dev_add) with the updated key material.
I don't think the commit message needs that level of detail. You'll
probably want to look through the git log for net/ to get an idea of
what commit messages typically look like for kernel networking.
> 2. Graceful degradation: If hardware key update fails, the connection
> gracefully degrades to software.
In do_tls_setsockopt_conf you have:
+ } else if (update && ctx->tx_conf == TLS_HW) {
+ /* HW rekey failed - return the actual error.
+ * Cannot fall back to SW for an existing HW connection.
+ */
goto err_crypto_info;
which doesn't look very graceful.
> For TX, TLS_TX_DEV_CLOSED is set
> and sk_validate_xmit_skb switches to tls_validate_xmit_skb_sw for
> software encryption. For RX, TLS_RX_DEV_DEGRADED and TLS_RX_DEV_CLOSED
> are set for software decryption. tx_conf/rx_conf remains TLS_HW.
>
> 3. Record sequence management: During TX rekey, old pending records
> are deleted and unacked_record_sn is reset to the new rec_seq.
So what happens if we need to retransmit them? We just give up?
There was quite some discussion about how rekey would work with HW
offload back when I implemented rekey for SW, which got recorded in
the cover letter and committed as da3e3186ef13 ("Merge branch
'tls1.3-key-updates'"). I would expect to either see those things
implemented in this series, or a justification for why they need to be
done differently (it's possible that things turn out different in
practice from what we discussed back then).
> 4. SW context refactoring: Split tls_set_sw_offload() into
> tls_sw_ctx_init() and tls_sw_ctx_finalize() to allow the HW offload
> RX path to initialize SW context first, attempt HW setup, then
> finalize (memzero new_crypto_info, call tls_finish_key_update).
To ease reviews, maybe extract the refactoring into another patch to
separate it from the actual rekey implementation.
> 5. Added TLS_TX_DEV_CLOSED flag to track TX hardware context state,
> to avoid double tls_dev_del call, symmetric with existing
> TLS_RX_DEV_CLOSED.
AFAICT this bit is equivalent to having
sk->sk_validate_xmit_skb == tls_validate_xmit_skb_sw
Do we need the bit or can we just check sk_validate_xmit_skb?
> This removes the rekey rejection checks added in the previous patch,
> replacing them with full rekey support including graceful degradation.
(nit: this precision is probably not needed)
[...]
> diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
> index 459963c254f4..4aaa51a35f1f 100644
[...]
> crypto_info = &ctx->crypto_send.info;
> - if (crypto_info->version != TLS_1_2_VERSION &&
> - crypto_info->version != TLS_1_3_VERSION) {
> + src_crypto_info = new_crypto_info ?: crypto_info;
> + if (src_crypto_info->version != TLS_1_2_VERSION &&
> + src_crypto_info->version != TLS_1_3_VERSION) {
> rc = -EOPNOTSUPP;
> goto release_netdev;
> }
>
> - cipher_desc = get_cipher_desc(crypto_info->cipher_type);
> + cipher_desc = get_cipher_desc(src_crypto_info->cipher_type);
This shouldn't be necessary, since do_tls_setsockopt_conf checks that
we're not changing cipher type during a rekey.
> /* Avoid offloading if the device is down
> * We don't want to offload new flows after
> @@ -1182,29 +1194,91 @@ int tls_set_device_offload(struct sock *sk)
> goto release_lock;
> }
>
> - ctx->priv_ctx_tx = offload_ctx;
> + if (!new_crypto_info) {
> + ctx->priv_ctx_tx = offload_ctx;
> + } else {
> + char *key = crypto_info_key(src_crypto_info, cipher_desc);
> +
> + offload_ctx = tls_offload_ctx_tx(ctx);
> +
> + rc = crypto_aead_setkey(offload_ctx->aead_send, key,
> + cipher_desc->key);
> + if (rc)
> + goto release_lock;
> +
> + /* For rekey, delete old HW context before adding new one. */
As in the other patch, some unnecessary comments.
[...]
> + memcpy(ctx->tx.iv + cipher_desc->salt, iv, cipher_desc->iv);
> + memcpy(ctx->tx.rec_seq, rec_seq, cipher_desc->rec_seq);
> +
> + spin_lock_irqsave(&offload_ctx->lock, flags);
> + /* Delete old records, can't be retransmitted with new key */
> + delete_all_records(offload_ctx);
> +
> + /* Update unacked_record_sn for the new key's rec_seq.
> + * This is critical for SW fallback encryption to use
> + * the correct record sequence number after rekey.
The whole comment should probably be removed, but if it stays, it
should explain why this is critical, rather than just asserting that
it is. (but I guess this code will need to be changed to handle
unacked records anyway)
[...]
> dev_put(netdev);
>
> return 0;
>
> release_lock:
> up_read(&device_offload_lock);
> + if (new_crypto_info)
> + goto release_netdev;
nit: not super elegant, but I'm not sure we can do much better :/
> clean_acked_data_disable(tcp_sk(sk));
> crypto_free_aead(offload_ctx->aead_send);
> free_offload_ctx:
> @@ -1217,17 +1291,33 @@ int tls_set_device_offload(struct sock *sk)
> return rc;
> }
>
--
Sabrina
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC PATCH v7 3/4] mlx5: TLS 1.3 hardware offload support
2026-02-05 23:15 [RFC PATCH v7 0/4] tls: Add TLS 1.3 hardware offload support Rishikesh Jethwani
2026-02-05 23:15 ` [RFC PATCH v7 1/4] tls: add " Rishikesh Jethwani
2026-02-05 23:15 ` [RFC PATCH v7 2/4] tls: add hardware offload key update support Rishikesh Jethwani
@ 2026-02-05 23:15 ` Rishikesh Jethwani
2026-02-05 23:15 ` [RFC PATCH v7 4/4] selftests: drivers: net: hw: add TLS hardware offload test Rishikesh Jethwani
3 siblings, 0 replies; 8+ messages in thread
From: Rishikesh Jethwani @ 2026-02-05 23:15 UTC (permalink / raw)
To: netdev
Cc: saeedm, tariqt, mbloch, borisp, john.fastabend, kuba, sd, davem,
pabeni, edumazet, leon, Rishikesh Jethwani
Add TLS 1.3 hardware offload support to mlx5 driver, enabling both
TX and RX hardware acceleration for TLS 1.3 connections on Mellanox
ConnectX-6 Dx and newer adapters.
This patch enables:
- TLS 1.3 version detection and validation with proper capability
checking
- TLS 1.3 crypto context configuration using
MLX5E_STATIC_PARAMS_CONTEXT_TLS_1_3 (0x3)
- Correct IV handling for TLS 1.3 (12-byte IV vs TLS 1.2's 4-byte salt)
- Hardware offload for both TLS 1.3 AES-GCM-128 and AES-GCM-256 cipher
suites
Key differences from TLS 1.2:
- TLS 1.2: Only 4-byte salt copied to gcm_iv, explicit IV in each record
- TLS 1.3: Full 12-byte IV (salt + iv) copied to gcm_iv + implicit_iv
* salt (4 bytes) → gcm_iv[0:3]
* iv (8 bytes) → gcm_iv[4:7] + implicit_iv[0:3]
* Note: gcm_iv and implicit_iv are contiguous in memory
The EXTRACT_INFO_FIELDS macro is updated to also extract the 'iv' field
which is needed for TLS 1.3.
Tested on Mellanox ConnectX-6 Dx (Crypto Enabled) with TLS 1.3 AES-GCM-128
and AES-GCM-256 cipher suites.
Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
---
.../ethernet/mellanox/mlx5/core/en_accel/ktls.h | 8 +++++++-
.../mellanox/mlx5/core/en_accel/ktls_txrx.c | 14 +++++++++++---
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.h b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.h
index 07a04a142a2e..0469ca6a0762 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls.h
@@ -30,7 +30,9 @@ static inline bool mlx5e_is_ktls_device(struct mlx5_core_dev *mdev)
return false;
return (MLX5_CAP_TLS(mdev, tls_1_2_aes_gcm_128) ||
- MLX5_CAP_TLS(mdev, tls_1_2_aes_gcm_256));
+ MLX5_CAP_TLS(mdev, tls_1_2_aes_gcm_256) ||
+ MLX5_CAP_TLS(mdev, tls_1_3_aes_gcm_128) ||
+ MLX5_CAP_TLS(mdev, tls_1_3_aes_gcm_256));
}
static inline bool mlx5e_ktls_type_check(struct mlx5_core_dev *mdev,
@@ -40,10 +42,14 @@ static inline bool mlx5e_ktls_type_check(struct mlx5_core_dev *mdev,
case TLS_CIPHER_AES_GCM_128:
if (crypto_info->version == TLS_1_2_VERSION)
return MLX5_CAP_TLS(mdev, tls_1_2_aes_gcm_128);
+ else if (crypto_info->version == TLS_1_3_VERSION)
+ return MLX5_CAP_TLS(mdev, tls_1_3_aes_gcm_128);
break;
case TLS_CIPHER_AES_GCM_256:
if (crypto_info->version == TLS_1_2_VERSION)
return MLX5_CAP_TLS(mdev, tls_1_2_aes_gcm_256);
+ else if (crypto_info->version == TLS_1_3_VERSION)
+ return MLX5_CAP_TLS(mdev, tls_1_3_aes_gcm_256);
break;
}
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_txrx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_txrx.c
index 570a912dd6fa..f3f90ad6c6cf 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_txrx.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_txrx.c
@@ -6,6 +6,7 @@
enum {
MLX5E_STATIC_PARAMS_CONTEXT_TLS_1_2 = 0x2,
+ MLX5E_STATIC_PARAMS_CONTEXT_TLS_1_3 = 0x3,
};
enum {
@@ -15,8 +16,10 @@ enum {
#define EXTRACT_INFO_FIELDS do { \
salt = info->salt; \
rec_seq = info->rec_seq; \
+ iv = info->iv; \
salt_sz = sizeof(info->salt); \
rec_seq_sz = sizeof(info->rec_seq); \
+ iv_sz = sizeof(info->iv); \
} while (0)
static void
@@ -25,8 +28,8 @@ fill_static_params(struct mlx5_wqe_tls_static_params_seg *params,
u32 key_id, u32 resync_tcp_sn)
{
char *initial_rn, *gcm_iv;
- u16 salt_sz, rec_seq_sz;
- char *salt, *rec_seq;
+ u16 salt_sz, rec_seq_sz, iv_sz;
+ char *salt, *rec_seq, *iv;
u8 tls_version;
u8 *ctx;
@@ -59,7 +62,12 @@ fill_static_params(struct mlx5_wqe_tls_static_params_seg *params,
memcpy(gcm_iv, salt, salt_sz);
memcpy(initial_rn, rec_seq, rec_seq_sz);
- tls_version = MLX5E_STATIC_PARAMS_CONTEXT_TLS_1_2;
+ if (crypto_info->crypto_info.version == TLS_1_3_VERSION) {
+ memcpy(gcm_iv + salt_sz, iv, iv_sz);
+ tls_version = MLX5E_STATIC_PARAMS_CONTEXT_TLS_1_3;
+ } else {
+ tls_version = MLX5E_STATIC_PARAMS_CONTEXT_TLS_1_2;
+ }
MLX5_SET(tls_static_params, ctx, tls_version, tls_version);
MLX5_SET(tls_static_params, ctx, const_1, 1);
--
2.25.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [RFC PATCH v7 4/4] selftests: drivers: net: hw: add TLS hardware offload test
2026-02-05 23:15 [RFC PATCH v7 0/4] tls: Add TLS 1.3 hardware offload support Rishikesh Jethwani
` (2 preceding siblings ...)
2026-02-05 23:15 ` [RFC PATCH v7 3/4] mlx5: TLS 1.3 hardware offload support Rishikesh Jethwani
@ 2026-02-05 23:15 ` Rishikesh Jethwani
3 siblings, 0 replies; 8+ messages in thread
From: Rishikesh Jethwani @ 2026-02-05 23:15 UTC (permalink / raw)
To: netdev
Cc: saeedm, tariqt, mbloch, borisp, john.fastabend, kuba, sd, davem,
pabeni, edumazet, leon, Rishikesh Jethwani
Add TLS hardware offload test using the NetDrvEpEnv framework. The test
requires two physical endpoints to trigger actual NIC hardware offload.
The test consists of:
- Python wrapper (tls_hw_offload.py): orchestrates tests, reads and
verifies /proc/net/tls_stat counters on both endpoints
- C binary (tls_hw_offload.c): performs TLS operations using kTLS
with hardcoded keys
Test coverage (9 tests):
- TLS 1.2/1.3 with AES-GCM-128/256
- TLS 1.3 rekey (1x and 3x)
- Buffer sizes: 512B, 16KB, 32KB, random (1-8KB)
Validates hardware offload via TlsTxDevice/TlsRxDevice counters and
rekey operations via TlsTxRekeyOk/TlsRxRekeyOk counters.
Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
---
.../testing/selftests/drivers/net/hw/Makefile | 2 +
.../selftests/drivers/net/hw/tls_hw_offload.c | 1009 +++++++++++++++++
.../drivers/net/hw/tls_hw_offload.py | 353 ++++++
3 files changed, 1364 insertions(+)
create mode 100644 tools/testing/selftests/drivers/net/hw/tls_hw_offload.c
create mode 100755 tools/testing/selftests/drivers/net/hw/tls_hw_offload.py
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index 9c163ba6feee..0d12e26bc665 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -15,6 +15,7 @@ endif
TEST_GEN_FILES := \
$(COND_GEN_FILES) \
+ tls_hw_offload \
# end of TEST_GEN_FILES
TEST_PROGS = \
@@ -37,6 +38,7 @@ TEST_PROGS = \
rss_ctx.py \
rss_flow_label.py \
rss_input_xfrm.py \
+ tls_hw_offload.py \
toeplitz.py \
tso.py \
xsk_reconfig.py \
diff --git a/tools/testing/selftests/drivers/net/hw/tls_hw_offload.c b/tools/testing/selftests/drivers/net/hw/tls_hw_offload.c
new file mode 100644
index 000000000000..fa19af2b79c8
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/tls_hw_offload.c
@@ -0,0 +1,1009 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TLS Hardware Offload Two-Node Test
+ *
+ * This test uses hardcoded keys (no TLS handshake) to test kTLS
+ * hardware offload between two physical nodes. Both nodes must
+ * use the same key material.
+ *
+ * For rekey testing, proper TLS KeyUpdate handshake messages are
+ * sent via sendmsg/recvmsg with TLS_SET_RECORD_TYPE/TLS_GET_RECORD_TYPE.
+ *
+ * This binary performs TLS operations only. Counter verification is
+ * handled by the Python test wrapper (tls_hw_offload.py) which reads
+ * /proc/net/tls_stat before and after the test.
+ *
+ * Usage:
+ * Server: ./tls_hw_offload server [OPTIONS]
+ * Client: ./tls_hw_offload client -s <ip> [OPTIONS]
+ *
+ * Options:
+ * -s <ip> Server IP (client only, required)
+ * -p <port> Port number (default: 4433)
+ * -c <128|256> Cipher (default: 128)
+ * -v <1.2|1.3> TLS version (default: 1.3)
+ * -b <size> Fixed buffer size (default: 16384)
+ * -r <max> Random buffer sizes from 1 to max
+ * --rekey[=N] Enable rekey testing (default: 1, max: 4)
+ *
+ * Example:
+ * Node A: ./tls_hw_offload server
+ * Node B: ./tls_hw_offload client -s 192.168.20.2
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <linux/tls.h>
+
+/* TLS record types for sendmsg/recvmsg with kTLS */
+#define TLS_RECORD_TYPE_HANDSHAKE 22
+#define TLS_RECORD_TYPE_APPLICATION_DATA 23
+
+/* TLS 1.3 KeyUpdate handshake message type (RFC 8446) */
+#define TLS_HANDSHAKE_KEY_UPDATE 0x18
+#define KEY_UPDATE_NOT_REQUESTED 0
+#define KEY_UPDATE_REQUESTED 1
+
+/* Number of messages to send in the test loop */
+#define TEST_ITERATIONS 10
+
+/*
+ * Maximum number of rekeys allowed per test run.
+ * With TEST_ITERATIONS=10, this ensures at least 2 messages between rekeys.
+ */
+#define MAX_REKEYS 4
+
+/* TLS 1.3 AES-GCM-128 key material - initial key (generation 0) */
+static struct tls12_crypto_info_aes_gcm_128 tls_info_key0_128 = {
+ .info = {
+ .version = TLS_1_3_VERSION,
+ .cipher_type = TLS_CIPHER_AES_GCM_128,
+ },
+ .iv = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 },
+ .key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 },
+ .salt = { 0x01, 0x02, 0x03, 0x04 },
+ .rec_seq = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+};
+
+/* TLS 1.3 AES-GCM-256 key material - initial key (generation 0) */
+static struct tls12_crypto_info_aes_gcm_256 tls_info_key0_256 = {
+ .info = {
+ .version = TLS_1_3_VERSION,
+ .cipher_type = TLS_CIPHER_AES_GCM_256,
+ },
+ .iv = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 },
+ .key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 },
+ .salt = { 0x01, 0x02, 0x03, 0x04 },
+ .rec_seq = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+};
+
+static int do_rekey; /* Set via command line */
+static int num_rekeys = 1; /* Number of rekeys to perform */
+static int rekeys_done; /* Counter for completed rekeys */
+
+/* Cipher selection: 128 or 256 */
+static int cipher_type = 128;
+
+/* TLS version: 12 for TLS 1.2, 13 for TLS 1.3 (default) */
+static int tls_version = 13;
+
+/* Server port (default: 4433) */
+static int server_port = 4433;
+
+/* Server IP for client to connect to */
+static char *server_ip;
+
+/* Send buffer size (default: 16384) */
+static int send_size = 16384;
+
+/* Random send size max (0 = disabled, use fixed send_size) */
+static int random_size_max;
+
+/*
+ * Derive AES-GCM-128 key for a given generation number.
+ * Both sides use the same derivation so keys match.
+ * Generation 0 = initial key, Generation N = Nth rekey.
+ */
+static void derive_key_128(struct tls12_crypto_info_aes_gcm_128 *key,
+ int generation)
+{
+ unsigned char pattern;
+ int i;
+
+ /* Start with initial key */
+ memcpy(key, &tls_info_key0_128, sizeof(*key));
+
+ /* Set TLS version based on global setting */
+ if (tls_version == 12)
+ key->info.version = TLS_1_2_VERSION;
+ else
+ key->info.version = TLS_1_3_VERSION;
+
+ if (generation == 0)
+ return;
+
+ /* Derive new key by XORing with generation-based pattern */
+ pattern = (unsigned char)((generation * 0x1B) ^ 0x63);
+
+ for (i = 0; i < TLS_CIPHER_AES_GCM_128_KEY_SIZE; i++) {
+ key->key[i] ^= pattern;
+ pattern = (pattern << 1) | (pattern >> 7); /* Rotate */
+ }
+
+ pattern = (unsigned char)((generation * 0x2D) ^ 0x7C);
+ for (i = 0; i < TLS_CIPHER_AES_GCM_128_IV_SIZE; i++) {
+ key->iv[i] ^= pattern;
+ pattern = (pattern << 1) | (pattern >> 7);
+ }
+
+ for (i = 0; i < TLS_CIPHER_AES_GCM_128_SALT_SIZE; i++)
+ key->salt[i] ^= (unsigned char)(generation & 0xFF);
+
+ /* Reset record sequence for new key */
+ memset(key->rec_seq, 0, TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE);
+}
+
+/*
+ * Derive AES-GCM-256 key for a given generation number.
+ */
+static void derive_key_256(struct tls12_crypto_info_aes_gcm_256 *key,
+ int generation)
+{
+ unsigned char pattern;
+ int i;
+
+ /* Start with initial key */
+ memcpy(key, &tls_info_key0_256, sizeof(*key));
+
+ /* Set TLS version based on global setting */
+ if (tls_version == 12)
+ key->info.version = TLS_1_2_VERSION;
+ else
+ key->info.version = TLS_1_3_VERSION;
+
+ if (generation == 0)
+ return;
+
+ /* Derive new key by XORing with generation-based pattern */
+ pattern = (unsigned char)((generation * 0x1B) ^ 0x63);
+
+ for (i = 0; i < TLS_CIPHER_AES_GCM_256_KEY_SIZE; i++) {
+ key->key[i] ^= pattern;
+ pattern = (pattern << 1) | (pattern >> 7); /* Rotate */
+ }
+
+ pattern = (unsigned char)((generation * 0x2D) ^ 0x7C);
+ for (i = 0; i < TLS_CIPHER_AES_GCM_256_IV_SIZE; i++) {
+ key->iv[i] ^= pattern;
+ pattern = (pattern << 1) | (pattern >> 7);
+ }
+
+ for (i = 0; i < TLS_CIPHER_AES_GCM_256_SALT_SIZE; i++)
+ key->salt[i] ^= (unsigned char)(generation & 0xFF);
+
+ /* Reset record sequence for new key */
+ memset(key->rec_seq, 0, TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE);
+}
+
+/* Return human-readable cipher name for logging */
+static const char *cipher_name(int cipher)
+{
+ switch (cipher) {
+ case 128: return "AES-GCM-128";
+ case 256: return "AES-GCM-256";
+ default: return "unknown";
+ }
+}
+
+/* Return human-readable TLS version name for logging */
+static const char *version_name(int version)
+{
+ switch (version) {
+ case 12: return "TLS 1.2";
+ case 13: return "TLS 1.3";
+ default: return "unknown";
+ }
+}
+
+/* Enable kTLS by setting TCP Upper Layer Protocol to "tls" */
+static int setup_tls_ulp(int fd)
+{
+ int ret;
+
+ ret = setsockopt(fd, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls"));
+ if (ret < 0) {
+ printf("TCP_ULP failed: %s\n", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Install TLS key for TX or RX direction.
+ * Derives key material for the given generation and installs it via setsockopt.
+ */
+static int setup_tls_key(int fd, int is_tx, int generation, int cipher)
+{
+ int ret;
+
+ if (cipher == 256) {
+ struct tls12_crypto_info_aes_gcm_256 key;
+
+ derive_key_256(&key, generation);
+ ret = setsockopt(fd, SOL_TLS, is_tx ? TLS_TX : TLS_RX,
+ &key, sizeof(key));
+ } else {
+ struct tls12_crypto_info_aes_gcm_128 key;
+
+ derive_key_128(&key, generation);
+ ret = setsockopt(fd, SOL_TLS, is_tx ? TLS_TX : TLS_RX,
+ &key, sizeof(key));
+ }
+
+ if (ret < 0) {
+ printf("TLS_%s %s (gen %d) failed: %s\n",
+ is_tx ? "TX" : "RX", cipher_name(cipher),
+ generation, strerror(errno));
+ return -1;
+ }
+
+ printf("TLS_%s %s gen %d installed\n",
+ is_tx ? "TX" : "RX", cipher_name(cipher), generation);
+ return 0;
+}
+
+/*
+ * Send a TLS 1.3 KeyUpdate handshake message via kTLS.
+ *
+ * This signals to the peer's kernel kTLS layer that we are updating
+ * our TX key. The peer must receive this before updating their RX key.
+ *
+ * KeyUpdate message format (RFC 8446):
+ * HandshakeType: key_update (24/0x18) - 1 byte
+ * Length: 1 - 3 bytes (24-bit)
+ * KeyUpdateRequest: 0 or 1 - 1 byte
+ * Total: 5 bytes
+ */
+static int send_tls_key_update(int fd, int request_update)
+{
+ char cmsg_buf[CMSG_SPACE(sizeof(unsigned char))];
+ unsigned char key_update_msg[5];
+ struct msghdr msg = {0};
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+
+ /* Build TLS 1.3 KeyUpdate handshake message */
+ key_update_msg[0] = TLS_HANDSHAKE_KEY_UPDATE; /* HandshakeType */
+ key_update_msg[1] = 0; /* Length (24-bit) */
+ key_update_msg[2] = 0;
+ key_update_msg[3] = 1; /* Length = 1 */
+ key_update_msg[4] = request_update ? KEY_UPDATE_REQUESTED
+ : KEY_UPDATE_NOT_REQUESTED;
+
+ iov.iov_base = key_update_msg;
+ iov.iov_len = sizeof(key_update_msg);
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_TLS;
+ cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(unsigned char));
+ *CMSG_DATA(cmsg) = TLS_RECORD_TYPE_HANDSHAKE;
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ if (sendmsg(fd, &msg, 0) < 0) {
+ printf("sendmsg KeyUpdate failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ printf("Sent TLS KeyUpdate handshake message\n");
+ return 0;
+}
+
+/*
+ * Receive a TLS message and get its record type via cmsg.
+ * Returns bytes received, or -1 on error.
+ */
+static int recv_tls_message(int fd, char *buf, size_t buflen, int *record_type)
+{
+ char cmsg_buf[CMSG_SPACE(sizeof(unsigned char))];
+ struct msghdr msg = {0};
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ int ret;
+
+ iov.iov_base = buf;
+ iov.iov_len = buflen;
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+
+ ret = recvmsg(fd, &msg, 0);
+ if (ret <= 0)
+ return ret;
+
+ *record_type = TLS_RECORD_TYPE_APPLICATION_DATA; /* default */
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg && cmsg->cmsg_level == SOL_TLS &&
+ cmsg->cmsg_type == TLS_GET_RECORD_TYPE)
+ *record_type = *((unsigned char *)CMSG_DATA(cmsg));
+
+ return ret;
+}
+
+/*
+ * Receive and verify a TLS KeyUpdate handshake message.
+ * Returns 0 on success, -1 on error.
+ */
+static int recv_tls_keyupdate(int fd)
+{
+ int record_type;
+ char buf[16];
+ int ret;
+
+ ret = recv_tls_message(fd, buf, sizeof(buf), &record_type);
+ if (ret < 0) {
+ printf("recv_tls_message failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (record_type != TLS_RECORD_TYPE_HANDSHAKE) {
+ printf("Expected handshake record (0x%02x), got 0x%02x\n",
+ TLS_RECORD_TYPE_HANDSHAKE, record_type);
+ return -1;
+ }
+
+ if (ret >= 1 && buf[0] == TLS_HANDSHAKE_KEY_UPDATE) {
+ printf("Received TLS KeyUpdate handshake (%d bytes)\n", ret);
+ return 0;
+ }
+
+ printf("Expected KeyUpdate (0x%02x), got 0x%02x\n",
+ TLS_HANDSHAKE_KEY_UPDATE, (unsigned char)buf[0]);
+ return -1;
+}
+
+/*
+ * Check for EKEYEXPIRED after receiving KeyUpdate.
+ * The kernel returns this to signal it's waiting for RX key update.
+ */
+static void check_ekeyexpired(int fd)
+{
+ char buf[16];
+ int ret;
+
+ ret = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
+ if (ret == -1 && errno == EKEYEXPIRED)
+ printf("recv() returned EKEYEXPIRED as expected\n");
+ else if (ret == -1 && errno == EAGAIN)
+ printf("recv() returned EAGAIN (no pending data)\n");
+ else if (ret == -1)
+ printf("recv() returned error: %s\n", strerror(errno));
+}
+
+/*
+ * Update kTLS key (TX or RX direction) for a given generation.
+ */
+static int do_tls_rekey(int fd, int is_tx, int generation, int cipher)
+{
+ int ret;
+
+ printf("Performing TLS_%s %s rekey to generation %d...\n",
+ is_tx ? "TX" : "RX", cipher_name(cipher), generation);
+
+ if (cipher == 256) {
+ struct tls12_crypto_info_aes_gcm_256 key;
+
+ derive_key_256(&key, generation);
+ ret = setsockopt(fd, SOL_TLS, is_tx ? TLS_TX : TLS_RX,
+ &key, sizeof(key));
+ } else {
+ struct tls12_crypto_info_aes_gcm_128 key;
+
+ derive_key_128(&key, generation);
+ ret = setsockopt(fd, SOL_TLS, is_tx ? TLS_TX : TLS_RX,
+ &key, sizeof(key));
+ }
+
+ if (ret < 0) {
+ printf("TLS_%s %s rekey failed: %s\n", is_tx ? "TX" : "RX",
+ cipher_name(cipher), strerror(errno));
+ return -1;
+ }
+ printf("TLS_%s %s rekey to gen %d successful!\n",
+ is_tx ? "TX" : "RX", cipher_name(cipher), generation);
+ return 0;
+}
+
+static int do_client(void)
+{
+ char *buf = NULL, *echo_buf = NULL;
+ int max_size, rekey_interval;
+ ssize_t echo_total, echo_n;
+ int csk = -1, ret, i, j;
+ struct sockaddr_in sa;
+ int test_result = 0;
+ int current_gen = 0;
+ int next_rekey_at;
+ ssize_t n;
+
+ if (!server_ip) {
+ printf("ERROR: Client requires -s <ip> option\n");
+ return -1;
+ }
+
+ /* Allocate buffers based on max possible size */
+ max_size = random_size_max > 0 ? random_size_max : send_size;
+ buf = malloc(max_size);
+ echo_buf = malloc(max_size);
+ if (!buf || !echo_buf) {
+ printf("failed to allocate buffers\n");
+ test_result = -1;
+ goto out;
+ }
+
+ csk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (csk < 0) {
+ printf("failed to create socket: %s\n", strerror(errno));
+ test_result = -1;
+ goto out;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = inet_addr(server_ip);
+ sa.sin_port = htons(server_port);
+ printf("Connecting to %s:%d...\n", server_ip, server_port);
+
+ ret = connect(csk, (struct sockaddr *)&sa, sizeof(sa));
+ if (ret < 0) {
+ printf("connect failed: %s\n", strerror(errno));
+ test_result = -1;
+ goto out;
+ }
+ printf("Connected!\n");
+
+ /* Setup TLS ULP first */
+ if (setup_tls_ulp(csk) < 0) {
+ test_result = -1;
+ goto out;
+ }
+
+ /* Setup TLS TX and RX with initial key (generation 0) */
+ if (setup_tls_key(csk, 1, 0, cipher_type) < 0) { /* TLS_TX, key0 */
+ test_result = -1;
+ goto out;
+ }
+ if (setup_tls_key(csk, 0, 0, cipher_type) < 0) { /* TLS_RX, key0 */
+ test_result = -1;
+ goto out;
+ }
+
+ if (do_rekey)
+ printf("TLS %s setup complete. Will perform %d rekey(s).\n",
+ cipher_name(cipher_type), num_rekeys);
+ else
+ printf("TLS setup complete.\n");
+
+ if (random_size_max > 0)
+ printf("Sending %d messages of random size (1..%d bytes)...\n",
+ TEST_ITERATIONS, random_size_max);
+ else
+ printf("Sending %d messages of %d bytes...\n",
+ TEST_ITERATIONS, send_size);
+
+ /*
+ * Calculate rekey interval to spread rekeys evenly across messages.
+ * With N rekeys and M messages, rekey every M/(N+1) messages.
+ */
+ rekey_interval = TEST_ITERATIONS / (num_rekeys + 1);
+ if (rekey_interval < 1)
+ rekey_interval = 1;
+ next_rekey_at = rekey_interval;
+
+ /* Send test data */
+ for (i = 0; i < TEST_ITERATIONS; i++) {
+ int this_size;
+
+ /* Determine size for this message */
+ if (random_size_max > 0)
+ this_size = (rand() % random_size_max) + 1;
+ else
+ this_size = send_size;
+
+ /* Fill buffer with random data */
+ for (j = 0; j < this_size; j++)
+ buf[j] = rand() & 0xFF;
+
+ n = send(csk, buf, this_size, 0);
+ if (n != this_size) {
+ printf("FAIL: send failed: %s\n", strerror(errno));
+ test_result = -1;
+ break;
+ }
+ printf("Sent %zd bytes (iteration %d)\n", n, i + 1);
+
+ /* Wait for echo from server - may need multiple recv() calls */
+ echo_total = 0;
+ while (echo_total < n) {
+ echo_n = recv(csk, echo_buf + echo_total,
+ n - echo_total, 0);
+ if (echo_n < 0) {
+ printf("FAIL: Echo recv failed: %s\n",
+ strerror(errno));
+ test_result = -1;
+ break;
+ }
+ if (echo_n == 0) {
+ printf("FAIL: Connection closed during echo\n");
+ test_result = -1;
+ break;
+ }
+ echo_total += echo_n;
+ }
+ if (test_result != 0)
+ break;
+ /* Verify echo data matches what we sent */
+ if (memcmp(buf, echo_buf, n) != 0) {
+ printf("FAIL: Echo data mismatch!\n");
+ test_result = -1;
+ break;
+ }
+ printf("Received echo %zd bytes (ok)\n", echo_total);
+
+ /*
+ * Perform rekey at intervals if enabled.
+ *
+ * kTLS Rekey Protocol (client side):
+ * 1. Send TLS KeyUpdate handshake message (with OLD TX key)
+ * 2. Update TX key via setsockopt
+ * 3. Wait for server's KeyUpdate response
+ * 4. Update RX key via setsockopt
+ */
+ if (do_rekey && rekeys_done < num_rekeys &&
+ (i + 1) == next_rekey_at) {
+ current_gen++;
+ printf("\n=== Client Rekey #%d (gen %d) ===\n",
+ rekeys_done + 1, current_gen);
+
+ /* Step 1: Send KeyUpdate to server */
+ printf("Step 1: Sending TLS KeyUpdate to server\n");
+ ret = send_tls_key_update(csk, KEY_UPDATE_REQUESTED);
+ if (ret < 0) {
+ printf("FAIL: send KeyUpdate\n");
+ test_result = -1;
+ break;
+ }
+
+ /* Step 2: Update client TX key */
+ printf("Step 2: Updating client TX key\n");
+ ret = do_tls_rekey(csk, 1, current_gen, cipher_type);
+ if (ret < 0) {
+ test_result = -1;
+ break;
+ }
+
+ /* Step 3: Wait for server's KeyUpdate */
+ printf("Step 3: Waiting for server's KeyUpdate\n");
+ if (recv_tls_keyupdate(csk) < 0) {
+ printf("FAIL: recv KeyUpdate from server\n");
+ test_result = -1;
+ break;
+ }
+
+ /* Check for EKEYEXPIRED */
+ check_ekeyexpired(csk);
+
+ /* Step 4: Update client RX key */
+ printf("Step 4: Updating client RX key\n");
+ ret = do_tls_rekey(csk, 0, current_gen, cipher_type);
+ if (ret < 0) {
+ test_result = -1;
+ break;
+ }
+
+ rekeys_done++;
+ next_rekey_at += rekey_interval;
+ printf("=== Client Rekey #%d Complete ===\n\n",
+ rekeys_done);
+ }
+ }
+
+ /* Check that all iterations completed */
+ if (i < TEST_ITERATIONS && test_result == 0) {
+ printf("FAIL: Only %d of %d iterations\n", i, TEST_ITERATIONS);
+ test_result = -1;
+ }
+
+ close(csk);
+ csk = -1;
+ if (do_rekey)
+ printf("Rekeys completed: %d/%d\n", rekeys_done, num_rekeys);
+
+out:
+ if (csk >= 0)
+ close(csk);
+ free(buf);
+ free(echo_buf);
+ return test_result;
+}
+
+static int do_server(void)
+{
+ int lsk = -1, csk = -1, ret;
+ ssize_t n, total = 0, sent;
+ struct sockaddr_in sa;
+ int current_gen = 0;
+ int test_result = 0;
+ int recv_count = 0;
+ char *buf = NULL;
+ int record_type;
+ int max_size;
+ int one = 1;
+
+ /* Allocate buffer based on max possible size */
+ max_size = random_size_max > 0 ? random_size_max : send_size;
+ buf = malloc(max_size);
+ if (!buf) {
+ printf("failed to allocate buffer\n");
+ test_result = -1;
+ goto out;
+ }
+
+ lsk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (lsk < 0) {
+ printf("failed to create socket: %s\n", strerror(errno));
+ test_result = -1;
+ goto out;
+ }
+
+ setsockopt(lsk, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+ /* Bind to INADDR_ANY:PORT */
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = INADDR_ANY;
+ sa.sin_port = htons(server_port);
+
+ ret = bind(lsk, (struct sockaddr *)&sa, sizeof(sa));
+ if (ret < 0) {
+ printf("bind failed: %s\n", strerror(errno));
+ test_result = -1;
+ goto out;
+ }
+
+ ret = listen(lsk, 5);
+ if (ret < 0) {
+ printf("listen failed: %s\n", strerror(errno));
+ test_result = -1;
+ goto out;
+ }
+
+ printf("Server listening on port %d\n", server_port);
+ printf("Waiting for client connection...\n");
+
+ csk = accept(lsk, (struct sockaddr *)NULL, (socklen_t *)NULL);
+ if (csk < 0) {
+ printf("accept failed: %s\n", strerror(errno));
+ test_result = -1;
+ goto out;
+ }
+ printf("Client connected!\n");
+
+ /* Setup TLS ULP first */
+ if (setup_tls_ulp(csk) < 0) {
+ test_result = -1;
+ goto out;
+ }
+
+ /* Setup TLS TX and RX with initial key (generation 0) */
+ if (setup_tls_key(csk, 1, 0, cipher_type) < 0) { /* TLS_TX, key0 */
+ test_result = -1;
+ goto out;
+ }
+ if (setup_tls_key(csk, 0, 0, cipher_type) < 0) { /* TLS_RX, key0 */
+ test_result = -1;
+ goto out;
+ }
+
+ printf("TLS %s setup complete. Receiving...\n",
+ cipher_name(cipher_type));
+
+ /*
+ * Main receive loop using recvmsg to detect KeyUpdate messages.
+ *
+ * kTLS Rekey Protocol (server side):
+ * 1. Receive TLS KeyUpdate handshake from client
+ * 2. Check for EKEYEXPIRED
+ * 3. Update RX key via setsockopt
+ * 4. Send TLS KeyUpdate back to client
+ * 5. Update TX key via setsockopt
+ *
+ * Per kernel kTLS test pattern (from selftests/net/tls.c):
+ * - First try plain recv with MSG_PEEK | MSG_DONTWAIT
+ * - If it fails with EIO/ENOMSG, a handshake record is pending
+ * - Use recvmsg with cmsg to get the actual record type
+ */
+ while (1) {
+ /*
+ * First try plain recv - this fails for non-data records.
+ * This pattern is from tls_keyupdate_test.c which works.
+ */
+ n = recv(csk, buf, max_size, MSG_PEEK | MSG_DONTWAIT);
+ if (n < 0 &&
+ (errno == EIO || errno == ENOMSG || errno == EAGAIN)) {
+ /* Handshake record or no data - use recvmsg */
+ if (errno != EAGAIN)
+ printf("DEBUG: recv -1 (errno=%d: %s)\n",
+ errno, strerror(errno));
+ n = recv_tls_message(csk, buf, max_size, &record_type);
+ } else if (n > 0) {
+ /* Application data - receive it properly */
+ n = recv_tls_message(csk, buf, max_size, &record_type);
+ } else if (n == 0) {
+ printf("Connection closed by client\n");
+ break;
+ }
+
+ /* Other error from MSG_PEEK recv */
+ if (n < 0) {
+ printf("recv failed: %s\n", strerror(errno));
+ break;
+ }
+
+ if (n <= 0) {
+ if (n == 0)
+ printf("Connection closed by client\n");
+ else
+ printf("recv_tls_message: %s\n",
+ strerror(errno));
+ break;
+ }
+
+ /* Check if we received a TLS KeyUpdate handshake message */
+ if (record_type == TLS_RECORD_TYPE_HANDSHAKE &&
+ n >= 1 && buf[0] == TLS_HANDSHAKE_KEY_UPDATE) {
+ current_gen++;
+ printf("\n=== Server Rekey #%d (gen %d) ===\n",
+ rekeys_done + 1, current_gen);
+ printf("Received KeyUpdate from client (%zd bytes)\n",
+ n);
+
+ /* Step 1: Check for EKEYEXPIRED */
+ printf("Step 1: Checking for EKEYEXPIRED\n");
+ check_ekeyexpired(csk);
+
+ /* Step 2: Update server RX key */
+ printf("Step 2: Updating server RX key\n");
+ ret = do_tls_rekey(csk, 0, current_gen, cipher_type);
+ if (ret < 0) {
+ test_result = -1;
+ break;
+ }
+
+ /* Step 3: Send KeyUpdate back to client */
+ printf("Step 3: Sending TLS KeyUpdate to client\n");
+ ret = send_tls_key_update(csk,
+ KEY_UPDATE_NOT_REQUESTED);
+ if (ret < 0) {
+ printf("Failed to send KeyUpdate\n");
+ test_result = -1;
+ break;
+ }
+
+ /* Step 4: Update server TX key */
+ printf("Step 4: Updating server TX key\n");
+ ret = do_tls_rekey(csk, 1, current_gen, cipher_type);
+ if (ret < 0) {
+ test_result = -1;
+ break;
+ }
+
+ rekeys_done++;
+ printf("=== Server Rekey #%d Complete ===\n\n",
+ rekeys_done);
+ continue;
+ }
+
+ /* Application data */
+ total += n;
+ recv_count++;
+ printf("Received %zd bytes (total: %zd, count: %d)\n",
+ n, total, recv_count);
+
+ /* Echo data back to client */
+ sent = send(csk, buf, n, 0);
+ if (sent < 0) {
+ printf("Echo send failed: %s\n", strerror(errno));
+ break;
+ }
+ if (sent != n)
+ printf("Echo partial: %zd of %zd bytes\n", sent, n);
+ printf("Echoed %zd bytes back to client\n", sent);
+ }
+
+ printf("Connection closed. Total received: %zd bytes\n", total);
+ if (do_rekey)
+ printf("Rekeys completed: %d\n", rekeys_done);
+
+ close(csk);
+ csk = -1;
+ close(lsk);
+ lsk = -1;
+
+out:
+ if (csk >= 0)
+ close(csk);
+ if (lsk >= 0)
+ close(lsk);
+ free(buf);
+ return test_result;
+}
+
+static void parse_rekey_option(const char *arg)
+{
+ int requested;
+
+ /* Parse --rekey or --rekey=N */
+ if (strncmp(arg, "--rekey=", 8) == 0) {
+ requested = atoi(arg + 8);
+ if (requested < 1) {
+ printf("WARNING: Invalid rekey count, using 1\n");
+ num_rekeys = 1;
+ } else if (requested > MAX_REKEYS) {
+ printf("WARNING: Rekey count %d > max %d, using %d\n",
+ requested, MAX_REKEYS, MAX_REKEYS);
+ num_rekeys = MAX_REKEYS;
+ } else {
+ num_rekeys = requested;
+ }
+ do_rekey = 1;
+ } else if (strcmp(arg, "--rekey") == 0) {
+ do_rekey = 1;
+ num_rekeys = 1;
+ }
+}
+
+static int parse_cipher_option(const char *arg)
+{
+ /* Parse -c <cipher> where cipher is 128 or 256 */
+ if (strcmp(arg, "128") == 0) {
+ cipher_type = 128;
+ return 0;
+ } else if (strcmp(arg, "256") == 0) {
+ cipher_type = 256;
+ return 0;
+ }
+ printf("ERROR: Invalid cipher '%s'. Must be 128 or 256.\n", arg);
+ return -1;
+}
+
+static int parse_version_option(const char *arg)
+{
+ /* Parse -v <version> where version is 1.2 or 1.3 */
+ if (strcmp(arg, "1.2") == 0) {
+ tls_version = 12;
+ return 0;
+ } else if (strcmp(arg, "1.3") == 0) {
+ tls_version = 13;
+ return 0;
+ }
+ printf("ERROR: Invalid TLS version '%s'. Must be 1.2 or 1.3.\n", arg);
+ return -1;
+}
+
+static void print_usage(const char *prog)
+{
+ printf("TLS Hardware Offload Two-Node Test\n\n");
+ printf("Usage:\n");
+ printf(" %s server [OPTIONS]\n", prog);
+ printf(" %s client -s <ip> [OPTIONS]\n", prog);
+ printf("\nOptions:\n");
+ printf(" -s <ip> Server IP to connect (client, required)\n");
+ printf(" -p <port> Server port (default: 4433)\n");
+ printf(" -b <size> Send buffer (record) size (default: 16384)\n");
+ printf(" -r <max> Use random send buffer sizes (1..<max>)\n");
+ printf(" -v <version> TLS version: 1.2 or 1.3 (default: 1.3)\n");
+ printf(" -c <cipher> Cipher: 128 or 256 (default: 128)\n");
+ printf(" --rekey[=N] Enable rekey (default: 1, TLS 1.3 only)\n");
+ printf(" --help Show this help message\n");
+ printf("\nExample:\n");
+ printf(" Node A: %s server\n", prog);
+ printf(" Node B: %s client -s 192.168.20.2\n", prog);
+ printf("\nRekey Example (3 rekeys, TLS 1.3 only):\n");
+ printf(" Node A: %s server --rekey=3\n", prog);
+ printf(" Node B: %s client -s 192.168.20.2 --rekey=3\n", prog);
+}
+
+int main(int argc, char *argv[])
+{
+ int i;
+
+ /* Check for --help first */
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "--help") == 0 ||
+ strcmp(argv[i], "-h") == 0) {
+ print_usage(argv[0]);
+ return 0;
+ }
+ }
+
+ /* Parse options anywhere in args */
+ for (i = 1; i < argc; i++) {
+ parse_rekey_option(argv[i]);
+ if (strcmp(argv[i], "-s") == 0 && i + 1 < argc)
+ server_ip = argv[i + 1];
+ if (strcmp(argv[i], "-p") == 0 && i + 1 < argc)
+ server_port = atoi(argv[i + 1]);
+ if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
+ send_size = atoi(argv[i + 1]);
+ if (send_size < 1)
+ send_size = 1;
+ }
+ if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
+ random_size_max = atoi(argv[i + 1]);
+ if (random_size_max < 1)
+ random_size_max = 1;
+ }
+ if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) {
+ if (parse_cipher_option(argv[i + 1]) < 0)
+ return -1;
+ }
+ if (strcmp(argv[i], "-v") == 0 && i + 1 < argc) {
+ if (parse_version_option(argv[i + 1]) < 0)
+ return -1;
+ }
+ }
+
+ /* TLS 1.2 does not support rekey - warn and disable */
+ if (tls_version == 12 && do_rekey) {
+ printf("WARNING: TLS 1.2 does not support rekey\n");
+ printf(" (KeyUpdate is TLS 1.3 only)\n");
+ do_rekey = 0;
+ }
+
+ printf("TLS Version: %s\n", version_name(tls_version));
+ printf("Cipher: %s\n", cipher_name(cipher_type));
+ if (random_size_max > 0)
+ printf("Buffer size: random (1..%d)\n", random_size_max);
+ else
+ printf("Buffer size: %d\n", send_size);
+
+ if (do_rekey)
+ printf("Rekey testing ENABLED: %d rekey(s)\n", num_rekeys);
+
+ /* Initialize random seed for random data and buffer sizes */
+ srand(time(NULL));
+
+ if (argc < 2 ||
+ (strcmp(argv[1], "server") && strcmp(argv[1], "client"))) {
+ print_usage(argv[0]);
+ return -1;
+ }
+
+ if (!strcmp(argv[1], "client"))
+ return do_client();
+
+ return do_server();
+}
diff --git a/tools/testing/selftests/drivers/net/hw/tls_hw_offload.py b/tools/testing/selftests/drivers/net/hw/tls_hw_offload.py
new file mode 100755
index 000000000000..48e01903d17b
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/tls_hw_offload.py
@@ -0,0 +1,353 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+TLS Hardware Offload Test
+
+This test verifies kTLS hardware offload functionality between two endpoints
+using the existing driver test framework (NetDrvEpEnv).
+
+The test uses a C helper binary (tls_hw_offload)
+to perform the actual TLS operations with hardcoded keys (no TLS handshake).
+
+For rekey testing, proper TLS KeyUpdate handshake messages are sent via
+sendmsg/recvmsg with TLS_SET_RECORD_TYPE/TLS_GET_RECORD_TYPE.
+
+The test verifies TLS counters from /proc/net/tls_stat:
+ - TlsTxDevice/TlsRxDevice: HW offload was used
+ - TlsTxRekeyOk/TlsRxRekeyOk: Rekey operations succeeded (TLS 1.3 only)
+ - TlsRxRekeyReceived: KeyUpdate messages received (server)
+ - TlsDecryptError: No decryption errors occurred
+
+Note: This test requires actual hardware with TLS offload support when run
+in HW mode. It will not trigger hardware offload on loopback or veth pairs.
+"""
+
+from lib.py import ksft_run, ksft_exit, ksft_pr, KsftSkipEx, ksft_true
+from lib.py import NetDrvEpEnv
+from lib.py import cmd, bkg, wait_port_listen, rand_port
+import time
+
+
+def check_tls_support(cfg):
+ """Check if kTLS is supported on both local and remote."""
+ # Check if /proc/net/tls_stat exists
+ try:
+ cmd("test -f /proc/net/tls_stat")
+ cmd("test -f /proc/net/tls_stat", host=cfg.remote)
+ except Exception as e:
+ raise KsftSkipEx(f"kTLS not supported: {e}")
+
+
+def read_tls_stats():
+ """Read TLS statistics from /proc/net/tls_stat."""
+ stats = {}
+ output = cmd("cat /proc/net/tls_stat")
+ for line in output.stdout.strip().split('\n'):
+ parts = line.split()
+ if len(parts) == 2:
+ stats[parts[0]] = int(parts[1])
+ return stats
+
+
+def verify_tls_counters(stats_before, stats_after, expected_rekeys, is_server):
+ """
+ Verify TLS counters after test completion.
+ Returns True on success, False on failure.
+ """
+ tx_device_diff = (stats_after.get('TlsTxDevice', 0) -
+ stats_before.get('TlsTxDevice', 0))
+ rx_device_diff = (stats_after.get('TlsRxDevice', 0) -
+ stats_before.get('TlsRxDevice', 0))
+ tx_sw_diff = (stats_after.get('TlsTxSw', 0) -
+ stats_before.get('TlsTxSw', 0))
+ rx_sw_diff = (stats_after.get('TlsRxSw', 0) -
+ stats_before.get('TlsRxSw', 0))
+ decrypt_err_diff = (stats_after.get('TlsDecryptError', 0) -
+ stats_before.get('TlsDecryptError', 0))
+
+ used_tx_hw = tx_device_diff >= 1
+ used_rx_hw = rx_device_diff >= 1
+ used_tx_sw = tx_sw_diff >= 1
+ used_rx_sw = rx_sw_diff >= 1
+
+ errors = 0
+
+ role = 'Server' if is_server else 'Client'
+ ksft_pr(f"=== Counter Verification ({role}) ===")
+
+ tx_dev_before = stats_before.get('TlsTxDevice', 0)
+ tx_dev_after = stats_after.get('TlsTxDevice', 0)
+ ksft_pr(f"TlsTxDevice: {tx_dev_before} -> {tx_dev_after} "
+ f"(diff: {tx_device_diff})")
+
+ tx_sw_before = stats_before.get('TlsTxSw', 0)
+ tx_sw_after = stats_after.get('TlsTxSw', 0)
+ ksft_pr(f"TlsTxSw: {tx_sw_before} -> {tx_sw_after} "
+ f"(diff: {tx_sw_diff})")
+
+ if used_tx_hw:
+ ksft_pr("TX Path: HARDWARE OFFLOAD")
+ elif used_tx_sw:
+ ksft_pr("TX Path: SOFTWARE")
+ else:
+ ksft_pr("TX Path: FAIL (no TLS TX activity detected)")
+ errors += 1
+
+ rx_dev_before = stats_before.get('TlsRxDevice', 0)
+ rx_dev_after = stats_after.get('TlsRxDevice', 0)
+ ksft_pr(f"TlsRxDevice: {rx_dev_before} -> {rx_dev_after} "
+ f"(diff: {rx_device_diff})")
+
+ rx_sw_before = stats_before.get('TlsRxSw', 0)
+ rx_sw_after = stats_after.get('TlsRxSw', 0)
+ ksft_pr(f"TlsRxSw: {rx_sw_before} -> {rx_sw_after} "
+ f"(diff: {rx_sw_diff})")
+
+ if used_rx_hw:
+ ksft_pr("RX Path: HARDWARE OFFLOAD")
+ elif used_rx_sw:
+ ksft_pr("RX Path: SOFTWARE")
+ else:
+ ksft_pr("RX Path: FAIL (no TLS RX activity detected)")
+ errors += 1
+
+ # Check rekey counters if rekeys were expected
+ if expected_rekeys > 0:
+ tx_rekey_diff = (stats_after.get('TlsTxRekeyOk', 0) -
+ stats_before.get('TlsTxRekeyOk', 0))
+ rx_rekey_diff = (stats_after.get('TlsRxRekeyOk', 0) -
+ stats_before.get('TlsRxRekeyOk', 0))
+ rx_rekey_recv_diff = (stats_after.get('TlsRxRekeyReceived', 0) -
+ stats_before.get('TlsRxRekeyReceived', 0))
+ tx_rekey_err_diff = (stats_after.get('TlsTxRekeyError', 0) -
+ stats_before.get('TlsTxRekeyError', 0))
+ rx_rekey_err_diff = (stats_after.get('TlsRxRekeyError', 0) -
+ stats_before.get('TlsRxRekeyError', 0))
+
+ tx_rekey_before = stats_before.get('TlsTxRekeyOk', 0)
+ tx_rekey_after = stats_after.get('TlsTxRekeyOk', 0)
+ ksft_pr(f"TlsTxRekeyOk: {tx_rekey_before} -> {tx_rekey_after} "
+ f"(diff: {tx_rekey_diff})")
+ if tx_rekey_diff < expected_rekeys:
+ ksft_pr(f"FAIL: Expected >= {expected_rekeys} TX rekeys")
+ errors += 1
+
+ rx_rekey_before = stats_before.get('TlsRxRekeyOk', 0)
+ rx_rekey_after = stats_after.get('TlsRxRekeyOk', 0)
+ ksft_pr(f"TlsRxRekeyOk: {rx_rekey_before} -> {rx_rekey_after} "
+ f"(diff: {rx_rekey_diff})")
+ if rx_rekey_diff < expected_rekeys:
+ ksft_pr(f"FAIL: Expected >= {expected_rekeys} RX rekeys")
+ errors += 1
+
+ if is_server:
+ rx_recv_before = stats_before.get('TlsRxRekeyReceived', 0)
+ rx_recv_after = stats_after.get('TlsRxRekeyReceived', 0)
+ ksft_pr(f"TlsRxRekeyReceived: {rx_recv_before} -> "
+ f"{rx_recv_after} (diff: {rx_rekey_recv_diff})")
+ if rx_rekey_recv_diff < expected_rekeys:
+ ksft_pr(f"FAIL: Expected >= {expected_rekeys} "
+ f"KeyUpdate messages")
+ errors += 1
+
+ if tx_rekey_err_diff > 0:
+ ksft_pr(f"ERROR: TlsTxRekeyError increased by "
+ f"{tx_rekey_err_diff}")
+ errors += 1
+ if rx_rekey_err_diff > 0:
+ ksft_pr(f"ERROR: TlsRxRekeyError increased by "
+ f"{rx_rekey_err_diff}")
+ errors += 1
+
+ # Check for decrypt errors
+ if decrypt_err_diff > 0:
+ ksft_pr(f"ERROR: TlsDecryptError increased by {decrypt_err_diff}")
+ errors += 1
+
+ ksft_pr(f"=== Verification {'PASSED' if errors == 0 else 'FAILED'} ===\n")
+ return errors == 0
+
+
+def run_tls_test(cfg, cipher="128", tls_version="1.3", rekey=0, buffer_size=None, random_max=None):
+ """
+ Run TLS hardware offload test using the C binary.
+
+ Args:
+ cfg: NetDrvEpEnv configuration
+ cipher: "128" or "256" for AES-GCM key size
+ tls_version: "1.2" or "1.3"
+ rekey: Number of rekeys to perform (0 = no rekey, TLS 1.3 only)
+ buffer_size: Fixed buffer size in bytes (default: 16384)
+ random_max: Use random buffer sizes from 1 to random_max (overrides buffer_size)
+ """
+ port = rand_port()
+
+ # Build server command
+ server_cmd = f"{cfg.bin_remote} server -p {port} -c {cipher} -v {tls_version}"
+ if rekey > 0:
+ server_cmd += f" --rekey={rekey}"
+ if random_max:
+ server_cmd += f" -r {random_max}"
+ elif buffer_size:
+ server_cmd += f" -b {buffer_size}"
+
+ # Build client command
+ client_cmd = (f"{cfg.bin_local} client -s {cfg.remote_addr_v['4']} "
+ f"-p {port} -c {cipher} -v {tls_version}")
+ if rekey > 0:
+ client_cmd += f" --rekey={rekey}"
+ if random_max:
+ client_cmd += f" -r {random_max}"
+ elif buffer_size:
+ client_cmd += f" -b {buffer_size}"
+
+ # Build test description
+ test_desc = f"cipher={cipher}, version={tls_version}, rekey={rekey}"
+ if random_max:
+ test_desc += f", random_size=1-{random_max}"
+ elif buffer_size:
+ test_desc += f", buffer={buffer_size}"
+ ksft_pr(f"Starting TLS test: {test_desc}")
+
+ # Read stats before test
+ stats_before_local = read_tls_stats()
+ stats_before_remote = read_tls_stats_remote(cfg)
+
+ # Run server in background on remote
+ with bkg(server_cmd, host=cfg.remote, exit_wait=True):
+ # Wait for server to be ready
+ wait_port_listen(port, host=cfg.remote)
+ time.sleep(0.5) # Extra time for server setup
+
+ # Run client
+ ksft_pr("Running client...")
+ result = cmd(client_cmd, fail=False)
+
+ # Give server time to finish
+ time.sleep(1)
+
+ # Read stats after test
+ stats_after_local = read_tls_stats()
+ stats_after_remote = read_tls_stats_remote(cfg)
+
+ # Verify client side (local)
+ ksft_pr("\n=== Client Side Verification ===")
+ client_ok = verify_tls_counters(stats_before_local, stats_after_local, rekey, False)
+
+ # Verify server side (remote)
+ ksft_pr("\n=== Server Side Verification ===")
+ server_ok = verify_tls_counters(stats_before_remote, stats_after_remote, rekey, True)
+
+ # Check that client exited successfully
+ ksft_true(result.ret == 0, "Client completed successfully")
+ ksft_true(client_ok, "Client TLS counters verified")
+ ksft_true(server_ok, "Server TLS counters verified")
+
+
+def read_tls_stats_remote(cfg):
+ """Read TLS statistics from remote endpoint."""
+ stats = {}
+ output = cmd("cat /proc/net/tls_stat", host=cfg.remote)
+ for line in output.stdout.strip().split('\n'):
+ parts = line.split()
+ if len(parts) == 2:
+ stats[parts[0]] = int(parts[1])
+ return stats
+
+
+def test_tls_offload_basic(cfg):
+ """Test basic TLS 1.3 hardware offload with AES-GCM-128 (no rekey)."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="128", tls_version="1.3", rekey=0)
+
+
+def test_tls_offload_aes256(cfg):
+ """Test TLS 1.3 hardware offload with AES-GCM-256 (no rekey)."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="256", tls_version="1.3", rekey=0)
+
+
+def test_tls_offload_tls12(cfg):
+ """Test TLS 1.2 hardware offload with AES-GCM-128 (no rekey)."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="128", tls_version="1.2", rekey=0)
+
+
+def test_tls_offload_tls12_aes256(cfg):
+ """Test TLS 1.2 hardware offload with AES-GCM-256 (no rekey)."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="256", tls_version="1.2", rekey=0)
+
+
+def test_tls_offload_rekey(cfg):
+ """Test TLS 1.3 hardware offload with rekey."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="128", tls_version="1.3", rekey=1)
+
+
+def test_tls_offload_rekey_multiple(cfg):
+ """Test TLS 1.3 hardware offload with multiple rekeys."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="128", tls_version="1.3", rekey=3)
+
+
+def test_tls_offload_small_records(cfg):
+ """Test TLS 1.3 with small record size (512 bytes)."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="128", tls_version="1.3", rekey=0, buffer_size=512)
+
+
+def test_tls_offload_large_records(cfg):
+ """Test TLS 1.3 with large record size (32KB)."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="128", tls_version="1.3", rekey=0, buffer_size=32768)
+
+
+def test_tls_offload_random_sizes(cfg):
+ """Test TLS 1.3 with random record sizes (1-8192 bytes)."""
+ cfg.require_ipver("4")
+ check_tls_support(cfg)
+ run_tls_test(cfg, cipher="128", tls_version="1.3", rekey=0, random_max=8192)
+
+
+def main() -> None:
+ with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
+ # Deploy the C binary to both local and remote
+ # The binary is built in the same directory as this test
+ cfg.bin_local = cfg.test_dir / "tls_hw_offload"
+
+ # Check if binary exists
+ if not cfg.bin_local.exists():
+ raise KsftSkipEx(
+ f"tls_hw_offload binary not found at {cfg.bin_local}. "
+ "Please build it first: make -C "
+ "tools/testing/selftests/drivers/net/hw tls_hw_offload")
+
+ cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+
+ ksft_run([
+ test_tls_offload_basic,
+ test_tls_offload_aes256,
+ test_tls_offload_tls12,
+ test_tls_offload_tls12_aes256,
+ test_tls_offload_rekey,
+ test_tls_offload_rekey_multiple,
+ test_tls_offload_small_records,
+ test_tls_offload_large_records,
+ test_tls_offload_random_sizes,
+ ], args=(cfg, ))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
+
--
2.25.1
^ permalink raw reply related [flat|nested] 8+ messages in thread