Use struct_group() to group the three fields in tcp_out_options that are
read unconditionally by tcp_options_write() and bpf_skops_write_hdr_opt()
(mss, bpf_opt_len, num_sack_blocks), then replace the full-struct memset
with a targeted memset of only that group.
struct tcp_out_options is 40 bytes without MPTCP and 96 bytes with
CONFIG_MPTCP=y (typical distro config). Every remaining field is either
assigned before first use by tcp_established_options()/tcp_syn_options(),
or gated behind its OPTION_* flag in tcp_options_write(). This memset
runs on every transmitted TCP packet, so shrinking it from 96 (or 40)
bytes to 4 bytes reduces per-packet overhead on the hot path.
Assembly comparison (x86-64, GCC 13, CONFIG_MPTCP=y):
Before: rep stos zeroing 96 bytes (5 instructions, 12 8-byte stores)
After: movl $0x0 zeroing 4 bytes (1 instruction, 1 store)
Also add opts->options = 0 at the top of tcp_syn_options(), which
already used |= without a prior clear. tcp_established_options() already
clears opts->options at its top.
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Signed-off-by: Keita Morisaki <kmta1236@gmail.com>
---
net/ipv4/tcp_output.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index 326b58ff1118d..63ee037f46e50 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -429,14 +429,19 @@ static void smc_options_write(__be32 *ptr, u16 *options)
}
struct tcp_out_options {
+ /* Following group is cleared in __tcp_transmit_skb() */
+ struct_group(cleared,
+ u16 mss; /* 0 to disable */
+ u8 bpf_opt_len; /* length of BPF hdr option */
+ u8 num_sack_blocks; /* number of SACK blocks to include */
+ );
+
+ /* Caution: following fields are not cleared in __tcp_transmit_skb() */
u16 options; /* bit field of OPTION_* */
- u16 mss; /* 0 to disable */
u8 ws; /* window scale, 0 to disable */
- u8 num_sack_blocks; /* number of SACK blocks to include */
u8 num_accecn_fields:7, /* number of AccECN fields needed */
use_synack_ecn_bytes:1; /* Use synack_ecn_bytes or not */
u8 hash_size; /* bytes in hash_location */
- u8 bpf_opt_len; /* length of BPF hdr option */
__u8 *hash_location; /* temporary pointer, overloaded */
__u32 tsval, tsecr; /* need to include OPTION_TS */
struct tcp_fastopen_cookie *fastopen_cookie; /* Fast open cookie */
@@ -965,6 +970,8 @@ static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
struct tcp_fastopen_request *fastopen = tp->fastopen_req;
bool timestamps;
+ opts->options = 0;
+
/* Better than switch (key.type) as it has static branches */
if (tcp_key_is_md5(key)) {
timestamps = false;
@@ -1549,7 +1556,7 @@ static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
inet = inet_sk(sk);
tcb = TCP_SKB_CB(skb);
- memset(&opts, 0, sizeof(opts));
+ memset(&opts.cleared, 0, sizeof(opts.cleared));
tcp_get_current_key(sk, &key);
if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
base-commit: af4e9ef3d78420feb8fe58cd9a1ab80c501b3c08
--
2.34.1
On Wed, 4 Mar 2026 20:15:17 +0900 Keita Morisaki wrote: > Use struct_group() to group the three fields in tcp_out_options that are > read unconditionally by tcp_options_write() and bpf_skops_write_hdr_opt() > (mss, bpf_opt_len, num_sack_blocks), then replace the full-struct memset > with a targeted memset of only that group. > > struct tcp_out_options is 40 bytes without MPTCP and 96 bytes with > CONFIG_MPTCP=y (typical distro config). Every remaining field is either > assigned before first use by tcp_established_options()/tcp_syn_options(), > or gated behind its OPTION_* flag in tcp_options_write(). This memset > runs on every transmitted TCP packet, so shrinking it from 96 (or 40) > bytes to 4 bytes reduces per-packet overhead on the hot path. > > Assembly comparison (x86-64, GCC 13, CONFIG_MPTCP=y): > > Before: rep stos zeroing 96 bytes (5 instructions, 12 8-byte stores) > After: movl $0x0 zeroing 4 bytes (1 instruction, 1 store) > > Also add opts->options = 0 at the top of tcp_syn_options(), which > already used |= without a prior clear. tcp_established_options() already > clears opts->options at its top. > > Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com> > Signed-off-by: Keita Morisaki <kmta1236@gmail.com> Applied (cfcceb7a39f), thanks! -- pw-bot: accept
On Wed, Mar 4, 2026 at 3:15 AM Keita Morisaki <kmta1236@gmail.com> wrote: > > Use struct_group() to group the three fields in tcp_out_options that are > read unconditionally by tcp_options_write() and bpf_skops_write_hdr_opt() > (mss, bpf_opt_len, num_sack_blocks), then replace the full-struct memset > with a targeted memset of only that group. > > struct tcp_out_options is 40 bytes without MPTCP and 96 bytes with > CONFIG_MPTCP=y (typical distro config). Every remaining field is either > assigned before first use by tcp_established_options()/tcp_syn_options(), > or gated behind its OPTION_* flag in tcp_options_write(). This memset > runs on every transmitted TCP packet, so shrinking it from 96 (or 40) > bytes to 4 bytes reduces per-packet overhead on the hot path. > > Assembly comparison (x86-64, GCC 13, CONFIG_MPTCP=y): > > Before: rep stos zeroing 96 bytes (5 instructions, 12 8-byte stores) > After: movl $0x0 zeroing 4 bytes (1 instruction, 1 store) > > Also add opts->options = 0 at the top of tcp_syn_options(), which > already used |= without a prior clear. tcp_established_options() already > clears opts->options at its top. > > Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com> > Signed-off-by: Keita Morisaki <kmta1236@gmail.com> Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Hi Keita, On 04/03/2026 12:15, Keita Morisaki wrote: > Use struct_group() to group the three fields in tcp_out_options that are > read unconditionally by tcp_options_write() and bpf_skops_write_hdr_opt() > (mss, bpf_opt_len, num_sack_blocks), then replace the full-struct memset > with a targeted memset of only that group. > > struct tcp_out_options is 40 bytes without MPTCP and 96 bytes with > CONFIG_MPTCP=y (typical distro config). Every remaining field is either > assigned before first use by tcp_established_options()/tcp_syn_options(), > or gated behind its OPTION_* flag in tcp_options_write(). This memset > runs on every transmitted TCP packet, so shrinking it from 96 (or 40) > bytes to 4 bytes reduces per-packet overhead on the hot path. > > Assembly comparison (x86-64, GCC 13, CONFIG_MPTCP=y): > > Before: rep stos zeroing 96 bytes (5 instructions, 12 8-byte stores) > After: movl $0x0 zeroing 4 bytes (1 instruction, 1 store) > > Also add opts->options = 0 at the top of tcp_syn_options(), which > already used |= without a prior clear. tcp_established_options() already > clears opts->options at its top. Thank you for this patch! It looks good to me: Acked-by: Matthieu Baerts (NGI0) <matttbe@kernel.org> Cheers, Matt -- Sponsored by the NGI0 Core fund.
On Wed, Mar 4, 2026 at 7:09 PM Matthieu Baerts <matttbe@kernel.org> wrote: > > Hi Keita, > > On 04/03/2026 12:15, Keita Morisaki wrote: > > Use struct_group() to group the three fields in tcp_out_options that are > > read unconditionally by tcp_options_write() and bpf_skops_write_hdr_opt() > > (mss, bpf_opt_len, num_sack_blocks), then replace the full-struct memset > > with a targeted memset of only that group. > > > > struct tcp_out_options is 40 bytes without MPTCP and 96 bytes with > > CONFIG_MPTCP=y (typical distro config). Every remaining field is either > > assigned before first use by tcp_established_options()/tcp_syn_options(), > > or gated behind its OPTION_* flag in tcp_options_write(). This memset > > runs on every transmitted TCP packet, so shrinking it from 96 (or 40) > > bytes to 4 bytes reduces per-packet overhead on the hot path. > > > > Assembly comparison (x86-64, GCC 13, CONFIG_MPTCP=y): > > > > Before: rep stos zeroing 96 bytes (5 instructions, 12 8-byte stores) > > After: movl $0x0 zeroing 4 bytes (1 instruction, 1 store) > > > > Also add opts->options = 0 at the top of tcp_syn_options(), which > > already used |= without a prior clear. tcp_established_options() already > > clears opts->options at its top. > > Thank you for this patch! It looks good to me: > > Acked-by: Matthieu Baerts (NGI0) <matttbe@kernel.org> Apparently my Reviewed-by: tag was lost. Reviewed-by: Eric Dumazet <edumazet@google.com>
Hi Keita,
Thank you for your modifications, that's great!
Our CI did some validations and here is its report:
- KVM Validation: normal (except selftest_mptcp_join): Success! ✅
- KVM Validation: normal (only selftest_mptcp_join): Success! ✅
- KVM Validation: debug (except selftest_mptcp_join): Success! ✅
- KVM Validation: debug (only selftest_mptcp_join): Success! ✅
- KVM Validation: btf-normal (only bpftest_all): Success! ✅
- KVM Validation: btf-debug (only bpftest_all): Success! ✅
- Task: https://github.com/multipath-tcp/mptcp_net-next/actions/runs/22667429692
Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/9d4f29f31cd6
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1061210
If there are some issues, you can reproduce them using the same environment as
the one used by the CI thanks to a docker image, e.g.:
$ cd [kernel source code]
$ docker run -v "${PWD}:${PWD}:rw" -w "${PWD}" --privileged --rm -it \
--pull always mptcp/mptcp-upstream-virtme-docker:latest \
auto-normal
For more details:
https://github.com/multipath-tcp/mptcp-upstream-virtme-docker
Please note that despite all the efforts that have been already done to have a
stable tests suite when executed on a public CI like here, it is possible some
reported issues are not due to your modifications. Still, do not hesitate to
help us improve that ;-)
Cheers,
MPTCP GH Action bot
Bot operated by Matthieu Baerts (NGI0 Core)
© 2016 - 2026 Red Hat, Inc.