Netdev List
 help / color / mirror / Atom feed
* RE: [PATCH v4 net-next 06/14] net: enetc: add support for the "Update" operation to buffer pool table
From: Wei Fang @ 2026-04-08  7:25 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Claudiu Manoil, Vladimir Oltean, Clark Wang,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	pabeni@redhat.com, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org, f.fainelli@gmail.com, Frank Li,
	chleroy@kernel.org, horms@kernel.org, linux@armlinux.org.uk,
	andrew@lunn.ch, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev
In-Reply-To: <20260403011731.1795517-1-kuba@kernel.org>

> > @@ -702,5 +708,38 @@ int ntmp_vft_add_entry(struct ntmp_user *user,
> u16 vid,
> >  }
> >  EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
> >
> > +int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
> > +			  const struct bpt_cfge_data *cfge)
> > +{
> > +	struct ntmp_dma_buf data = {
> > +		.dev = user->dev,
> > +		.size = sizeof(struct bpt_req_update),
> > +	};
> > +	struct bpt_req_update *req;
> > +	union netc_cbd cbd;
> > +	int err;
> > +
> > +	err = ntmp_alloc_data_mem(&data, (void **)&req);
> > +	if (err)
> > +		return err;
> > +
> > +	ntmp_fill_crd_eid(&req->rbe, user->tbl.bpt_ver, 0,
> > +			  NTMP_GEN_UA_CFGEU | BPT_UA_BPSEU, entry_id);
> 
> Does this update action mask match the size of the request buffer?
> 
> The mask includes both NTMP_GEN_UA_CFGEU and BPT_UA_BPSEU, which
> instructs
> the hardware to update both the configuration entry and the status entry.
> However, struct bpt_req_update only contains the configuration data,
> omitting the status data.
> 
> If the hardware attempts to read the missing BPSE data, could it reject
> the command due to an undersized request length, or read beyond the
> intended payload into zero-initialized DMA padding? This might overwrite
> critical buffer pool tracking variables like amount_used_hwm.
> 
> If only the configuration is being updated, should the BPT_UA_BPSEU flag
> be removed?

The BPSE_DATA is a set of statistics and status records kept by the hardware
(internal buffer), and does not require the user to provide additional data to
the hardware for updates. Therefore, for the 'update' operation, the request
buffer does not need to provide BPSE_DATA data.


^ permalink raw reply

* [PATCH net-next] tools: ynl: tests: fix leading space on Makefile target
From: Hangbin Liu @ 2026-04-08  7:19 UTC (permalink / raw)
  To: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman
  Cc: netdev, linux-kernel, Hangbin Liu

The ../generated/protos.a rule had a spurious leading space before the
target name. In make, target rules must start at column 0; only recipe
lines are indented with a tab. The extra space caused make to misparse
the rule.

Remove the leading space to match the style of the adjacent
../lib/ynl.a rule.

Fixes: e0aa0c61758f ("tools: ynl: move samples to tests")
Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 tools/net/ynl/tests/Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/net/ynl/tests/Makefile b/tools/net/ynl/tests/Makefile
index 2a02958c7039..9215e84cca05 100644
--- a/tools/net/ynl/tests/Makefile
+++ b/tools/net/ynl/tests/Makefile
@@ -50,7 +50,7 @@ all: $(TEST_GEN_PROGS) $(TEST_GEN_FILES)
 ../lib/ynl.a:
 	@$(MAKE) -C ../lib
 
- ../generated/protos.a:
+../generated/protos.a:
 	@$(MAKE) -C ../generated
 
 $(TEST_GEN_PROGS) $(TEST_GEN_FILES): %: %.c ../lib/ynl.a ../generated/protos.a

---
base-commit: e65d8b6f3092398efd7c74e722cb7a516d9a0d6d
change-id: 20260408-ynl_makefile-ee540ab81eea

Best regards,
-- 
Hangbin Liu <liuhangbin@gmail.com>


^ permalink raw reply related

* nmap scan command in forever loop to trigger Snort IDS IPS to generate intrusion alerts
From: Turritopsis Dohrnii Teo En Ming @ 2026-04-08  7:16 UTC (permalink / raw)
  To: Linux Networking; +Cc: ceo@teo-en-ming-corp.com

Subject: nmap scan command in forever loop to trigger Snort IDS IPS to generate intrusion alerts

Good day from Singapore,

Running the following nmap scan command in a forever loop will trigger Snort Intrusion Detection System (IDS) Intrusion Prevention System (IPS) to generate lots of intrusion alerts.

# nohup sudo bash -c 'while true; do nmap -sS -O -p 22 tdtem.duckdns.org; sleep 1; done' > nmap.log 2>&1 &

Regards,

Mr. Turritopsis Dohrnii Teo En Ming
Extremely Democratic People's Republic of Singapore
8 Apr 2026 Wednesday 3.16 pm Singapore Time



^ permalink raw reply

* Re: [PATCH net-next v2 1/2] tcp: rehash onto different ECMP path on retransmit timeout
From: Eric Dumazet @ 2026-04-08  7:12 UTC (permalink / raw)
  To: Neil Spring; +Cc: netdev, davem, kuba
In-Reply-To: <20260408070514.1840227-2-ntspring@meta.com>

On Wed, Apr 8, 2026 at 12:05 AM Neil Spring <ntspring@meta.com> wrote:
>
> Add sk_dst_reset() alongside sk_rethink_txhash() in the RTO, PLB,
> and spurious-retrans paths so that the next transmit triggers a fresh
> route lookup.  Propagate sk_txhash into fl6->mp_hash in
> inet6_csk_route_req() and inet6_csk_route_socket() so
> fib6_select_path() uses the socket's current hash for ECMP selection.
>
> The ir_iif update in tcp_check_req() covers both IPv4 and IPv6
> because it was cleaner than gating on address family; IPv4 is
> otherwise unaltered, and not having autoflowlabel in IPv4 means
> I wouldn't expect a new path on timeout.
>
> It is possible that PLB does not need this (that there are other
> methods of reacting to local congestion); I added the sk_dst_reset
> for consistency.
>
> Signed-off-by: Neil Spring <ntspring@meta.com>

Please make sure to wait ~24 hours between sending a new version.

Documentation/process/maintainer-netdev.rst


> ---
>  net/ipv4/tcp_input.c             |  4 +++-
>  net/ipv4/tcp_minisocks.c         | 13 +++++++++++++
>  net/ipv4/tcp_plb.c               |  1 +
>  net/ipv4/tcp_timer.c             |  1 +
>  net/ipv6/inet6_connection_sock.c | 11 +++++++++++
>  5 files changed, 29 insertions(+), 1 deletion(-)
>
> diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
> index 7171442c3ed7..3d42ab45066c 100644
> --- a/net/ipv4/tcp_input.c
> +++ b/net/ipv4/tcp_input.c
> @@ -5014,8 +5014,10 @@ static void tcp_rcv_spurious_retrans(struct sock *sk,
>             skb->protocol == htons(ETH_P_IPV6) &&
>             (tcp_sk(sk)->inet_conn.icsk_ack.lrcv_flowlabel !=
>              ntohl(ip6_flowlabel(ipv6_hdr(skb)))) &&
> -           sk_rethink_txhash(sk))
> +           sk_rethink_txhash(sk)) {
>                 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDUPLICATEDATAREHASH);
> +               sk_dst_reset(sk);
> +       }
>
>         /* Save last flowlabel after a spurious retrans. */
>         tcp_save_lrcv_flowlabel(sk, skb);
> diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
> index 199f0b579e89..27edf71effc2 100644
> --- a/net/ipv4/tcp_minisocks.c
> +++ b/net/ipv4/tcp_minisocks.c
> @@ -750,6 +750,19 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
>                  * Reset timer after retransmitting SYNACK, similar to
>                  * the idea of fast retransmit in recovery.
>                  */
> +
> +               /* Update ir_iif to match the interface the retransmitted
> +                * SYN arrived on; inet6_csk_route_req() uses this as
> +                * flowi6_oif, constraining ECMP path for the SYN/ACK.
> +                */

Please put this part in a separate patch.

Explain why it is important that the SYN/ACK is sent on the same interface ?

What happens if it is not ?

Why are we keeping tcp_v6_init_req() part? Would it be dead code ?

/* So that link locals have meaning */
if ((!sk_listener->sk_bound_dev_if || l3_slave) &&
    ipv6_addr_type(&ireq->ir_v6_rmt_addr) & IPV6_ADDR_LINKLOCAL)
        ireq->ir_iif = tcp_v6_iif(skb);


> +#if IS_ENABLED(CONFIG_IPV6)
> +               if (sk->sk_family == AF_INET6)
> +                       inet_rsk(req)->ir_iif = tcp_v6_iif(skb);
> +               else
> +#endif
> +                       inet_rsk(req)->ir_iif =
> +                               inet_request_bound_dev_if(sk, skb);
> +
>                 if (!tcp_oow_rate_limited(sock_net(sk), skb,
>                                           LINUX_MIB_TCPACKSKIPPEDSYNRECV,
>                                           &tcp_rsk(req)->last_oow_ack_time)) {
> diff --git a/net/ipv4/tcp_plb.c b/net/ipv4/tcp_plb.c
> index 68ccdb9a5412..d7cc00a58e53 100644
> --- a/net/ipv4/tcp_plb.c
> +++ b/net/ipv4/tcp_plb.c
> @@ -79,6 +79,7 @@ void tcp_plb_check_rehash(struct sock *sk, struct tcp_plb_state *plb)
>                 return;
>
>         sk_rethink_txhash(sk);
> +       sk_dst_reset(sk);
>         plb->consec_cong_rounds = 0;
>         tcp_sk(sk)->plb_rehash++;
>         NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPPLBREHASH);
> diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
> index ea99988795e7..acc22fc532c2 100644
> --- a/net/ipv4/tcp_timer.c
> +++ b/net/ipv4/tcp_timer.c
> @@ -299,6 +299,7 @@ static int tcp_write_timeout(struct sock *sk)
>         if (sk_rethink_txhash(sk)) {
>                 tp->timeout_rehash++;
>                 __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPTIMEOUTREHASH);
> +               sk_dst_reset(sk);
>         }
>
>         return 0;
> diff --git a/net/ipv6/inet6_connection_sock.c b/net/ipv6/inet6_connection_sock.c
> index 37534e116899..3fd7acbe2c49 100644
> --- a/net/ipv6/inet6_connection_sock.c
> +++ b/net/ipv6/inet6_connection_sock.c
> @@ -14,6 +14,7 @@
>  #include <linux/ipv6.h>
>  #include <linux/jhash.h>
>  #include <linux/slab.h>
> +#include <linux/tcp.h>
>
>  #include <net/addrconf.h>
>  #include <net/inet_connection_sock.h>
> @@ -48,6 +49,12 @@ struct dst_entry *inet6_csk_route_req(const struct sock *sk,
>         fl6->flowi6_uid = sk_uid(sk);
>         security_req_classify_flow(req, flowi6_to_flowi_common(fl6));
>
> +       /* Use the request socket's txhash (re-rolled by tcp_rtx_synack())
> +        * for ECMP path selection; >> 1 for 31-bit mp_hash range.
> +        */
> +       if (tcp_rsk(req)->txhash)
> +               fl6->mp_hash = tcp_rsk(req)->txhash >> 1;
> +
>         if (!dst) {
>                 dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_p);
>                 if (IS_ERR(dst))
> @@ -70,6 +77,10 @@ struct dst_entry *inet6_csk_route_socket(struct sock *sk,
>         fl6->saddr = np->saddr;
>         fl6->flowlabel = np->flow_label;
>         IP6_ECN_flow_xmit(sk, fl6->flowlabel);
> +
> +       /* >> 1 for 31-bit mp_hash range matching nhc_upper_bound. */
> +       if (sk->sk_txhash)

Why is the test needed ?
Note that because of the right shift ,  sk_txhash == 1 would become 0 anyway.

> +               fl6->mp_hash = sk->sk_txhash >> 1;
>         fl6->flowi6_oif = sk->sk_bound_dev_if;
>         fl6->flowi6_mark = sk->sk_mark;
>         fl6->fl6_sport = inet->inet_sport;
> --
> 2.52.0
>

^ permalink raw reply

* RE: [PATCH v4 net-next 05/14] net: enetc: add support for the "Add" operation to VLAN filter table
From: Wei Fang @ 2026-04-08  7:12 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Claudiu Manoil, Vladimir Oltean, Clark Wang,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	pabeni@redhat.com, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org, f.fainelli@gmail.com, Frank Li,
	chleroy@kernel.org, horms@kernel.org, linux@armlinux.org.uk,
	andrew@lunn.ch, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev
In-Reply-To: <20260403011729.1795413-1-kuba@kernel.org>

> > diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c
> b/drivers/net/ethernet/freescale/enetc/ntmp.c
> > --- a/drivers/net/ethernet/freescale/enetc/ntmp.c
> > +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
> 
> [ ... ]
> 
> > @@ -652,5 +655,52 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user
> *user, int port,
> > +int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
> > +		       const struct vft_cfge_data *cfge)
> > +{
> > +	struct ntmp_dma_buf data = {
> > +		.dev = user->dev,
> > +		.size = sizeof(struct vft_req_ua),
> > +	};
> > +	struct vft_req_ua *req;
> > +	union netc_cbd cbd;
> > +	u32 len;
> > +	int err;
> > +
> > +	err = ntmp_alloc_data_mem(&data, (void **)&req);
> > +	if (err)
> > +		return err;
> > +
> > +	/* Request data */
> > +	ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0,
> > +		      NTMP_GEN_UA_CFGEU);
> > +	req->ak.exact.vid = cpu_to_le16(vid);
> > +	req->cfge = *cfge;
> > +
> > +	/* Request header */
> > +	len = NTMP_LEN(data.size, NTMP_STATUS_RESP_LEN);
> > +	ntmp_fill_request_hdr(&cbd, data.dma, len, NTMP_VFT_ID,
> > +			      NTMP_CMD_ADD, NTMP_AM_EXACT_KEY);
> > +
> > +	err = netc_xmit_ntmp_cmd(user, &cbd);
> > +	if (err)
> > +		dev_err(user->dev,
> > +			"Failed to add %s entry, vid: %u, err: %pe\n",
> > +			ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err));
> > +
> > +	ntmp_free_data_mem(&data);
> 
> Does this code safely handle hardware timeouts?
> 
> If netc_xmit_ntmp_cmd() times out and returns an error, the pending
> transaction is not explicitly aborted before ntmp_free_data_mem()
> unconditionally frees the DMA buffer.
> 
> Could the hardware eventually process the pending command and perform a
> DMA write of the response to the physical address of the freed buffer?
> 
> This might lead to silent memory corruption if that memory has already
> been reallocated to another subsystem. Would it be safer to explicitly
> abort the pending DMA transaction, reset the device on timeout, or
> intentionally delay freeing the buffer until it is proven safe?

This is indeed a potential issue, I will add a patch to fix it.


^ permalink raw reply

* [PATCH net-next v2 5/5] ethtool: strset: check nla_len overflow
From: Hangbin Liu @ 2026-04-08  7:08 UTC (permalink / raw)
  To: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Andrew Lunn
  Cc: netdev, linux-kernel, Hangbin Liu
In-Reply-To: <20260408-b4-ynl_ethtool-v2-0-7623a5e8f70b@gmail.com>

The netlink attribute length field nla_len is a __u16, which can only
represent values up to 65535 bytes. NICs with a large number of
statistics strings (e.g. mlx5_core with thousands of ETH_SS_STATS
entries) can produce a ETHTOOL_A_STRINGSET_STRINGS nest that exceeds
this limit.

When nla_nest_end() writes the actual nest size back to nla_len, the
value is silently truncated. This results in a corrupted netlink message
being sent to userspace: the parser reads a wrong (truncated) attribute
length and misaligns all subsequent attribute boundaries, causing decode
errors.

Fix this by using the new helper nla_nest_end_safe and error out if
the size exceeds U16_MAX.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 net/ethtool/strset.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
index 9271aba8255e..bb1e829ba099 100644
--- a/net/ethtool/strset.c
+++ b/net/ethtool/strset.c
@@ -443,7 +443,8 @@ static int strset_fill_set(struct sk_buff *skb,
 			if (strset_fill_string(skb, set_info, i) < 0)
 				goto nla_put_failure;
 		}
-		nla_nest_end(skb, strings_attr);
+		if (nla_nest_end_safe(skb, strings_attr) < 0)
+			goto nla_put_failure;
 	}
 
 	nla_nest_end(skb, stringset_attr);

-- 
Git-155)


^ permalink raw reply related

* [PATCH net-next v2 4/5] netlink: add a nla_nest_end_safe() helper
From: Hangbin Liu @ 2026-04-08  7:08 UTC (permalink / raw)
  To: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Andrew Lunn
  Cc: netdev, linux-kernel, Hangbin Liu
In-Reply-To: <20260408-b4-ynl_ethtool-v2-0-7623a5e8f70b@gmail.com>

The nla_len field in struct nlattr is a __u16, which can only hold
values up to 65535. If a nested attribute grows beyond this limit,
nla_nest_end() silently truncates the length, producing a corrupted
netlink message with no indication of the problem.

Since nla_nest_end() is used everywhere and this issue rarely happens,
let's add a new helper to check the length.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 include/net/netlink.h | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/include/net/netlink.h b/include/net/netlink.h
index 1a8356ca4b78..546d10586576 100644
--- a/include/net/netlink.h
+++ b/include/net/netlink.h
@@ -2264,6 +2264,25 @@ static inline int nla_nest_end(struct sk_buff *skb, struct nlattr *start)
 	return skb->len;
 }
 
+/**
+ * nla_nest_end_safe - Validate and finalize nesting of attributes
+ * @skb: socket buffer the attributes are stored in
+ * @start: container attribute
+ *
+ * Corrects the container attribute header to include all appended
+ * attributes.
+ *
+ * Returns: the total data length of the skb, or -EMSGSIZE if the
+ * nested attribute length exceeds U16_MAX.
+ */
+static inline int nla_nest_end_safe(struct sk_buff *skb, struct nlattr *start)
+{
+	if (skb_tail_pointer(skb) - (unsigned char *)start > U16_MAX)
+		return -EMSGSIZE;
+
+	return nla_nest_end(skb, start);
+}
+
 /**
  * nla_nest_cancel - Cancel nesting of attributes
  * @skb: socket buffer the message is stored in

-- 
Git-155)


^ permalink raw reply related

* [PATCH net-next v2 3/5] tools: ynl: ethtool: add --dbg-small-recv option
From: Hangbin Liu @ 2026-04-08  7:08 UTC (permalink / raw)
  To: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Andrew Lunn
  Cc: netdev, linux-kernel, Hangbin Liu
In-Reply-To: <20260408-b4-ynl_ethtool-v2-0-7623a5e8f70b@gmail.com>

Add a --dbg-small-recv debug option to control the recv() buffer size
used by YNL, matching the same option already present in cli.py. This
is useful if user need to get large netlink message.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 tools/net/ynl/tests/ethtool.py | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py
index 63854d21818c..db3b62c652e7 100755
--- a/tools/net/ynl/tests/ethtool.py
+++ b/tools/net/ynl/tests/ethtool.py
@@ -166,12 +166,19 @@ def main():
     parser.add_argument('device', metavar='device', type=str)
     parser.add_argument('args', metavar='args', type=str, nargs='*')
 
+    dbg_group = parser.add_argument_group('Debug options')
+    dbg_group.add_argument('--dbg-small-recv', default=0, const=4000,
+                           action='store', nargs='?', type=int, metavar='INT',
+                           help="Length of buffers used for recv()")
+
     args = parser.parse_args()
 
     spec = os.path.join(spec_dir(), 'ethtool.yaml')
     schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml')
 
-    ynl = YnlFamily(spec, schema)
+    ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv)
+    if args.dbg_small_recv:
+        ynl.set_recv_dbg(True)
 
     if args.set_priv_flags:
         # TODO: parse the bitmask

-- 
Git-155)


^ permalink raw reply related

* [PATCH net-next v2 2/5] tools: ynl: ethtool: use doit instead of dumpit for per-device GET
From: Hangbin Liu @ 2026-04-08  7:08 UTC (permalink / raw)
  To: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Andrew Lunn
  Cc: netdev, linux-kernel, Hangbin Liu
In-Reply-To: <20260408-b4-ynl_ethtool-v2-0-7623a5e8f70b@gmail.com>

Rename the local helper doit() to do_set() and dumpit() to do_get() to
better reflect their purpose.

Convert do_get() to use ynl.do() with an explicit device header instead
of ynl.dump() followed by client-side filtering. This is more efficient
as the kernel only processes and returns data for the requested device,
rather than dumping all devices across the netns.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 tools/net/ynl/tests/ethtool.py | 68 ++++++++++++++++++++----------------------
 1 file changed, 33 insertions(+), 35 deletions(-)

diff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py
index 6eeeb867edcf..63854d21818c 100755
--- a/tools/net/ynl/tests/ethtool.py
+++ b/tools/net/ynl/tests/ethtool.py
@@ -84,9 +84,9 @@ def print_speed(name, value):
     speed = [ k for k, v in value.items() if v and speed_re.match(k) ]
     print(f'{name}: {" ".join(speed)}')
 
-def doit(ynl, args, op_name):
+def do_set(ynl, args, op_name):
     """
-    Prepare request header, parse arguments and doit.
+    Prepare request header, parse arguments and do a set operation.
     """
     req = {
         'header': {
@@ -97,26 +97,24 @@ def doit(ynl, args, op_name):
     args_to_req(ynl, op_name, args.args, req)
     ynl.do(op_name, req)
 
-def dumpit(ynl, args, op_name, extra=None):
+def do_get(ynl, args, op_name, extra=None):
     """
-    Prepare request header, parse arguments and dumpit (filtering out the
-    devices we're not interested in).
+    Prepare request header and get info for a specific device using doit.
     """
     extra = extra or {}
-    reply = ynl.dump(op_name, { 'header': {} } | extra)
+    req = {'header': {'dev-name': args.device}}
+    req['header'].update(extra.pop('header', {}))
+    req.update(extra)
+
+    reply = ynl.do(op_name, req)
     if not reply:
         return {}
 
-    for msg in reply:
-        if msg['header']['dev-name'] == args.device:
-            if args.json:
-                pprint.PrettyPrinter().pprint(msg)
-                sys.exit(0)
-            msg.pop('header', None)
-            return msg
-
-    print(f"Not supported for device {args.device}")
-    sys.exit(1)
+    if args.json:
+        pprint.PrettyPrinter().pprint(reply)
+        sys.exit(0)
+    reply.pop('header', None)
+    return reply
 
 def bits_to_dict(attr):
     """
@@ -181,15 +179,15 @@ def main():
         return
 
     if args.set_eee:
-        doit(ynl, args, 'eee-set')
+        do_set(ynl, args, 'eee-set')
         return
 
     if args.set_pause:
-        doit(ynl, args, 'pause-set')
+        do_set(ynl, args, 'pause-set')
         return
 
     if args.set_coalesce:
-        doit(ynl, args, 'coalesce-set')
+        do_set(ynl, args, 'coalesce-set')
         return
 
     if args.set_features:
@@ -198,20 +196,20 @@ def main():
         return
 
     if args.set_channels:
-        doit(ynl, args, 'channels-set')
+        do_set(ynl, args, 'channels-set')
         return
 
     if args.set_ring:
-        doit(ynl, args, 'rings-set')
+        do_set(ynl, args, 'rings-set')
         return
 
     if args.show_priv_flags:
-        flags = bits_to_dict(dumpit(ynl, args, 'privflags-get')['flags'])
+        flags = bits_to_dict(do_get(ynl, args, 'privflags-get')['flags'])
         print_field(flags)
         return
 
     if args.show_eee:
-        eee = dumpit(ynl, args, 'eee-get')
+        eee = do_get(ynl, args, 'eee-get')
         ours = bits_to_dict(eee['modes-ours'])
         peer = bits_to_dict(eee['modes-peer'])
 
@@ -232,18 +230,18 @@ def main():
         return
 
     if args.show_pause:
-        print_field(dumpit(ynl, args, 'pause-get'),
+        print_field(do_get(ynl, args, 'pause-get'),
                 ('autoneg', 'Autonegotiate', 'bool'),
                 ('rx', 'RX', 'bool'),
                 ('tx', 'TX', 'bool'))
         return
 
     if args.show_coalesce:
-        print_field(dumpit(ynl, args, 'coalesce-get'))
+        print_field(do_get(ynl, args, 'coalesce-get'))
         return
 
     if args.show_features:
-        reply = dumpit(ynl, args, 'features-get')
+        reply = do_get(ynl, args, 'features-get')
         available = bits_to_dict(reply['hw'])
         requested = bits_to_dict(reply['wanted']).keys()
         active = bits_to_dict(reply['active']).keys()
@@ -270,7 +268,7 @@ def main():
         return
 
     if args.show_channels:
-        reply = dumpit(ynl, args, 'channels-get')
+        reply = do_get(ynl, args, 'channels-get')
         print(f'Channel parameters for {args.device}:')
 
         print('Pre-set maximums:')
@@ -290,7 +288,7 @@ def main():
         return
 
     if args.show_ring:
-        reply = dumpit(ynl, args, 'channels-get')
+        reply = do_get(ynl, args, 'channels-get')
 
         print(f'Ring parameters for {args.device}:')
 
@@ -319,7 +317,7 @@ def main():
         print('NIC statistics:')
 
         # TODO: pass id?
-        strset = dumpit(ynl, args, 'strset-get')
+        strset = do_get(ynl, args, 'strset-get')
         pprint.PrettyPrinter().pprint(strset)
 
         req = {
@@ -338,7 +336,7 @@ def main():
           },
         }
 
-        rsp = dumpit(ynl, args, 'stats-get', req)
+        rsp = do_get(ynl, args, 'stats-get', req)
         pprint.PrettyPrinter().pprint(rsp)
         return
 
@@ -349,7 +347,7 @@ def main():
           },
         }
 
-        tsinfo = dumpit(ynl, args, 'tsinfo-get', req)
+        tsinfo = do_get(ynl, args, 'tsinfo-get', req)
 
         print(f'Time stamping parameters for {args.device}:')
 
@@ -377,7 +375,7 @@ def main():
         return
 
     print(f'Settings for {args.device}:')
-    linkmodes = dumpit(ynl, args, 'linkmodes-get')
+    linkmodes = do_get(ynl, args, 'linkmodes-get')
     ours = bits_to_dict(linkmodes['ours'])
 
     supported_ports = ('TP',  'AUI', 'BNC', 'MII', 'FIBRE', 'Backplane')
@@ -425,7 +423,7 @@ def main():
             5: 'Directly Attached Copper',
             0xef: 'None',
     }
-    linkinfo = dumpit(ynl, args, 'linkinfo-get')
+    linkinfo = do_get(ynl, args, 'linkinfo-get')
     print(f'Port: {ports.get(linkinfo["port"], "Other")}')
 
     print_field(linkinfo, ('phyaddr', 'PHYAD'))
@@ -447,11 +445,11 @@ def main():
         mdix = mdix_ctrl.get(linkinfo['tp-mdix'], 'Unknown (auto)')
     print(f'MDI-X: {mdix}')
 
-    debug = dumpit(ynl, args, 'debug-get')
+    debug = do_get(ynl, args, 'debug-get')
     msgmask = bits_to_dict(debug.get("msgmask", [])).keys()
     print(f'Current message level: {" ".join(msgmask)}')
 
-    linkstate = dumpit(ynl, args, 'linkstate-get')
+    linkstate = do_get(ynl, args, 'linkstate-get')
     detected_states = {
             0: 'no',
             1: 'yes',

-- 
Git-155)


^ permalink raw reply related

* [PATCH net-next v2 1/5] tools: ynl: move ethtool.py to selftest
From: Hangbin Liu @ 2026-04-08  7:08 UTC (permalink / raw)
  To: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Andrew Lunn
  Cc: netdev, linux-kernel, Hangbin Liu
In-Reply-To: <20260408-b4-ynl_ethtool-v2-0-7623a5e8f70b@gmail.com>

We have converted all the samples to selftests. This script is
the last piece of random "PoC" code we still have lying around.
Let's move it to tests.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
---
 tools/net/ynl/tests/Makefile              | 5 ++++-
 tools/net/ynl/{pyynl => tests}/ethtool.py | 2 +-
 tools/net/ynl/tests/test_ynl_ethtool.sh   | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/tools/net/ynl/tests/Makefile b/tools/net/ynl/tests/Makefile
index 2a02958c7039..94bf0346b54d 100644
--- a/tools/net/ynl/tests/Makefile
+++ b/tools/net/ynl/tests/Makefile
@@ -36,7 +36,10 @@ TEST_GEN_FILES := \
 	rt-route \
 # end of TEST_GEN_FILES
 
-TEST_FILES := ynl_nsim_lib.sh
+TEST_FILES := \
+	ethtool.py \
+	ynl_nsim_lib.sh \
+# end of TEST_FILES
 
 CFLAGS_netdev:=$(CFLAGS_netdev) $(CFLAGS_rt-link)
 CFLAGS_ovs:=$(CFLAGS_ovs_datapath)
diff --git a/tools/net/ynl/pyynl/ethtool.py b/tools/net/ynl/tests/ethtool.py
similarity index 99%
rename from tools/net/ynl/pyynl/ethtool.py
rename to tools/net/ynl/tests/ethtool.py
index f1a2a2a89985..6eeeb867edcf 100755
--- a/tools/net/ynl/pyynl/ethtool.py
+++ b/tools/net/ynl/tests/ethtool.py
@@ -14,7 +14,7 @@ import re
 import os
 
 # pylint: disable=no-name-in-module,wrong-import-position
-sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
+sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix())
 # pylint: disable=import-error
 from cli import schema_dir, spec_dir
 from lib import YnlFamily
diff --git a/tools/net/ynl/tests/test_ynl_ethtool.sh b/tools/net/ynl/tests/test_ynl_ethtool.sh
index b826269017f4..b4480e9be7b7 100755
--- a/tools/net/ynl/tests/test_ynl_ethtool.sh
+++ b/tools/net/ynl/tests/test_ynl_ethtool.sh
@@ -8,7 +8,7 @@ KSELFTEST_KTAP_HELPERS="$(dirname "$(realpath "$0")")/../../../testing/selftests
 source "$KSELFTEST_KTAP_HELPERS"
 
 # Default ynl-ethtool path for direct execution, can be overridden by make install
-ynl_ethtool="../pyynl/ethtool.py"
+ynl_ethtool="./ethtool.py"
 
 readonly NSIM_ID="1337"
 readonly NSIM_DEV_NAME="nsim${NSIM_ID}"

-- 
Git-155)


^ permalink raw reply related

* [PATCH net-next v2 0/5] ynl/ethtool/netlink: fix nla_len overflow for large string sets
From: Hangbin Liu @ 2026-04-08  7:08 UTC (permalink / raw)
  To: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Andrew Lunn
  Cc: netdev, linux-kernel, Hangbin Liu

This series addresses a silent data corruption issue triggered when ynl
retrieves string sets from NICs with a large number of statistics entries
(e.g. mlx5_core with thousands of ETH_SS_STATS strings).

The root cause is that struct nlattr.nla_len is a __u16 (max 65535
bytes). When a NIC exports enough statistics strings, the
ETHTOOL_A_STRINGSET_STRINGS nest built by strset_fill_set() exceeds
this limit. nla_nest_end() silently truncates the length on assignment,
producing a corrupted netlink message.

Patch 1 moves ethtool.py to selftest.

Patch 2 improves the ethtool tool: rename the doit/dumpit helpers
to do_set/do_get and convert do_get to use ynl.do() with an
explicit device header instead of a full dump with client-side filtering.

Patch 3 adds a --dbg-small-recv option to the YNL ethtool tool,
matching the same option already present in cli.py, to help debug netlink
message size issues

Patch 4 adds a new helper nla_nest_end_safe() to check whether the nla_len
is overflow and return -EMSGSIZE early if so.

Patch 5 uses the new helper in ethtool to make sure the ethtool doesn't
reply a corrupted netlink message.

---
Changes in v2:
- move ethtool.py to selftest (Jakub Kicinski)
- add a new helper nla_nest_end_safe (Jakub Kicinski)
- Link to v1: https://lore.kernel.org/r/20260331-b4-ynl_ethtool-v1-0-dda2a9b55df8@gmail.com

---
Hangbin Liu (5):
      tools: ynl: move ethtool.py to selftest
      tools: ynl: ethtool: use doit instead of dumpit for per-device GET
      tools: ynl: ethtool: add --dbg-small-recv option
      netlink: add a nla_nest_end_safe() helper
      ethtool: strset: check nla_len overflow

 include/net/netlink.h                     | 19 ++++++++
 net/ethtool/strset.c                      |  3 +-
 tools/net/ynl/tests/Makefile              |  5 +-
 tools/net/ynl/{pyynl => tests}/ethtool.py | 79 ++++++++++++++++---------------
 tools/net/ynl/tests/test_ynl_ethtool.sh   |  2 +-
 5 files changed, 68 insertions(+), 40 deletions(-)
---
base-commit: e65d8b6f3092398efd7c74e722cb7a516d9a0d6d
change-id: 20260324-b4-ynl_ethtool-f87cd42f572c

Best regards,
-- 
Hangbin Liu <liuhangbin@gmail.com>


^ permalink raw reply

* Re: [PATCH v4 1/5] dt-bindings: phy: ti: phy-j721e-wiz: Add ti,j722s-wiz-10g compatible
From: Krzysztof Kozlowski @ 2026-04-08  7:07 UTC (permalink / raw)
  To: Nora Schiffer
  Cc: Nishanth Menon, Vignesh Raghavendra, Tero Kristo, Vinod Koul,
	Neil Armstrong, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Siddharth Vadapalli, Roger Quadros,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, netdev,
	devicetree, linux-kernel, linux-phy, linux-arm-kernel, linux
In-Reply-To: <1ef8adf850f2fd41b6c4e3c89e4f4e6e0f469a0e.1775559102.git.nora.schiffer@ew.tq-group.com>

On Tue, Apr 07, 2026 at 01:42:33PM +0200, Nora Schiffer wrote:
> The J722S WIZ is mostly identical to the AM64's, but additionally supports
> SGMII. The AM64 compatible ti,am64-wiz-10g is used as a fallback.
> 
> Signed-off-by: Nora Schiffer <nora.schiffer@ew.tq-group.com>
> ---
>  .../bindings/phy/ti,phy-j721e-wiz.yaml        | 19 ++++++++++++-------
>  1 file changed, 12 insertions(+), 7 deletions(-)

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v4 2/5] dt-bindings: phy: ti: phy-gmii-sel: Add ti,j722s-phy-gmii-sel compatible
From: Krzysztof Kozlowski @ 2026-04-08  7:06 UTC (permalink / raw)
  To: Nora Schiffer
  Cc: Nishanth Menon, Vignesh Raghavendra, Tero Kristo, Vinod Koul,
	Neil Armstrong, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Siddharth Vadapalli, Roger Quadros,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, netdev,
	devicetree, linux-kernel, linux-phy, linux-arm-kernel, linux
In-Reply-To: <b67c8b0bc9cc918667e9329d79f617d033d025d5.1775559102.git.nora.schiffer@ew.tq-group.com>

On Tue, Apr 07, 2026 at 01:42:34PM +0200, Nora Schiffer wrote:
> The J722S gmii-sel is mostly identical to the AM64's, but additionally
> supports SGMII. The AM64 compatible ti,am654-phy-gmii-sel is used as a
> fallback.
> 
> Signed-off-by: Nora Schiffer <nora.schiffer@ew.tq-group.com>
> ---
>  .../bindings/phy/ti,phy-gmii-sel.yaml         | 23 +++++++++++--------
>  1 file changed, 14 insertions(+), 9 deletions(-)

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH net-next v4 1/2] dt-bindings: net: ti: k3-am654-cpsw-nuss: Add ti,j722s-cpsw-nuss compatible
From: Krzysztof Kozlowski @ 2026-04-08  7:06 UTC (permalink / raw)
  To: Nora Schiffer
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Nishanth Menon, Vignesh Raghavendra, Tero Kristo,
	Siddharth Vadapalli, Roger Quadros, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, devicetree,
	linux-kernel, linux-arm-kernel, linux
In-Reply-To: <191e9f7e3a6c14eabe891a98c5fb646766479c0a.1775558273.git.nora.schiffer@ew.tq-group.com>

On Tue, Apr 07, 2026 at 12:48:01PM +0200, Nora Schiffer wrote:
> The J722S CPSW3G is mostly identical to the AM64's, but additionally
> supports SGMII. The AM64 compatible ti,am642-cpsw-nuss is used as a
> fallback.
> 
> Signed-off-by: Nora Schiffer <nora.schiffer@ew.tq-group.com>
> ---
> 
> v2: keep ti,am642-cpsw-nuss as a fallback
> v3: resubmission for net-next, no changes
> v4: remove redundant items: level

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


^ permalink raw reply

* [PATCH net-next v2 2/2] selftests: net: add ECMP rehash test
From: Neil Spring @ 2026-04-08  7:05 UTC (permalink / raw)
  To: netdev; +Cc: edumazet, davem, kuba
In-Reply-To: <20260408070514.1840227-1-ntspring@meta.com>

Add ecmp_rehash.sh to exercise TCP ECMP path re-selection on
retransmission timeout.  Three tests cover client SYN rehash, server
SYN/ACK rehash, and midstream RTO rehash of an established connection
over a two-path ECMP topology with one leg blocked by tc.

The SYN test retries 26 times, so has a false negative probability
of ~(1/2)^25 ≈ 3e-8.

Signed-off-by: Neil Spring <ntspring@meta.com>
---
 tools/testing/selftests/net/Makefile       |   1 +
 tools/testing/selftests/net/ecmp_rehash.sh | 361 +++++++++++++++++++++
 2 files changed, 362 insertions(+)
 create mode 100755 tools/testing/selftests/net/ecmp_rehash.sh

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 6bced3ed798b..acc61a51d7e2 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -25,6 +25,7 @@ TEST_PROGS := \
 	cmsg_time.sh \
 	double_udp_encap.sh \
 	drop_monitor_tests.sh \
+	ecmp_rehash.sh \
 	fcnal-ipv4.sh \
 	fcnal-ipv6.sh \
 	fcnal-other.sh \
diff --git a/tools/testing/selftests/net/ecmp_rehash.sh b/tools/testing/selftests/net/ecmp_rehash.sh
new file mode 100755
index 000000000000..a468ccf22d4f
--- /dev/null
+++ b/tools/testing/selftests/net/ecmp_rehash.sh
@@ -0,0 +1,361 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test ECMP path re-selection on TCP retransmission timeout.
+#
+# Two namespaces connected by two parallel veth pairs with a 2-way ECMP
+# route.  When a TCP path is blocked (via tc drop), RTO triggers
+# sk_rethink_txhash() + sk_dst_reset(), causing the next route lookup
+# to select the other ECMP path.
+#
+# False negative: ~(1/2)^25 ≈ 3e-8.  With tcp_syn_retries=25 and
+# tcp_syn_linear_timeouts=25 there are 26 SYN attempts at 1-second
+# intervals, each choosing one of 2 paths uniformly.
+
+source lib.sh
+
+SUBNETS=(a b)
+PORT=9900
+
+ALL_TESTS="
+	test_ecmp_rto_rehash
+	test_ecmp_synack_rehash
+	test_ecmp_midstream_rehash
+"
+
+link_tx_packets_get()
+{
+	local ns=$1; shift
+	local dev=$1; shift
+
+	ip netns exec "$ns" cat "/sys/class/net/$dev/statistics/tx_packets"
+}
+
+# Return the number of packets matched by the tc filter action on a device.
+# When tc drops packets via "action drop", the device's tx_packets is not
+# incremented (packet never reaches veth_xmit), but the tc action maintains
+# its own counter.
+tc_filter_pkt_count()
+{
+	local ns=$1; shift
+	local dev=$1; shift
+
+	ip netns exec "$ns" tc -s filter show dev "$dev" parent 1: 2>/dev/null |
+		awk '/Sent .* pkt/ { for (i=1;i<=NF;i++) if ($i=="pkt") { print $(i-1); exit } }'
+}
+
+# Read TcpTimeoutRehash counter from /proc/net/netstat in a namespace.
+# This counter increments in tcp_write_timeout() on every RTO that triggers
+# sk_rethink_txhash().
+get_timeout_rehash_count()
+{
+	local ns=$1; shift
+
+	ip netns exec "$ns" awk '
+		/^TcpExt:/ {
+			if (!h) { split($0, n); h=1 }
+			else {
+				split($0, v)
+				for (i in n)
+					if (n[i] == "TcpTimeoutRehash") print v[i]
+			}
+		}
+	' /proc/net/netstat
+}
+
+# Block TCP (IPv6 next-header = 6) egress, allowing ICMPv6 through.
+block_tcp()
+{
+	local ns=$1; shift
+	local dev=$1; shift
+
+	ip netns exec "$ns" tc qdisc add dev "$dev" root handle 1: prio
+	ip netns exec "$ns" tc filter add dev "$dev" parent 1: \
+		protocol ipv6 prio 1 u32 match u8 0x06 0xff at 6 action drop
+}
+
+unblock_tcp()
+{
+	local ns=$1; shift
+	local dev=$1; shift
+
+	ip netns exec "$ns" tc qdisc del dev "$dev" root 2>/dev/null
+}
+
+# Return success when both devices have dropped at least one TCP packet.
+both_devs_attempted()
+{
+	local ns=$1; shift
+	local dev0=$1; shift
+	local dev1=$1; shift
+
+	local c0 c1
+	c0=$(tc_filter_pkt_count "$ns" "$dev0")
+	c1=$(tc_filter_pkt_count "$ns" "$dev1")
+	[ "${c0:-0}" -ge 1 ] && [ "${c1:-0}" -ge 1 ]
+}
+
+setup()
+{
+	setup_ns NS1 NS2
+
+	local ns
+	for ns in "$NS1" "$NS2"; do
+		ip netns exec "$ns" sysctl -qw net.ipv6.conf.all.accept_dad=0
+		ip netns exec "$ns" sysctl -qw net.ipv6.conf.default.accept_dad=0
+		ip netns exec "$ns" sysctl -qw net.ipv6.conf.all.forwarding=1
+		ip netns exec "$ns" sysctl -qw net.core.txrehash=1
+	done
+
+	local i sub
+	for i in 0 1; do
+		sub=${SUBNETS[$i]}
+		ip link add "veth${i}a" type veth peer name "veth${i}b"
+		ip link set "veth${i}a" netns "$NS1"
+		ip link set "veth${i}b" netns "$NS2"
+		ip -n "$NS1" addr add "fd00:${sub}::1/64" dev "veth${i}a"
+		ip -n "$NS2" addr add "fd00:${sub}::2/64" dev "veth${i}b"
+		ip -n "$NS1" link set "veth${i}a" up
+		ip -n "$NS2" link set "veth${i}b" up
+	done
+
+	ip -n "$NS1" addr add fd00:ff::1/128 dev lo
+	ip -n "$NS2" addr add fd00:ff::2/128 dev lo
+
+	# Allow many SYN retries at 1-second intervals (linear, no
+	# exponential backoff) so the rehash test has enough attempts
+	# to exercise both ECMP paths.  tcp_syn_retries is a pure
+	# retransmission count (not time-based for SYN_SENT), so with
+	# linear 1-second intervals it also sets the total lifetime.
+	ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_syn_retries=25
+	ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_syn_linear_timeouts=25
+
+	# Keep the server's request socket alive during the blocking
+	# period so SYN/ACK retransmits continue.
+	ip netns exec "$NS2" sysctl -qw net.ipv4.tcp_synack_retries=10
+
+	ip -n "$NS1" -6 route add fd00:ff::2/128 \
+		nexthop via fd00:a::2 dev veth0a \
+		nexthop via fd00:b::2 dev veth1a
+
+	ip -n "$NS2" -6 route add fd00:ff::1/128 \
+		nexthop via fd00:a::1 dev veth0b \
+		nexthop via fd00:b::1 dev veth1b
+
+	for i in 0 1; do
+		sub=${SUBNETS[$i]}
+		ip netns exec "$NS1" \
+			ping -6 -c1 -W5 "fd00:${sub}::2" &>/dev/null
+		ip netns exec "$NS2" \
+			ping -6 -c1 -W5 "fd00:${sub}::1" &>/dev/null
+	done
+
+	if ! ip netns exec "$NS1" ping -6 -c1 -W5 fd00:ff::2 &>/dev/null; then
+		echo "Basic connectivity check failed"
+		return $ksft_skip
+	fi
+}
+
+# Block ALL paths, start a connection, wait until SYNs have been dropped
+# on both interfaces (proving rehash steered the SYN to a new path), then
+# unblock so the connection completes.
+test_ecmp_rto_rehash()
+{
+	RET=0
+
+	block_tcp "$NS1" veth0a
+	defer unblock_tcp "$NS1" veth0a
+	block_tcp "$NS1" veth1a
+	defer unblock_tcp "$NS1" veth1a
+
+	ip netns exec "$NS2" socat \
+		"TCP6-LISTEN:$PORT,bind=[fd00:ff::2],reuseaddr,fork" \
+		EXEC:"echo ESTABLISH_OK" &
+	defer kill_process $!
+
+	wait_local_port_listen "$NS2" $PORT tcp
+
+	local rehash_before
+	rehash_before=$(get_timeout_rehash_count "$NS1")
+
+	# Start the connection in the background; it will retry SYNs at
+	# 1-second intervals until an unblocked path is found.
+	# Use -u (unidirectional) to only receive from the server;
+	# sending data back would risk SIGPIPE if the server's EXEC
+	# child has already exited.
+	ip netns exec "$NS1" socat -u \
+		"TCP6:[fd00:ff::2]:$PORT,bind=[fd00:ff::1],connect-timeout=60" \
+		STDOUT >"/tmp/ecmp_rto_$$" 2>&1 &
+	local client_pid=$!
+	defer kill_process $client_pid
+
+	# Wait until both paths have seen at least one dropped SYN.
+	# This proves sk_rethink_txhash() rehashed the connection from
+	# one ECMP path to the other.
+	slowwait 30 both_devs_attempted "$NS1" veth0a veth1a
+	check_err $? "SYNs did not appear on both paths (rehash not working)"
+	if [ $RET -ne 0 ]; then
+		log_test "ECMP RTO rehash: establish with blocked paths"
+		return
+	fi
+
+	# Unblock both paths and let the next SYN retransmit succeed.
+	unblock_tcp "$NS1" veth0a
+	unblock_tcp "$NS1" veth1a
+
+	local rc=0
+	wait $client_pid || rc=$?
+
+	local result
+	result=$(cat "/tmp/ecmp_rto_$$" 2>/dev/null)
+	rm -f "/tmp/ecmp_rto_$$"
+
+	if [[ "$result" != *"ESTABLISH_OK"* ]]; then
+		check_err 1 "connection failed after unblocking (rc=$rc): $result"
+	fi
+
+	local rehash_after
+	rehash_after=$(get_timeout_rehash_count "$NS1")
+	if [ "$rehash_after" -le "$rehash_before" ]; then
+		check_err 1 "TcpTimeoutRehash counter did not increment"
+	fi
+
+	log_test "ECMP RTO rehash: establish with blocked paths"
+}
+
+# Block the server's return paths so SYN/ACKs are dropped.  The client
+# retransmits SYNs at 1-second intervals; each duplicate SYN arriving at
+# the server updates ir_iif to match the new arrival interface, so the
+# retransmitted SYN/ACK routes back via the interface the SYN arrived on.
+test_ecmp_synack_rehash()
+{
+	RET=0
+	local port=$((PORT + 2))
+
+	block_tcp "$NS2" veth0b
+	defer unblock_tcp "$NS2" veth0b
+	block_tcp "$NS2" veth1b
+	defer unblock_tcp "$NS2" veth1b
+
+	ip netns exec "$NS2" socat \
+		"TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr,fork" \
+		EXEC:"echo SYNACK_OK" &
+	defer kill_process $!
+
+	wait_local_port_listen "$NS2" $port tcp
+
+	# Start the connection; SYNs reach the server (client egress is
+	# open) but SYN/ACKs are dropped on the server's return path.
+	ip netns exec "$NS1" socat -u \
+		"TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=60" \
+		STDOUT >"/tmp/ecmp_synack_$$" 2>&1 &
+	local client_pid=$!
+	defer kill_process $client_pid
+
+	# Wait until both server-side interfaces have dropped at least
+	# one SYN/ACK, proving the server rehashed its return path.
+	slowwait 30 both_devs_attempted "$NS2" veth0b veth1b
+	check_err $? "SYN/ACKs did not appear on both return paths"
+	if [ $RET -ne 0 ]; then
+		log_test "ECMP SYN/ACK rehash: blocked return path"
+		return
+	fi
+
+	# Unblock and let the connection complete.
+	unblock_tcp "$NS2" veth0b
+	unblock_tcp "$NS2" veth1b
+
+	local rc=0
+	wait $client_pid || rc=$?
+
+	local result
+	result=$(cat "/tmp/ecmp_synack_$$" 2>/dev/null)
+	rm -f "/tmp/ecmp_synack_$$"
+
+	if [[ "$result" != *"SYNACK_OK"* ]]; then
+		check_err 1 "connection failed after unblocking (rc=$rc): $result"
+	fi
+
+	log_test "ECMP SYN/ACK rehash: blocked return path"
+}
+
+# Establish a data transfer with both paths open, then block the
+# active path.  Verify the transfer continues via rehash and that
+# TcpTimeoutRehash incremented.
+test_ecmp_midstream_rehash()
+{
+	RET=0
+	local port=$((PORT + 1))
+
+	ip netns exec "$NS2" socat -u \
+		"TCP6-LISTEN:$port,bind=[fd00:ff::2],reuseaddr" - >/dev/null &
+	defer kill_process $!
+
+	wait_local_port_listen "$NS2" $port tcp
+
+	local base_tx0 base_tx1
+	base_tx0=$(link_tx_packets_get "$NS1" veth0a)
+	base_tx1=$(link_tx_packets_get "$NS1" veth1a)
+
+	ip netns exec "$NS1" bash -c "
+		for i in \$(seq 1 40); do
+			dd if=/dev/zero bs=10k count=1 2>/dev/null
+			sleep 0.25
+		done | timeout 60 socat - 'TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1]'
+	" &>/dev/null &
+	local client_pid=$!
+	defer kill_process $client_pid
+
+	busywait $BUSYWAIT_TIMEOUT until_counter_is \
+			">= $((base_tx0 + base_tx1 + 20))" \
+		link_tx_packets_total "$NS1"
+	check_err $? "no TX activity detected"
+	if [ $RET -ne 0 ]; then
+		log_test "ECMP midstream rehash: block active path"
+		return
+	fi
+
+	# Find the active path and block it.
+	local cur0 cur1 active_idx
+	cur0=$(link_tx_packets_get "$NS1" veth0a)
+	cur1=$(link_tx_packets_get "$NS1" veth1a)
+	if [ $((cur0 - base_tx0)) -ge $((cur1 - base_tx1)) ]; then
+		active_idx=0
+	else
+		active_idx=1
+	fi
+
+	local rehash_before
+	rehash_before=$(get_timeout_rehash_count "$NS1")
+
+	block_tcp "$NS1" "veth${active_idx}a"
+	defer unblock_tcp "$NS1" "veth${active_idx}a"
+
+	local rc=0
+	wait $client_pid || rc=$?
+
+	check_err $rc "data transfer failed after blocking veth${active_idx}a"
+
+	local rehash_after
+	rehash_after=$(get_timeout_rehash_count "$NS1")
+	if [ "$rehash_after" -le "$rehash_before" ]; then
+		check_err 1 "TcpTimeoutRehash counter did not increment"
+	fi
+
+	log_test "ECMP midstream rehash: block active path"
+}
+
+link_tx_packets_total()
+{
+	local ns=$1; shift
+
+	echo $(( $(link_tx_packets_get "$ns" veth0a) +
+		 $(link_tx_packets_get "$ns" veth1a) ))
+}
+
+require_command socat
+
+trap cleanup_all_ns EXIT
+setup || exit $?
+tests_run
+exit $EXIT_STATUS
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next v2 1/2] tcp: rehash onto different ECMP path on retransmit timeout
From: Neil Spring @ 2026-04-08  7:05 UTC (permalink / raw)
  To: netdev; +Cc: edumazet, davem, kuba
In-Reply-To: <20260408070514.1840227-1-ntspring@meta.com>

Add sk_dst_reset() alongside sk_rethink_txhash() in the RTO, PLB,
and spurious-retrans paths so that the next transmit triggers a fresh
route lookup.  Propagate sk_txhash into fl6->mp_hash in
inet6_csk_route_req() and inet6_csk_route_socket() so
fib6_select_path() uses the socket's current hash for ECMP selection.

The ir_iif update in tcp_check_req() covers both IPv4 and IPv6
because it was cleaner than gating on address family; IPv4 is
otherwise unaltered, and not having autoflowlabel in IPv4 means
I wouldn't expect a new path on timeout.

It is possible that PLB does not need this (that there are other
methods of reacting to local congestion); I added the sk_dst_reset
for consistency.

Signed-off-by: Neil Spring <ntspring@meta.com>
---
 net/ipv4/tcp_input.c             |  4 +++-
 net/ipv4/tcp_minisocks.c         | 13 +++++++++++++
 net/ipv4/tcp_plb.c               |  1 +
 net/ipv4/tcp_timer.c             |  1 +
 net/ipv6/inet6_connection_sock.c | 11 +++++++++++
 5 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 7171442c3ed7..3d42ab45066c 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -5014,8 +5014,10 @@ static void tcp_rcv_spurious_retrans(struct sock *sk,
 	    skb->protocol == htons(ETH_P_IPV6) &&
 	    (tcp_sk(sk)->inet_conn.icsk_ack.lrcv_flowlabel !=
 	     ntohl(ip6_flowlabel(ipv6_hdr(skb)))) &&
-	    sk_rethink_txhash(sk))
+	    sk_rethink_txhash(sk)) {
 		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDUPLICATEDATAREHASH);
+		sk_dst_reset(sk);
+	}
 
 	/* Save last flowlabel after a spurious retrans. */
 	tcp_save_lrcv_flowlabel(sk, skb);
diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
index 199f0b579e89..27edf71effc2 100644
--- a/net/ipv4/tcp_minisocks.c
+++ b/net/ipv4/tcp_minisocks.c
@@ -750,6 +750,19 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
 		 * Reset timer after retransmitting SYNACK, similar to
 		 * the idea of fast retransmit in recovery.
 		 */
+
+		/* Update ir_iif to match the interface the retransmitted
+		 * SYN arrived on; inet6_csk_route_req() uses this as
+		 * flowi6_oif, constraining ECMP path for the SYN/ACK.
+		 */
+#if IS_ENABLED(CONFIG_IPV6)
+		if (sk->sk_family == AF_INET6)
+			inet_rsk(req)->ir_iif = tcp_v6_iif(skb);
+		else
+#endif
+			inet_rsk(req)->ir_iif =
+				inet_request_bound_dev_if(sk, skb);
+
 		if (!tcp_oow_rate_limited(sock_net(sk), skb,
 					  LINUX_MIB_TCPACKSKIPPEDSYNRECV,
 					  &tcp_rsk(req)->last_oow_ack_time)) {
diff --git a/net/ipv4/tcp_plb.c b/net/ipv4/tcp_plb.c
index 68ccdb9a5412..d7cc00a58e53 100644
--- a/net/ipv4/tcp_plb.c
+++ b/net/ipv4/tcp_plb.c
@@ -79,6 +79,7 @@ void tcp_plb_check_rehash(struct sock *sk, struct tcp_plb_state *plb)
 		return;
 
 	sk_rethink_txhash(sk);
+	sk_dst_reset(sk);
 	plb->consec_cong_rounds = 0;
 	tcp_sk(sk)->plb_rehash++;
 	NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPPLBREHASH);
diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
index ea99988795e7..acc22fc532c2 100644
--- a/net/ipv4/tcp_timer.c
+++ b/net/ipv4/tcp_timer.c
@@ -299,6 +299,7 @@ static int tcp_write_timeout(struct sock *sk)
 	if (sk_rethink_txhash(sk)) {
 		tp->timeout_rehash++;
 		__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPTIMEOUTREHASH);
+		sk_dst_reset(sk);
 	}
 
 	return 0;
diff --git a/net/ipv6/inet6_connection_sock.c b/net/ipv6/inet6_connection_sock.c
index 37534e116899..3fd7acbe2c49 100644
--- a/net/ipv6/inet6_connection_sock.c
+++ b/net/ipv6/inet6_connection_sock.c
@@ -14,6 +14,7 @@
 #include <linux/ipv6.h>
 #include <linux/jhash.h>
 #include <linux/slab.h>
+#include <linux/tcp.h>
 
 #include <net/addrconf.h>
 #include <net/inet_connection_sock.h>
@@ -48,6 +49,12 @@ struct dst_entry *inet6_csk_route_req(const struct sock *sk,
 	fl6->flowi6_uid = sk_uid(sk);
 	security_req_classify_flow(req, flowi6_to_flowi_common(fl6));
 
+	/* Use the request socket's txhash (re-rolled by tcp_rtx_synack())
+	 * for ECMP path selection; >> 1 for 31-bit mp_hash range.
+	 */
+	if (tcp_rsk(req)->txhash)
+		fl6->mp_hash = tcp_rsk(req)->txhash >> 1;
+
 	if (!dst) {
 		dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_p);
 		if (IS_ERR(dst))
@@ -70,6 +77,10 @@ struct dst_entry *inet6_csk_route_socket(struct sock *sk,
 	fl6->saddr = np->saddr;
 	fl6->flowlabel = np->flow_label;
 	IP6_ECN_flow_xmit(sk, fl6->flowlabel);
+
+	/* >> 1 for 31-bit mp_hash range matching nhc_upper_bound. */
+	if (sk->sk_txhash)
+		fl6->mp_hash = sk->sk_txhash >> 1;
 	fl6->flowi6_oif = sk->sk_bound_dev_if;
 	fl6->flowi6_mark = sk->sk_mark;
 	fl6->fl6_sport = inet->inet_sport;
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next v2 0/2] tcp: rehash onto different ECMP path on retransmit timeout
From: Neil Spring @ 2026-04-08  7:05 UTC (permalink / raw)
  To: netdev; +Cc: edumazet, davem, kuba
In-Reply-To: <20260408002802.2448424-1-ntspring@meta.com>

Make TCP retransmission timeouts select a different ECMP path for IPv6.

Currently sk_rethink_txhash() changes the socket's txhash on RTO, but the
cached route is reused and the new hash is not propagated into the ECMP
path selection logic.  This series adds sk_dst_reset() alongside
sk_rethink_txhash() to force a fresh route lookup, and sets fl6->mp_hash
from sk_txhash so fib6_select_path() picks a path based on the new hash.

Three selftest scenarios verify the behavior: SYN retransmission, SYN/ACK
retransmission (server-side), and midstream RTO on an established
connection.

Changes since v1:
- Use tcp_rsk(req)->txhash instead of jhash_1word(req->num_retrans, ...)
  for ECMP path selection in inet6_csk_route_req(), making the request
  socket path consistent with the established socket path (Eric Dumazet)
- Add comments explaining the >> 1 shift for 31-bit mp_hash range
- Add comment explaining the ir_iif update rationale in tcp_check_req()
- Use socat -u (unidirectional) in selftest to avoid SIGPIPE race
- Increase tcp_syn_retries and tcp_syn_linear_timeouts to 25 for
  better rehash coverage; add tcp_synack_retries=10 on the server

Neil Spring (2):
  tcp: rehash onto different ECMP path on retransmit timeout
  selftests: net: add ECMP rehash test

 net/ipv4/tcp_input.c                       |   4 +-
 net/ipv4/tcp_minisocks.c                   |  13 +
 net/ipv4/tcp_plb.c                         |   1 +
 net/ipv4/tcp_timer.c                       |   1 +
 net/ipv6/inet6_connection_sock.c           |  11 +
 tools/testing/selftests/net/Makefile       |   1 +
 tools/testing/selftests/net/ecmp_rehash.sh | 361 +++++++++++++++++++++
 7 files changed, 391 insertions(+), 1 deletion(-)
 create mode 100755 tools/testing/selftests/net/ecmp_rehash.sh

-- 
2.52.0


^ permalink raw reply

* RE: [PATCH v4 net-next 04/14] net: enetc: add basic operations to the FDB table
From: Wei Fang @ 2026-04-08  7:04 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Claudiu Manoil, Vladimir Oltean, Clark Wang,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	pabeni@redhat.com, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org, f.fainelli@gmail.com, Frank Li,
	chleroy@kernel.org, horms@kernel.org, linux@armlinux.org.uk,
	andrew@lunn.ch, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev
In-Reply-To: <20260403011727.1795379-1-kuba@kernel.org>

> > @@ -116,6 +158,31 @@ static inline int ntmp_rsst_query_entry(struct
> ntmp_user *user,
> >  	return 0;
> >  }
> >
> > +static inline int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
> > +				      const struct fdbt_keye_data *keye,
> > +				      const struct fdbt_cfge_data *data)
> > +{
> > +	return 0;
> > +}
> 
> If CONFIG_NXP_NETC_LIB is disabled, will returning 0 here cause callers to
> falsely assume the operation succeeded and proceed to use an uninitialized
> entry_id pointer? Would it be safer to return an error code such as
> -EOPNOTSUPP for stubs that have output parameters?
> 

The ntmp_fdbt_add_entry() is only called by the netc switch driver,
and the driver will select CONFIG_NXP_NETC_LIB option, so actually
this inline function is not used by any drivers. I suppose such inline
functions could be removed. Thanks.


^ permalink raw reply

* Re: [PATCH net-next 1/2] tcp: rehash onto different ECMP path on retransmit timeout
From: Neil Spring @ 2026-04-08  6:59 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: netdev, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
	shuah, linux-kselftest, Ido Schimmel
In-Reply-To: <CANn89iKEgkbS=FEssas9upr4kTpAuH2PRehthBC4Oc4bBfW8hQ@mail.gmail.com>

Thanks Eric for looking at this; sorry for the duplicate email as I
failed to reply all; comments below.

On Tue, Apr 7, 2026 at 6:09=E2=80=AFPM Eric Dumazet <edumazet@google.com> w=
rote:
> On Tue, Apr 7, 2026 at 5:28=E2=80=AFPM Neil Spring <ntspring@meta.com> wr=
ote:
> >
> > Add sk_dst_reset() alongside sk_rethink_txhash() in the RTO, PLB,
> > and spurious-retrans paths so that the next transmit triggers a fresh
> > route lookup.  Propagate sk_txhash into fl6->mp_hash in
> > inet6_csk_route_req() and inet6_csk_route_socket() so
> > fib6_select_path() uses the socket's current hash for ECMP selection.
> >
> > The ir_iif update in tcp_check_req() covers both IPv4 and IPv6
> > because it was cleaner than gating on address family; IPv4 is
> > otherwise unaltered, and not having autoflowlabel in IPv4 means
> > I wouldn't expect a new path on timeout.
> >
> > It is possible that PLB does not need this (that there are other
> > methods of reacting to local congestion); I added the sk_dst_reset
> > for consistency.
> >
> > Signed-off-by: Neil Spring <ntspring@meta.com>
> > ---
> >  net/ipv4/tcp_input.c             | 4 +++-
> >  net/ipv4/tcp_minisocks.c         | 9 +++++++++
> >  net/ipv4/tcp_plb.c               | 1 +
> >  net/ipv4/tcp_timer.c             | 1 +
> >  net/ipv6/inet6_connection_sock.c | 8 ++++++++
> >  5 files changed, 22 insertions(+), 1 deletion(-)
> >
> > diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
> > index 7171442c3ed7..3d42ab45066c 100644
> > --- a/net/ipv4/tcp_input.c
> > +++ b/net/ipv4/tcp_input.c
> > @@ -5014,8 +5014,10 @@ static void tcp_rcv_spurious_retrans(struct sock=
 *sk,
> >             skb->protocol =3D=3D htons(ETH_P_IPV6) &&
> >             (tcp_sk(sk)->inet_conn.icsk_ack.lrcv_flowlabel !=3D
> >              ntohl(ip6_flowlabel(ipv6_hdr(skb)))) &&
> > -           sk_rethink_txhash(sk))
> > +           sk_rethink_txhash(sk)) {
> >                 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDUPLICATEDATAR=
EHASH);
> > +               sk_dst_reset(sk);
> > +       }
> >
> >         /* Save last flowlabel after a spurious retrans. */
> >         tcp_save_lrcv_flowlabel(sk, skb);
> > diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
> > index 199f0b579e89..ef4b3771e9d8 100644
> > --- a/net/ipv4/tcp_minisocks.c
> > +++ b/net/ipv4/tcp_minisocks.c
> > @@ -750,6 +750,15 @@ struct sock *tcp_check_req(struct sock *sk, struct=
 sk_buff *skb,
> >                  * Reset timer after retransmitting SYNACK, similar to
> >                  * the idea of fast retransmit in recovery.
> >                  */
> > +
>
> What is the following part doing?
> tcp_v6_init_req() uses something quite different before setting ir_iif
> A comment explaining the rationale would be nice.

Comment added in v2.  The behavior I thought I observed was that the
ir_iif goes into fl6->flowi6_oif in inet6_csk_route_req(), limiting options
to any next hop on that interface, so if we received a retransmitted syn
on a new interface, we should try to use the new interface for the
response.

>
> > +#if IS_ENABLED(CONFIG_IPV6)
> > +               if (sk->sk_family =3D=3D AF_INET6)
> > +                       inet_rsk(req)->ir_iif =3D tcp_v6_iif(skb);
> > +               else
> > +#endif
> > +                       inet_rsk(req)->ir_iif =3D
> > +                               inet_request_bound_dev_if(sk, skb);
> > +
> >                 if (!tcp_oow_rate_limited(sock_net(sk), skb,
> >                                           LINUX_MIB_TCPACKSKIPPEDSYNREC=
V,
> >                                           &tcp_rsk(req)->last_oow_ack_t=
ime)) {
> > diff --git a/net/ipv4/tcp_plb.c b/net/ipv4/tcp_plb.c
> > index 68ccdb9a5412..d7cc00a58e53 100644
> > --- a/net/ipv4/tcp_plb.c
> > +++ b/net/ipv4/tcp_plb.c
> > @@ -79,6 +79,7 @@ void tcp_plb_check_rehash(struct sock *sk, struct tcp=
_plb_state *plb)
> >                 return;
> >
> >         sk_rethink_txhash(sk);
> > +       sk_dst_reset(sk);
> >         plb->consec_cong_rounds =3D 0;
> >         tcp_sk(sk)->plb_rehash++;
> >         NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPPLBREHASH);
> > diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
> > index ea99988795e7..acc22fc532c2 100644
> > --- a/net/ipv4/tcp_timer.c
> > +++ b/net/ipv4/tcp_timer.c
> > @@ -299,6 +299,7 @@ static int tcp_write_timeout(struct sock *sk)
> >         if (sk_rethink_txhash(sk)) {
> >                 tp->timeout_rehash++;
> >                 __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPTIMEOUTREHAS=
H);
> > +               sk_dst_reset(sk);
> >         }
> >
> >         return 0;
> > diff --git a/net/ipv6/inet6_connection_sock.c b/net/ipv6/inet6_connecti=
on_sock.c
> > index 37534e116899..2fe753bb38b4 100644
> > --- a/net/ipv6/inet6_connection_sock.c
> > +++ b/net/ipv6/inet6_connection_sock.c
> > @@ -48,6 +48,11 @@ struct dst_entry *inet6_csk_route_req(const struct s=
ock *sk,
> >         fl6->flowi6_uid =3D sk_uid(sk);
> >         security_req_classify_flow(req, flowi6_to_flowi_common(fl6));
> >
> > +       if (req->num_retrans)
> > +               fl6->mp_hash =3D jhash_1word(req->num_retrans,
> > +                                          (__force u32)ireq->ir_rmt_po=
rt)
> > +                               >> 1;
>
> Why not setting mp_hash to sk_txhash ?

To be honest, I was trying to avoid adding #include <tcp.h> to the top of
inet6_connection_sock.c, which is needed to get tcp_rsk(req)->txhash, but
that does make better code, using that in v2.

> Why are you using ">> 1" ?

The >> 1 is due to rt6_multipath_hash returning its computed
value shifted right one before going into mp_hash in route.c sites.
I understood mp_hash can't have the high bit set and still work
due to fib6_select_path treating it as a signed integer when comparing
to fib_nh_upper_bound.

> rt6_multipath_hash() seems to be bypassed, it might be time to add a
> comment there
> explaining that mp_hash needs to be 31-bit only...
>
> Perhaps use rt6_multipath_hash() and expand it to use a socket pointer
> to retrieve sk->sk_txhash when/if possible
> instead of yet another flow dissection.

I think thyour comment above (grabbing txhash) avoids the dissection.

>
> > +
> >         if (!dst) {
> >                 dst =3D ip6_dst_lookup_flow(sock_net(sk), sk, fl6, fina=
l_p);
> >                 if (IS_ERR(dst))
> > @@ -70,6 +75,9 @@ struct dst_entry *inet6_csk_route_socket(struct sock =
*sk,
> >         fl6->saddr =3D np->saddr;
> >         fl6->flowlabel =3D np->flow_label;
> >         IP6_ECN_flow_xmit(sk, fl6->flowlabel);
> > +
> > +       if (sk->sk_txhash)
> > +               fl6->mp_hash =3D sk->sk_txhash >> 1;
>
> Seems inconsistent, and same question about the right shift.

Adding comment and standardizing on
* tcp_rsk(req)->txhash >> 1 for request sockets,
* sk->sk_txhash >> 1 for established sockets.

>
>
> >         fl6->flowi6_oif =3D sk->sk_bound_dev_if;
> >         fl6->flowi6_mark =3D sk->sk_mark;
> >         fl6->fl6_sport =3D inet->inet_sport;
> > --
> > 2.52.0
> >

On Tue, Apr 7, 2026 at 6:09 PM Eric Dumazet <edumazet@google.com> wrote:
>
> >
> On Tue, Apr 7, 2026 at 5:28 PM Neil Spring <ntspring@meta.com> wrote:
> >
> > Add sk_dst_reset() alongside sk_rethink_txhash() in the RTO, PLB,
> > and spurious-retrans paths so that the next transmit triggers a fresh
> > route lookup.  Propagate sk_txhash into fl6->mp_hash in
> > inet6_csk_route_req() and inet6_csk_route_socket() so
> > fib6_select_path() uses the socket's current hash for ECMP selection.
> >
> > The ir_iif update in tcp_check_req() covers both IPv4 and IPv6
> > because it was cleaner than gating on address family; IPv4 is
> > otherwise unaltered, and not having autoflowlabel in IPv4 means
> > I wouldn't expect a new path on timeout.
> >
> > It is possible that PLB does not need this (that there are other
> > methods of reacting to local congestion); I added the sk_dst_reset
> > for consistency.
> >
> > Signed-off-by: Neil Spring <ntspring@meta.com>
> > ---
> >  net/ipv4/tcp_input.c             | 4 +++-
> >  net/ipv4/tcp_minisocks.c         | 9 +++++++++
> >  net/ipv4/tcp_plb.c               | 1 +
> >  net/ipv4/tcp_timer.c             | 1 +
> >  net/ipv6/inet6_connection_sock.c | 8 ++++++++
> >  5 files changed, 22 insertions(+), 1 deletion(-)
> >
> > diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
> > index 7171442c3ed7..3d42ab45066c 100644
> > --- a/net/ipv4/tcp_input.c
> > +++ b/net/ipv4/tcp_input.c
> > @@ -5014,8 +5014,10 @@ static void tcp_rcv_spurious_retrans(struct sock *sk,
> >             skb->protocol == htons(ETH_P_IPV6) &&
> >             (tcp_sk(sk)->inet_conn.icsk_ack.lrcv_flowlabel !=
> >              ntohl(ip6_flowlabel(ipv6_hdr(skb)))) &&
> > -           sk_rethink_txhash(sk))
> > +           sk_rethink_txhash(sk)) {
> >                 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDUPLICATEDATAREHASH);
> > +               sk_dst_reset(sk);
> > +       }
> >
> >         /* Save last flowlabel after a spurious retrans. */
> >         tcp_save_lrcv_flowlabel(sk, skb);
> > diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
> > index 199f0b579e89..ef4b3771e9d8 100644
> > --- a/net/ipv4/tcp_minisocks.c
> > +++ b/net/ipv4/tcp_minisocks.c
> > @@ -750,6 +750,15 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
> >                  * Reset timer after retransmitting SYNACK, similar to
> >                  * the idea of fast retransmit in recovery.
> >                  */
> > +
>
> What is the following part doing?
> tcp_v6_init_req() uses something quite different before setting ir_iif
> A comment explaining the rationale would be nice.
>
> > +#if IS_ENABLED(CONFIG_IPV6)
> > +               if (sk->sk_family == AF_INET6)
> > +                       inet_rsk(req)->ir_iif = tcp_v6_iif(skb);
> > +               else
> > +#endif
> > +                       inet_rsk(req)->ir_iif =
> > +                               inet_request_bound_dev_if(sk, skb);
> > +
> >                 if (!tcp_oow_rate_limited(sock_net(sk), skb,
> >                                           LINUX_MIB_TCPACKSKIPPEDSYNRECV,
> >                                           &tcp_rsk(req)->last_oow_ack_time)) {
> > diff --git a/net/ipv4/tcp_plb.c b/net/ipv4/tcp_plb.c
> > index 68ccdb9a5412..d7cc00a58e53 100644
> > --- a/net/ipv4/tcp_plb.c
> > +++ b/net/ipv4/tcp_plb.c
> > @@ -79,6 +79,7 @@ void tcp_plb_check_rehash(struct sock *sk, struct tcp_plb_state *plb)
> >                 return;
> >
> >         sk_rethink_txhash(sk);
> > +       sk_dst_reset(sk);
> >         plb->consec_cong_rounds = 0;
> >         tcp_sk(sk)->plb_rehash++;
> >         NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPPLBREHASH);
> > diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
> > index ea99988795e7..acc22fc532c2 100644
> > --- a/net/ipv4/tcp_timer.c
> > +++ b/net/ipv4/tcp_timer.c
> > @@ -299,6 +299,7 @@ static int tcp_write_timeout(struct sock *sk)
> >         if (sk_rethink_txhash(sk)) {
> >                 tp->timeout_rehash++;
> >                 __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPTIMEOUTREHASH);
> > +               sk_dst_reset(sk);
> >         }
> >
> >         return 0;
> > diff --git a/net/ipv6/inet6_connection_sock.c b/net/ipv6/inet6_connection_sock.c
> > index 37534e116899..2fe753bb38b4 100644
> > --- a/net/ipv6/inet6_connection_sock.c
> > +++ b/net/ipv6/inet6_connection_sock.c
> > @@ -48,6 +48,11 @@ struct dst_entry *inet6_csk_route_req(const struct sock *sk,
> >         fl6->flowi6_uid = sk_uid(sk);
> >         security_req_classify_flow(req, flowi6_to_flowi_common(fl6));
> >
> > +       if (req->num_retrans)
> > +               fl6->mp_hash = jhash_1word(req->num_retrans,
> > +                                          (__force u32)ireq->ir_rmt_port)
> > +                               >> 1;
>
> Why not setting mp_hash to sk_txhash ?
>
> Why are you using ">> 1" ?
>
> rt6_multipath_hash() seems to be bypassed, it might be time to add a
> comment there
> explaining that mp_hash needs to be 31-bit only...
>
> Perhaps use rt6_multipath_hash() and expand it to use a socket pointer
> to retrieve sk->sk_txhash when/if possible
> instead of yet another flow dissection.
>
> > +
> >         if (!dst) {
> >                 dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_p);
> >                 if (IS_ERR(dst))
> > @@ -70,6 +75,9 @@ struct dst_entry *inet6_csk_route_socket(struct sock *sk,
> >         fl6->saddr = np->saddr;
> >         fl6->flowlabel = np->flow_label;
> >         IP6_ECN_flow_xmit(sk, fl6->flowlabel);
> > +
> > +       if (sk->sk_txhash)
> > +               fl6->mp_hash = sk->sk_txhash >> 1;
>
> Seems inconsistent, and same question about the right shift.
>
>
> >         fl6->flowi6_oif = sk->sk_bound_dev_if;
> >         fl6->flowi6_mark = sk->sk_mark;
> >         fl6->fl6_sport = inet->inet_sport;
> > --
> > 2.52.0
> >

^ permalink raw reply

* Re: [PATCH net-next v2] net: bcmasp: Switch to page pool for RX path
From: Nicolai Buchwitz @ 2026-04-08  6:58 UTC (permalink / raw)
  To: Florian Fainelli
  Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Justin Chen, Vikas Gupta,
	Rajashekar Hudumula, Bhargava Marreddy, Arnd Bergmann,
	Markus Blöchl, Heiner Kallweit, Fernando Fernandez Mancera,
	linux-kernel, bcm-kernel-feedback-list
In-Reply-To: <20260408001813.635679-1-florian.fainelli@broadcom.com>

On 8.4.2026 02:18, Florian Fainelli wrote:
> This shows an improvement of 1.9% in reducing the CPU cycles and data
> cache misses.
> 
> Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
> ---
> Changes in v2:
> 
> - addressed Nicolai's comments by setting the .netdev and .napi members
>   and dropped the useless comment and .dma_dir initialization

> [...]

Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>

^ permalink raw reply

* RE: [PATCH v4 net-next 03/14] net: enetc: add pre-boot initialization for i.MX94 switch
From: Wei Fang @ 2026-04-08  6:55 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Claudiu Manoil, Vladimir Oltean, Clark Wang,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	pabeni@redhat.com, robh@kernel.org, krzk+dt@kernel.org,
	conor+dt@kernel.org, f.fainelli@gmail.com, Frank Li,
	chleroy@kernel.org, horms@kernel.org, linux@armlinux.org.uk,
	andrew@lunn.ch, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	linuxppc-dev@lists.ozlabs.org,
	linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev
In-Reply-To: <20260403011723.1795349-1-kuba@kernel.org>

> > +static int imx94_switch_link_config(struct netc_blk_ctrl *priv,
> > +				    struct device_node *np,
> > +				    bool *swp2_en)
> > +{
> > +	struct device_node *ports;
> > +	int port_id, err = 0;
> 
> Since of_property_read_u32() expects a u32 pointer, does passing a
> signed int pointer here trigger an incompatible pointer types warning
> during compilation?
> 

Okay, I will change port_id to u32 type.

> > +
> > +	ports = netc_get_switch_ports(np);
> > +	if (!ports)
> > +		return -ENODEV;
> > +
> > +	for_each_available_child_of_node_scoped(ports, child) {
> > +		if (of_property_read_u32(child, "reg", &port_id) < 0) {
> 
> If the device tree provides a reg value with the MSB set, port_id
> would be evaluated as a negative number.
> 
> [ ... ]
> >  static int imx94_netcmix_init(struct platform_device *pdev)
> >  {
> >  	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
> >  	struct device_node *np = pdev->dev.of_node;
> > +	bool enetc0_en = false, swp2_en = false;
> >  	u32 val;
> >  	int err;
> >
> >  	for_each_child_of_node_scoped(np, child) {
> >  		for_each_child_of_node_scoped(child, gchild) {
> > -			if (!of_device_is_compatible(gchild, "pci1131,e101"))
> > -				continue;
> > -
> > -			err = imx94_enetc_link_config(priv, gchild);
> > -			if (err)
> > -				return err;
> > +			if (of_device_is_compatible(gchild, "pci1131,e101")) {
> > +				err = imx94_enetc_link_config(priv, gchild,
> > +							      &enetc0_en);
> > +				if (err)
> > +					return err;
> > +			} else if (of_device_is_compatible(gchild,
> > +							   "pci1131,eef2")) {
> 
> Is it possible for the switch node to be disabled in the device tree?
> 
> If so, should we check of_device_is_available() for gchild before proceeding,
> similar to how it is checked for enetc0_en?

The switch may be owned by a guest OS, in this case, the switch node of the
host OS will be disabled, but we still hope that the host OS could do some
configurations for the switch, as the netc_blk_ctrl is owned by host OS, so
we cannot check of_device_is_available() for the switch node.

> 
> Without checking availability, if the disabled switch lacks a ports subnode,
> imx94_switch_link_config() might return -ENODEV and abort the entire probe.

This is the expected behavior, only one of swp2 and enetc0 can be enabled,
as they share the same parallel interface. If all of them are enabled, the driver
will detect this incorrect DTS configuration and return a failure.

> Alternatively, if the disabled switch node has a ports subnode with an
> available port 2, it could spuriously set swp2_en = true and cause a false
> conflict with ENETC 0.
> 
> > +				err = imx94_switch_link_config(priv, gchild,
> > +							       &swp2_en);
> > +				if (err)
> > +					return err;
> > +			}
> >  		}
> >  	}

^ permalink raw reply

* [PATCH 3/3] io_uring/zcrx: fix resource leak and double-free hazard in io_import_umem
From: KobaK @ 2026-04-08  6:54 UTC (permalink / raw)
  To: Jens Axboe, Pavel Begunkov
  Cc: Keith Busch, Ming Lei, io-uring, netdev, linux-kernel, Koba Ko
In-Reply-To: <20260408065408.2017967-1-kobak@nvidia.com>

From: Koba Ko <kobak@nvidia.com>

io_import_umem() has two problems:

1. When io_account_mem() fails, the function returns an error but leaves
   live pinned pages and sg_table in the mem struct without cleaning them
   up. The caller happens to handle this today via io_zcrx_free_area() ->
   io_release_area_mem(), but the contract is fragile.

2. io_release_area_mem() doesn't NULL out mem->pages after kvfree(),
   making it unsafe to call twice. Since io_zcrx_free_area() always
   calls it during teardown, any earlier cleanup call would cause a
   double-free.

Fix both: populate mem fields before io_account_mem() so
io_release_area_mem() can do a proper cleanup on failure, and add
mem->pages = NULL in io_release_area_mem() to make it idempotent.

Fixes: 262ab205180d2 ("io_uring/zcrx: account area memory")
Signed-off-by: Koba Ko <kobak@nvidia.com>
---
 io_uring/zcrx.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 62d693287457f..c9ed1139c7bcd 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -188,6 +188,8 @@ static unsigned long io_count_account_pages(struct page **pages, unsigned nr_pag
 	return res;
 }
 
+static void io_release_area_mem(struct io_zcrx_mem *mem);
+
 static int io_import_umem(struct io_zcrx_ifq *ifq,
 			  struct io_zcrx_mem *mem,
 			  struct io_uring_zcrx_area_reg *area_reg)
@@ -213,16 +215,20 @@ static int io_import_umem(struct io_zcrx_ifq *ifq,
 		return ret;
 	}
 
-	mem->account_pages = io_count_account_pages(pages, nr_pages);
-	ret = io_account_mem(ifq->user, ifq->mm_account, mem->account_pages);
-	if (ret < 0)
-		mem->account_pages = 0;
-
 	mem->sgt = &mem->page_sg_table;
 	mem->pages = pages;
 	mem->nr_folios = nr_pages;
 	mem->size = area_reg->len;
-	return ret;
+
+	mem->account_pages = io_count_account_pages(pages, nr_pages);
+	ret = io_account_mem(ifq->user, ifq->mm_account, mem->account_pages);
+	if (ret < 0) {
+		mem->account_pages = 0;
+		io_release_area_mem(mem);
+		return ret;
+	}
+
+	return 0;
 }
 
 static void io_release_area_mem(struct io_zcrx_mem *mem)
@@ -236,6 +242,7 @@ static void io_release_area_mem(struct io_zcrx_mem *mem)
 		sg_free_table(mem->sgt);
 		mem->sgt = NULL;
 		kvfree(mem->pages);
+		mem->pages = NULL;
 	}
 }
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH 2/3] io_uring/rsrc: use io_cache_free for node in io_buffer_register_bvec error path
From: KobaK @ 2026-04-08  6:54 UTC (permalink / raw)
  To: Jens Axboe, Pavel Begunkov
  Cc: Keith Busch, Ming Lei, io-uring, netdev, linux-kernel, Koba Ko
In-Reply-To: <20260408065408.2017967-1-kobak@nvidia.com>

From: Koba Ko <kobak@nvidia.com>

io_buffer_register_bvec() allocates the rsrc node via
io_rsrc_node_alloc() which pulls from ctx->node_cache. On imu allocation
failure, the node is freed with raw kfree() instead of
io_cache_free(&ctx->node_cache, node), bypassing the cache return path
and wasting a reuse opportunity. Every other error path in this file
correctly uses io_cache_free for nodes.

Fixes: 27cb27b6d5ea4 ("io_uring: add support for kernel registered bvecs")
Signed-off-by: Koba Ko <kobak@nvidia.com>
---
 io_uring/rsrc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index 1b96ab5e98c99..6f46cf9cd13d7 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -961,7 +961,7 @@ int io_buffer_register_bvec(struct io_uring_cmd *cmd, struct request *rq,
 	 */
 	imu = io_alloc_imu(ctx, blk_rq_nr_phys_segments(rq));
 	if (!imu) {
-		kfree(node);
+		io_cache_free(&ctx->node_cache, node);
 		ret = -ENOMEM;
 		goto unlock;
 	}
-- 
2.43.0


^ permalink raw reply related

* [PATCH 1/3] io_uring: fix pinned pages and pages array leak in io_region_pin_pages()
From: KobaK @ 2026-04-08  6:54 UTC (permalink / raw)
  To: Jens Axboe, Pavel Begunkov
  Cc: Keith Busch, Ming Lei, io-uring, netdev, linux-kernel, Koba Ko
In-Reply-To: <20260408065408.2017967-1-kobak@nvidia.com>

From: Koba Ko <kobak@nvidia.com>

When io_pin_pages() succeeds but the subsequent nr_pages sanity check
fires (WARN_ON_ONCE), the function returns -EFAULT without unpinning the
user pages or freeing the kvmalloc'd pages array. The caller's cleanup
via io_free_region() won't help either, because mr->pages was never
assigned — so the entire cleanup block is skipped.

Add unpin_user_pages() and kvfree() before the error return to prevent
the leak.

Fixes: a90558b36ccee ("io_uring/memmap: helper for pinning region pages")
Signed-off-by: Koba Ko <kobak@nvidia.com>
---
 io_uring/memmap.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/io_uring/memmap.c b/io_uring/memmap.c
index e6958968975a8..9f0d3750ce3bc 100644
--- a/io_uring/memmap.c
+++ b/io_uring/memmap.c
@@ -141,8 +141,11 @@ static int io_region_pin_pages(struct io_mapped_region *mr,
 	pages = io_pin_pages(reg->user_addr, size, &nr_pages);
 	if (IS_ERR(pages))
 		return PTR_ERR(pages);
-	if (WARN_ON_ONCE(nr_pages != mr->nr_pages))
+	if (WARN_ON_ONCE(nr_pages != mr->nr_pages)) {
+		unpin_user_pages(pages, nr_pages);
+		kvfree(pages);
 		return -EFAULT;
+	}
 
 	mr->pages = pages;
 	mr->flags |= IO_REGION_F_USER_PROVIDED;
-- 
2.43.0


^ permalink raw reply related

* [PATCH 0/3] io_uring: fix resource leak issues
From: KobaK @ 2026-04-08  6:54 UTC (permalink / raw)
  To: Jens Axboe, Pavel Begunkov
  Cc: Keith Busch, Ming Lei, io-uring, netdev, linux-kernel, Koba Ko

From: Koba Ko <kobak@nvidia.com>

Three resource leak fixes found by code audit:

1. memmap: pinned pages and pages array leak on WARN_ON path in
   io_region_pin_pages() — mr->pages is never assigned so the caller's
   cleanup is a no-op.

2. rsrc: kfree() used instead of io_cache_free() in
   io_buffer_register_bvec() error path — bypasses cache return.

3. zcrx: io_import_umem() leaves live pinned pages in a partially
   initialized struct on io_account_mem() failure, and
   io_release_area_mem() is not idempotent (missing pages = NULL),
   creating a double-free hazard.

Koba Ko (3):
  io_uring: fix pinned pages and pages array leak in
    io_region_pin_pages()
  io_uring/rsrc: use io_cache_free for node in io_buffer_register_bvec
    error path
  io_uring/zcrx: fix resource leak and double-free hazard in
    io_import_umem

 io_uring/memmap.c |  5 ++++-
 io_uring/rsrc.c   |  2 +-
 io_uring/zcrx.c   | 19 +++++++++++++------
 3 files changed, 18 insertions(+), 8 deletions(-)

-- 
2.43.0


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox