Netdev List
 help / color / mirror / Atom feed
From: Rishikesh Jethwani <rjethwani@purestorage.com>
To: Jakub Kicinski <kuba@kernel.org>,
	Sabrina Dubroca <sd@queasysnail.net>,
	John Fastabend <john.fastabend@gmail.com>
Cc: "David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
	Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	netdev@vger.kernel.org, linux-doc@vger.kernel.org,
	linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
	Rishikesh Jethwani <rjethwani@purestorage.com>
Subject: [PATCH net] tls: refuse RX records after record-number exhaustion
Date: Mon, 12 Oct 2026 18:27:01 -0600	[thread overview]
Message-ID: <20261013002701.1957686-1-rjethwani@purestorage.com> (raw)

A receive socket records sequence-number exhaustion only in sk_err.
After processing record UINT64_MAX, it advances the counter to zero and
reports EBADMSG, but SO_ERROR or sock_error() can clear that error. A
parser-ready record also bypasses the socket-error check in the record
wait. The receiver can then accept a record authenticated for sequence
zero under the same exhausted key.

Getting there needs an initial record number installed close to the
limit, since 2^64 records cannot be exchanged in practice, and a peer
that sends the wrapped record, which the Linux TX path refuses to do.

Mark the RX key exhausted when its record number wraps, and refuse to
wait for or decrypt another record under it, or to replace the key.
Plaintext that is already decrypted can still be read. RFC 8446 lets a
sender retire a key with a KeyUpdate as its last record, so in that
case leave sk_err alone, report the pending key update as usual and
let the new TLS_RX key clear the mark.

Add fixed AES-GCM wire vectors and TLS 1.2/1.3 selftests for queued
wrapped records, SO_ERROR clearing, splice, peek, partial reads, TLS
1.3 key replacement and a KeyUpdate in the last record. Every case
first verifies the penultimate record succeeds. The vectors avoid a
userspace crypto dependency and the TX path's refusal to generate the
maximum-number record.

Fixes: c46234ebb4d1 ("tls: RX path for ktls")
Assisted-by: LLM
Signed-off-by: Rishikesh Jethwani <rjethwani@purestorage.com>
---
 Documentation/networking/tls.rst  |  17 ++
 include/net/tls.h                 |   2 +
 net/tls/tls.h                     |  19 ++-
 net/tls/tls_main.c                |   9 ++
 net/tls/tls_sw.c                  |  19 ++-
 tools/testing/selftests/net/tls.c | 258 +++++++++++++++++++++++++++++-
 6 files changed, 315 insertions(+), 9 deletions(-)

diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
index 980c442d7161..4b3c7a5a0780 100644
--- a/Documentation/networking/tls.rst
+++ b/Documentation/networking/tls.rst
@@ -202,6 +202,23 @@ received without a cmsg buffer set.
 
 recv will never return data from mixed types of TLS records.
 
+Receive record-number exhaustion
+--------------------------------
+
+The receive record number must not wrap under the same key. Once the
+record with the last 64-bit record number has been received, no further
+record is decrypted under that key: a receive that needs another record
+fails with ``EBADMSG``, even after ``SO_ERROR`` has been read. Plaintext
+that was already decrypted, such as the rest of a partially read or
+peeked final record, can still be read. This applies to both software
+and hardware receive offload.
+
+Unless the final record is a TLS 1.3 KeyUpdate, the condition is
+terminal: the socket error is also set to ``EBADMSG``, and a ``TLS_RX``
+key replacement fails with ``EBADMSG``. A KeyUpdate in the final record
+retires the key the usual way: receives fail with ``EKEYEXPIRED`` until a
+new ``TLS_RX`` key is installed, after which receiving continues.
+
 TLS 1.3 Key Updates
 -------------------
 
diff --git a/include/net/tls.h b/include/net/tls.h
index e57bef58851e..73f2d0f24e54 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -139,6 +139,8 @@ struct tls_sw_context_rx {
 	u8 zc_capable:1;
 	u8 reader_contended:1;
 	bool key_update_pending;
+	/* record numbers of this key are used up; cleared by a KeyUpdate */
+	bool rec_seq_exhausted;
 
 	struct tls_strparser strp;
 
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 60a37bdaaa25..3b2af529d977 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -307,17 +307,26 @@ static inline void tls_bigint_subtract(unsigned char *seq, int  n)
 	*p = cpu_to_be64(rcd_sn - n);
 }
 
-static inline void
-tls_advance_record_sn(struct sock *sk, struct tls_prot_info *prot,
-		      struct cipher_context *ctx)
+/* Returns true if the record number wrapped */
+static inline bool
+__tls_advance_record_sn(struct tls_prot_info *prot, struct cipher_context *ctx)
 {
-	if (tls_bigint_increment(ctx->rec_seq, prot->rec_seq_size))
-		tls_err_abort(sk, -EBADMSG);
+	bool overflow = tls_bigint_increment(ctx->rec_seq, prot->rec_seq_size);
 
 	if (prot->version != TLS_1_3_VERSION &&
 	    prot->cipher_type != TLS_CIPHER_CHACHA20_POLY1305)
 		tls_bigint_increment(ctx->iv + prot->salt_size,
 				     prot->iv_size);
+
+	return overflow;
+}
+
+static inline void
+tls_advance_record_sn(struct sock *sk, struct tls_prot_info *prot,
+		      struct cipher_context *ctx)
+{
+	if (__tls_advance_record_sn(prot, ctx))
+		tls_err_abort(sk, -EBADMSG);
 }
 
 static inline void
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index fbb274287aa5..63bef0153182 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -730,6 +730,15 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
 			conf = TLS_SW;
 		}
 	} else {
+		struct tls_sw_context_rx *rx_ctx = tls_sw_ctx_rx(ctx);
+
+		/* Only a KeyUpdate may follow the last record number */
+		if (update && rx_ctx->rec_seq_exhausted &&
+		    !rx_ctx->key_update_pending) {
+			rc = -EBADMSG;
+			goto err_crypto_info;
+		}
+
 		rc = tls_set_device_offload_rx(sk, ctx);
 		conf = TLS_HW;
 		if (!rc) {
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index d1ad31986cf2..cd1590e1f20f 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -1104,6 +1104,10 @@ tls_rx_rec_wait(struct sock *sk, bool nonblock, bool released, bool has_copied)
 	if (unlikely(ctx->key_update_pending))
 		return -EKEYEXPIRED;
 
+	/* no record can follow the last one under this key */
+	if (unlikely(ctx->rec_seq_exhausted))
+		return -EBADMSG;
+
 	timeo = sock_rcvtimeo(sk, nonblock);
 
 	while (!tls_strp_msg_ready(ctx)) {
@@ -1566,6 +1570,7 @@ static int tls_rx_one_record(struct sock *sk, struct msghdr *msg,
 {
 	struct tls_context *tls_ctx = tls_get_ctx(sk);
 	struct tls_prot_info *prot = &tls_ctx->prot_info;
+	struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx);
 	struct strp_msg *rxm;
 	int err;
 
@@ -1580,9 +1585,18 @@ static int tls_rx_one_record(struct sock *sk, struct msghdr *msg,
 	rxm = strp_msg(darg->skb);
 	rxm->offset += prot->prepend_size;
 	rxm->full_len -= prot->overhead_size;
-	tls_advance_record_sn(sk, prot, &tls_ctx->rx);
+	err = tls_check_pending_rekey(sk, tls_ctx, darg->skb);
+	if (__tls_advance_record_sn(prot, &tls_ctx->rx)) {
+		/* sk_err alone can't stop the next record, SO_ERROR clears
+		 * it. A KeyUpdate in the last record properly retires the
+		 * key, so don't treat that one as an error.
+		 */
+		ctx->rec_seq_exhausted = true;
+		if (!ctx->key_update_pending)
+			tls_err_abort(sk, -EBADMSG);
+	}
 
-	return tls_check_pending_rekey(sk, tls_ctx, darg->skb);
+	return err;
 }
 
 int decrypt_skb(struct sock *sk, struct scatterlist *sgout)
@@ -2518,6 +2532,7 @@ static void tls_finish_key_update(struct sock *sk, struct tls_context *tls_ctx)
 	struct tls_sw_context_rx *ctx = tls_ctx->priv_ctx_rx;
 
 	WRITE_ONCE(ctx->key_update_pending, false);
+	ctx->rec_seq_exhausted = false;
 	/* wake-up pre-existing poll() */
 	ctx->saved_data_ready(sk);
 }
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index 9d3cd4fff062..f69c3a736b94 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -26,6 +26,7 @@
 #define TLS_PAYLOAD_MAX_LEN 16384
 #define TLS_HDR_LEN 5
 #define SOL_TLS 282
+#define TLS_RECORD_TYPE_HANDSHAKE      0x16
 
 static int fips_enabled;
 
@@ -300,6 +301,261 @@ TEST_F(tls_basic, recseq_wrap)
 	EXPECT_EQ(errno, EBADMSG);
 }
 
+/* Fixed AES-GCM records avoid a dependency on a userspace crypto library.
+ * Each authenticates "test", with zero key and static IV, at sequence
+ * numbers UINT64_MAX - 1, UINT64_MAX and zero. TLS 1.2 uses the record
+ * number as its explicit IV; TLS 1.3 appends inner content type 23.
+ * The last record must never be accepted after counter exhaustion.
+ * tls_rx_recseq_ku_record is a TLS 1.3 KeyUpdate at UINT64_MAX.
+ */
+static const char tls_rx_recseq_ku_record[] =
+	"\x17\x03\x03\x00\x16\x49\xd1\xc7\x79\x45\x74\xd7"
+	"\x12\xe8\xbf\xe4\xaa\x70\x17\x58\x60\x66\xa0\xf9"
+	"\x5b\xac\x6a";
+
+FIXTURE(tls_rx_recseq)
+{
+	int fd, cfd;
+	bool notls;
+};
+
+FIXTURE_VARIANT(tls_rx_recseq)
+{
+	uint16_t tls_version;
+	size_t record_len;
+	const char *records[3];
+};
+
+FIXTURE_VARIANT_ADD(tls_rx_recseq, 12_aes_gcm)
+{
+	.tls_version = TLS_1_2_VERSION,
+	.record_len = 33,
+	.records = {
+		"\x17\x03\x03\x00\x1c\xff\xff\xff\xff\xff\xff\xff"
+		"\xfe\x62\x3c\xf3\xee\x4d\x18\x45\xf7\x62\x62\x2c"
+		"\x01\x9a\xfc\x47\x3d\x82\xb0\x9f\x7f",
+		"\x17\x03\x03\x00\x1c\xff\xff\xff\xff\xff\xff\xff"
+		"\xff\x25\xb4\xb4\x0c\xf7\x4c\xef\x86\xa2\xe1\x2d"
+		"\xa6\x31\xb5\x64\xf6\x21\x81\x7f\xad",
+		"\x17\x03\x03\x00\x1c\x00\x00\x00\x00\x00\x00\x00"
+		"\x00\x77\xed\xa9\xba\xbb\x29\xb7\x1a\x44\x35\x46"
+		"\x0f\x55\x94\x07\xdd\xc3\xe4\x91\x80",
+	},
+};
+
+FIXTURE_VARIANT_ADD(tls_rx_recseq, 13_aes_gcm)
+{
+	.tls_version = TLS_1_3_VERSION,
+	.record_len = 26,
+	.records = {
+		"\x17\x03\x03\x00\x15\x62\x3c\xf3\xee\xd7\x87\xd1"
+		"\x21\x46\x96\xc2\xfc\x59\x1c\x4c\x15\xec\x57\x6d"
+		"\xc8\x0a",
+		"\x17\x03\x03\x00\x15\x25\xb4\xb4\x0c\x52\x2e\x4d"
+		"\xcd\xf4\xec\xc2\x7e\xb8\x85\xed\xb7\x79\xf8\x39"
+		"\x68\xd6",
+		"\x17\x03\x03\x00\x15\x77\xed\xa9\xba\x77\x59\x33"
+		"\x88\x89\x13\x8d\x3c\xb4\x2e\xe0\x64\x72\xa5\x17"
+		"\x71\x45",
+	},
+};
+
+FIXTURE_SETUP(tls_rx_recseq)
+{
+	struct timeval timeout = { .tv_sec = 2 };
+	struct tls_crypto_info_keys key;
+	socklen_t len;
+	char buf[4];
+	int err;
+
+	ulp_sock_pair(_metadata, &self->fd, &self->cfd, &self->notls);
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	ASSERT_EQ(setsockopt(self->cfd, SOL_SOCKET, SO_RCVTIMEO,
+			     &timeout, sizeof(timeout)), 0);
+	tls_crypto_info_init(variant->tls_version, TLS_CIPHER_AES_GCM_128,
+			     &key, 0);
+	memset(key.aes128.rec_seq, 0xff, sizeof(key.aes128.rec_seq));
+	key.aes128.rec_seq[sizeof(key.aes128.rec_seq) - 1]--;
+	ASSERT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX, &key, key.len), 0);
+
+	/* The penultimate record must still advance normally. */
+	ASSERT_EQ(send(self->fd, variant->records[0], variant->record_len, 0),
+		  variant->record_len);
+	ASSERT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), sizeof(buf));
+	ASSERT_EQ(memcmp(buf, "test", sizeof(buf)), 0);
+	len = sizeof(err);
+	ASSERT_EQ(getsockopt(self->cfd, SOL_SOCKET, SO_ERROR, &err, &len), 0);
+	ASSERT_EQ(err, 0);
+};
+
+FIXTURE_TEARDOWN(tls_rx_recseq)
+{
+	close(self->fd);
+	close(self->cfd);
+};
+
+static void tls_clear_rx_error(struct __test_metadata *_metadata, int fd)
+{
+	socklen_t len;
+	int err;
+
+	len = sizeof(err);
+	ASSERT_EQ(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len), 0);
+	ASSERT_EQ(err, EBADMSG);
+	len = sizeof(err);
+	ASSERT_EQ(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len), 0);
+	ASSERT_EQ(err, 0);
+}
+
+TEST_F(tls_rx_recseq, queued)
+{
+	char records[80], buf[8];
+
+	memcpy(records, variant->records[1], variant->record_len);
+	memcpy(records + variant->record_len, variant->records[2],
+	       variant->record_len);
+	ASSERT_EQ(send(self->fd, records, 2 * variant->record_len, 0),
+		  2 * variant->record_len);
+
+	/* Both records are queued, but only the final valid one can pass. */
+	ASSERT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), 4);
+	EXPECT_EQ(memcmp(buf, "test", 4), 0);
+	tls_clear_rx_error(_metadata, self->cfd);
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), -1);
+	EXPECT_EQ(errno, EBADMSG);
+}
+
+TEST_F(tls_rx_recseq, so_error)
+{
+	char buf[4];
+
+	ASSERT_EQ(send(self->fd, variant->records[1], variant->record_len, 0),
+		  variant->record_len);
+	ASSERT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), sizeof(buf));
+	tls_clear_rx_error(_metadata, self->cfd);
+
+	ASSERT_EQ(send(self->fd, variant->records[2], variant->record_len, 0),
+		  variant->record_len);
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+	EXPECT_EQ(errno, EBADMSG);
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), -1);
+	EXPECT_EQ(errno, EBADMSG);
+}
+
+TEST_F(tls_rx_recseq, splice)
+{
+	char buf[4];
+	int p[2];
+
+	ASSERT_EQ(send(self->fd, variant->records[1], variant->record_len, 0),
+		  variant->record_len);
+	ASSERT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), sizeof(buf));
+	tls_clear_rx_error(_metadata, self->cfd);
+
+	ASSERT_EQ(send(self->fd, variant->records[2], variant->record_len, 0),
+		  variant->record_len);
+	ASSERT_EQ(pipe(p), 0);
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, sizeof(buf), 0), -1);
+	EXPECT_EQ(errno, EBADMSG);
+	close(p[0]);
+	close(p[1]);
+}
+
+TEST_F(tls_rx_recseq, peek)
+{
+	char buf[4];
+
+	ASSERT_EQ(send(self->fd, variant->records[1], variant->record_len, 0),
+		  variant->record_len);
+	ASSERT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_PEEK), sizeof(buf));
+	tls_clear_rx_error(_metadata, self->cfd);
+
+	/* Plaintext decrypted before exhaustion can still be read. */
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), sizeof(buf));
+	EXPECT_EQ(memcmp(buf, "test", sizeof(buf)), 0);
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), -1);
+	EXPECT_EQ(errno, EBADMSG);
+}
+
+TEST_F(tls_rx_recseq, partial_read)
+{
+	char buf[4];
+
+	ASSERT_EQ(send(self->fd, variant->records[1], variant->record_len, 0),
+		  variant->record_len);
+	ASSERT_EQ(recv(self->cfd, buf, 1, 0), 1);
+	tls_clear_rx_error(_metadata, self->cfd);
+
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), 3);
+	EXPECT_EQ(memcmp(buf, "est", 3), 0);
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), -1);
+	EXPECT_EQ(errno, EBADMSG);
+}
+
+/* Only a KeyUpdate may introduce a key after the last record number. */
+TEST_F(tls_rx_recseq, rekey)
+{
+	struct tls_crypto_info_keys key;
+	struct tls12_crypto_info_aes_gcm_128 before, after;
+	socklen_t len;
+	char buf[4];
+
+	if (variant->tls_version != TLS_1_3_VERSION)
+		SKIP(return, "rekey requires TLS 1.3");
+
+	ASSERT_EQ(send(self->fd, variant->records[1], variant->record_len, 0),
+		  variant->record_len);
+	ASSERT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), sizeof(buf));
+	tls_clear_rx_error(_metadata, self->cfd);
+
+	len = sizeof(before);
+	ASSERT_EQ(getsockopt(self->cfd, SOL_TLS, TLS_RX, &before, &len), 0);
+	tls_crypto_info_init(variant->tls_version, TLS_CIPHER_AES_GCM_128,
+			     &key, 1);
+	EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX, &key, key.len), -1);
+	EXPECT_EQ(errno, EBADMSG);
+	len = sizeof(after);
+	ASSERT_EQ(getsockopt(self->cfd, SOL_TLS, TLS_RX, &after, &len), 0);
+	EXPECT_EQ(memcmp(&before, &after, sizeof(before)), 0);
+}
+
+/* A KeyUpdate in the last record retires the key without an error. */
+TEST_F(tls_rx_recseq, keyupdate)
+{
+	size_t len = sizeof(tls_rx_recseq_ku_record) - 1;
+	struct tls_crypto_info_keys key;
+	socklen_t optlen;
+	char buf[16];
+	int err;
+
+	if (variant->tls_version != TLS_1_3_VERSION)
+		SKIP(return, "KeyUpdate requires TLS 1.3");
+
+	ASSERT_EQ(send(self->fd, tls_rx_recseq_ku_record, len, 0), len);
+	EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, TLS_RECORD_TYPE_HANDSHAKE,
+				buf, sizeof(buf), 0), 5);
+	EXPECT_EQ(memcmp(buf, "\x18\x00\x00\x01\x00", 5), 0);
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_DONTWAIT), -1);
+	EXPECT_EQ(errno, EKEYEXPIRED);
+	optlen = sizeof(err);
+	ASSERT_EQ(getsockopt(self->cfd, SOL_SOCKET, SO_ERROR, &err, &optlen), 0);
+	EXPECT_EQ(err, 0);
+
+	/* The new key resets the record number, so receiving continues. */
+	tls_crypto_info_init(variant->tls_version, TLS_CIPHER_AES_GCM_128,
+			     &key, 1);
+	ASSERT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX, &key, key.len), 0);
+	ASSERT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX, &key, key.len), 0);
+	ASSERT_EQ(send(self->fd, "test", 4, 0), 4);
+	ASSERT_EQ(recv(self->cfd, buf, 4, MSG_WAITALL), 4);
+	EXPECT_EQ(memcmp(buf, "test", 4), 0);
+	optlen = sizeof(err);
+	ASSERT_EQ(getsockopt(self->cfd, SOL_SOCKET, SO_ERROR, &err, &optlen), 0);
+	EXPECT_EQ(err, 0);
+}
+
 FIXTURE(tls)
 {
 	int fd, cfd;
@@ -1839,8 +2095,6 @@ TEST_F(tls, recv_efault)
 		EXPECT_EQ(memcmp(rec2, recv_mem + 9, ret - 9), 0);
 }
 
-#define TLS_RECORD_TYPE_HANDSHAKE      0x16
-
 TEST_F(tls_basic, recvmsg_nopad_retry_iov)
 {
 	char payload[32];

base-commit: 8e2fbe0e75d38690d551456658194c632933cb57
-- 
2.50.1


             reply	other threads:[~2026-10-13  0:27 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13  0:27 Rishikesh Jethwani [this message]
2026-10-13  0:29 ` [PATCH net] tls: refuse RX records after record-number exhaustion netdev-bot+sinfo
2026-10-13  9:38 ` Sabrina Dubroca
2026-10-13 17:55   ` Rishikesh Jethwani

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261013002701.1957686-1-rjethwani@purestorage.com \
    --to=rjethwani@purestorage.com \
    --cc=corbet@lwn.net \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=john.fastabend@gmail.com \
    --cc=kuba@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sd@queasysnail.net \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox