* Re: [PATCH v10 net-next 2/5] psp: add new netlink cmd for dev-assoc and dev-disassoc
From: Wei Wang @ 2026-04-07 23:36 UTC (permalink / raw)
To: Daniel Zahka
Cc: netdev, Jakub Kicinski, Willem de Bruijn, David Wei, Andrew Lunn,
David S . Miller, Eric Dumazet, Simon Horman, Wei Wang
In-Reply-To: <2758ac3a-50dc-451a-990a-93e4db9d4bd6@gmail.com>
On Mon, Apr 6, 2026 at 6:27 AM Daniel Zahka <daniel.zahka@gmail.com> wrote:
>
>
> On 4/5/26 1:58 AM, Wei Wang wrote:
> > From: Wei Wang <weibunny@fb.com>
> >
> > The main purpose of this cmd is to be able to associate a
> > non-psp-capable device (e.g. veth or netkit) with a psp device.
> > One use case is if we create a pair of veth/netkit, and assign 1 end
> > inside a netns, while leaving the other end within the default netns,
> > with a real PSP device, e.g. netdevsim or a physical PSP-capable NIC.
> > With this command, we could associate the veth/netkit inside the netns
> > with PSP device, so the virtual device could act as PSP-capable device
> > to initiate PSP connections, and performs PSP encryption/decryption on
> > the real PSP device.
> >
> > Signed-off-by: Wei Wang <weibunny@fb.com>
> > ---
> > Documentation/netlink/specs/psp.yaml | 67 +++++-
> > include/net/psp/types.h | 15 ++
> > include/uapi/linux/psp.h | 13 ++
> > net/psp/psp-nl-gen.c | 32 +++
> > net/psp/psp-nl-gen.h | 2 +
> > net/psp/psp_main.c | 20 ++
> > net/psp/psp_nl.c | 319 ++++++++++++++++++++++++++-
> > 7 files changed, 457 insertions(+), 11 deletions(-)
> >
> ...
> >
> > +/**
> > + * Admin version of psp_device_get_locked() where it returns psd only if
> > + * current netns is the same as psd->main_netdev's netns.
> > + */
> > int psp_device_get_locked_admin(const struct genl_split_ops *ops,
> > struct sk_buff *skb, struct genl_info *info)
> > {
> > return __psp_device_get_locked(ops, skb, info, true);
> > }
> >
> > +/**
> > + * Non-admin version of psp_device_get_locked() where it returns psd in netns
> > + * for not only psd->main_netdev but all netdevs in psd->assoc_dev_list.
> > + */
> > int psp_device_get_locked(const struct genl_split_ops *ops,
> > struct sk_buff *skb, struct genl_info *info)
> > {
> > @@ -103,11 +179,74 @@ psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
> > sockfd_put(socket);
> > }
>
>
> There's a warning that these comments have the kdoc open sequence, but
> are not proper kdoc comments.
>
> > +
> > static int
> > psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp,
> > const struct genl_info *info)
> > {
> > + struct net *cur_net;
> > void *hdr;
> > + int err;
> > +
> > + cur_net = genl_info_net(info);
> > +
> > + /* Skip this device if we're in an associated netns but have no
> > + * associated devices in cur_net
> > + */
> > + if (cur_net != dev_net(psd->main_netdev) &&
> > + !psp_has_assoc_dev_in_ns(psd, cur_net))
> > + return 0;
> >
>
>
> Is this branch dead code given we either arrived here via
> psp_dev_check_access(), or psp_nl_build_dev_ntf() which should only use
> associated netns's?
>
Yes. But should we keep this check to prevent future usage of this
function to misbehave?
> >
> > +int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info)
> > +{
> > + struct psp_dev *psd = info->user_ptr[0];
> > + struct psp_assoc_dev *psp_assoc_dev;
> > + struct net_device *assoc_dev;
> > + struct sk_buff *rsp;
> > + u32 assoc_ifindex;
> > + struct net *net;
> > + int nsid;
> > +
> > + if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX))
> > + return -EINVAL;
> > +
> > + if (info->attrs[PSP_A_DEV_NSID]) {
> > + nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
> > +
> > + net = get_net_ns_by_id(genl_info_net(info), nsid);
> > + if (!net) {
> > + NL_SET_BAD_ATTR(info->extack,
> > + info->attrs[PSP_A_DEV_NSID]);
> > + return -EINVAL;
> > + }
> > + } else {
> > + net = get_net(genl_info_net(info));
> > + }
> > +
> > + psp_assoc_dev = kzalloc(sizeof(*psp_assoc_dev), GFP_KERNEL);
> > + if (!psp_assoc_dev) {
> > + put_net(net);
> > + return -ENOMEM;
> > + }
> > +
> > + assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
> > + assoc_dev = netdev_get_by_index(net, assoc_ifindex,
> > + &psp_assoc_dev->dev_tracker,
> > + GFP_KERNEL);
> > + if (!assoc_dev) {
> > + put_net(net);
> > + kfree(psp_assoc_dev);
> > + NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
> > + return -ENODEV;
> > + }
> > +
> > + /* Check if device is already associated with a PSP device */
> > + if (cmpxchg(&assoc_dev->psp_dev, NULL, RCU_INITIALIZER(psd))) {
> > + NL_SET_ERR_MSG(info->extack,
> > + "Device already associated with a PSP device");
> > + netdev_put(assoc_dev, &psp_assoc_dev->dev_tracker);
> > + put_net(net);
> > + kfree(psp_assoc_dev);
> > + return -EBUSY;
> > + }
> > +
> > + psp_assoc_dev->assoc_dev = assoc_dev;
> > + rsp = psp_nl_reply_new(info);
> > + if (!rsp) {
> > + rcu_assign_pointer(assoc_dev->psp_dev, NULL);
> > + netdev_put(assoc_dev, &psp_assoc_dev->dev_tracker);
> > + put_net(net);
> > + kfree(psp_assoc_dev);
> > + return -ENOMEM;
> > + }
> > +
> > + list_add_tail(&psp_assoc_dev->dev_list, &psd->assoc_dev_list);
> > +
> > + put_net(net);
> > +
> > + psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
> > +
> > + return psp_nl_reply_send(rsp, info);
> > +}
> > +
>
>
> This function could probably benefit from a goto style cleanup chain,
> given the overlapping set of actions to unwind at each error.
>
Ack. Updating in the new version.
> >
> > int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
> > @@ -320,7 +617,9 @@ int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
> >
> > psd = psp_dev_get_for_sock(socket->sk);
> > if (psd) {
> > + mutex_lock(&psd->lock);
> > err = psp_dev_check_access(psd, genl_info_net(info), false);
> > + mutex_unlock(&psd->lock);
>
>
> This looks like a "TOCTOU" issue on the mutable assoc_dev_list, but I
> think it ends up being a benign race.
Yea. Will address in the next version.
>
>
> > if (err) {
> > psp_dev_put(psd);
> > psd = NULL;
>
>
> Some minor comments, but otherwise:
>
> Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
>
>
^ permalink raw reply
* Re: [PATCH v9 02/10] x86/bhi: Make clear_bhb_loop() effective on newer CPUs
From: Dave Hansen @ 2026-04-07 23:41 UTC (permalink / raw)
To: Jim Mattson, Pawan Gupta
Cc: x86, Jon Kohler, Nikolay Borisov, H. Peter Anvin, Josh Poimboeuf,
David Kaplan, Sean Christopherson, Borislav Petkov, Dave Hansen,
Peter Zijlstra, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, KP Singh, Jiri Olsa, David S. Miller,
David Laight, Andy Lutomirski, Thomas Gleixner, Ingo Molnar,
David Ahern, Martin KaFai Lau, Eduard Zingerman, Song Liu,
Yonghong Song, John Fastabend, Stanislav Fomichev, Hao Luo,
Paolo Bonzini, Jonathan Corbet, linux-kernel, kvm, Asit Mallick,
Tao Zhang, bpf, netdev, linux-doc, chao.gao
In-Reply-To: <CALMp9eQjSqwnvJz4JVzYpMkkTiucSJtW48zC4Hj9GBiUhOH-Eg@mail.gmail.com>
On 4/7/26 16:27, Jim Mattson wrote:
> What is your proposed BHI_DIS_S override mechanism, then?
Let me make sure I get this right. The desire is to:
1. Have hypervisors lie to guests about the CPU they are running on (for
the benefit of large/diverse migration pools)
2. Have guests be allowed to boot with BHI_DIS_S for performance
3. Have apps in those guests that care about security to opt back in to
BHI_DIS_S for themselves?
^ permalink raw reply
* Re: [PATCH net-next v1] r8169: implement get_module functions for rtl8127atf
From: Andrew Lunn @ 2026-04-07 23:50 UTC (permalink / raw)
To: Fabio Baltieri
Cc: Heiner Kallweit, nic_swsd, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King - ARM Linux, netdev, linux-kernel
In-Reply-To: <adWIy9d9VgDha_ub@google.com>
> > Yes, that would be best. Call i2c_add_adapter() to add it to the I2C
> > core. The SFP code in drivers/net/phy can then make use of it.
>
> Hey Andrew, thanks for the pointers, I was able to instantiate the dw
> i2c controller, same as txgbe/txgbe_phy.c really.
Cool.
> For some reasons I had to revert 560072246088, was getting a "spurious
> STOP detected" but I'll follow up on that separately.
I2C transfers need to be multiples of 2 bytes, in order for the HWMON
to work correctly. You need to read the 16 bits in a single
operation. If they get chopped up into multiple transfers they become
unreliable. So it would be good to get to the bottom of this.
> Anyway I was able to make detection and los work, so I have some code
> with gpio, i2c and the sfp module detect and showing up in hwmon, and
> sitting in waitdev state.
Nice.
> >
> > And you will need a PCS driver.
> >
> > But first step is probably to work with the existing Base-T devices
> > and convert the driver to phylink.
>
> Ok I was going to ask how to connect the one above with the ethtool
> handlers, so you are saying that first the whole driver needs to be
> converted to use phylink/pcs and then that should work automatically-ish
> right?
Yes, once phylink is coordinating all the parts, accessing the module
via ethtool should just start working.
> Can I send the current code I have as an RFC in the meantime to get some
> early feedback?
Sure.
> It's a pretty substantial amount of boilerplate code so I suspect Heiner
> may want to refactor things about it, feels like it could live in its
> own file.
How much is identical to the txgbe? I wounder if it makes sense to add
some helpers which can be shared by both drivers?
Andrew
^ permalink raw reply
* Re: [PATCH v10 net-next 3/5] psp: add a new netdev event for dev unregister
From: Wei Wang @ 2026-04-08 0:01 UTC (permalink / raw)
To: Daniel Zahka
Cc: netdev, Jakub Kicinski, Willem de Bruijn, David Wei, Andrew Lunn,
David S . Miller, Eric Dumazet, Simon Horman, Wei Wang
In-Reply-To: <c502c837-6f01-4854-bd6b-e81aba940dc8@gmail.com>
On Mon, Apr 6, 2026 at 7:12 AM Daniel Zahka <daniel.zahka@gmail.com> wrote:
>
>
> On 4/5/26 1:58 AM, Wei Wang wrote:
> > From: Wei Wang <weibunny@fb.com>
> >
> > Add a new netdev event for dev unregister and handle the removal of this
> > dev from psp->assoc_dev_list, upon the first dev-assoc operation.
> >
> > Signed-off-by: Wei Wang <weibunny@fb.com>
> > ---
> ...
> > +static bool psp_notifier_registered;
> > +
> > +/**
> > + * psp_attach_netdev_notifier() - register netdev notifier on first use
> > + *
> > + * Register the netdevice notifier when the first device association
> > + * is created. In many installations no associations will be created and
> > + * the notifier won't be needed.
> > + *
> > + * Must be called without psd->lock held, due to lock ordering:
> > + * rtnl_lock -> psd->lock (the notifier callback runs under rtnl_lock
> > + * and takes psd->lock).
> > + */
> > +int psp_attach_netdev_notifier(void)
> > +{
> > + int err = 0;
> > +
> > + if (READ_ONCE(psp_notifier_registered))
> > + return 0;
> > +
> > + mutex_lock(&psp_devs_lock);
> > + if (!psp_notifier_registered) {
> > + err = register_netdevice_notifier(&psp_netdev_notifier);
> > + if (!err)
> > + WRITE_ONCE(psp_notifier_registered, true);
> > + }
> > + mutex_unlock(&psp_devs_lock);
> > +
> > + return err;
> > +}
> > +
>
>
> This looks like an abuse of the psp_devs_lock to provide mutual
> exclusion on psp_notifier_registered, which has the undesirable property
> of establishing a lock dependency from psp_devs_lock -> rtnl_lock.
> Perhaps a dedicated mutex would be cleaner.
>
Ack. Will address in the next version.
> >
> > +/**
> > + * Non-admin version of psp_device_get_locked() + psp_attach_netdev_notifier()
> > + * only used for dev-assoc.
> > + */
> > +int psp_device_get_locked_dev_assoc(const struct genl_split_ops *ops,
> > + struct sk_buff *skb, struct genl_info *info)
>
>
> kdoc open, but not a kdoc comment.
Ack.
>
> Minor comments, otherwise:
>
> Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
>
^ permalink raw reply
* Re: [PATCH net-next 13/13] netfilter: ctnetlink: restrict expectfn to helper
From: Pablo Neira Ayuso @ 2026-04-08 0:05 UTC (permalink / raw)
To: Florian Westphal; +Cc: netdev
In-Reply-To: <adUUTtWT8ITs83It@strlen.de>
On Tue, Apr 07, 2026 at 04:27:26PM +0200, Florian Westphal wrote:
> Florian Westphal <fw@strlen.de> wrote:
> > list_for_each_entry_rcu(cur, &nf_ct_helper_expectfn_list, head) {
> > - if (!strcmp(cur->name, name)) {
> > + if ((cur->helper && !strcmp(cur->helper, helper)) ||
> > + !strcmp(cur->name, name)) {
>
> Sigh, I don't know why I did not see this earlier. It looks wrong.
>
> Should this be:
>
> if ((cur->helper && strcmp(cur->helper, helper))
> continue; // skip, name doesn't match
>
> if (!strcmp(cur->name, name)) {
> ...
>
> as is, this restriction has no effect in case the requested
> name matches?
>
> AI suggests
>
> if ((cur->helper && !strcmp(cur->helper, helper)) &&
> !strcmp(cur->name, name)) {
>
> ... but i think thats bogus too. What to do?
>
> Send v2 or do you want to followup later?
Keep it back, not urgent.
I should have withdraw this patch, I wanted to use an enum instead of
strings in v2.
Sorry.
^ permalink raw reply
* Re: [PATCH net-next v5 00/10] Decouple receive and transmit enablement in team driver
From: Marc Harvey @ 2026-04-08 0:12 UTC (permalink / raw)
To: Jiri Pirko
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <utlz3oebjyd4lqp5pd4qdbth2kywxo4hrlrnwcxsx3br76fwfg@vy3tx5f3lv32>
On Tue, Apr 7, 2026 at 4:55 AM Jiri Pirko <jiri@resnulli.us> wrote:
>
> Mon, Apr 06, 2026 at 05:03:36AM +0200, marcharvey@google.com wrote:
> >Allow independent control over receive and transmit enablement states
> >for aggregated ports in the team driver.
> >
> >The motivation is that IEE 802.3ad LACP "independent control" can't
> >be implemented for the team driver currently. This was added to the
> >bonding driver in commit 240fd405528b ("bonding: Add independent
> >control state machine").
> >
> >This series also has a few patches that add tests to show that the old
> >coupled enablement still works and that the new decoupled enablement
> >works as intended (4, 5, and 10).
> >
> >There are three patches with small fixes as well, with the goal of
> >making the final decoupling patch clearer (1, 2, and 3).
>
> Looks fine to me now. Do you have libteam/teamd counterpart?
I don't see a need for this to be used in any of the teamd runners.
Libteam should support this out of the box, since the options are
identified over netlink by their string names. The options.sh test
uses teamnl, which uses libteam, to set the new options.
^ permalink raw reply
* [PATCH net] tcp: update window_clamp when SO_RCVBUF is set
From: Jakub Kicinski @ 2026-04-08 0:14 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, Jakub Kicinski,
ncardwell, kuniyu, willemb, dsahern, quic_subashab, quic_stranche
Commit under Fixes moved recomputing the window clamp to
tcp_measure_rcv_mss() (when scaling_ratio changes).
I suspect it missed the fact that we don't recompute the clamp
when rcvbuf is set. Until scaling_ratio changes we are
stuck with the old window clamp which may be based on
the small initial buffer. scaling_ratio may never change.
Inspired by Eric's recent commit d1361840f8c5 ("tcp: fix
SO_RCVLOWAT and RCVBUF autotuning") plumb the user action
thru to TCP and have it update the clamp.
A smaller fix would be to just have tcp_rcvbuf_grow()
adjust the clamp even if SOCK_RCVBUF_LOCK is set.
But IIUC this is what we were trying to get away from
in the first place.
Fixes: a2cbb1603943 ("tcp: Update window clamping condition")
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
CC: ncardwell@google.com
CC: kuniyu@google.com
CC: willemb@google.com
CC: dsahern@kernel.org
CC: quic_subashab@quicinc.com
CC: quic_stranche@quicinc.com
---
include/linux/net.h | 1 +
include/net/tcp.h | 1 +
net/core/sock.c | 9 +++++++++
net/ipv4/af_inet.c | 1 +
net/ipv4/tcp.c | 5 +++++
net/ipv6/af_inet6.c | 1 +
6 files changed, 18 insertions(+)
diff --git a/include/linux/net.h b/include/linux/net.h
index a8e818de95b3..ca6a7bc5c9ae 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -223,6 +223,7 @@ struct proto_ops {
int (*sendmsg_locked)(struct sock *sk, struct msghdr *msg,
size_t size);
int (*set_rcvlowat)(struct sock *sk, int val);
+ void (*set_rcvbuf)(struct sock *sk, int val);
};
#define DECLARE_SOCKADDR(type, dst, src) \
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 6156d1d068e1..b9db447892dd 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -516,6 +516,7 @@ void tcp_syn_ack_timeout(const struct request_sock *req);
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
int flags);
int tcp_set_rcvlowat(struct sock *sk, int val);
+void tcp_set_rcvbuf(struct sock *sk, int val);
int tcp_set_window_clamp(struct sock *sk, int val);
static inline void
diff --git a/net/core/sock.c b/net/core/sock.c
index fdaf66e6dc18..f3a186376bc5 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -973,6 +973,8 @@ EXPORT_SYMBOL(sock_set_keepalive);
static void __sock_set_rcvbuf(struct sock *sk, int val)
{
+ struct socket *sock = sk->sk_socket;
+
/* Ensure val * 2 fits into an int, to prevent max_t() from treating it
* as a negative value.
*/
@@ -990,6 +992,13 @@ static void __sock_set_rcvbuf(struct sock *sk, int val)
* we actually used in getsockopt is the most desirable behavior.
*/
WRITE_ONCE(sk->sk_rcvbuf, max_t(int, val * 2, SOCK_MIN_RCVBUF));
+
+ if (sock) {
+ const struct proto_ops *ops = READ_ONCE(sock->ops);
+
+ if (ops->set_rcvbuf)
+ ops->set_rcvbuf(sk, sk->sk_rcvbuf);
+ }
}
void sock_set_rcvbuf(struct sock *sk, int val)
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index f98e46ae3e30..0e62032e76b1 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1091,6 +1091,7 @@ const struct proto_ops inet_stream_ops = {
.compat_ioctl = inet_compat_ioctl,
#endif
.set_rcvlowat = tcp_set_rcvlowat,
+ .set_rcvbuf = tcp_set_rcvbuf,
};
EXPORT_SYMBOL(inet_stream_ops);
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index e57eaffc007a..1a494d18c5fd 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -1858,6 +1858,11 @@ int tcp_set_rcvlowat(struct sock *sk, int val)
return 0;
}
+void tcp_set_rcvbuf(struct sock *sk, int val)
+{
+ tcp_set_window_clamp(sk, tcp_win_from_space(sk, val));
+}
+
#ifdef CONFIG_MMU
static const struct vm_operations_struct tcp_vm_ops = {
};
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index ee341a8254bf..0a88b376141d 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -690,6 +690,7 @@ const struct proto_ops inet6_stream_ops = {
.compat_ioctl = inet6_compat_ioctl,
#endif
.set_rcvlowat = tcp_set_rcvlowat,
+ .set_rcvbuf = tcp_set_rcvbuf,
};
EXPORT_SYMBOL_GPL(inet6_stream_ops);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2] net: bcmasp: Switch to page pool for RX path
From: Florian Fainelli @ 2026-04-08 0:18 UTC (permalink / raw)
To: netdev
Cc: Florian Fainelli, 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,
open list, open list:BROADCOM ASP 2.0 ETHERNET DRIVER,
Nicolai Buchwitz
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
drivers/net/ethernet/broadcom/Kconfig | 1 +
drivers/net/ethernet/broadcom/asp2/bcmasp.h | 8 +-
.../net/ethernet/broadcom/asp2/bcmasp_intf.c | 125 +++++++++++++++---
.../ethernet/broadcom/asp2/bcmasp_intf_defs.h | 4 +
4 files changed, 115 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig
index dd164acafd01..4287edc7ddd6 100644
--- a/drivers/net/ethernet/broadcom/Kconfig
+++ b/drivers/net/ethernet/broadcom/Kconfig
@@ -272,6 +272,7 @@ config BCMASP
depends on OF
select PHYLIB
select MDIO_BCM_UNIMAC
+ select PAGE_POOL
help
This configuration enables the Broadcom ASP 2.0 Ethernet controller
driver which is present in Broadcom STB SoCs such as 72165.
diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp.h b/drivers/net/ethernet/broadcom/asp2/bcmasp.h
index 29cd87335ec8..8c8ffaeadc79 100644
--- a/drivers/net/ethernet/broadcom/asp2/bcmasp.h
+++ b/drivers/net/ethernet/broadcom/asp2/bcmasp.h
@@ -6,6 +6,7 @@
#include <linux/phy.h>
#include <linux/io-64-nonatomic-hi-lo.h>
#include <uapi/linux/ethtool.h>
+#include <net/page_pool/helpers.h>
#define ASP_INTR2_OFFSET 0x1000
#define ASP_INTR2_STATUS 0x0
@@ -298,16 +299,19 @@ struct bcmasp_intf {
void __iomem *rx_edpkt_cfg;
void __iomem *rx_edpkt_dma;
int rx_edpkt_index;
- int rx_buf_order;
struct bcmasp_desc *rx_edpkt_cpu;
dma_addr_t rx_edpkt_dma_addr;
dma_addr_t rx_edpkt_dma_read;
dma_addr_t rx_edpkt_dma_valid;
- /* RX buffer prefetcher ring*/
+ /* Streaming RX data ring (RBUF_4K mode) */
void *rx_ring_cpu;
dma_addr_t rx_ring_dma;
dma_addr_t rx_ring_dma_valid;
+ int rx_buf_order;
+
+ /* Page pool for recycling RX SKB data pages */
+ struct page_pool *rx_page_pool;
struct napi_struct rx_napi;
struct bcmasp_res res;
diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c
index b368ec2fea43..ec63f50a849e 100644
--- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c
+++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c
@@ -15,6 +15,7 @@
#include <linux/platform_device.h>
#include <net/ip.h>
#include <net/ipv6.h>
+#include <net/page_pool/helpers.h>
#include "bcmasp.h"
#include "bcmasp_intf_defs.h"
@@ -482,10 +483,14 @@ static int bcmasp_rx_poll(struct napi_struct *napi, int budget)
struct bcmasp_desc *desc;
struct sk_buff *skb;
dma_addr_t valid;
+ struct page *page;
void *data;
u64 flags;
u32 len;
+ /* Hardware advances DMA_VALID as it writes each descriptor
+ * (RBUF_4K streaming mode); software chases with rx_edpkt_dma_read.
+ */
valid = rx_edpkt_dma_rq(intf, RX_EDPKT_DMA_VALID) + 1;
if (valid == intf->rx_edpkt_dma_addr + DESC_RING_SIZE)
valid = intf->rx_edpkt_dma_addr;
@@ -493,12 +498,12 @@ static int bcmasp_rx_poll(struct napi_struct *napi, int budget)
while ((processed < budget) && (valid != intf->rx_edpkt_dma_read)) {
desc = &intf->rx_edpkt_cpu[intf->rx_edpkt_index];
- /* Ensure that descriptor has been fully written to DRAM by
- * hardware before reading by the CPU
+ /* Ensure the descriptor has been fully written to DRAM by
+ * the hardware before the CPU reads it.
*/
rmb();
- /* Calculate virt addr by offsetting from physical addr */
+ /* Locate the packet data inside the streaming ring buffer. */
data = intf->rx_ring_cpu +
(DESC_ADDR(desc->buf) - intf->rx_ring_dma);
@@ -524,19 +529,38 @@ static int bcmasp_rx_poll(struct napi_struct *napi, int budget)
len = desc->size;
- skb = napi_alloc_skb(napi, len);
- if (!skb) {
+ /* Allocate a page pool page as the SKB data area so the
+ * kernel can recycle it efficiently after the packet is
+ * consumed, avoiding repeated slab allocations.
+ */
+ page = page_pool_dev_alloc_pages(intf->rx_page_pool);
+ if (!page) {
u64_stats_update_begin(&stats->syncp);
u64_stats_inc(&stats->rx_dropped);
u64_stats_update_end(&stats->syncp);
intf->mib.alloc_rx_skb_failed++;
+ goto next;
+ }
+ skb = napi_build_skb(page_address(page), PAGE_SIZE);
+ if (!skb) {
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->rx_dropped);
+ u64_stats_update_end(&stats->syncp);
+ intf->mib.alloc_rx_skb_failed++;
+ page_pool_recycle_direct(intf->rx_page_pool, page);
goto next;
}
+ /* Reserve headroom then copy the full descriptor payload
+ * (hardware prepends a 2-byte alignment pad at the start).
+ */
+ skb_reserve(skb, NET_SKB_PAD);
skb_put(skb, len);
memcpy(skb->data, data, len);
+ skb_mark_for_recycle(skb);
+ /* Skip the 2-byte hardware alignment pad. */
skb_pull(skb, 2);
len -= 2;
if (likely(intf->crc_fwd)) {
@@ -558,6 +582,7 @@ static int bcmasp_rx_poll(struct napi_struct *napi, int budget)
u64_stats_update_end(&stats->syncp);
next:
+ /* Return this portion of the streaming ring buffer to HW. */
rx_edpkt_cfg_wq(intf, (DESC_ADDR(desc->buf) + desc->size),
RX_EDPKT_RING_BUFFER_READ);
@@ -661,12 +686,31 @@ static void bcmasp_adj_link(struct net_device *dev)
phy_print_status(phydev);
}
-static int bcmasp_alloc_buffers(struct bcmasp_intf *intf)
+static struct page_pool *
+bcmasp_rx_page_pool_create(struct bcmasp_intf *intf)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .flags = 0,
+ .pool_size = NUM_4K_BUFFERS,
+ .nid = NUMA_NO_NODE,
+ .dev = &intf->parent->pdev->dev,
+ .napi = &intf->rx_napi,
+ .netdev = intf->ndev,
+ .offset = 0,
+ .max_len = PAGE_SIZE,
+ };
+
+ return page_pool_create(&pp_params);
+}
+
+static int bcmasp_alloc_rx_buffers(struct bcmasp_intf *intf)
{
struct device *kdev = &intf->parent->pdev->dev;
struct page *buffer_pg;
+ int ret;
- /* Alloc RX */
+ /* Contiguous streaming ring that hardware writes packet data into. */
intf->rx_buf_order = get_order(RING_BUFFER_SIZE);
buffer_pg = alloc_pages(GFP_KERNEL, intf->rx_buf_order);
if (!buffer_pg)
@@ -675,13 +719,55 @@ static int bcmasp_alloc_buffers(struct bcmasp_intf *intf)
intf->rx_ring_cpu = page_to_virt(buffer_pg);
intf->rx_ring_dma = dma_map_page(kdev, buffer_pg, 0, RING_BUFFER_SIZE,
DMA_FROM_DEVICE);
- if (dma_mapping_error(kdev, intf->rx_ring_dma))
- goto free_rx_buffer;
+ if (dma_mapping_error(kdev, intf->rx_ring_dma)) {
+ ret = -ENOMEM;
+ goto free_ring_pages;
+ }
+
+ /* Page pool for SKB data areas (copy targets, not DMA buffers). */
+ intf->rx_page_pool = bcmasp_rx_page_pool_create(intf);
+ if (IS_ERR(intf->rx_page_pool)) {
+ ret = PTR_ERR(intf->rx_page_pool);
+ intf->rx_page_pool = NULL;
+ goto free_ring_dma;
+ }
+
+ return 0;
+
+free_ring_dma:
+ dma_unmap_page(kdev, intf->rx_ring_dma, RING_BUFFER_SIZE,
+ DMA_FROM_DEVICE);
+free_ring_pages:
+ __free_pages(buffer_pg, intf->rx_buf_order);
+ return ret;
+}
+
+static void bcmasp_reclaim_rx_buffers(struct bcmasp_intf *intf)
+{
+ struct device *kdev = &intf->parent->pdev->dev;
+
+ page_pool_destroy(intf->rx_page_pool);
+ intf->rx_page_pool = NULL;
+ dma_unmap_page(kdev, intf->rx_ring_dma, RING_BUFFER_SIZE,
+ DMA_FROM_DEVICE);
+ __free_pages(virt_to_page(intf->rx_ring_cpu), intf->rx_buf_order);
+}
+
+static int bcmasp_alloc_buffers(struct bcmasp_intf *intf)
+{
+ struct device *kdev = &intf->parent->pdev->dev;
+ int ret;
+
+ /* Alloc RX */
+ ret = bcmasp_alloc_rx_buffers(intf);
+ if (ret)
+ return ret;
intf->rx_edpkt_cpu = dma_alloc_coherent(kdev, DESC_RING_SIZE,
- &intf->rx_edpkt_dma_addr, GFP_KERNEL);
+ &intf->rx_edpkt_dma_addr,
+ GFP_KERNEL);
if (!intf->rx_edpkt_cpu)
- goto free_rx_buffer_dma;
+ goto free_rx_buffers;
/* Alloc TX */
intf->tx_spb_cpu = dma_alloc_coherent(kdev, DESC_RING_SIZE,
@@ -701,11 +787,8 @@ static int bcmasp_alloc_buffers(struct bcmasp_intf *intf)
free_rx_edpkt_dma:
dma_free_coherent(kdev, DESC_RING_SIZE, intf->rx_edpkt_cpu,
intf->rx_edpkt_dma_addr);
-free_rx_buffer_dma:
- dma_unmap_page(kdev, intf->rx_ring_dma, RING_BUFFER_SIZE,
- DMA_FROM_DEVICE);
-free_rx_buffer:
- __free_pages(buffer_pg, intf->rx_buf_order);
+free_rx_buffers:
+ bcmasp_reclaim_rx_buffers(intf);
return -ENOMEM;
}
@@ -717,9 +800,7 @@ static void bcmasp_reclaim_free_buffers(struct bcmasp_intf *intf)
/* RX buffers */
dma_free_coherent(kdev, DESC_RING_SIZE, intf->rx_edpkt_cpu,
intf->rx_edpkt_dma_addr);
- dma_unmap_page(kdev, intf->rx_ring_dma, RING_BUFFER_SIZE,
- DMA_FROM_DEVICE);
- __free_pages(virt_to_page(intf->rx_ring_cpu), intf->rx_buf_order);
+ bcmasp_reclaim_rx_buffers(intf);
/* TX buffers */
dma_free_coherent(kdev, DESC_RING_SIZE, intf->tx_spb_cpu,
@@ -738,7 +819,7 @@ static void bcmasp_init_rx(struct bcmasp_intf *intf)
/* Make sure channels are disabled */
rx_edpkt_cfg_wl(intf, 0x0, RX_EDPKT_CFG_ENABLE);
- /* Rx SPB */
+ /* Streaming data ring: hardware writes raw packet bytes here. */
rx_edpkt_cfg_wq(intf, intf->rx_ring_dma, RX_EDPKT_RING_BUFFER_READ);
rx_edpkt_cfg_wq(intf, intf->rx_ring_dma, RX_EDPKT_RING_BUFFER_WRITE);
rx_edpkt_cfg_wq(intf, intf->rx_ring_dma, RX_EDPKT_RING_BUFFER_BASE);
@@ -747,7 +828,9 @@ static void bcmasp_init_rx(struct bcmasp_intf *intf)
rx_edpkt_cfg_wq(intf, intf->rx_ring_dma_valid,
RX_EDPKT_RING_BUFFER_VALID);
- /* EDPKT */
+ /* EDPKT descriptor ring: hardware fills descriptors pointing into
+ * the streaming ring buffer above (RBUF_4K mode).
+ */
rx_edpkt_cfg_wl(intf, (RX_EDPKT_CFG_CFG0_RBUF_4K <<
RX_EDPKT_CFG_CFG0_DBUF_SHIFT) |
(RX_EDPKT_CFG_CFG0_64_ALN <<
diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf_defs.h b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf_defs.h
index af7418348e81..0318f257452a 100644
--- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf_defs.h
+++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf_defs.h
@@ -246,6 +246,10 @@
((((intf)->channel - 6) * 0x14) + 0xa2000)
#define RX_SPB_TOP_BLKOUT 0x00
+/*
+ * Number of 4 KB pages that make up the contiguous RBUF_4K streaming ring
+ * and the page pool used as copy-target SKB data areas.
+ */
#define NUM_4K_BUFFERS 32
#define RING_BUFFER_SIZE (PAGE_SIZE * NUM_4K_BUFFERS)
--
2.34.1
^ permalink raw reply related
* [PATCH net] eth: fbnic: Use wake instead of start
From: Mohsin Bashir @ 2026-04-08 0:24 UTC (permalink / raw)
To: netdev
Cc: alexanderduyck, alok.a.tiwari, andrew+netdev, davem, dg573847474,
edumazet, horms, jacob.e.keller, kernel-team, kuba, lee,
linux-kernel, mohsin.bashr, pabeni
From: Mohsin Bashir <hmohsin@meta.com>
fbnic_up() calls netif_tx_start_all_queues(), which only clears
__QUEUE_STATE_DRV_XOFF. If qdisc backlog has accumulated on any TX
queue before the reconfiguration (e.g. ring resize via ethtool -G),
start does not call __netif_schedule() to kick the qdisc, so the
pending backlog is never drained and the queue stalls.
Switch to netif_tx_wake_all_queues(), which clears DRV_XOFF and also
calls __netif_schedule() on every queue, ensuring any backlog that
built up before the down/up cycle is promptly dequeued.
Fixes: bc6107771bb4 ("eth: fbnic: Allocate a netdevice and napi vectors with queues")
Signed-off-by: Mohsin Bashir <hmohsin@meta.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
index 3fa9d1910daa..8f331358c972 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
@@ -139,7 +139,7 @@ void fbnic_up(struct fbnic_net *fbn)
/* Enable Tx/Rx processing */
fbnic_napi_enable(fbn);
- netif_tx_start_all_queues(fbn->netdev);
+ netif_tx_wake_all_queues(fbn->netdev);
fbnic_service_task_start(fbn);
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 0/2] tcp: rehash onto different ECMP path on retransmit timeout
From: Neil Spring @ 2026-04-08 0:28 UTC (permalink / raw)
To: netdev
Cc: edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
shuah, linux-kselftest
I configured an ECMP route across two local interfaces, expecting
autoflowlabel to adapt when one of those interfaces is blocked in
reaching the destination. However, it did not. This affected SYN,
SYN/ACK, and established traffic.
I was able to make this work as I expected it to by calling
sk_dst_reset() at times when txhash was updated, and propagating
sk_txhash into fl6->mp_hash so fib6_select_path() uses the socket's
current hash for ECMP selection.
I expected autoflowlabel to apply to local ECMP routes, and think
it should, but am open to feedback if this isn't the right way to
do it.
Patch 1 has the kernel changes; patch 2 adds a selftest exercising
SYN, SYN/ACK, and established connection rehash over a two-path
ECMP topology. The selftest retries a SYN 26 times, so has a tiny
(~3e-8) probability of false failure if repeatedly unlucky with ECMP
path selection.
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 | 9 +
net/ipv4/tcp_plb.c | 1 +
net/ipv4/tcp_timer.c | 1 +
net/ipv6/inet6_connection_sock.c | 8 +
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/ecmp_rehash.sh | 354 +++++++++++++++++++++
7 files changed, 377 insertions(+), 1 deletion(-)
create mode 100755 tools/testing/selftests/net/ecmp_rehash.sh
--
2.52.0
^ permalink raw reply
* [PATCH net-next 1/2] tcp: rehash onto different ECMP path on retransmit timeout
From: Neil Spring @ 2026-04-08 0:28 UTC (permalink / raw)
To: netdev
Cc: edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
shuah, linux-kselftest
In-Reply-To: <20260408002802.2448424-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 | 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.
*/
+
+#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;
+
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;
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 2/2] selftests: net: add ECMP rehash test
From: Neil Spring @ 2026-04-08 0:28 UTC (permalink / raw)
To: netdev
Cc: edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
shuah, linux-kselftest
In-Reply-To: <20260408002802.2448424-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 | 354 +++++++++++++++++++++
2 files changed, 355 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..a062c0b51fd6
--- /dev/null
+++ b/tools/testing/selftests/net/ecmp_rehash.sh
@@ -0,0 +1,354 @@
+#!/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=6 (~127 s
+# timeout) and tcp_syn_linear_timeouts=20 there are roughly 26
+# independent rehash attempts, 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 deterministically.
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_syn_retries=6
+ ip netns exec "$NS1" sysctl -qw net.ipv4.tcp_syn_linear_timeouts=20
+
+ 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.
+ ip netns exec "$NS1" bash -c \
+ "echo test | socat - \
+ 'TCP6:[fd00:ff::2]:$PORT,bind=[fd00:ff::1],connect-timeout=60'" \
+ >"/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 [ $rc -ne 0 ] || [[ "$result" != *"ESTABLISH_OK"* ]]; then
+ check_err 1 "connection failed after unblocking: $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" bash -c \
+ "echo test | socat - \
+ 'TCP6:[fd00:ff::2]:$port,bind=[fd00:ff::1],connect-timeout=60'" \
+ >"/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 [ $rc -ne 0 ] || [[ "$result" != *"SYNACK_OK"* ]]; then
+ check_err 1 "connection failed after unblocking: $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
* Re: [PATCH net-next v2 3/4] bpf-timestamp: keep track of the skb when wait_for_space occurs
From: Jason Xing @ 2026-04-08 0:35 UTC (permalink / raw)
To: Willem de Bruijn
Cc: davem, edumazet, kuba, pabeni, horms, willemb, martin.lau, netdev,
bpf, Jason Xing, Yushan Zhou
In-Reply-To: <willemdebruijn.kernel.1b49845d3acc7@gmail.com>
On Wed, Apr 8, 2026 at 5:17 AM Willem de Bruijn
<willemdebruijn.kernel@gmail.com> wrote:
>
> Jason Xing wrote:
> > On Tue, Apr 7, 2026 at 11:33 AM Jason Xing <kerneljasonxing@gmail.com> wrote:
> > >
> > > On Mon, Apr 6, 2026 at 10:37 PM Willem de Bruijn
> > > <willemdebruijn.kernel@gmail.com> wrote:
> > > >
> > > > Jason Xing wrote:
> > > > > On Mon, Apr 6, 2026 at 10:28 AM Willem de Bruijn
> > > > > <willemdebruijn.kernel@gmail.com> wrote:
> > > > > >
> > > > > > Jason Xing wrote:
> > > > > > > From: Jason Xing <kernelxing@tencent.com>
> > > > > > >
> > > > > > > The patch is the 1/2 part of push-level granularity feature.
> > > > > > >
> > > > > > > Tag the skb in tcp_sendmsg_locked() when wait_for_space occurs even
> > > > > > > though it might not carry the last byte of the sendmsg.
> > > > > > >
> > > > > > > Prior to the patch, BPF timestamping cannot cover this case:
> > > > > > > The following steps reproduce this:
> > > > > > > 1) skb A is the current last skb before entering wait_for_space process
> > > > > > > 2) tcp_push() pushes A without any tag
> > > > > > > 3) A is transmitted from TCP to driver without putting any skb carrying
> > > > > > > timestamps in the error queue, like SCHED, DRV/HARDWARE.
> > > > > > > 4) sk_stream_wait_memory() sleeps for a while and then returns with an
> > > > > > > error code. Note that the socket lock is released.
> > > > > > > 5) skb A finally gets acked and removed from the rtx queue.
> > > > > > > 6) continue with the rest of tcp_sendmsg_locked(): it will jump to(goto)
> > > > > > > 'do_error' label and then 'out' label.
> > > > > > > 7) at this moment, skb A turns out to be the last one in this send
> > > > > > > syscall, and miss the following tcp_bpf_tx_timestamp() opportunity
> > > > > > > before the final tcp_push()
> > > > > > > 8) BPF script fails to see any timestamps this time
> > > > > > >
> > > > > > > Signed-off-by: Yushan Zhou <katrinzhou@tencent.com>
> > > > > > > Signed-off-by: Jason Xing <kernelxing@tencent.com>
> > > > > > > ---
> > > > > > > net/ipv4/tcp.c | 4 +++-
> > > > > > > 1 file changed, 3 insertions(+), 1 deletion(-)
> > > > > > >
> > > > > > > diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
> > > > > > > index c603b90057f6..7d030a11d004 100644
> > > > > > > --- a/net/ipv4/tcp.c
> > > > > > > +++ b/net/ipv4/tcp.c
> > > > > > > @@ -1400,9 +1400,11 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
> > > > > > > wait_for_space:
> > > > > > > set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
> > > > > > > tcp_remove_empty_skb(sk);
> > > > > > > - if (copied)
> > > > > > > + if (copied) {
> > > > > > > + tcp_bpf_tx_timestamp(sk);
> > > > > > > tcp_push(sk, flags & ~MSG_MORE, mss_now,
> > > > > > > TCP_NAGLE_PUSH, size_goal);
> > > > > >
> > > > > > Now the number of skbs that will be tracked will be unpredictable,
> > > > > > varying based on memory pressure.
> > > > >
> > > > > Right, I put some effort into writing a selftests to check how many
> > > > > push functions get called at one time and failed to do so.
> > > > >
> > > > > >
> > > > > > That sounds hard to use to me. Especially if these extra pushes
> > > > > > cannot be identified as such.
> > > > > >
> > > > > > Perhaps if all skbs from the same sendmsg call can be identified,
> > > > > > that would help explain pattern in data resulting from these
> > > > > > uncommon extra data points.
> > > > >
> > > > > You meant move tcp_bpf_tx_timestamp before tcp_skb_entail()? That is
> > > > > close to packet basis without considering fragmentation of skb :)
> > > >
> > > > No, I meant somehow in the notification having a way to identify all
> > > > the skbs belonging to the same sendmsg call, to allow filtering on
> > > > that. But I also don't immediately see how to do that (without adding
> > > > yet another counter say).
> > >
> > > If we don't build the relationship between skb and sendmsg (just like
> > > the SENDMSG sock option), we will have no clue on how to calculate. If
> > > we only take care of the skb from the view of the syscall layer, it's
> > > fine by moving tcp_bpf_tx_timestamp() before tcp_skb_entail(). But in
> > > terms of per skb even generated beneath TCP due to gso/tso, there is
> > > only one way to correlate: adding an additional member in the skb
> > > structure to store its sendmsg time. This discussion is only suitable
> > > for use cases like net_timestamping.
> > >
> > > Well, my key point is that, I have to admit, the above (including
> > > existing bpf script net_timestamping) is a less effective way which
> > > definitely harms the performance because of the extremely frequent
> > > look-up process. It's not suitable for 7x24 observability in
> > > production. What we've done internally is make the kernel layer as
> > > lightweight/easy as possible and let the timestamping feature throw
> > > each record into a ring buffer that the application can read, sort and
> > > calculate. This arch survives the performance. But that's simply what
> > > the design of the kernel module is, given the fast deployment in
> > > production. I suppose in the future we could build a userspace tool
> > > like blktrace to monitor efficiently instead of the selftest sample.
> > > Honestly I don't like look-up action.
> > >
> > > Since we're modifying the kernel, how about adding a new member to
> > > record sendmsg time which bpf script is able to read. The whole
> > > scenario looks like this:
> > > 1) in tcp_sendmsg_locked(), record the sendmsg time for each skb
> > > 2) in either tso_fragment() or tcp_gso_tstamp(), each new skb will get
> > > a copy of its original skb
> > > 3) in each stage, bpf script reads the skb's sendmsg time and the
> > > current time, and then effortlessly do the math.
> > >
> > > At this point, what I had in mind is we have two options:
> > > 1) only handle the skb from the view of the send syscall layer, which
> > > is, for sure, very simple but not thorough.
> > > 2) stick to a pure authentic packet basis, then adding a new member
> > > seems inevitable. so the question would be where to add? The space of
> > > the skb structure is very precious :(
> >
> > Finding a suitable place to put this timestamp is really hard. IIRC,
> > we can't expand the size of struct skb_shared_info so easily since
> > it's a global effect.
> >
> > I'm wondering if we can turn the per-packet mode into a non-compatible
> > feature by reusing 'u32 tskey' to store a microsecond timestamp of
> > sendmsg.
>
> Agreed that an extra field is hard. We should avoid that.
Avoiding adding a new one makes the whole work extremely hard. I'm
wondering since we have hwtstamp in shared info, why not add a
software one for timestamping use? Then, we would support more
different protocols in more different stages in a finer grain, which
is a big coarse picture in my mind.
Adding a software bit will completely reduce the whole complexity and
be very easy to use. Would you expect to see a draft by adding such a
bit first?
Or just like I mentioned, repurposing tskey seems an alternative,
which, however, makes the new feature incompatible.
>
> If the purpose is to group skbs by sendmsg call (e.g., to filter out
> all but the last one), it is probably also unnecessary.
>
> From a process PoV, since the process knows the sendmsg len and each
> skb has a tskey in byte offset, it can correlate the skb with a given
> sendmsg buffer.
>
> The BPF program is under control of a third-party admin. So that does
> not follow directly. But it can be passed additional metadata.
>
> I thought about passing the offset of the skb from the start of the
> sendmsg buffer to identify all consecutive skbs for a sendmsg call,
> as each new buffer will start with an skb with offset 0 ..
>
> .. but that won't work as there is no guarantee that a sendmsg call
> will not append to an existing outstanding skb.
Right. TCP is way too complex and we indeed see some tough issues when
trying to deploy the feature. So my humble take is to make the design
as simple as possible.
>
> Anyway, the general idea is to pass to the BPF program through
> bpf_skops_tx_timestamping some relevant signal , without having to
> expand either skb or sk itself.
>
> I hear you on that measuring every skb is too frequent. But is calling
> the BPF program and letting it decide whether to measure too? BPF
> program invocation itself should be cheap.
Oh, I was clear enough. Sorry. I meant tracing per skb is definitely
an awesome way to go. My ultimate goal is to do so. Instead of letting
people implement various fine grained bpf progs, we can provide a very
easy/understandable/efficient approach with more samples. It should be
very beneficial.
>
> If per-push is preferable, with a filter ability like the above, it
> seems more useful to me already.
Push-level is a compromise plan. Packet-level is what I always pursue :)
The current series has this ability: the bpf prog noticed it's a
SENDMSG sock option and will selectively call
bpf_sock_ops_enable_tx_tstamp() to do so. Only by calling
bpf_sock_ops_enable_tx_tstamp() could the skb be tracked.
Thanks,
Jason
^ permalink raw reply
* Re: [PATCH net-next v1] r8169: implement get_module functions for rtl8127atf
From: Andrew Lunn @ 2026-04-08 0:37 UTC (permalink / raw)
To: Fabio Baltieri
Cc: Heiner Kallweit, nic_swsd, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King - ARM Linux, netdev, linux-kernel
In-Reply-To: <adWIy9d9VgDha_ub@google.com>
> Also find an issue
> in the txgbe driver (I think?), sent
> https://lore.kernel.org/netdev/20260405222013.5347-1-fabio.baltieri@gmail.com/
> for it.
I looked at that, and it seems like you are correct, but i also don't
understand why it has not crashed. So it would be good to get some
feedback from the trustnetic and net-swift people.
Andrew
^ permalink raw reply
* Re: [PATCH net-next] selftests: net: py: add test case filtering and listing
From: Willem de Bruijn @ 2026-04-08 0:38 UTC (permalink / raw)
To: Jakub Kicinski, davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, Jakub Kicinski,
shuah, petrm, willemb, linux-kselftest
In-Reply-To: <20260407151715.3800579-1-kuba@kernel.org>
Jakub Kicinski wrote:
> When developing new test cases and reproducing failures in
> existing ones we currently have to run the entire test which
> can take minutes to finish.
>
> Add command line options for test selection, modeled after
> kselftest_harness.h:
>
> -l list tests (all or filtered)
> -t name include test
> -T name exclude test
>
> Since we don't have as clean separation into fixture / variant /
> test as kselftest_harness this is not really a 1 to 1 match.
> We have to lean on glob patterns instead.
>
> Like in kselftest_harness filters are evaluated in order, first
> match wins. If only exclusions are specified everything else is
> included and vice versa.
>
> Glob patterns (*, ?, [) are supported in addition to exact
> matching.
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Willem de Bruijn <willemb@google.com>
^ permalink raw reply
* Re: [PATCH v9 02/10] x86/bhi: Make clear_bhb_loop() effective on newer CPUs
From: Jim Mattson @ 2026-04-08 0:47 UTC (permalink / raw)
To: Dave Hansen
Cc: Pawan Gupta, x86, Jon Kohler, Nikolay Borisov, H. Peter Anvin,
Josh Poimboeuf, David Kaplan, Sean Christopherson,
Borislav Petkov, Dave Hansen, Peter Zijlstra, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, KP Singh, Jiri Olsa,
David S. Miller, David Laight, Andy Lutomirski, Thomas Gleixner,
Ingo Molnar, David Ahern, Martin KaFai Lau, Eduard Zingerman,
Song Liu, Yonghong Song, John Fastabend, Stanislav Fomichev,
Hao Luo, Paolo Bonzini, Jonathan Corbet, linux-kernel, kvm,
Asit Mallick, Tao Zhang, bpf, netdev, linux-doc, chao.gao
In-Reply-To: <a605fb45-f8e3-45ad-8924-1da43b9a9e05@intel.com>
On Tue, Apr 7, 2026 at 4:41 PM Dave Hansen <dave.hansen@intel.com> wrote:
>
> On 4/7/26 16:27, Jim Mattson wrote:
> > What is your proposed BHI_DIS_S override mechanism, then?
>
> Let me make sure I get this right. The desire is to:
>
> 1. Have hypervisors lie to guests about the CPU they are running on (for
> the benefit of large/diverse migration pools)
> 2. Have guests be allowed to boot with BHI_DIS_S for performance
> 3. Have apps in those guests that care about security to opt back in to
> BHI_DIS_S for themselves?
I just want guests on heterogeneous migration pools to properly
protect themselves from native BHI when running on host kernels at
least as far back as Linux v6.6.
To that end, I would be satisfied with using the longer BHB clearing
sequence when HYPERVISOR is true and BHI_CTRL is false.
^ permalink raw reply
* Re: [PATCH net-next] vsock/virtio: remove unnecessary call to `virtio_transport_get_ops`
From: Jakub Kicinski @ 2026-04-08 0:59 UTC (permalink / raw)
To: Luigi Leonardi
Cc: Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Stefan Hajnoczi, Stefano Garzarella, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Arseniy Krasnov, kvm,
virtualization, netdev, linux-kernel
In-Reply-To: <adUvRegULccN0hiL@leonardi-redhat>
On Tue, 7 Apr 2026 18:22:57 +0200 Luigi Leonardi wrote:
> >> Fixes: 581512a6dc93 ("vsock/virtio: MSG_ZEROCOPY flag support")
> >> Signed-off-by: Luigi Leonardi <leonardi@redhat.com>
> >
> >Acked-by: Michael S. Tsirkin <mst@redhat.com>
> >
> >but I think we should drop the Fixes tag.
>
> ok, should I send a v2 without the tag?
Yes please
--
pw-bot: cr
^ permalink raw reply
* Re: [PATCH net-next 00/13] netfilter: updates for net-next
From: Jakub Kicinski @ 2026-04-08 1:00 UTC (permalink / raw)
To: Florian Westphal
Cc: netdev, Paolo Abeni, David S. Miller, Eric Dumazet,
netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>
On Tue, 7 Apr 2026 16:15:27 +0200 Florian Westphal wrote:
> netfilter pull request nf-next-26-04-07
IIUC plan is to send v2, LMK if I misread
^ permalink raw reply
* Re: [PATCH 6/8] smb: smbdirect: add in kernel only support for IPPROTO_SMBDIRECT
From: Kuniyuki Iwashima @ 2026-04-08 1:04 UTC (permalink / raw)
To: Stefan Metzmacher
Cc: linux-cifs, samba-technical, Steve French, Tom Talpey, Long Li,
Namjae Jeon, David Howells, Henrique Carvalho, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
linux-kernel
In-Reply-To: <4563333e367417d98a32779ae1358c0964eb6e79.1775571957.git.metze@samba.org>
On Tue, Apr 7, 2026 at 7:47 AM Stefan Metzmacher <metze@samba.org> wrote:
>
> For userspace callers of socket() still get -EPROTONOSUPPORT,
> so we are sure we'll only have in kernel callers, cifs.ko and
> ksmbd.ko, for now. This makes it possible to relax the
> constrains generic stream socket consumers would otherwise
> assume.
>
> There's a prototype for userspace sockets on top of
> this and there's working userspace code for Samba as
> client and server, so this is just the first step,
> but a very important one.
>
> The SMBDIRECT protocol is defined in [MS-SMBD] by Microsoft.
> It is used as wrapper around RDMA in order to provide a transport for SMB3,
> but Microsoft also uses it as transport for other protocols.
>
> SMBDIRECT works over Infiniband, RoCE and iWarp.
> RoCEv2 is based on IP/UDP and iWarp is based on IP/TCP,
> so these use IP addresses natively.
> Infiniband and RoCEv1 require IPOIB in order to be used for
> SMBDIRECT.
>
> So instead of adding a PF_SMBDIRECT, which would only use AF_INET[6],
> we use IPPROTO_SMBDIRECT instead, this uses a number not
> allocated from IANA, as it would not appear in an IP header.
Overall I don't see the upside of reusing AF_INET6? infra. It just
adds an unnecessary sk->sk_prot layer, which can be simply
implemented as sock->ops.
It seems only inet_getname() is the valid user of inet_sock.
>
> This is similar to IPPROTO_SMC, IPPROTO_MPTCP and IPPROTO_QUIC,
> which are linux specific values for the socket() syscall.
SMBDIRECT seems more like SMC, rather than MPTCP and QUIC.
SMC was implemented with AF_SMC first, and IPPROT_SMC
was added just to hook at userspace and silently convert TCP
sockets to AF_SMC sockets easily.
But the is not the case of SMBDIRECT because the socket is
only created from kernel space.
>
> socket(AF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT);
> socket(AF_INET6, SOCK_STREAM, IPPROTO_SMBDIRECT);
>
> This will allow the existing smbdirect code used by
> cifs.ko and ksmbd.ko to be moved behind the socket layer,
Reusing AF_INET6? is not related to this statement as long
as the upper layer uses in-kernel socket API (sock_XXX()/kernel_XXX())
instead of calling sk->sk_prot->XXX() directly.
> so that there's less special handling. Only sock_sendmsg()
> sock_recvmsg() are used, so that the main stream handling
> is done all the same for tcp, smbdirect and later also quic.
>
> The special RDMA read/write handling will be via direct
> function calls as they are currently done for the in kernel
> consumers.
>
> For now the core smbdirect code still supports both
> modes, direct calls in indirect via the socket layer.
> The core code uses if (sc->sk.sk_family) as indication
> for the new socket mode. Once cifs.ko and ksmbd.ko
> are converted we can remove the old mode slowly,
> but I'll deferr that to a future patchset.
>
> There's still a way to go in order to make this
> as generic as tcp and quic e.g. adding MSG_SPLICE_PAGES support or
> splice_read/read_sock/read_skb.
>
> But it's a good start, which will make changes
> much easier.
>
> Cc: Steve French <smfrench@gmail.com>
> Cc: Tom Talpey <tom@talpey.com>
> Cc: Long Li <longli@microsoft.com>
> Cc: Namjae Jeon <linkinjeon@kernel.org>
> Cc: David Howells <dhowells@redhat.com>
> Cc: Henrique Carvalho <henrique.carvalho@suse.com>
> Cc: linux-cifs@vger.kernel.org
> Cc: samba-technical@lists.samba.org
> Cc: David S. Miller <davem@davemloft.net>
> Cc: Eric Dumazet <edumazet@google.com>
> Cc: Jakub Kicinski <kuba@kernel.org>
> Cc: Paolo Abeni <pabeni@redhat.com>
> Cc: Simon Horman <horms@kernel.org>
> Cc: Kuniyuki Iwashima <kuniyu@google.com>
> Cc: Willem de Bruijn <willemb@google.com>
> Cc: netdev@vger.kernel.org
> Cc: Xin Long <lucien.xin@gmail.com>
> Cc: quic@lists.linux.dev
> Cc: linux-rdma@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Stefan Metzmacher <metze@samba.org>
> ---
> fs/smb/common/smbdirect/Makefile | 1 +
> fs/smb/common/smbdirect/smbdirect.h | 62 +
> fs/smb/common/smbdirect/smbdirect_accept.c | 14 +-
> .../common/smbdirect/smbdirect_connection.c | 58 +
> fs/smb/common/smbdirect/smbdirect_devices.c | 2 +-
> fs/smb/common/smbdirect/smbdirect_internal.h | 59 +-
> fs/smb/common/smbdirect/smbdirect_listen.c | 49 +-
> fs/smb/common/smbdirect/smbdirect_main.c | 45 +
> fs/smb/common/smbdirect/smbdirect_mr.c | 10 +
> fs/smb/common/smbdirect/smbdirect_proto.c | 1549 +++++++++++++++++
> fs/smb/common/smbdirect/smbdirect_public.h | 3 +
> fs/smb/common/smbdirect/smbdirect_rw.c | 29 +-
> fs/smb/common/smbdirect/smbdirect_socket.c | 147 ++
> fs/smb/common/smbdirect/smbdirect_socket.h | 26 +-
> 14 files changed, 2039 insertions(+), 15 deletions(-)
> create mode 100644 fs/smb/common/smbdirect/smbdirect_proto.c
>
> diff --git a/fs/smb/common/smbdirect/Makefile b/fs/smb/common/smbdirect/Makefile
> index 423f533e1002..fcff485d7c45 100644
> --- a/fs/smb/common/smbdirect/Makefile
> +++ b/fs/smb/common/smbdirect/Makefile
> @@ -10,6 +10,7 @@ smbdirect-y := \
> smbdirect_connection.o \
> smbdirect_mr.o \
> smbdirect_rw.o \
> + smbdirect_proto.o \
> smbdirect_debug.o \
> smbdirect_connect.o \
> smbdirect_listen.o \
> diff --git a/fs/smb/common/smbdirect/smbdirect.h b/fs/smb/common/smbdirect/smbdirect.h
> index bbab5f7f7cc9..cf3d4957f94c 100644
> --- a/fs/smb/common/smbdirect/smbdirect.h
> +++ b/fs/smb/common/smbdirect/smbdirect.h
> @@ -6,7 +6,10 @@
> #ifndef __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
> #define __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
>
> +#include <linux/stddef.h>
> #include <linux/types.h>
> +#include <linux/socket.h>
> +#include <asm/ioctls.h>
>
> /* SMB-DIRECT buffer descriptor V1 structure [MS-SMBD] 2.2.3.1 */
> struct smbdirect_buffer_descriptor_v1 {
> @@ -49,4 +52,63 @@ struct smbdirect_socket_parameters {
> SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB | \
> SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
>
> +enum {
> + __SMBDIRECT_BUFFER_REMOTE_INVALIDATE = 0x20,
> +};
> +
> +struct smbdirect_cmsg_buffer {
> + uint8_t msg_control[CMSG_SPACE(24)];
> +};
> +
> +static __always_inline
> +void __smbdirect_cmsg_prepare(struct msghdr *msg,
> + struct smbdirect_cmsg_buffer *cbuffer,
> + int cmsg_type,
> + const void *payload,
> + size_t payloadlen)
> +{
> + size_t cmsg_space = CMSG_SPACE(payloadlen);
> + size_t cmsg_len = CMSG_LEN(payloadlen);
> + struct cmsghdr *cmsg = NULL;
> + void *dataptr = NULL;
> +
> + BUILD_BUG_ON(cmsg_space > sizeof(cbuffer->msg_control));
> +
> + memset(cbuffer, 0, sizeof(*cbuffer));
> +
> + msg->msg_control = cbuffer->msg_control;
> + msg->msg_controllen = cmsg_space;
> +
> + cmsg = CMSG_FIRSTHDR(msg);
> + cmsg->cmsg_level = SOL_SMBDIRECT;
> + cmsg->cmsg_type = cmsg_type;
> + cmsg->cmsg_len = cmsg_len;
> + dataptr = CMSG_DATA(cmsg);
> + memcpy(dataptr, payload, payloadlen);
> + msg->msg_controllen = cmsg->cmsg_len;
> +}
> +
> +struct smbdirect_buffer_remote_invalidate_args {
> + __u32 remote_token;
> +} __packed;
> +#define SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE \
> + _IOW('S', __SMBDIRECT_BUFFER_REMOTE_INVALIDATE, \
> + struct smbdirect_buffer_remote_invalidate_args)
> +
> +static __always_inline
> +void smbdirect_buffer_remote_invalidate_cmsg_prepare(struct msghdr *msg,
> + struct smbdirect_cmsg_buffer *cbuffer,
> + const __u32 *remote_token)
> +{
> + if (remote_token) {
> + struct smbdirect_buffer_remote_invalidate_args args = {
> + .remote_token = *remote_token,
> + };
> +
> + __smbdirect_cmsg_prepare(msg, cbuffer,
> + SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE,
> + &args, sizeof(args));
> + }
> +}
> +
> #endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__ */
> diff --git a/fs/smb/common/smbdirect/smbdirect_accept.c b/fs/smb/common/smbdirect/smbdirect_accept.c
> index d6d5e6a3f5de..6d7d869cdbc3 100644
> --- a/fs/smb/common/smbdirect/smbdirect_accept.c
> +++ b/fs/smb/common/smbdirect/smbdirect_accept.c
> @@ -6,7 +6,6 @@
> */
>
> #include "smbdirect_internal.h"
> -#include <net/sock.h>
> #include "../../common/smb2status.h"
>
> static int smbdirect_accept_rdma_event_handler(struct rdma_cm_id *id,
> @@ -460,6 +459,12 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work)
> spin_lock_irqsave(&lsc->listen.lock, flags);
> list_del(&sc->accept.list);
> list_add_tail(&sc->accept.list, &lsc->listen.ready);
> + if (lsc->sk.sk_family) {
> + struct sock *lsk = &lsc->sk;
> +
> + if (!sock_flag(lsk, SOCK_DEAD) && lsk->sk_socket)
> + lsk->sk_data_ready(lsk);
> + }
> wake_up(&lsc->listen.wait_queue);
> spin_unlock_irqrestore(&lsc->listen.lock, flags);
>
> @@ -774,11 +779,13 @@ static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long
> {
> long ret;
>
> + smbdirect_socket_sk_unlock(lsc);
> ret = wait_event_interruptible_timeout(lsc->listen.wait_queue,
> !list_empty_careful(&lsc->listen.ready) ||
> lsc->status != SMBDIRECT_SOCKET_LISTENING ||
> lsc->first_error,
> timeo);
> + smbdirect_socket_sk_lock(lsc);
> if (lsc->status != SMBDIRECT_SOCKET_LISTENING)
> return -EINVAL;
> if (lsc->first_error)
> @@ -850,6 +857,11 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
> * order to grant credits to the peer.
> */
> nsc->status = SMBDIRECT_SOCKET_CONNECTED;
> + if (nsc->sk.sk_family) {
> + struct sock *nsk = &nsc->sk;
> +
> + inet_sk_set_state(nsk, TCP_ESTABLISHED);
> + }
> smbdirect_accept_negotiate_finish(nsc, 0);
>
> return nsc;
> diff --git a/fs/smb/common/smbdirect/smbdirect_connection.c b/fs/smb/common/smbdirect/smbdirect_connection.c
> index 1e946f78e935..2c426aefd16d 100644
> --- a/fs/smb/common/smbdirect/smbdirect_connection.c
> +++ b/fs/smb/common/smbdirect/smbdirect_connection.c
> @@ -153,6 +153,15 @@ void smbdirect_connection_rdma_established(struct smbdirect_socket *sc)
>
> sc->rdma.cm_id->event_handler = smbdirect_connection_rdma_event_handler;
> sc->rdma.expected_event = RDMA_CM_EVENT_DISCONNECTED;
> +
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + smbdirect_socket_sync_saddr_to_sk(sc, NULL);
> + smbdirect_socket_sync_daddr_to_sk(sc);
> +
> + inet_sk_set_state(sk, TCP_SYN_RECV);
> + }
> }
>
> void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
> @@ -189,6 +198,13 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
> smbdirect_socket_status_string(sc->status),
> SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
> sc->status = SMBDIRECT_SOCKET_CONNECTED;
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + inet_sk_set_state(sk, TCP_ESTABLISHED);
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_socket->state = SS_CONNECTED;
> + }
>
> /*
> * We need to setup the refill and send immediate work
> @@ -203,6 +219,13 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
> &sc->rdma.cm_id->route.addr.src_addr,
> &sc->rdma.cm_id->route.addr.dst_addr);
>
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_state_change(sk);
> + }
> +
> wake_up(&sc->status_wait);
> }
>
> @@ -739,10 +762,12 @@ int smbdirect_connection_wait_for_connected(struct smbdirect_socket *sc)
> "waiting for connection: device: %.*s local: %pISpsfc remote: %pISpsfc\n",
> IB_DEVICE_NAME_MAX, devname, src, dst);
>
> + smbdirect_socket_sk_unlock(sc);
> ret = wait_event_interruptible_timeout(sc->status_wait,
> sc->status == SMBDIRECT_SOCKET_CONNECTED ||
> sc->first_error,
> msecs_to_jiffies(sp->negotiate_timeout_msec));
> + smbdirect_socket_sk_lock(sc);
> if (sc->rdma.cm_id) {
> /*
> * Maybe src and dev are updated in the meantime.
> @@ -954,6 +979,12 @@ int smbdirect_connection_send_batch_flush(struct smbdirect_socket *sc,
> atomic_add(batch->credit, &sc->send_io.bcredits.count);
> batch->credit = 0;
> wake_up(&sc->send_io.bcredits.wait_queue);
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_write_space(sk);
> + }
> }
>
> return ret;
> @@ -1091,6 +1122,8 @@ int smbdirect_connection_send_single_iter(struct smbdirect_socket *sc,
> u32 data_length = 0;
> int ret;
>
> + smbdirect_socket_sk_owned_by_me(sc);
> +
> if (WARN_ON_ONCE(flags))
> return -EINVAL; /* no flags support for now */
>
> @@ -1150,10 +1183,12 @@ int smbdirect_connection_send_single_iter(struct smbdirect_socket *sc,
> * wait until either the refill work or the peer
> * granted new credits
> */
> + smbdirect_socket_sk_unlock(sc);
> ret = wait_event_interruptible(sc->send_io.credits.wait_queue,
> atomic_read(&sc->send_io.credits.count) >= 1 ||
> atomic_read(&sc->recv_io.credits.available) >= 1 ||
> sc->status != SMBDIRECT_SOCKET_CONNECTED);
> + smbdirect_socket_sk_lock(sc);
> if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
> ret = -ENOTCONN;
> if (ret < 0)
> @@ -1268,9 +1303,11 @@ int smbdirect_connection_send_wait_zero_pending(struct smbdirect_socket *sc)
> * that means all the I/Os have been out and we are good to return
> */
>
> + smbdirect_socket_sk_unlock(sc);
> wait_event(sc->send_io.pending.zero_wait_queue,
> atomic_read(&sc->send_io.pending.count) == 0 ||
> sc->status != SMBDIRECT_SOCKET_CONNECTED);
> + smbdirect_socket_sk_lock(sc);
> if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
> smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
> "status=%s first_error=%1pe => %1pe\n",
> @@ -1297,6 +1334,8 @@ int smbdirect_connection_send_iter(struct smbdirect_socket *sc,
> int error = 0;
> __be32 hdr;
>
> + smbdirect_socket_sk_owned_by_me(sc);
> +
> if (WARN_ONCE(flags, "unexpected flags=0x%x\n", flags))
> return -EINVAL; /* no flags support for now */
>
> @@ -1448,7 +1487,9 @@ static void smbdirect_connection_send_immediate_work(struct work_struct *work)
> smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_INFO,
> "send an empty message\n");
> sc->statistics.send_empty++;
> + smbdirect_socket_sk_lock(sc);
> ret = smbdirect_connection_send_single_iter(sc, NULL, NULL, 0, 0);
> + smbdirect_socket_sk_unlock(sc);
> if (ret < 0) {
> smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
> "smbdirect_connection_send_single_iter ret=%1pe\n",
> @@ -1632,6 +1673,12 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
> * If any sender is waiting for credits, unblock it
> */
> wake_up(&sc->send_io.credits.wait_queue);
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_write_space(sk);
> + }
> }
>
> /* Send an immediate response right away if requested */
> @@ -1652,6 +1699,12 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
>
> smbdirect_connection_reassembly_append_recv_io(sc, recv_io, data_length);
> wake_up(&sc->recv_io.reassembly.wait_queue);
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_data_ready(sk);
> + }
> } else
> smbdirect_connection_put_recv_io(recv_io);
>
> @@ -1735,6 +1788,9 @@ int smbdirect_connection_recv_io_refill(struct smbdirect_socket *sc)
> /*
> * If the last send credit is waiting for credits
> * it can grant we need to wake it up
> + *
> + * This needs to wake up smbdirect_connection_send_single_iter()
> + * only, so we don't need sk->sk_write_space() here.
> */
> if (atomic_read(&sc->send_io.bcredits.count) == 0 &&
> atomic_read(&sc->send_io.credits.count) == 0)
> @@ -1922,9 +1978,11 @@ int smbdirect_connection_recvmsg(struct smbdirect_socket *sc,
>
> smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
> "wait_event on more data\n");
> + smbdirect_socket_sk_unlock(sc);
> ret = wait_event_interruptible(sc->recv_io.reassembly.wait_queue,
> sc->recv_io.reassembly.data_length >= size ||
> sc->status != SMBDIRECT_SOCKET_CONNECTED);
> + smbdirect_socket_sk_lock(sc);
> /* Don't return any data if interrupted */
> if (ret)
> return ret;
> diff --git a/fs/smb/common/smbdirect/smbdirect_devices.c b/fs/smb/common/smbdirect/smbdirect_devices.c
> index aaab99e9c045..da0edc104e48 100644
> --- a/fs/smb/common/smbdirect/smbdirect_devices.c
> +++ b/fs/smb/common/smbdirect/smbdirect_devices.c
> @@ -257,7 +257,7 @@ __init int smbdirect_devices_init(void)
> return 0;
> }
>
> -__exit void smbdirect_devices_exit(void)
> +__cold void smbdirect_devices_exit(void)
> {
> struct smbdirect_device *sdev, *tmp;
>
> diff --git a/fs/smb/common/smbdirect/smbdirect_internal.h b/fs/smb/common/smbdirect/smbdirect_internal.h
> index 30a1b8643657..517ff0533032 100644
> --- a/fs/smb/common/smbdirect/smbdirect_internal.h
> +++ b/fs/smb/common/smbdirect/smbdirect_internal.h
> @@ -12,8 +12,6 @@
> #include "smbdirect_pdu.h"
> #include "smbdirect_public.h"
>
> -#include <linux/mutex.h>
> -
> struct smbdirect_module_state {
> struct mutex mutex;
>
> @@ -30,6 +28,8 @@ struct smbdirect_module_state {
> rwlock_t lock;
> struct list_head list;
> } devices;
> +
> + struct smbdirect_socket_parameters default_parameters;
> };
>
> extern struct smbdirect_module_state smbdirect_globals;
> @@ -46,10 +46,58 @@ struct smbdirect_device {
> char ib_name[IB_DEVICE_NAME_MAX];
> };
>
> +static __always_inline void smbdirect_socket_sk_owned_by_me(struct smbdirect_socket *sc)
> +{
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> + }
> +}
> +
> +static __always_inline void smbdirect_socket_sk_not_owned_by_me(struct smbdirect_socket *sc)
> +{
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> + }
> +}
> +
> +static __always_inline void smbdirect_socket_sk_lock(struct smbdirect_socket *sc)
> +{
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + lock_sock(sk);
> + }
> +}
> +
> +static __always_inline void smbdirect_socket_sk_unlock(struct smbdirect_socket *sc)
> +{
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + release_sock(sk);
> + }
> +}
> +
> int smbdirect_socket_init_new(struct net *net, struct smbdirect_socket *sc);
>
> int smbdirect_socket_init_accepting(struct rdma_cm_id *id, struct smbdirect_socket *sc);
>
> +int smbdirect_socket_sync_saddr_to_sk(struct smbdirect_socket *sc, bool *_is_any_addr);
> +
> +int smbdirect_socket_sync_daddr_to_sk(struct smbdirect_socket *sc);
> +
> void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
> const char *macro_name,
> unsigned int lvl,
> @@ -135,7 +183,12 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
>
> void smbdirect_accept_negotiate_finish(struct smbdirect_socket *sc, u32 ntstatus);
>
> +void smbdirect_sk_reclassify(struct sock *sk);
> +
> __init int smbdirect_devices_init(void);
> -__exit void smbdirect_devices_exit(void);
> +__cold void smbdirect_devices_exit(void);
> +
> +__init int smbdirect_proto_init(void);
> +__exit void smbdirect_proto_exit(void);
>
> #endif /* __FS_SMB_COMMON_SMBDIRECT_INTERNAL_H__ */
> diff --git a/fs/smb/common/smbdirect/smbdirect_listen.c b/fs/smb/common/smbdirect/smbdirect_listen.c
> index 05c7902e7020..a6e08d82dc73 100644
> --- a/fs/smb/common/smbdirect/smbdirect_listen.c
> +++ b/fs/smb/common/smbdirect/smbdirect_listen.c
> @@ -74,6 +74,12 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog)
> */
> sc->listen.backlog = backlog;
>
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + inet_sk_set_state(sk, TCP_LISTEN);
> + }
> +
> if (sc->rdma.cm_id->device)
> smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
> "listening on addr: %pISpsfc dev: %.*s\n",
> @@ -209,6 +215,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
> const struct rdma_cm_event *event)
> {
> const struct smbdirect_socket_parameters *lsp = &lsc->parameters;
> + struct sock *nsk = NULL;
> struct smbdirect_socket *nsc;
> unsigned long flags;
> size_t backlog = max_t(size_t, 1, lsc->listen.backlog);
> @@ -265,9 +272,39 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
> return -EBUSY;
> }
>
> - ret = smbdirect_socket_create_accepting(new_id, &nsc);
> - if (ret)
> - goto socket_init_failed;
> + if (lsc->sk.sk_family) {
> + struct sock *lsk = &lsc->sk;
> +
> + ret = -ENOMEM;
> + nsk = sk_clone(lsk, lsk->sk_allocation, false);
> + if (!nsk)
> + goto sk_clone_failed;
> + /* sk_clone_lock() increments refcnt to 2; drop the extra. */
> + __sock_put(nsk);
> + /* sk_clone() already called sk_sockets_allocated_inc(sk); */
> + sock_prot_inuse_add(sock_net(nsk), nsk->sk_prot, 1);
> +
> + smbdirect_sk_reclassify(nsk);
> + inet_sk_set_state(nsk, TCP_SYN_RECV);
> + nsc = smbdirect_socket_from_sk(nsk);
> +
> + ret = smbdirect_socket_init_accepting(new_id, nsc);
> + if (ret)
> + goto socket_init_failed;
> +
> + /*
> + * Note that smbdirect_sock_accept() will set
> + * SOCK_CUSTOM_SOCKOPT once [__]inet_accept()
> + * called sk_set_socket() via sock_graft().
> + */
> + WARN_ON_ONCE(nsc->orig_sk_destruct != lsc->orig_sk_destruct);
> + WARN_ON_ONCE(nsk->sk_destruct != lsk->sk_destruct);
> + WARN_ON_ONCE(nsk->sk_ipv6only != lsk->sk_ipv6only);
> + } else {
> + ret = smbdirect_socket_create_accepting(new_id, &nsc);
> + if (ret)
> + goto socket_init_failed;
> + }
>
> nsc->logging = lsc->logging;
> ret = smbdirect_socket_set_initial_parameters(nsc, &lsc->parameters);
> @@ -302,7 +339,11 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
> */
> nsc->ib.dev = NULL;
> nsc->rdma.cm_id = NULL;
> - smbdirect_socket_release(nsc);
> + if (!nsk)
> + smbdirect_socket_release(nsc);
> socket_init_failed:
> + if (nsk)
> + sk_free(nsk);
> +sk_clone_failed:
> return ret;
> }
> diff --git a/fs/smb/common/smbdirect/smbdirect_main.c b/fs/smb/common/smbdirect/smbdirect_main.c
> index fe6e8d93c34c..ccbe979332af 100644
> --- a/fs/smb/common/smbdirect/smbdirect_main.c
> +++ b/fs/smb/common/smbdirect/smbdirect_main.c
> @@ -12,6 +12,7 @@ struct smbdirect_module_state smbdirect_globals = {
>
> static __init int smbdirect_module_init(void)
> {
> + struct smbdirect_socket_parameters *sp;
> int ret = -ENOMEM;
>
> pr_notice("subsystem loading...\n");
> @@ -73,10 +74,52 @@ static __init int smbdirect_module_init(void)
> if (ret)
> goto devices_init_failed;
>
> + /*
> + * Create the global default parameters
> + */
> + sp = &smbdirect_globals.default_parameters;
> + sp->resolve_addr_timeout_msec = 5 * 1000;
> + sp->resolve_route_timeout_msec = 5 * 1000;
> + sp->rdma_connect_timeout_msec = 5 * 1000;
> + sp->negotiate_timeout_msec = 120 * 1000;
> + sp->initiator_depth = 1; /* the server should change this */
> + sp->responder_resources = 1; /* the client should change this */
> + sp->recv_credit_max = 255;
> + sp->send_credit_target = 255;
> + sp->max_send_size = 1364;
> + /*
> + * The maximum fragmented upper-layer payload receive size supported
> + *
> + * Assume max_payload_per_credit is
> + * smbd_max_receive_size - 24 = 1340
> + *
> + * The maximum number would be
> + * smbd_receive_credit_max * max_payload_per_credit
> + *
> + * 1340 * 255 = 341700 (0x536C4)
> + *
> + * The minimum value from the spec is 131072 (0x20000)
> + *
> + * For now we use the logic we used before:
> + * (1364 * 255) / 2 = 173910 (0x2A756)
> + */
> + sp->max_fragmented_recv_size = (1364 * 255) / 2;
> + sp->max_recv_size = 1364;
> + sp->max_read_write_size = 0; /* the server should change this */
> + sp->max_frmr_depth = 0; /* the client should change this */
> + sp->keepalive_interval_msec = 120 * 1000;
> + sp->keepalive_timeout_msec = 5 * 1000;
> +
> + ret = smbdirect_proto_init();
> + if (ret)
> + goto proto_init_failed;
> +
> mutex_unlock(&smbdirect_globals.mutex);
> pr_notice("subsystem loaded\n");
> return 0;
>
> +proto_init_failed:
> + smbdirect_devices_exit();
> devices_init_failed:
> destroy_workqueue(smbdirect_globals.workqueues.cleanup);
> alloc_cleanup_wq_failed:
> @@ -101,6 +144,8 @@ static __exit void smbdirect_module_exit(void)
> pr_notice("subsystem unloading...\n");
> mutex_lock(&smbdirect_globals.mutex);
>
> + smbdirect_proto_exit();
> +
> smbdirect_devices_exit();
>
> destroy_workqueue(smbdirect_globals.workqueues.accept);
> diff --git a/fs/smb/common/smbdirect/smbdirect_mr.c b/fs/smb/common/smbdirect/smbdirect_mr.c
> index fa9be8089925..86bb72ed10ae 100644
> --- a/fs/smb/common/smbdirect/smbdirect_mr.c
> +++ b/fs/smb/common/smbdirect/smbdirect_mr.c
> @@ -167,9 +167,11 @@ smbdirect_connection_get_mr_io(struct smbdirect_socket *sc)
> int ret;
>
> again:
> + smbdirect_socket_sk_unlock(sc);
> ret = wait_event_interruptible(sc->mr_io.ready.wait_queue,
> atomic_read(&sc->mr_io.ready.count) ||
> sc->status != SMBDIRECT_SOCKET_CONNECTED);
> + smbdirect_socket_sk_lock(sc);
> if (ret) {
> smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
> "wait_event_interruptible ret=%d (%1pe)\n",
> @@ -281,7 +283,9 @@ smbdirect_connection_register_mr_io(struct smbdirect_socket *sc,
> return NULL;
> }
>
> + smbdirect_socket_sk_lock(sc);
> mr = smbdirect_connection_get_mr_io(sc);
> + smbdirect_socket_sk_unlock(sc);
> if (!mr) {
> smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
> "smbdirect_connection_get_mr_io returning NULL\n");
> @@ -415,6 +419,12 @@ void smbdirect_connection_deregister_mr_io(struct smbdirect_mr_io *mr)
> if (mr->state == SMBDIRECT_MR_DISABLED)
> goto put_kref;
>
> + /*
> + * We are protected by mr->mutex
> + * without lock_sock().
> + */
> + smbdirect_socket_sk_not_owned_by_me(sc);
> +
> if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
> smbdirect_mr_io_disable_locked(mr);
> goto put_kref;
> diff --git a/fs/smb/common/smbdirect/smbdirect_proto.c b/fs/smb/common/smbdirect/smbdirect_proto.c
> new file mode 100644
> index 000000000000..1a832d52eb89
> --- /dev/null
> +++ b/fs/smb/common/smbdirect/smbdirect_proto.c
> @@ -0,0 +1,1549 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2025 Stefan Metzmacher
> + */
> +
> +#include "smbdirect_internal.h"
> +#include <net/protocol.h>
> +#include <net/inet_common.h>
> +#include <linux/bpf-cgroup.h>
> +#include <linux/errname.h>
> +
> +#define SMBDIRECT_FN_GENERIC(__sk, __fmt, __args...) do { \
> + struct smbdirect_socket *__sc = smbdirect_socket_from_sk(__sk); \
> + __smbdirect_log_generic(__sc, SMBDIRECT_LOG_INFO, SMBDIRECT_LOG_SK, \
> + __fmt " sc=%p %s first_error=%1pe kern=%u locked=%u refs=%u dead=%u mrefs=%u\n", \
> + ##__args, __sc, \
> + smbdirect_socket_status_string(__sc->status), \
> + SMBDIRECT_DEBUG_ERR_PTR(__sc->first_error), \
> + (__sk)->sk_kern_sock, \
> + sock_owned_by_user_nocheck(__sk), \
> + refcount_read(&((__sk)->sk_refcnt)), \
> + sock_flag(__sk, SOCK_DEAD), \
> + module_refcount(THIS_MODULE)); \
> +} while (0)
> +
> +#define SMBDIRECT_FN_COMMENT(__sk, __comment) \
> + SMBDIRECT_FN_GENERIC(__sk, "%s with", __comment)
> +
> +#define SMBDIRECT_FN_CALLED(__sk) \
> + SMBDIRECT_FN_GENERIC(__sk, "Called for")
> +
> +#define SMBDIRECT_FN_RETURN_VOID(__sk) \
> + SMBDIRECT_FN_GENERIC(__sk, "Returning for")
> +
> +#define SMBDIRECT_FN_RETURN_POLL(__sk, __mask) \
> + SMBDIRECT_FN_GENERIC(__sk, "Returning mask=0x%x for", __mask)
> +
> +#define SMBDIRECT_FN_RETURN_INT(__sk, __ret) do { \
> + bool __is_err = IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(__ret)); \
> + SMBDIRECT_FN_GENERIC(__sk, "Returning ret=%d%s%s%s for", \
> + (__ret), \
> + __is_err ? " (" : "", \
> + __is_err ? errname(__ret) : "", \
> + __is_err ? ")" : ""); \
> +} while (0)
> +
> +static bool smbdirect_sk_logging_needed(struct smbdirect_socket *sc,
> + void *private_ptr,
> + unsigned int lvl,
> + unsigned int cls)
> +{
> + /*
> + * Only errors by default.
> + */
> + if (lvl <= SMBDIRECT_LOG_ERR)
> + return true;
> + return false;
> +}
> +
> +static void smbdirect_sk_logging_vaprintf(struct smbdirect_socket *sc,
> + const char *func,
> + unsigned int line,
> + void *private_ptr,
> + unsigned int lvl,
> + unsigned int cls,
> + struct va_format *vaf)
> +{
> + if (lvl <= SMBDIRECT_LOG_ERR)
> + pr_err("%s:%u %pV", func, line, vaf);
> + else
> + pr_info("%s:%u %pV", func, line, vaf);
> +}
> +
> +void smbdirect_sk_reclassify(struct sock *sk)
> +{
> +#ifdef CONFIG_DEBUG_LOCK_ALLOC
> + static struct lock_class_key sk_key[2];
> + static struct lock_class_key slock_key[2];
> +
> + if (WARN_ON_ONCE(!sock_allow_reclassification(sk)))
> + return;
> +
> + switch (sk->sk_family) {
> + case AF_INET:
> + /*
> + * Before we reset the owner we
> + * need to drop the reference of the
> + * existing module, this is only
> + * really relevant for AF_INET,
> + * as that is always builtin
> + * there's no potential leak
> + * of module references. We do it
> + * mainly in order to match the
> + * AF_INET6 case.
> + */
> + sk_owner_put(sk);
> + sk_owner_clear(sk);
> +
> + sock_lock_init_class_and_name(sk,
> + "slock-AF_INET-IPPROTO-SMBDIRECT",
> + &slock_key[0],
> + "sk_lock-AF_INET-IPPROTO-SMBDIRECT",
> + &sk_key[0]);
> +
> + /*
> + * Now that we reclassified the socket
> + * we're also the new sk_owner, but that's
> + * not needed as there's still a reference
> + * on sk->sk_prot->owner, which is dropped
> + * in sk_prot_free(). But in order to
> + * avoid module reference leaks to our
> + * own module we need to put and clear
> + * sk_owner, in order to allow callers
> + * to do their own reclassification.
> + */
> + sk_owner_put(sk);
> + sk_owner_clear(sk);
> + break;
> + case AF_INET6:
> + /*
> + * Before we reset the owner we
> + * need to drop the reference of the
> + * existing module.
> + *
> + * As we also use inet6_register_protosw()
> + * and other symbols from a possible
> + * ipv6.ko, we already have enough
> + * module references in order to avoid
> + * unloading of ipv6.ko, while smbdirect.ko
> + * is loaded.
> + *
> + * However when smbdirect.ko is unloaded
> + * we should not leak references in order
> + * to allow ipv6.ko to be unloaded as well.
> + */
> + sk_owner_put(sk);
> + sk_owner_clear(sk);
> +
> + sock_lock_init_class_and_name(sk,
> + "slock-AF_INET6-IPPROTO-SMBDIRECT",
> + &slock_key[1],
> + "sk_lock-AF_INET6-IPPROTO-SMBDIRECT",
> + &sk_key[1]);
> +
> + /*
> + * Now that we reclassified the socket
> + * we're also the new sk_owner, but that's
> + * not needed as there's still a reference
> + * on sk->sk_prot->owner, which is dropped
> + * in sk_prot_free(). But in order to
> + * avoid module reference leaks to our
> + * own module we need to put and clear
> + * sk_owner, in order to allow callers
> + * to do their own reclassification.
> + */
> + sk_owner_put(sk);
> + sk_owner_clear(sk);
> + break;
> + default:
> + WARN_ON_ONCE(1);
> + }
> +#endif /* CONFIG_DEBUG_LOCK_ALLOC */
> +}
> +
> +static void smbdirect_sk_destruct(struct sock *sk)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> +
> + /*
> + * Called by sk_free()
> + */
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + if (WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_DESTROYED)) {
> + pr_err("Attempt to release SMBDIRECT socket in status %s sc %p\n",
> + smbdirect_socket_status_string(sc->status), sc);
> + SMBDIRECT_FN_RETURN_VOID(sk);
> + return;
> + }
> +
> + SMBDIRECT_FN_COMMENT(sk, "calling orig_sk_destruct");
> + smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
> + "sc[%p]->orig_sk_destruct[%ps]\n",
> + sc, sc->orig_sk_destruct);
> + sc->orig_sk_destruct(sk);
> + SMBDIRECT_FN_RETURN_VOID(sk);
> +}
> +
> +static int smbdirect_sk_init(struct sock *sk)
> +{
> + struct socket *sock = sk->sk_socket;
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + const struct smbdirect_socket_parameters *sp = &smbdirect_globals.default_parameters;
> + void (*orig_sk_destruct)(struct sock *sk);
> + int ret;
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + smbdirect_sk_reclassify(sk);
> +
> + smbdirect_socket_init(sc);
> + smbdirect_socket_set_logging(sc,
> + NULL,
> + smbdirect_sk_logging_needed,
> + smbdirect_sk_logging_vaprintf);
> +
> + smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
> + "Called for sk=%p family=%u protocol=%u type=%u\n",
> + sk, sk->sk_family, sk->sk_protocol, sk->sk_type);
> +
> + sk_sockets_allocated_inc(sk);
> + sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
> +
> + orig_sk_destruct = sk->sk_destruct;
> + SMBDIRECT_FN_COMMENT(sk, "remembered orig_sk_destruct");
> + smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
> + "sc[%p]->orig_sk_destruct[%ps]\n",
> + sc, orig_sk_destruct);
> + sc->orig_sk_destruct = orig_sk_destruct;
> + sk->sk_destruct = smbdirect_sk_destruct;
> +
> + /*
> + * We want to handle all sockopts explicitly
> + * and only support what we really support.
> + */
> + set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags);
> + /*
> + * There are no legacy callers, so we are strict
> + * regarding ipv4 vs. ipv6.
> + */
> + sk->sk_ipv6only = true;
> +
> + /*
> + * No userspace sockets yet...
> + */
> + if (!sk->sk_kern_sock) {
> + sc->first_error = -EPROTONOSUPPORT;
> + SMBDIRECT_FN_COMMENT(sk, "No userspace sockets");
> + return -EPROTONOSUPPORT;
> + }
> +
> + ret = smbdirect_socket_init_new(sock_net(sk), sc);
> + if (ret)
> + goto socket_init_failed;
> + /*
> + * smbdirect_socket_init_new() called smbdirect_socket_init() again,
> + * so we need to call smbdirect_socket_set_logging() again!
> + */
> + smbdirect_socket_set_logging(sc,
> + NULL,
> + smbdirect_sk_logging_needed,
> + smbdirect_sk_logging_vaprintf);
> +
> + WARN_ON_ONCE(sc->orig_sk_destruct != orig_sk_destruct);
> + WARN_ON_ONCE(sk->sk_destruct != smbdirect_sk_destruct);
> +
> + ret = smbdirect_socket_set_initial_parameters(sc, sp);
> + if (ret)
> + goto set_params_failed;
> +
> + ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_SOFTIRQ, sk->sk_allocation);
> + if (ret)
> + goto set_settings_failed;
> +
> + SMBDIRECT_FN_RETURN_INT(sk, 0);
> + return 0;
> +
> +set_settings_failed:
> +set_params_failed:
> +socket_init_failed:
> + sc->first_error = ret;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static void smbdirect_sk_destroy(struct sock *sk)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + /*
> + * For now do a sync disconnect/destroy
> + *
> + * SMBDIRECT_LOG_INFO is enough here
> + * as this is the typical case where
> + * we terminate the connection ourself.
> + */
> + smbdirect_socket_schedule_cleanup_lvl(sc,
> + SMBDIRECT_LOG_INFO,
> + -ESHUTDOWN);
> + smbdirect_socket_destroy_sync(sc);
> +
> + sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
> + sk_sockets_allocated_dec(sk);
> +
> + SMBDIRECT_FN_RETURN_VOID(sk);
> +}
> +
> +static int smbdirect_sk_hash(struct sock *sk)
> +{
> + SMBDIRECT_FN_CALLED(sk);
> + return 0;
> +}
It seems this was implemented just to fill all function
pointers of sk->sk_prot but looks unnecessary.
Same for other NOP functions, unhash(), release_cb(), etc.
> +
> +static void smbdirect_sk_unhash(struct sock *sk)
> +{
> + SMBDIRECT_FN_CALLED(sk);
> +}
> +
> +static void smbdirect_sk_release_cb(struct sock *sk)
> +{
> + /*
> + * Called from release_sock()
> + */
> + SMBDIRECT_FN_CALLED(sk);
> +}
> +
> +static int smbdirect_sk_pre_bind(struct sock *sk,
> + struct sockaddr_unsized *uaddr,
> + int *addr_len,
> + u32 *flags,
> + u16 *port)
> +{
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + if (*addr_len < sizeof(uaddr->sa_family))
> + return -EINVAL;
> +
> + /* AF_UNSPEC is not allowed */
> + if (sk->sk_family != uaddr->sa_family)
> + return -EAFNOSUPPORT;
> +
> + /*
> + * BPF prog is run before any checks are done so that if the prog
> + * changes context in a wrong way it will be caught.
> + */
> + switch (sk->sk_family) {
> + case AF_INET:
> + if (*addr_len < sizeof(struct sockaddr_in))
> + return -EINVAL;
> +
> + *port = ntohs(((struct sockaddr_in *)uaddr)->sin_port);
> +
> + return BPF_CGROUP_RUN_PROG_INET_BIND_LOCK(sk, uaddr, addr_len,
> + CGROUP_INET4_BIND,
> + flags);
Do you really need these bpf hooks ?
It seems the smb sockets can be created from kthread
and tied to the root cgroup.
> + case AF_INET6:
> + /*
> + * We require a full struct sockaddr_in6 (28 bytes) instead of a
> + * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
> + * have any legacy callers in userspace and the
> + * rdma layer also expects that.
> + */
> + if (*addr_len < sizeof(struct sockaddr_in6))
> + return -EINVAL;
> +
> + *port = ntohs(((struct sockaddr_in6 *)uaddr)->sin6_port);
> +
> + return BPF_CGROUP_RUN_PROG_INET_BIND_LOCK(sk, uaddr, addr_len,
> + CGROUP_INET6_BIND,
> + flags);
> + }
> +
> + return -EAFNOSUPPORT;
> +}
> +
> +static int smbdirect_sk_do_bind(struct sock *sk,
> + struct sockaddr_unsized *uaddr,
> + const u32 flags,
> + const u16 port)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + bool is_any_addr = true;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + if (flags & BIND_WITH_LOCK)
> + sock_owned_by_me(sk);
> + else
> + sock_not_owned_by_me(sk);
> +
> + ret = smbdirect_socket_bind(sc, (struct sockaddr *)uaddr);
> + if (ret) {
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + ret = smbdirect_socket_sync_saddr_to_sk(sc, &is_any_addr);
> + if (ret) {
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + /* Make sure we are allowed to bind here. */
> + if (sk->sk_num && !(flags & BIND_FROM_BPF)) {
> + switch (sk->sk_family) {
> + case AF_INET:
> + ret = BPF_CGROUP_RUN_PROG_INET4_POST_BIND(sk);
> + if (ret) {
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> + break;
> +
> + case AF_INET6:
> + ret = BPF_CGROUP_RUN_PROG_INET6_POST_BIND(sk);
> + if (ret) {
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> + break;
> + }
> + }
> +
> + if (!is_any_addr)
> + sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
> + if (port)
> + sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
Can this socket be passed to SOCK_BINDPORT_LOCK user,
inet_bhash2_reset_saddr(), inet_sk_rebuild_header() ?
> +
> + ret = 0;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sk_bind(struct sock *sk, struct sockaddr_unsized *addr, int addr_len)
> +{
> + struct net *net = sock_net(sk);
> + u32 flags = BIND_WITH_LOCK;
> + u16 port = 0;
> + u16 check_port = 0;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + ret = smbdirect_sk_pre_bind(sk, addr, &addr_len, &flags, &port);
> + if (ret) {
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + /*
> + * treat the iwarp tcp port for
> + * smb (5445) as the main smb port (445)
> + * and only allow the bind if 445
> + * would be allowed.
> + */
> + if (port == 5445)
> + check_port = 445;
> + else
> + check_port = port;
> +
> + if (!(flags & BIND_NO_CAP_NET_BIND_SERVICE) &&
> + check_port && inet_port_requires_bind_service(net, check_port) &&
> + !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) {
> + ret = -EACCES;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + if (flags & BIND_WITH_LOCK)
> + lock_sock(sk);
Is connect() called without bind() and could a bpf prog
calls bpf_bind() for this socket ?
> +
> + ret = smbdirect_sk_do_bind(sk, addr, flags, port);
> +
> + if (flags & BIND_WITH_LOCK)
> + release_sock(sk);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static struct sock *smbdirect_sk_accept(struct sock *lsk, struct proto_accept_arg *arg)
> +{
> + struct smbdirect_socket *lsc = smbdirect_socket_from_sk(lsk);
> + long timeo = sock_rcvtimeo(lsk, arg->flags & O_NONBLOCK);
> + struct smbdirect_socket *nsc;
> + struct sock *nsk;
> +
> + SMBDIRECT_FN_CALLED(lsk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(lsk);
> +
> + lock_sock(lsk);
> + nsc = smbdirect_socket_accept(lsc, timeo, arg);
> + release_sock(lsk);
> + if (!nsc) {
> + SMBDIRECT_FN_RETURN_INT(lsk, arg->err);
> + return NULL;
> + }
> + nsk = &nsc->sk;
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(nsk);
Looks redundant.
> +
> + SMBDIRECT_FN_RETURN_INT(lsk, 0);
> + return nsk;
> +}
> +
> +static int smbdirect_sk_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len)
> +{
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + if (addr_len < sizeof(uaddr->sa_family))
> + return -EINVAL;
> +
> + if (sk->sk_family != uaddr->sa_family)
> + return -EAFNOSUPPORT;
> +
> + switch (sk->sk_family) {
> + case AF_INET:
> + if (addr_len < sizeof(struct sockaddr_in))
> + return -EINVAL;
> +
> + return BPF_CGROUP_RUN_PROG_INET4_CONNECT(sk, uaddr, &addr_len);
> + case AF_INET6:
> + /*
> + * We require a full struct sockaddr_in6 (28 bytes) instead of a
> + * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
> + * have any legacy callers in userspace and the
> + * rdma layer also expects that.
> + */
> + if (addr_len < sizeof(struct sockaddr_in6))
> + return -EINVAL;
> +
> + return BPF_CGROUP_RUN_PROG_INET6_CONNECT(sk, uaddr, &addr_len);
> + }
> +
> + return -EAFNOSUPPORT;
> +}
> +
> +static int smbdirect_sk_connect(struct sock *sk, struct sockaddr_unsized *addr, int addr_len)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + ret = smbdirect_connect(sc, (struct sockaddr *)addr);
Why is this called via sk->sk_prot instead of being called
directly from sock->ops->connect() ?
Same for other sk->sk_prot functions.
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sk_setsockopt(struct sock *sk, int level, int optname,
> + sockptr_t optval, unsigned int optlen)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + switch (level) {
> + default:
> + SMBDIRECT_FN_COMMENT(sk, "default");
> + smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
> + "level=%d optname=%d for sk=%p\n",
> + level, optname, sk);
> + ret = -EOPNOTSUPP;
> + break;
> + }
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sk_getsockopt(struct sock *sk, int level, int optname,
> + char __user *optval, int __user *optlen)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + switch (level) {
> + default:
> + SMBDIRECT_FN_COMMENT(sk, "default");
> + smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
> + "level=%d optname=%d for sk=%p\n",
> + level, optname, sk);
> + ret = -EOPNOTSUPP;
> + break;
> + }
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sk_ioctl(struct sock *sk, int cmd, int *karg)
Is there any in-kernel ioctl() user for this socket ?
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + switch (cmd) {
> + default:
> + SMBDIRECT_FN_COMMENT(sk, "default");
> + smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
> + "cmd=%d (0x%x) for sk=%p\n",
> + cmd, cmd, sk);
> + ret = -ENOIOCTLCMD;
> + break;
> + }
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static inline size_t smbdirect_cmsg_count(const struct msghdr *_msg,
> + int *first_sol_smbdirect_type)
> +{
> + struct msghdr *msg = (struct msghdr *)(uintptr_t)(const void *)_msg;
> + struct cmsghdr *cmsg = NULL;
> + size_t count = 0;
> +
> + if (first_sol_smbdirect_type != NULL)
> + *first_sol_smbdirect_type = -1;
> +
> + for (cmsg = CMSG_FIRSTHDR(msg);
> + cmsg != NULL;
> + cmsg = CMSG_NXTHDR(msg, cmsg)) {
> + count++;
> + if (cmsg->cmsg_level != SOL_SMBDIRECT)
> + continue;
> + if (first_sol_smbdirect_type != NULL) {
> + *first_sol_smbdirect_type = cmsg->cmsg_type;
> + first_sol_smbdirect_type = NULL;
> + }
> + }
> +
> + return count;
> +}
> +
> +static __always_inline
> +ssize_t __smbdirect_cmsg_extract(const struct msghdr *_msg,
> + int cmsg_type,
> + void *_payload,
> + size_t payloadmin,
> + size_t payloadmax)
> +{
> + struct msghdr *msg = (struct msghdr *)(uintptr_t)(const void *)_msg;
> + size_t cmsg_len_min = CMSG_LEN(payloadmin);
> + size_t cmsg_len_max = CMSG_LEN(payloadmax);
> + const size_t cmsg_len_hdr = CMSG_LEN(0);
> + uint8_t *payload = (uint8_t *)_payload;
> + struct cmsghdr *cmsg = NULL;
> + size_t payloadlen;
> +
> + BUILD_BUG_ON(cmsg_len_min > cmsg_len_max);
> + if (WARN_ON_ONCE(cmsg_len_min > cmsg_len_max))
> + return -EBADMSG;
> +
> + for (cmsg = CMSG_FIRSTHDR(msg);
> + cmsg != NULL;
> + cmsg = CMSG_NXTHDR(msg, cmsg)) {
> + if (cmsg->cmsg_level != SOL_SMBDIRECT)
> + continue;
> +
> + if (cmsg->cmsg_type != cmsg_type)
> + continue;
> +
> + if (cmsg->cmsg_len < cmsg_len_min)
> + return -EBADMSG;
> +
> + if (cmsg->cmsg_len > cmsg_len_max)
> + return -EMSGSIZE;
> +
> + payloadlen = cmsg->cmsg_len - cmsg_len_hdr;
> + if (payloadlen > 0)
> + memcpy(payload, CMSG_DATA(cmsg), payloadlen);
> + if (payloadlen < payloadmax)
> + memset(payload + payloadlen, 0, payloadmax - payloadlen);
> + return payloadlen;
> + }
> +
> + return -ENOMSG;
> +}
> +
> +static __always_inline
> +int smbdirect_buffer_remote_invalidate_cmsg_extract(const struct msghdr *msg,
> + u32 *remote_token)
> +{
> + struct smbdirect_buffer_remote_invalidate_args args = {
> + .remote_token = 0,
> + };
> + ssize_t ret;
> +
> + ret = __smbdirect_cmsg_extract(msg,
> + SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE,
> + &args, sizeof(args), sizeof(args));
> + if (ret < 0)
> + return ret;
> +
> + *remote_token = args.remote_token;
> + return 0;
> +}
> +
> +static int smbdirect_sk_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t msg_len)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + struct iov_iter *iter = &msg->msg_iter;
> + unsigned int flags = msg->msg_flags;
> + size_t cmsg_count = 0;
> + int cmsg_type = -1;
> + bool need_invalidate = false;
> + u32 remote_key = 0;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + cmsg_count = smbdirect_cmsg_count(msg, &cmsg_type);
> + if (cmsg_count > 1) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + if (flags & ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL)) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + if (cmsg_type == SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE) {
> + ret = smbdirect_buffer_remote_invalidate_cmsg_extract(msg, &remote_key);
> + if (!ret)
> + need_invalidate = true; /* remote_key is valid */
> + else if (ret != -ENOMSG) {
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> + } else if (cmsg_count) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_SOURCE)) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + if (WARN_ON_ONCE(iov_iter_count(iter) != msg_len)) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + if (flags & MSG_DONTWAIT) {
> + if (!sc->first_error && msg_len && atomic_read(&sc->send_io.credits.count) == 0) {
> + ret = -EAGAIN;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> + }
> + flags &= ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL);
> +
> + ret = smbdirect_connection_send_iter(sc,
> + iter,
> + flags,
> + need_invalidate,
> + remote_key);
> + if (ret < 0)
> + /* Handle error and possibly send SIGPIPE. */
> + ret = sk_stream_error(sk, msg->msg_flags, ret);
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sk_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)
> +{
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + lock_sock(sk);
> + ret = smbdirect_sk_sendmsg_locked(sk, msg, msg_len);
> + release_sock(sk);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sk_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + struct iov_iter *iter = &msg->msg_iter;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + if (flags & ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL)) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_DEST)) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + /*
> + * For now smbdirect_connection_recvmsg() relies
> + * on this assertion and the current in kernel
> + * users are working that way.
> + */
> + if (WARN_ON_ONCE(iov_iter_count(iter) != len)) {
> + ret = -EINVAL;
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> +
> + lock_sock(sk);
> + if (flags & MSG_DONTWAIT) {
> + if (!sc->first_error && len && sc->recv_io.reassembly.data_length == 0) {
> + ret = -EAGAIN;
> + release_sock(sk);
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> + }
> + }
> + flags &= ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL);
> + ret = smbdirect_connection_recvmsg(sc, msg, flags);
> + if (msg->msg_get_inq && ret >= 0)
> + msg->msg_inq = sc->recv_io.reassembly.data_length;
> + release_sock(sk);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static void smbdirect_sk_shutdown(struct sock *sk, int how)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + smbdirect_socket_schedule_cleanup(sc, -ESHUTDOWN);
> +
> + SMBDIRECT_FN_RETURN_VOID(sk);
> +}
> +
> +static int smbdirect_sk_disconnect(struct sock *sk, int flags)
> +{
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + smbdirect_socket_schedule_cleanup(sc, -ESHUTDOWN);
> +
> + if (flags & O_NONBLOCK) {
> + if (sc->status >= SMBDIRECT_SOCKET_DISCONNECTED) {
> + SMBDIRECT_FN_RETURN_INT(sk, 0);
> + return 0;
> + }
> +
> + /*
> + * This will cause SS_DISCONNECTING in
> + * smbdirect_sock_connect_locked().
> + */
> + SMBDIRECT_FN_RETURN_INT(sk, sc->first_error);
> + return sc->first_error;
> + }
> +
> + smbdirect_socket_destroy_sync(sc);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, 0);
> + return 0;
> +}
> +
> +static void smbdirect_sk_close(struct sock *sk, long timeout)
> +{
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + /*
> + * We hold an additional reference so
> + * that the sock_put() in sk_common_release()
> + * doesn't call sk_free(), that is potentially
> + * deferred to our sock_put() after release_sock().
> + *
> + * Note that sk_common_release() calls
> + * smbdirect_sk_destroy() as the first thing.
> + */
> + sock_hold(sk);
> + lock_sock(sk);
> + sk_common_release(sk);
> + release_sock(sk);
> + SMBDIRECT_FN_COMMENT(sk, "before sock_put()");
> + sock_put(sk);
> +}
> +
> +static struct percpu_counter smbdirect_sockets_allocated;
> +
> +static struct proto smbdirect_prot = {
> + .name = "smbdirect",
> + .owner = THIS_MODULE,
> + .obj_size = sizeof(struct smbdirect_socket),
> + .ipv6_pinfo_offset = offsetof(struct smbdirect_socket, inet6),
> + .init = smbdirect_sk_init,
> + .destroy = smbdirect_sk_destroy,
> + .hash = smbdirect_sk_hash,
> + .unhash = smbdirect_sk_unhash,
> + .release_cb = smbdirect_sk_release_cb,
> + .bind = smbdirect_sk_bind,
> + .accept = smbdirect_sk_accept,
> + .pre_connect = smbdirect_sk_pre_connect,
> + .connect = smbdirect_sk_connect,
> + .setsockopt = smbdirect_sk_setsockopt,
> + .getsockopt = smbdirect_sk_getsockopt,
> + .ioctl = smbdirect_sk_ioctl,
> + .sendmsg = smbdirect_sk_sendmsg,
> + .recvmsg = smbdirect_sk_recvmsg,
> + .shutdown = smbdirect_sk_shutdown,
> + .disconnect = smbdirect_sk_disconnect,
> + .close = smbdirect_sk_close,
> + .sockets_allocated = &smbdirect_sockets_allocated,
> +};
> +
> +static int smbdirect_sock_release(struct socket *sock)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not locked */
> + sock_not_owned_by_me(sk);
> + WARN_ON_ONCE(sock_owned_by_user_nocheck(sk));
> +
> + switch (sk->sk_family) {
> + case AF_INET:
> + SMBDIRECT_FN_COMMENT(sk, "calling inet_release()");
> + ret = inet_release(sock);
Given setsockopt() is banned, smbdirect_sk_close() can be
inlined here.
> + break;
> + case AF_INET6:
> +#if IS_ENABLED(CONFIG_IPV6)
> + SMBDIRECT_FN_COMMENT(sk, "calling inet6_release()");
> + ret = inet6_release(sock);
> +#else
> + ret = -EAFNOSUPPORT;
> +#endif
> + break;
> + default:
> + ret = -EAFNOSUPPORT;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int smbdirect_sock_bind(struct socket *sock, struct sockaddr_unsized *saddr, int len)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + switch (sk->sk_family) {
> + case AF_INET:
> + ret = inet_bind(sock, saddr, len);
inet_bind() just calls sk->sk_prot->bind() if set.
So, the same question applies; why not inline
sk->sk_prot->bind() here.
> + break;
> + case AF_INET6:
> +#if IS_ENABLED(CONFIG_IPV6)
> + ret = inet6_bind(sock, saddr, len);
> +#else
> + ret = -EAFNOSUPPORT;
> +#endif
> + break;
> + default:
> + ret = -EAFNOSUPPORT;
> + break;
> + }
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_connect_locked(struct socket *sock,
> + struct sockaddr_unsized *uaddr,
> + int addr_len, int flags)
> +{
> + struct sock *sk = sock->sk;
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is already locked */
> + sock_owned_by_me(sk);
> +
> + if (addr_len < sizeof(uaddr->sa_family))
> + return -EINVAL;
> +
> + if (sk->sk_family != uaddr->sa_family)
> + return -EAFNOSUPPORT;
> +
> + switch (sk->sk_family) {
> + case AF_INET:
> + if (addr_len < sizeof(struct sockaddr_in))
> + return -EINVAL;
> + break;
> + case AF_INET6:
> + /*
> + * We require a full struct sockaddr_in6 (28 bytes) instead of a
> + * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
> + * have any legacy callers in userspace and the
> + * rdma layer also expects that.
> + */
> + if (addr_len < sizeof(struct sockaddr_in6))
> + return -EINVAL;
> + break;
> + default:
> + return -EAFNOSUPPORT;
> + }
> +
> + switch (sock->state) {
> + case SS_CONNECTED:
> + return -EISCONN;
> + case SS_CONNECTING:
> + return -EALREADY;
> + case SS_UNCONNECTED:
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (sc->status == SMBDIRECT_SOCKET_CONNECTED)
> + return -EISCONN;
> +
> + if (sc->status != SMBDIRECT_SOCKET_CREATED)
> + return -EINVAL;
> +
> + if (BPF_CGROUP_PRE_CONNECT_ENABLED(sk)) {
> + ret = sk->sk_prot->pre_connect(sk, uaddr, addr_len);
> + if (ret)
> + return ret;
> + }
> +
> + ret = sk->sk_prot->connect(sk, uaddr, addr_len);
> + if (ret < 0)
> + return ret;
> +
> + inet_sk_set_state(sk, TCP_SYN_SENT);
> + sock->state = SS_CONNECTING;
> +
> + if (flags & O_NONBLOCK)
> + return -EINPROGRESS;
> +
> + ret = smbdirect_connection_wait_for_connected(sc);
> + if (ret)
> + goto sock_error;
> +
> + return 0;
> +
> +sock_error:
> + sock->state = SS_UNCONNECTED;
> + sk->sk_disconnects++;
> + if (sk->sk_prot->disconnect(sk, flags))
> + sock->state = SS_DISCONNECTING;
> + return ret;
> +}
> +
> +static int smbdirect_sock_connect(struct socket *sock,
> + struct sockaddr_unsized *uaddr,
> + int addr_len, int flags)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + lock_sock(sk);
> + ret = smbdirect_sock_connect_locked(sock, uaddr, addr_len, flags);
> + release_sock(sk);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_listen(struct socket *sock, int backlog)
> +{
> + struct sock *sk = sock->sk;
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + lock_sock(sk);
> + ret = smbdirect_socket_listen(sc, backlog);
> + release_sock(sk);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_accept(struct socket *lsock, struct socket *nsock,
> + struct proto_accept_arg *arg)
> +{
> + struct sock *lsk = lsock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(lsk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(lsk);
> +
> + ret = inet_accept(lsock, nsock, arg);
Could this account socket memory to memcg twice ?
see 4a997d49d92a and 16942cf4d3e3
> + if (!ret)
> + /*
> + * We want to handle all sockopts explicitly
> + * and only support what we really support.
> + */
> + set_bit(SOCK_CUSTOM_SOCKOPT, &nsock->flags);
> +
> + SMBDIRECT_FN_RETURN_INT(lsk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + switch (sk->sk_family) {
> + case AF_INET:
> + ret = inet_getname(sock, uaddr, peer);
> + break;
> + case AF_INET6:
> +#if IS_ENABLED(CONFIG_IPV6)
> + ret = inet6_getname(sock, uaddr, peer);
> +#else
> + ret = -EAFNOSUPPORT;
> +#endif
> + break;
> + default:
> + ret = -EAFNOSUPPORT;
> + break;
> + }
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static __poll_t smbdirect_sock_poll(struct file *file, struct socket *sock, poll_table *wait)
> +{
> + struct sock *sk = sock->sk;
> + struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
> + __poll_t mask = 0;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + sock_poll_wait(file, sock, wait);
> +
> + if (sc->status == SMBDIRECT_SOCKET_LISTENING) {
> + if (!list_empty_careful(&sc->listen.ready))
> + mask |= EPOLLIN | EPOLLRDNORM;
> + SMBDIRECT_FN_RETURN_POLL(sk, mask);
> + return mask;
> + }
> +
> + if (sc->first_error) {
> + /*
> + * A broken connection should report almost everything in order to let
> + * applications to detect it reliable.
> + */
> + mask |= EPOLLHUP;
> + mask |= EPOLLERR;
> + mask |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP;
> + mask |= EPOLLOUT | EPOLLWRNORM;
> + SMBDIRECT_FN_RETURN_POLL(sk, mask);
> + return mask;
> + }
> +
> + if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
> + /*
> + * A just created socket.
> + */
> + SMBDIRECT_FN_RETURN_POLL(sk, mask);
> + return mask;
> + }
> +
> + if (sc->recv_io.reassembly.data_length > 0)
> + mask |= EPOLLIN | EPOLLRDNORM;
> +
> + if (atomic_read(&sc->send_io.bcredits.count) > 0 &&
> + atomic_read(&sc->send_io.lcredits.count) > 0 &&
> + atomic_read(&sc->send_io.credits.count) > 0)
> + mask |= EPOLLOUT | EPOLLWRNORM;
> + else {
> + sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
> + set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
> +
> + /*
> + * Race breaker. If space is freed after
> + * wspace test but before the flags are set,
> + * IO signal will be lost. Memory barrier
> + * pairs with the input side.
> + */
> + smp_mb__after_atomic();
> + if (atomic_read(&sc->send_io.bcredits.count) > 0 &&
> + atomic_read(&sc->send_io.lcredits.count) > 0 &&
> + atomic_read(&sc->send_io.credits.count) > 0)
> + mask |= EPOLLOUT | EPOLLWRNORM;
> + }
> +
> + SMBDIRECT_FN_RETURN_POLL(sk, mask);
> + return mask;
> +}
> +
> +static int smbdirect_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + /*
> + * We may need to handle some here as
> + * smbirect_sk_ioctl() only gets a kernel
> + * int pointer as arg, but we may
> + * need to the whole struct
> + */
> + switch (cmd) {
> + default:
> + /*
> + * Note this has some special handling for
> + * sk->sk_type == SOCK_RAW, in case we ever
> + * implement SOCK_RAW...
> + *
> + * It calls smbdirect_sk_ioctl()...
> + */
> + ret = sk_ioctl(sk, cmd, (void __user *)arg);
> + break;
> + }
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_shutdown(struct socket *sock, int how)
> +{
> + struct sock *sk = sock->sk;
> + int ret = 0;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + /*
> + * We have these from userspace:
> + * SHUT_RD = 0, SHUT_WR = 1 and SHUT_RDWR = 2
> + *
> + * And we map them to SHUTDOWN_MASK = 3
> + * RCV_SHUTDOWN = 1, SEND_SHUTDOWN = 2, BOTH = 3
> + */
> + how++;
> + if ((how & ~SHUTDOWN_MASK) || !how) /* MAXINT->0 */
> + return -EINVAL;
> +
> + lock_sock(sk);
> +
> + switch (sk->sk_state) {
> + case TCP_CLOSE:
> + ret = -ENOTCONN;
> + fallthrough;
> + default:
> + WRITE_ONCE(sk->sk_shutdown, sk->sk_shutdown | how);
> + sk->sk_prot->shutdown(sk, how);
> + break;
> +
> + case TCP_SYN_SENT:
> + case TCP_SYN_RECV:
> + ret = sk->sk_prot->disconnect(sk, O_NONBLOCK);
> + break;
> + }
> +
> + /* Wake up anyone sleeping in poll. */
> + sk->sk_state_change(sk);
> + release_sock(sk);
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_setsockopt(struct socket *sock, int level, int optname,
> + sockptr_t optval, unsigned int optlen)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + ret = sock_common_setsockopt(sock, level, optname, optval, optlen);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_getsockopt(struct socket *sock, int level, int optname,
> + char __user *optval, int __user *optlen)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + ret = sock_common_getsockopt(sock, level, optname, optval, optlen);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + ret = sk->sk_prot->sendmsg(sk, msg, len);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static int smbdirect_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
> + int flags)
> +{
> + struct sock *sk = sock->sk;
> + int ret;
> +
> + SMBDIRECT_FN_CALLED(sk);
> +
> + /* assert it is not already locked */
> + sock_not_owned_by_me(sk);
> +
> + ret = sock_common_recvmsg(sock, msg, size, flags);
> +
> + SMBDIRECT_FN_RETURN_INT(sk, ret);
> + return ret;
> +}
> +
> +static const struct proto_ops smbdirect_inet_proto_ops = {
> + .family = PF_INET,
> + .owner = THIS_MODULE,
> + .release = smbdirect_sock_release,
> + .bind = smbdirect_sock_bind,
> + .connect = smbdirect_sock_connect,
> + .socketpair = sock_no_socketpair,
> + .listen = smbdirect_sock_listen,
> + .accept = smbdirect_sock_accept,
> + .getname = smbdirect_sock_getname,
> + .poll = smbdirect_sock_poll,
> + .ioctl = smbdirect_sock_ioctl,
> + .shutdown = smbdirect_sock_shutdown,
> + .setsockopt = smbdirect_sock_setsockopt,
> + .getsockopt = smbdirect_sock_getsockopt,
> + .sendmsg = smbdirect_sock_sendmsg,
> + .sendmsg_locked = smbdirect_sk_sendmsg_locked,
> + .recvmsg = smbdirect_sock_recvmsg,
> + .mmap = sock_no_mmap,
> +};
> +
> +#if IS_ENABLED(CONFIG_IPV6)
> +static const struct proto_ops smbdirect_inet6_proto_ops = {
> + .family = PF_INET6,
> + .owner = THIS_MODULE,
> + .release = smbdirect_sock_release,
> + .bind = smbdirect_sock_bind,
> + .connect = smbdirect_sock_connect,
> + .socketpair = sock_no_socketpair,
> + .listen = smbdirect_sock_listen,
> + .accept = smbdirect_sock_accept,
> + .getname = smbdirect_sock_getname,
> + .poll = smbdirect_sock_poll,
> + .ioctl = smbdirect_sock_ioctl,
> + .shutdown = smbdirect_sock_shutdown,
> + .setsockopt = smbdirect_sock_setsockopt,
> + .getsockopt = smbdirect_sock_getsockopt,
> + .sendmsg = smbdirect_sock_sendmsg,
> + .sendmsg_locked = smbdirect_sk_sendmsg_locked,
> + .recvmsg = smbdirect_sock_recvmsg,
> + .mmap = sock_no_mmap,
> +};
> +#endif
> +
> +static struct inet_protosw smbdirect_inet_stream_protosw = {
> + .type = SOCK_STREAM,
> + .protocol = IPPROTO_SMBDIRECT,
> + .prot = &smbdirect_prot,
> + .ops = &smbdirect_inet_proto_ops,
> +};
> +
> +#if IS_ENABLED(CONFIG_IPV6)
> +static struct inet_protosw smbdirect_inet6_stream_protosw = {
> + .type = SOCK_STREAM,
> + .protocol = IPPROTO_SMBDIRECT,
> + .prot = &smbdirect_prot,
> + .ops = &smbdirect_inet6_proto_ops,
> +};
> +#endif
> +
> +struct smbdirect_socket *smbdirect_socket_from_sock(const struct socket *sock)
> +{
> + if (!sock ||
> + !sock->sk ||
> + sock->sk->sk_protocol != IPPROTO_SMBDIRECT)
> + return NULL;
> +
> + if (WARN_ON_ONCE(sock->sk->sk_destruct != smbdirect_sk_destruct))
> + return NULL;
> +
> + return smbdirect_socket_from_sk(sock->sk);
> +}
> +__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_from_sock);
> +
> +static __init int smbdirect_protosw_init(void)
> +{
> + int err;
> +
> + err = proto_register(&smbdirect_prot, 1);
> + if (err)
> + return err;
> +
> + inet_register_protosw(&smbdirect_inet_stream_protosw);
> +#if IS_ENABLED(CONFIG_IPV6)
> + inet6_register_protosw(&smbdirect_inet6_stream_protosw);
> +#endif
> +
> + return 0;
> +}
> +
> +static __exit void smbdirect_protosw_exit(void)
> +{
> +#if IS_ENABLED(CONFIG_IPV6)
> + inet6_unregister_protosw(&smbdirect_inet6_stream_protosw);
> +#endif
> + inet_unregister_protosw(&smbdirect_inet_stream_protosw);
> +
> + proto_unregister(&smbdirect_prot);
> +}
> +
> +__init int smbdirect_proto_init(void)
> +{
> + int err;
> +
> + err = percpu_counter_init(&smbdirect_sockets_allocated, 0, GFP_KERNEL);
> + if (err)
> + goto err_percpu_counter;
> +
> + err = smbdirect_protosw_init();
> + if (err)
> + goto err_protosw;
> +
> + return 0;
> +
> +err_protosw:
> + percpu_counter_destroy(&smbdirect_sockets_allocated);
> +err_percpu_counter:
> + return err;
> +}
> +
> +__exit void smbdirect_proto_exit(void)
> +{
> + smbdirect_protosw_exit();
> + percpu_counter_destroy(&smbdirect_sockets_allocated);
> +}
> +
> +MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET, 257 /* IPPROTO_SMBDIRECT */, SOCK_STREAM);
> +MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET6, 257 /* IPPROTO_SMBDIRECT */, SOCK_STREAM);
> diff --git a/fs/smb/common/smbdirect/smbdirect_public.h b/fs/smb/common/smbdirect/smbdirect_public.h
> index 50088155e7c3..9f96c66bbe32 100644
> --- a/fs/smb/common/smbdirect/smbdirect_public.h
> +++ b/fs/smb/common/smbdirect/smbdirect_public.h
> @@ -49,6 +49,7 @@ int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
> #define SMBDIRECT_LOG_RDMA_MR 0x100
> #define SMBDIRECT_LOG_RDMA_RW 0x200
> #define SMBDIRECT_LOG_NEGOTIATE 0x400
> +#define SMBDIRECT_LOG_SK 0x800
> void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
> void *private_ptr,
> bool (*needed)(struct smbdirect_socket *sc,
> @@ -145,4 +146,6 @@ void smbdirect_connection_legacy_debug_proc_show(struct smbdirect_socket *sc,
> unsigned int rdma_readwrite_threshold,
> struct seq_file *m);
>
> +struct smbdirect_socket *smbdirect_socket_from_sock(const struct socket *sock);
> +
> #endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_PUBLIC_H__ */
> diff --git a/fs/smb/common/smbdirect/smbdirect_rw.c b/fs/smb/common/smbdirect/smbdirect_rw.c
> index 3b2eb8c48efc..154339955617 100644
> --- a/fs/smb/common/smbdirect/smbdirect_rw.c
> +++ b/fs/smb/common/smbdirect/smbdirect_rw.c
> @@ -105,11 +105,11 @@ static void smbdirect_connection_rdma_write_done(struct ib_cq *cq, struct ib_wc
> smbdirect_connection_rdma_rw_done(cq, wc, DMA_TO_DEVICE);
> }
>
> -int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
> - void *buf, size_t buf_len,
> - struct smbdirect_buffer_descriptor_v1 *desc,
> - size_t desc_len,
> - bool is_read)
> +static int smbdirect_connection_rdma_xmit_locked(struct smbdirect_socket *sc,
> + void *buf, size_t buf_len,
> + struct smbdirect_buffer_descriptor_v1 *desc,
> + size_t desc_len,
> + bool is_read)
> {
> const struct smbdirect_socket_parameters *sp = &sc->parameters;
> enum dma_data_direction direction = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
> @@ -123,6 +123,8 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
> int credits_needed;
> size_t desc_buf_len, desc_num = 0;
>
> + smbdirect_socket_sk_owned_by_me(sc);
> +
> if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
> return -ENOTCONN;
>
> @@ -235,7 +237,9 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
> }
>
> msg = list_last_entry(&msg_list, struct smbdirect_rw_io, list);
> + smbdirect_socket_sk_unlock(sc);
> wait_for_completion(&completion);
> + smbdirect_socket_sk_lock(sc);
> ret = msg->error;
> out:
> list_for_each_entry_safe(msg, next_msg, &msg_list, list) {
> @@ -252,4 +256,19 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
> kfree(msg);
> goto out;
> }
> +
> +int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
> + void *buf, size_t buf_len,
> + struct smbdirect_buffer_descriptor_v1 *desc,
> + size_t desc_len,
> + bool is_read)
> +{
> + int ret;
> +
> + smbdirect_socket_sk_lock(sc);
> + ret = smbdirect_connection_rdma_xmit_locked(sc, buf, buf_len, desc, desc_len, is_read);
> + smbdirect_socket_sk_unlock(sc);
> +
> + return ret;
> +}
> __SMBDIRECT_EXPORT_SYMBOL__(smbdirect_connection_rdma_xmit);
> diff --git a/fs/smb/common/smbdirect/smbdirect_socket.c b/fs/smb/common/smbdirect/smbdirect_socket.c
> index 9153e1dbf53d..76e406999588 100644
> --- a/fs/smb/common/smbdirect/smbdirect_socket.c
> +++ b/fs/smb/common/smbdirect/smbdirect_socket.c
> @@ -5,6 +5,7 @@
> */
>
> #include "smbdirect_internal.h"
> +#include <net/transp_v6.h>
>
> bool smbdirect_frwr_is_supported(const struct ib_device_attr *attrs)
> {
> @@ -217,6 +218,7 @@ int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
> sc->send_io.mem.gfp_mask = gfp_mask;
> sc->recv_io.mem.gfp_mask = gfp_mask;
> sc->rw_io.mem.gfp_mask = gfp_mask;
> + sc->sk.sk_allocation = gfp_mask;
>
> return 0;
> }
> @@ -242,6 +244,106 @@ void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
> }
> __SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_set_logging);
>
> +int smbdirect_socket_sync_saddr_to_sk(struct smbdirect_socket *sc, bool *_is_any_addr)
> +{
> + struct sock *sk = &sc->sk;
> + const struct sockaddr_storage *saddr;
> + const struct sockaddr_in *sin;
> + const struct sockaddr_in6 *sin6;
> + struct in_addr sin_addr = { .s_addr = htonl(INADDR_ANY), };
> + struct in6_addr sin6_addr = in6addr_any;
> + __be32 sin6_flowinfo = 0;
> + bool is_any_addr = true;
> + u16 sport = 0;
> + int ret;
> +
> + saddr = &sc->rdma.cm_id->route.addr.src_addr;
> +
> + if (WARN_ON_ONCE(saddr->ss_family != sk->sk_family)) {
> + ret = -EINVAL;
> + return ret;
> + }
> +
> + switch (saddr->ss_family) {
> + case AF_INET:
> + sin = (struct sockaddr_in *)saddr;
> + sport = ntohs(sin->sin_port);
> + sin_addr = sin->sin_addr;
> + is_any_addr = (sin_addr.s_addr == htonl(INADDR_ANY));
> + break;
> +
> + case AF_INET6:
> + sin6 = (struct sockaddr_in6 *)saddr;
> + sport = ntohs(sin6->sin6_port);
> + sin_addr.s_addr = LOOPBACK4_IPV6;
> + sin6_addr = sin6->sin6_addr;
> + is_any_addr = ipv6_addr_any(&sin6_addr);
> + sin6_flowinfo = sin6->sin6_flowinfo;
> + break;
> + }
> +
> + sk->sk_bound_dev_if = sc->rdma.cm_id->route.addr.dev_addr.bound_dev_if;
> + sk->sk_rcv_saddr = sc->inet.inet_saddr = sin_addr.s_addr;
> +#if IS_ENABLED(CONFIG_IPV6)
> + sk->sk_v6_rcv_saddr = sc->inet6.saddr = sin6_addr;
> +#else
> + sc->inet6.saddr = sin6_addr;
> +#endif
> + sc->inet6.flow_label = sin6_flowinfo;
> + sk->sk_num = sport;
> + sc->inet.inet_sport = htons(sport);
> +
> + if (_is_any_addr)
> + *_is_any_addr = is_any_addr;
> + return 0;
> +}
> +
> +int smbdirect_socket_sync_daddr_to_sk(struct smbdirect_socket *sc)
> +{
> + struct sock *sk = &sc->sk;
> + const struct sockaddr_storage *daddr;
> + const struct sockaddr_in *sin;
> + const struct sockaddr_in6 *sin6;
> + struct in_addr sin_addr = { .s_addr = htonl(INADDR_ANY), };
> +#if IS_ENABLED(CONFIG_IPV6)
> + struct in6_addr sin6_addr = in6addr_any;
> +#endif
> + u16 dport = 0;
> + int ret;
> +
> + daddr = &sc->rdma.cm_id->route.addr.dst_addr;
> +
> + if (WARN_ON_ONCE(daddr->ss_family != sk->sk_family)) {
> + ret = -EINVAL;
> + return ret;
> + }
> +
> + switch (daddr->ss_family) {
> + case AF_INET:
> + sin = (struct sockaddr_in *)daddr;
> + dport = ntohs(sin->sin_port);
> + sin_addr = sin->sin_addr;
> + break;
> +
> + case AF_INET6:
> + sin6 = (struct sockaddr_in6 *)daddr;
> + dport = ntohs(sin6->sin6_port);
> + sin_addr.s_addr = LOOPBACK4_IPV6;
> +#if IS_ENABLED(CONFIG_IPV6)
> + sin6_addr = sin6->sin6_addr;
> +#endif
> + break;
> + }
> +
> + sk->sk_daddr = sc->inet.inet_daddr = sin_addr.s_addr;
> +#if IS_ENABLED(CONFIG_IPV6)
> + sk->sk_v6_daddr = sin6_addr;
> +#endif
> + sk->sk_dport = sc->inet.inet_dport = htons(dport);
> +
> + return 0;
> +}
> +
> static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
> {
> /*
> @@ -257,6 +359,38 @@ static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
> wake_up_all(&sc->recv_io.reassembly.wait_queue);
> wake_up_all(&sc->rw_io.credits.wait_queue);
> wake_up_all(&sc->mr_io.ready.wait_queue);
> +
> + if (sc->sk.sk_family) {
> + struct sock *sk = &sc->sk;
> +
> + WRITE_ONCE(sk->sk_shutdown, SHUTDOWN_MASK);
> +
> + WARN_ON_ONCE(sc->first_error == 0);
> + if (sc->first_error < 0)
> + WRITE_ONCE(sk->sk_err, -sc->first_error);
> + else
> + WRITE_ONCE(sk->sk_err, sc->first_error);
> +
> + if (sc->status >= SMBDIRECT_SOCKET_DISCONNECTED) {
> + inet_sk_set_state(sk, TCP_CLOSE);
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_socket->state = SS_UNCONNECTED;
> + } else {
> + inet_sk_set_state(sk, TCP_CLOSING);
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_socket->state = SS_DISCONNECTING;
> + }
> +
> + /*
> + * Note tcp_done_with_error() also calls both
> + * sk->sk_state_change(sk) via tcp_done()
> + * and sk_error_report() directly.
> + */
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk->sk_state_change(sk);
> + if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
> + sk_error_report(sk);
> + }
> }
>
> void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
> @@ -510,11 +644,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
> */
> smbdirect_socket_wake_up_all(sc);
>
> + smbdirect_socket_sk_unlock(sc);
> disable_work_sync(&sc->disconnect_work);
> disable_work_sync(&sc->connect.work);
> disable_work_sync(&sc->recv_io.posted.refill_work);
> disable_work_sync(&sc->idle.immediate_work);
> disable_delayed_work_sync(&sc->idle.timer_work);
> + smbdirect_socket_sk_lock(sc);
>
> if (sc->rdma.cm_id)
> rdma_lock_handler(sc->rdma.cm_id);
> @@ -600,6 +736,8 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
> */
> WARN_ON_ONCE(in_interrupt());
>
> + smbdirect_socket_sk_owned_by_me(sc);
> +
> /*
> * First we try to disable the work
> * without disable_work_sync() in a
> @@ -625,7 +763,9 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
>
> smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
> "cancelling and disable disconnect_work\n");
> + smbdirect_socket_sk_unlock(sc);
> disable_work_sync(&sc->disconnect_work);
> + smbdirect_socket_sk_lock(sc);
>
> smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
> "destroying rdma session\n");
> @@ -634,7 +774,9 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
> if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
> smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
> "wait for transport being disconnected\n");
> + smbdirect_socket_sk_unlock(sc);
> wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
> + smbdirect_socket_sk_lock(sc);
> smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
> "waited for transport being disconnected\n");
> }
> @@ -723,6 +865,8 @@ int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
> {
> int ret;
>
> + smbdirect_socket_sk_owned_by_me(sc);
> +
> if (WARN_ON_ONCE(needed < 0))
> return -EINVAL;
>
> @@ -731,9 +875,12 @@ int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
> return 0;
>
> atomic_add(needed, total_credits);
> +
> + smbdirect_socket_sk_unlock(sc);
> ret = wait_event_interruptible(*waitq,
> atomic_read(total_credits) >= needed ||
> sc->status != expected_status);
> + smbdirect_socket_sk_lock(sc);
>
> if (sc->status != expected_status)
> return unexpected_errno;
> diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h
> index c09eddd8ad16..6bb201683259 100644
> --- a/fs/smb/common/smbdirect/smbdirect_socket.h
> +++ b/fs/smb/common/smbdirect/smbdirect_socket.h
> @@ -104,6 +104,18 @@ enum smbdirect_keepalive_status {
> };
>
> struct smbdirect_socket {
> + union {
> + struct sock sk;
> + struct inet_sock inet;
> + };
> + /* needed by inet6_create() */
> + struct ipv6_pinfo inet6;
> + void (*orig_sk_destruct)(struct sock *sk);
> +
> + /*
> + * This is the first element that is
> + * initialized in smbdirect_socket_init()
> + */
> enum smbdirect_socket_status status;
> wait_queue_head_t status_wait;
> int first_error;
> @@ -548,14 +560,18 @@ static void __smbdirect_log_printf(struct smbdirect_socket *sc,
> __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_RW, fmt, ##args)
> #define smbdirect_log_negotiate(sc, lvl, fmt, args...) \
> __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_NEGOTIATE, fmt, ##args)
> +#define smbdirect_log_sk(sc, lvl, fmt, args...) \
> + __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_SK, fmt, ##args)
>
> static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
> {
> + const size_t status_offset = offsetof(struct smbdirect_socket, status);
> +
> /*
> * This also sets status = SMBDIRECT_SOCKET_CREATED
> */
> BUILD_BUG_ON(SMBDIRECT_SOCKET_CREATED != 0);
> - memset(sc, 0, sizeof(*sc));
> + memset(((u8 *)sc)+status_offset, 0, sizeof(*sc)-status_offset);
>
> init_waitqueue_head(&sc->status_wait);
>
> @@ -700,6 +716,14 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
> __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, \
> __SMBDIRECT_SOCKET_DISCONNECT(__sc);)
>
> +static __always_inline struct smbdirect_socket *
> +smbdirect_socket_from_sk(const struct sock *sk)
> +{
> + WARN_ON_ONCE(!sk);
> + BUILD_BUG_ON(offsetof(struct smbdirect_socket, sk) != 0);
> + return container_of(sk, struct smbdirect_socket, sk);
> +}
> +
> struct smbdirect_send_io {
> struct smbdirect_socket *socket;
> struct ib_cqe cqe;
> --
> 2.43.0
>
^ permalink raw reply
* Re: [PATCH net-next 1/3] psp: add crypt-offset and spi-threshold get/set attributes
From: Jakub Kicinski @ 2026-04-08 1:04 UTC (permalink / raw)
To: Willem de Bruijn
Cc: Akhilesh Samineni, davem, edumazet, pabeni, andrew+netdev, horms,
willemb, daniel.zahka, netdev, linux-kernel,
jayakrishnan.udayavarma, ajit.khaparde, kiran.kella, sachin.suman
In-Reply-To: <willemdebruijn.kernel.1d7f9f774aa55@gmail.com>
On Tue, 07 Apr 2026 17:37:41 -0400 Willem de Bruijn wrote:
> > + if (info->attrs[PSP_A_DEV_CRYPT_OFFSET])
> > + new_config.crypt_offset =
> > + nla_get_u8(info->attrs[PSP_A_DEV_CRYPT_OFFSET]);
>
> PSP defines a 6-bit field in 4 octet units. Does this need bounds checking?
More fundamentally, were we to support this -- is it a device property
or an assoc property?
^ permalink raw reply
* Re: [PATCH net-next 0/3] psp: add crypt-offset and spi-threshold attributes
From: Jakub Kicinski @ 2026-04-08 1:09 UTC (permalink / raw)
To: Akhilesh Samineni
Cc: davem, edumazet, pabeni, andrew+netdev, horms, willemb,
daniel.zahka, netdev, linux-kernel, jayakrishnan.udayavarma,
ajit.khaparde, kiran.kella, sachin.suman
In-Reply-To: <CANQF7iAF6DAMQWv=VSu=nTmb1CyALvVtH8xT2k0CCY0rKk3ggQ@mail.gmail.com>
On Tue, 7 Apr 2026 21:09:38 +0530 Akhilesh Samineni wrote:
> > Please read this document:
> > https://www.kernel.org/doc/html/next/process/maintainer-netdev.html
>
> Thank you for the link. I have reviewed the netdev process documentation.
It is one thing to make an unknowing mistake and another thing
to ignore someone asking you to read the documentation.
Please read the doc top to bottom and tell your entire team to read it.
^ permalink raw reply
* Re: [PATCH net-next 1/2] tcp: rehash onto different ECMP path on retransmit timeout
From: Eric Dumazet @ 2026-04-08 1:09 UTC (permalink / raw)
To: Neil Spring
Cc: netdev, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
shuah, linux-kselftest, Ido Schimmel
In-Reply-To: <20260408002802.2448424-2-ntspring@meta.com>
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] xfrm_user: fix info leak in build_mapping()
From: Jakub Kicinski @ 2026-04-08 1:12 UTC (permalink / raw)
To: Greg Kroah-Hartman, Steffen Klassert
Cc: netdev, linux-kernel, Herbert Xu, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman
In-Reply-To: <2026040745-crafter-awkward-8901@gregkh>
On Tue, 7 Apr 2026 07:51:15 +0200 Greg Kroah-Hartman wrote:
> > > I guess nlmsg_append() would work? It tries to do some zeroing out for
> > > alignment for some reason...
> > >
> > > Want me to do that? I don't have a way to test any of this, I just
> > > found it using some static code analysis tools that looked at holes in
> > > structures.
> >
> > Do you have any more Netlink leaks in the queue? If you do let's do it,
> > if you don't we can wait until the next victi^w patch to arrive.
>
> I do not have any more, sorry. So is it worth it for just these 2?
> Your call :)
These are fine. I would have applied but I think Steffen will take them
via the ipsec tree first (LMK if that's not the plan, Steffen)
^ permalink raw reply
* Re: [PATCH net-next v5 00/10] Decouple receive and transmit enablement in team driver
From: Jakub Kicinski @ 2026-04-08 1:17 UTC (permalink / raw)
To: Marc Harvey, kuniyu
Cc: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <CANkEMgmeHoJkffx3B+N04buW-V0pHD9c=jajNWafmev6f6K85A@mail.gmail.com>
On Tue, 7 Apr 2026 16:06:02 -0700 Marc Harvey wrote:
> Thank you very much to kuniyu@google.com, who figured out how to
> recreate the issue on Fedora. Fedora's /etc/services maps TCP port
> 1234 to the "search-agent" service (normal), which tcpdump then uses
> to text-replace port numbers in its output. So the tests were looking
> for ${ip_address}.1234, but tcpdump was spitting out
> ${ip_address}.search_agent. What is strange is that the test already
> uses tcpdump's "-n" option: "Don't convert addresses (i.e., host
> addresses, port numbers, etc.) to names."
>
> It turns out that Fedora has a patched version of tcpdump that
> separates the normal "-n" option into two options! "-n" handles host
> addresses, and "-nn" handles port and protocol numbers. The tcpdump
> invocation used by the selftests only uses "-n". What's stranger is
> that passing "-nn" to tcpdump is actually portable, because under the
> hood it is treated as a counter, with or without the Fedora patch:
> https://github.com/the-tcpdump-group/tcpdump/blob/master/tcpdump.c#L1915
> (thanks again to Kuniyuki for discovering this).
Oh wow! Thanks to both of you for not giving up and getting to the
bottom of this :)
^ permalink raw reply
* Re: [PATCH net-next] ipv6: sit: remove redundant ret = 0 assignment
From: Yue Haibing @ 2026-04-08 1:26 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, dsahern, edumazet, pabeni, horms, netdev, linux-kernel
In-Reply-To: <20260406184313.5c446354@kernel.org>
On 2026/4/7 9:43, Jakub Kicinski wrote:
> On Fri, 3 Apr 2026 16:44:02 +0800 Yue Haibing wrote:
>> The variable ret is initialized to 0 when it is defined
>> and is not modified before copy_to_user().
>
> Makes more sense to remove the init during definition IMO.
OK,will do this in v2
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox