* Re: [PATCH net v1 2/3] hinic3: fix use-after-free on DMA mapping failure
From: Fan Gong @ 2026-07-20 13:36 UTC (permalink / raw)
To: xuanqiang.luo
Cc: andrew+netdev, davem, edumazet, gongfan1, guoxin09, gur.stavi,
kuba, linux-kernel, luoxuanqiang, netdev, pabeni, stable, wudi234,
tengpeisen, shijing34, wulike1, chiqijun, ducheng11
In-Reply-To: <20260710090527.58354-3-xuanqiang.luo@linux.dev>
> drivers/net/ethernet/huawei/hinic3/hinic3_tx.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
> index 9306bf0020caf..5739ecb08d0d3 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
> @@ -578,8 +578,6 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
> *wqe_combo.task = task;
>
> tx_info = &txq->tx_info[pi];
> - tx_info->skb = skb;
> - tx_info->wqebb_cnt = wqebb_cnt;
>
> err = hinic3_tx_map_skb(netdev, skb, txq, tx_info, &wqe_combo);
> if (err) {
> @@ -589,6 +587,9 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
> goto err_drop_pkt;
> }
>
> + tx_info->skb = skb;
> + tx_info->wqebb_cnt = wqebb_cnt;
> +
> netif_subqueue_sent(netdev, txq->sq->q_id, skb->len);
> netif_subqueue_maybe_stop(netdev, txq->sq->q_id,
> hinic3_wq_free_wqebbs(&txq->sq->wq),
Thanks for your fixes, the proposed modification of the hinic3 driver has been
tested on hardware, and it works fine.
Reviewed-by: Fan Gong <gongfan1@huawei.com>
^ permalink raw reply
* Re: [Patch net-next v3] net: phy: Add driver for Motorcomm Quad 2.5GbE phy
From: Andrew Lunn @ 2026-07-20 13:37 UTC (permalink / raw)
To: Kyle Switch
Cc: Frank.Sae, hkallweit1, linux, davem, edumazet, kuba, pabeni,
netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang, jie.han
In-Reply-To: <3d87f3d1-83c6-49b5-ac6f-59880c6a6c6c@motor-comm.com>
> >> -static struct phy_driver motorcomm_phy_drvs[] = {
> >> - {
> >> - PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
> >> - .name = "YT8511 Gigabit Ethernet",
> >> - .config_init = yt8511_config_init,
> >> - .suspend = genphy_suspend,
> >> - .resume = genphy_resume,
> >> - .read_page = yt8511_read_page,
> >> - .write_page = yt8511_write_page,
> >> - },
> >
> > I still don't like this. Please stop diff deleting all these and
> > putting them back later. It makes me thing something changed, maybe
> > white space?
>
> Ans: I tried to refactor this issue by hanging the position of the newly
> added code. However, the same issue may still arise in other code segments,
> without modifying the existing code.
Look at other patches on this list. Look at the diff generated. Do you
see the same sort of issue? I'm pretty sure something is wrong here.
Try
git reset HEAD~1
git diff -w
and see if you get a different diff. That would indicate there is a
whitespace change you need to revert.
Andrew
^ permalink raw reply
* Re: [PATCH 3/8] iommu/fsl: use platform_device_set_fwnode()
From: Bartosz Golaszewski @ 2026-07-20 13:39 UTC (permalink / raw)
To: Robin Murphy
Cc: brgl, driver-core, linuxppc-dev, linux-kernel, linux-i2c, iommu,
netdev, linux-pm, imx, linux-arm-kernel, mfd, linux-arm-msm,
linux-sound, Bartosz Golaszewski, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Christophe Leroy (CS GROUP),
Andi Shyti, Joerg Roedel (AMD), Will Deacon, Andy Shevchenko,
Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Lee Jones, Sebastian Hesselbarth,
Srinivas Kandagatla
In-Reply-To: <e4f9b266-8d9e-48fe-99c0-58b61e02c9bd@arm.com>
On Mon, 20 Jul 2026 14:58:27 +0200, Robin Murphy <robin.murphy@arm.com> said:
> On 20/07/2026 10:24 am, Bartosz Golaszewski wrote:
>> Prefer the higher-level platform_device_set_fwnode() over the
>> OF-specific platform_device_set_of_node() for dynamically allocated
>> platform devices.
>
> This is very much non-portable code specific to OF-only platforms, but
> if the intention is to remove platform_device_set_of_node() again
> already, then FWIW,
>
Providing platform_device_set_of_node() and using it was done to make the
transision to expanding reference counting to all firmware nodes possible.
I don't think we'll remove it just yet as it doesn't make sense to convert
the code under drivers/of/ to using the fwnode variant.
> Acked-by: Robin Murphy <robin.murphy@arm.com>
>
> (Although I'm slightly puzzled by the cover letter - AFAICS in -next,
> platform_device_set_of_node() is itself very much a user of
> platform_device_set_fwnode(), however in terms of symbol exports,
> perhaps the former could now just be a static inline wrapper?)
>
Sure that can be done independently later.
Bart
^ permalink raw reply
* Re: [PATCH RESEND v4 net-next 05/14] net: enetc: use PCI device name for debugfs directory
From: Joe Damato @ 2026-07-20 13:46 UTC (permalink / raw)
To: wei.fang
Cc: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, linux, wei.fang, chleroy,
maxime.chevallier, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260720014317.1059359-6-wei.fang@oss.nxp.com>
On Mon, Jul 20, 2026 at 09:43:07AM +0800, wei.fang@oss.nxp.com wrote:
> From: Wei Fang <wei.fang@nxp.com>
>
> enetc_create_debugfs() is called right after register_netdev(), at which
> point ndev->name still holds the format "eth%d" (e.g., eth0) rather than
> the final assigned name (e.g., via udev rules).
>
> Use pci_name() instead of netdev_name() to name the debugfs directory.
> The PCI device name is unique, stable, and available from the start,
> making it a more reliable identifier for the debugfs entry. Therefore,
> the observable debugfs path from something like
> /sys/kernel/debug/eth0/mac_filter to a PCI BDF-style path such as
> /sys/kernel/debug/0002:00:00.0/mac_filter.
>
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
> drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c
> index 4a769d9e5679..be378bf8f74d 100644
> --- a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c
> +++ b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c
> @@ -81,10 +81,9 @@ DEFINE_SHOW_ATTRIBUTE(enetc_mac_filter);
>
> void enetc_create_debugfs(struct enetc_si *si)
> {
> - struct net_device *ndev = si->ndev;
> struct dentry *root;
>
> - root = debugfs_create_dir(netdev_name(ndev), NULL);
> + root = debugfs_create_dir(pci_name(si->pdev), NULL);
> if (IS_ERR(root))
> return;
>
Reviewed-by: Joe Damato <joe@dama.to>
^ permalink raw reply
* Re: [PATCH] ovpn: prevent UAF re-add to by_transp_addr on float-vs-delete race
From: Antonio Quartulli @ 2026-07-20 13:47 UTC (permalink / raw)
To: Ibrahim Hashimov, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: Sabrina Dubroca, netdev, linux-kernel, stable, Ralf Lici
In-Reply-To: <20260708224631.1365-1-security@auditcode.ai>
Hi there,
On 09/07/2026 00:46, Ibrahim Hashimov wrote:
[...]
> Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint")
> Cc: stable@vger.kernel.org
> Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
> Assisted-by: AuditCode-AI:2026.07
Sorry for the delay in getting back to you.
We already have a fix for this bug that is queued for being sent to net.
You can find it here:
https://github.com/OpenVPN/ovpn-net-next/commit/beb427921aafd186eb139349bf04c6d54a4927f5
I was caught by Sashiko's fire and therefore had hard time sending all
the fixes in my queue, but I am getting there.
If you want, please check the patch at the link above.
Thanks a lot.
Best Regards,
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply
* Re: [PATCH net-next v4] net: phy: Add driver for Motorcomm Quad 2.5GbE phy
From: Andrew Lunn @ 2026-07-20 13:48 UTC (permalink / raw)
To: Kyle Switch
Cc: Frank.Sae, hkallweit1, linux, davem, edumazet, kuba, pabeni,
netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang, jie.han
In-Reply-To: <20260720091133.2438175-1-kyle.switch@motor-comm.com>
On Mon, Jul 20, 2026 at 05:11:33PM +0800, Kyle Switch wrote:
> Add a driver for motorcomm yt8824 quad 2.5G ethernet phy, supports
> 2.5G/1000M/100M/10M speed.
So this is better, however still not correct.
> - /* Enable WOL feature */
> - mask = YTPHY_WCR_PULSE_WIDTH_MASK | YTPHY_WCR_INTR_SEL;
> - val = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
> - val |= YTPHY_WCR_TYPE_PULSE | YTPHY_WCR_PULSE_WIDTH_672MS;
> - ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, val);
> + /* Enable WOL feature */
> + mask = YTPHY_WCR_PULSE_WIDTH_MASK | YTPHY_WCR_INTR_SEL;
> + val = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL;
> + val |= YTPHY_WCR_TYPE_PULSE | YTPHY_WCR_PULSE_WIDTH_672MS;
> + ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, val);
Look at these lines. What changed?
As a reviewer, i review diffs. Changes like this are just a waste of
my time. Please review your own diffs, look for issues like this,
solve them. I won't give comments on the actual code until these
problems are gone.
Andrew
---
pw-bot: cr
^ permalink raw reply
* Re: [PATCH RESEND v4 net-next 14/14] net: enetc: use kzalloc_flex() for enetc_psfp_gate allocation
From: Joe Damato @ 2026-07-20 13:48 UTC (permalink / raw)
To: wei.fang
Cc: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, linux, wei.fang, chleroy,
maxime.chevallier, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260720014317.1059359-15-wei.fang@oss.nxp.com>
On Mon, Jul 20, 2026 at 09:43:16AM +0800, wei.fang@oss.nxp.com wrote:
> From: Wei Fang <wei.fang@nxp.com>
>
> Replace the open-coded struct_size() + kzalloc() pattern with the
> kzalloc_flex() helper when allocating struct enetc_psfp_gate. This
> removes the intermediate entries_size local variable and makes the
> allocation site more concise.
>
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
> drivers/net/ethernet/freescale/enetc/enetc_qos.c | 4 +---
> 1 file changed, 1 insertion(+), 3 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/enetc/enetc_qos.c b/drivers/net/ethernet/freescale/enetc/enetc_qos.c
> index 7b17bca24f26..2aa0fcaafcd2 100644
> --- a/drivers/net/ethernet/freescale/enetc/enetc_qos.c
> +++ b/drivers/net/ethernet/freescale/enetc/enetc_qos.c
> @@ -1135,7 +1135,6 @@ static int enetc_psfp_parse_clsflower(struct enetc_ndev_priv *priv,
> struct flow_action_entry *entry;
> struct action_gate_entry *e;
> u8 sfi_overwrite = 0;
> - int entries_size;
> int i, err;
>
> if (f->common.chain_index >= priv->psfp_cap.max_streamid) {
> @@ -1242,8 +1241,7 @@ static int enetc_psfp_parse_clsflower(struct enetc_ndev_priv *priv,
> goto free_filter;
> }
>
> - entries_size = struct_size(sgi, entries, entryg->gate.num_entries);
> - sgi = kzalloc(entries_size, GFP_KERNEL);
> + sgi = kzalloc_flex(*sgi, entries, entryg->gate.num_entries);
> if (!sgi) {
> err = -ENOMEM;
> goto free_filter;
Reviewed-by: Joe Damato <joe@dama.to>
^ permalink raw reply
* [PATCH v2 net] gre: fix ERSPAN o_flags race/corruption in xmit and fill_info
From: Eric Dumazet @ 2026-07-20 13:51 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Ido Schimmel, netdev, eric.dumazet, Eric Dumazet,
stable
For IPv4 ERSPAN:
In erspan_xmit(), the driver clears IP_TUNNEL_SEQ_BIT (for version 0)
and IP_TUNNEL_KEY_BIT directly in the shared tunnel->parms.o_flags
structure. Since transmit paths can run locklessly and concurrently,
this leads to a data race.
Furthermore, modifying tunnel->parms.o_flags permanently alters the
tunnel configuration. To work around this, erspan_fill_info() (which
reports config to userspace) was setting IP_TUNNEL_KEY_BIT back. If
erspan_fill_info (running under RTNL) and erspan_xmit (running locklessly)
race, erspan_xmit might see IP_TUNNEL_KEY_BIT set when it shouldnt,
leading to GRE header corruption (injecting a key field into the ERSPAN
GRE header).
Fix this by:
1) Snapshotting tunnel->parms.o_flags onto the stack in ipgre_xmit(),
gre_tap_xmit(), and __gre6_xmit(), and computing the GRE header length via
gre_calc_hlen(flags) instead of reading tunnel->tun_hlen. This eliminates
TOCTOU races between flags, offload checks, and header length.
Note: Since tunnel->tun_hlen is no longer used in fast path xmit, it could be
removed altogether in net-next.
2) Using local flags in erspan_xmit(), clearing IP_TUNNEL_CSUM_BIT and
IP_TUNNEL_KEY_BIT (and IP_TUNNEL_SEQ_BIT for v0) before passing them to
__gre_xmit().
3) Removing the racy modification of t->parms.o_flags in erspan_fill_info().
4) Forcing IP_TUNNEL_KEY_BIT in the reported flags for ERSPAN locally
in ipgre_fill_info().
For IPv6 ERSPAN:
ip6erspan_tunnel_xmit() was locklessly clearing IP_TUNNEL_KEY_BIT in
t->parms.o_flags even though it does not use these flags for building
the GRE header (it uses local flags). This permanently corrupts the
configuration and races with ip6gre_fill_info() which reads it.
Remove the redundant and racy modification.
This should remove false sharing in a fast path.
Add const qualifiers in ipgre_fill_info(), erspan_fill_info()
and ip6gre_fill_info() to clarify that these methods are not
supposed to write any live parameters.
Fixes: 84e54fe0a5ea ("gre: introduce native tunnel support for ERSPAN")
Fixes: ee496694b9ee ("ip_gre: do not report erspan version on GRE interface")
Fixes: 5a963eb61b7c ("ip6_gre: Add ERSPAN native tunnel support")
Cc: stable@vger.kernel.org
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
v2: addressed Sashiko and Ido feedback.
v1: https://lore.kernel.org/netdev/20260615140333.3161072-1-edumazet@google.com/
net/ipv4/ip_gre.c | 45 +++++++++++++++++++++++++--------------------
net/ipv6/ip6_gre.c | 7 +++----
2 files changed, 28 insertions(+), 24 deletions(-)
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 3efdfb4ffa2112db3b056169d02d28e8853873ae..442a2c9513e84e84013b4a6d447dd5e243428c5c 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -475,15 +475,12 @@ static int gre_rcv(struct sk_buff *skb)
static void __gre_xmit(struct sk_buff *skb, struct net_device *dev,
const struct iphdr *tnl_params,
- __be16 proto)
+ __be16 proto, const unsigned long *flags)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
- IP_TUNNEL_DECLARE_FLAGS(flags);
-
- ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
/* Push GRE header. */
- gre_build_header(skb, tunnel->tun_hlen,
+ gre_build_header(skb, gre_calc_hlen(flags),
flags, proto, tunnel->parms.o_key,
test_bit(IP_TUNNEL_SEQ_BIT, flags) ?
htonl(atomic_fetch_inc(&tunnel->o_seqno)) : 0);
@@ -654,6 +651,7 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
{
struct ip_tunnel *tunnel = netdev_priv(dev);
const struct iphdr *tnl_params;
+ IP_TUNNEL_DECLARE_FLAGS(flags);
if (!pskb_inet_may_pull(skb))
goto free_skb;
@@ -663,6 +661,8 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
return NETDEV_TX_OK;
}
+ ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
+
if (dev->header_ops) {
int pull_len = tunnel->hlen + sizeof(struct iphdr);
@@ -688,11 +688,10 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
tnl_params = &tunnel->parms.iph;
}
- if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
- tunnel->parms.o_flags)))
+ if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags)))
goto free_skb;
- __gre_xmit(skb, dev, tnl_params, skb->protocol);
+ __gre_xmit(skb, dev, tnl_params, skb->protocol, flags);
return NETDEV_TX_OK;
free_skb:
@@ -705,6 +704,7 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
+ IP_TUNNEL_DECLARE_FLAGS(flags);
bool truncate = false;
__be16 proto;
@@ -728,10 +728,12 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
truncate = true;
}
+ ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
+
/* Push ERSPAN header */
if (tunnel->erspan_ver == 0) {
proto = htons(ETH_P_ERSPAN);
- __clear_bit(IP_TUNNEL_SEQ_BIT, tunnel->parms.o_flags);
+ __clear_bit(IP_TUNNEL_SEQ_BIT, flags);
} else if (tunnel->erspan_ver == 1) {
erspan_build_header(skb, ntohl(tunnel->parms.o_key),
tunnel->index,
@@ -746,8 +748,9 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
goto free_skb;
}
- __clear_bit(IP_TUNNEL_KEY_BIT, tunnel->parms.o_flags);
- __gre_xmit(skb, dev, &tunnel->parms.iph, proto);
+ __clear_bit(IP_TUNNEL_CSUM_BIT, flags);
+ __clear_bit(IP_TUNNEL_KEY_BIT, flags);
+ __gre_xmit(skb, dev, &tunnel->parms.iph, proto, flags);
return NETDEV_TX_OK;
free_skb:
@@ -760,6 +763,7 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
+ IP_TUNNEL_DECLARE_FLAGS(flags);
if (!pskb_inet_may_pull(skb))
goto free_skb;
@@ -769,14 +773,15 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
return NETDEV_TX_OK;
}
- if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
- tunnel->parms.o_flags)))
+ ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
+
+ if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags)))
goto free_skb;
if (skb_cow_head(skb, dev->needed_headroom))
goto free_skb;
- __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB));
+ __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB), flags);
return NETDEV_TX_OK;
free_skb:
@@ -1560,12 +1565,15 @@ static size_t ipgre_get_size(const struct net_device *dev)
static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
- struct ip_tunnel *t = netdev_priv(dev);
- struct ip_tunnel_parm_kern *p = &t->parms;
+ const struct ip_tunnel *t = netdev_priv(dev);
+ const struct ip_tunnel_parm_kern *p = &t->parms;
IP_TUNNEL_DECLARE_FLAGS(o_flags);
ip_tunnel_flags_copy(o_flags, p->o_flags);
+ if (t->erspan_ver != 0 && !t->collect_md)
+ __set_bit(IP_TUNNEL_KEY_BIT, o_flags);
+
if (nla_put_u32(skb, IFLA_GRE_LINK, p->link) ||
nla_put_be16(skb, IFLA_GRE_IFLAGS,
gre_tnl_flags_to_gre_flags(p->i_flags)) ||
@@ -1608,12 +1616,9 @@ static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
static int erspan_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
- struct ip_tunnel *t = netdev_priv(dev);
+ const struct ip_tunnel *t = netdev_priv(dev);
if (t->erspan_ver <= 2) {
- if (t->erspan_ver != 0 && !t->collect_md)
- __set_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags);
-
if (nla_put_u8(skb, IFLA_GRE_ERSPAN_VER, t->erspan_ver))
goto nla_put_failure;
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 7c09a269b3521618e264655ad2b93586f7214b0b..9e68afec9dce41ab7f094093a8607f9f7ebca4f9 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -777,7 +777,7 @@ static netdev_tx_t __gre6_xmit(struct sk_buff *skb,
ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
- gre_build_header(skb, tunnel->tun_hlen, flags,
+ gre_build_header(skb, gre_calc_hlen(flags), flags,
protocol, tunnel->parms.o_key,
test_bit(IP_TUNNEL_SEQ_BIT, flags) ?
htonl(atomic_fetch_inc(&tunnel->o_seqno)) :
@@ -964,7 +964,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
if (skb_cow_head(skb, dev->needed_headroom ?: t->hlen))
goto tx_err;
- __clear_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags);
IPCB(skb)->flags = 0;
/* For collect_md mode, derive fl6 from the tunnel key,
@@ -2115,8 +2114,8 @@ static size_t ip6gre_get_size(const struct net_device *dev)
static int ip6gre_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
- struct ip6_tnl *t = netdev_priv(dev);
- struct __ip6_tnl_parm *p = &t->parms;
+ const struct ip6_tnl *t = netdev_priv(dev);
+ const struct __ip6_tnl_parm *p = &t->parms;
IP_TUNNEL_DECLARE_FLAGS(o_flags);
ip_tunnel_flags_copy(o_flags, p->o_flags);
--
2.55.0.229.g6434b31f56-goog
^ permalink raw reply related
* [PATCH net] idpf: disable DIM work before freeing q_vectors
From: Myeonghun Pak @ 2026-07-20 13:58 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel, intel-wired-lan
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, netdev, linux-kernel, Ijae Kim
idpf never drains the Tx/Rx DIM works before freeing the memory they
live in. tx_dim and rx_dim are embedded in struct idpf_q_vector, they
are queued from the NAPI poll via net_dim(), and idpf_vport_intr_rel()
ends with kfree(rsrc->q_vectors). Nothing in the driver cancels them.
idpf_tx_dim_work() and idpf_rx_dim_work() then run on freed memory:
idpf_vport_intr_write_itr() writes the ITR register through
q_vector->intr_reg.tx_itr / rx_itr, void __iomem pointers loaded out of
the freed q_vector. No configuration is needed to get there --
IDPF_ITR_IS_DYNAMIC() is defined as (itr_mode) and idpf_vport_alloc()
initialises both modes to IDPF_ITR_DYNAMIC.
Draining after idpf_vport_intr_napi_dis_all() is not enough on its own.
idpf_net_dim() is called from inside the
"if (napi_complete_done(napi, work_done))" branch of the poll, and
napi_complete_done() has already cleared NAPIF_STATE_SCHED by then.
napi_disable_locked() waits only while (val & (NAPIF_STATE_SCHED |
NAPIF_STATE_NPSVC)), so napi_disable() can return while the poll tail is
still queueing the work, and a plain cancel_work_sync() would be
re-armed behind the drain.
Use disable_work_sync(): schedule_work() on a work with a non-zero
disable count is dropped by clear_pending_if_disabled() before
__queue_work() is reached.
Move idpf_init_dim() to idpf_vport_intr_alloc() so the works are
initialised on every path that can reach the drain -- the three
"goto intr_deinit" sites between idpf_vport_intr_init() and
idpf_vport_intr_ena() get there without the enable side having run.
Nothing re-enables them: rsrc->q_vectors is freed on every exit from
idpf_vport_open() and on every idpf_vport_stop(), so the count dies with
the object.
It is a race, not a deterministic failure -- net_dim() only schedules
once DIM_NEVENTS events have accumulated and the profile index changes.
A KASAN ifup/ifdown loop under load is the way to see it.
Fixes: c2d548cad150 ("idpf: add TX splitq napi poll support")
Fixes: 3a8845af66ed ("idpf: add RX splitq napi poll support")
Cc: <stable@vger.kernel.org> # see patch description, needs adjustments for <= 6.9
Co-developed-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
---
Found by static analysis; not tested on hardware. Testing by someone at
Intel with an idpf device would be very welcome.
disable_work_sync() landed in v6.10, which is newer than the commits
this fixes, hence the annotation on the stable tag. A backport to older
trees needs the work held down another way rather than a cherry-pick;
happy to send one if the bot reports a failure.
libwx does the same thing in the same order -- napi_disable() then
disable_work_sync() on the dim works -- in wx_napi_disable_all().
Based on v7.2-rc2.
drivers/net/ethernet/intel/idpf/idpf_txrx.c | 24 +++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c
index 7f9056404f64..cd98674fb648 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c
@@ -4148,6 +4148,26 @@ static void idpf_vport_intr_ena_irq_all(struct idpf_vport *vport,
writel(rsrc->noirq_dyn_ctl_ena, rsrc->noirq_dyn_ctl);
}
+/**
+ * idpf_vport_intr_dis_dim_all - Disable DIM work for all q_vectors
+ * @rsrc: pointer to queue and vector resources
+ *
+ * The DIM works are embedded in the q_vector array that
+ * idpf_vport_intr_rel() frees, and the poll arms them after
+ * napi_complete_done() has already cleared NAPI_STATE_SCHED. Disable
+ * rather than just cancel, so that a poll tail still running past
+ * napi_disable() cannot queue them again behind the drain.
+ */
+static void idpf_vport_intr_dis_dim_all(struct idpf_q_vec_rsrc *rsrc)
+{
+ for (u16 v_idx = 0; v_idx < rsrc->num_q_vectors; v_idx++) {
+ struct idpf_q_vector *q_vector = &rsrc->q_vectors[v_idx];
+
+ disable_work_sync(&q_vector->tx_dim.work);
+ disable_work_sync(&q_vector->rx_dim.work);
+ }
+}
+
/**
* idpf_vport_intr_deinit - Release all vector associations for the vport
* @vport: main vport structure
@@ -4158,6 +4178,7 @@ void idpf_vport_intr_deinit(struct idpf_vport *vport,
{
idpf_vport_intr_dis_irq_all(rsrc);
idpf_vport_intr_napi_dis_all(rsrc);
+ idpf_vport_intr_dis_dim_all(rsrc);
idpf_vport_intr_napi_del_all(rsrc);
idpf_vport_intr_rel_irq(vport, rsrc);
}
@@ -4238,7 +4259,6 @@ static void idpf_vport_intr_napi_ena_all(struct idpf_q_vec_rsrc *rsrc)
for (u16 q_idx = 0; q_idx < rsrc->num_q_vectors; q_idx++) {
struct idpf_q_vector *q_vector = &rsrc->q_vectors[q_idx];
- idpf_init_dim(q_vector);
napi_enable(&q_vector->napi);
}
}
@@ -4581,6 +4601,8 @@ int idpf_vport_intr_alloc(struct idpf_vport *vport,
q_coal = &user_config->q_coalesce[v_idx];
q_vector->vport = vport;
+ idpf_init_dim(q_vector);
+
q_vector->tx_itr_value = q_coal->tx_coalesce_usecs;
q_vector->tx_intr_mode = q_coal->tx_intr_mode;
q_vector->tx_itr_idx = VIRTCHNL2_ITR_IDX_1;
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net v2] net: stmmac: dwmac4: mask interrupts when stopping DMA in suspend
From: Andrew Lunn @ 2026-07-20 13:59 UTC (permalink / raw)
To: Luis Lang
Cc: netdev, Maxime Chevallier, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
Alexandre Torgue, Russell King (Oracle), Oleksij Rempel,
Ovidiu Panait, Rohan G Thomas,
moderated list:ARM/STM32 ARCHITECTURE,
moderated list:ARM/STM32 ARCHITECTURE, open list
In-Reply-To: <20260720111534.163416-1-luis.la@mail.de>
On Mon, Jul 20, 2026 at 01:15:29PM +0200, Luis Lang wrote:
> Since commit 1b9707e6f1a9 ("net: stmmac: enable RPS and RBU
> interrupts"), suspending causes an interrupt storm from the RPS
> interrupt.
> Fix this by adding a deinit_chan() op to stmmac_dma_ops, which
> masks all default dma channel interrupts. This is called from
> stmmac_stop_all_dma(), so interrupts don't trigger while suspending.
>
> Fixes: 1b9707e6f1a9 ("net: stmmac: enable RPS and RBU interrupts")
> Suggested-by: Andrew Lunn <andrew@lunn.ch>
> Suggested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
> Signed-off-by: Luis Lang <luis.la@mail.de>
Nice. Thanks for working on this.
These are the sort of patches i like to see. We see a lot of AI driven
patches fixing theoretical issues which nobody really cares about. The
submitter does not appear to understand what they are doing, maybe
compile tests it, but never boot it on real hardware.
You have built and booted lots of kernels, done git bisects, sent a
good bug report, done the work of actually fixing the problem with a
little help from us, and pretty much got the processes correct first
time. You have the basics for being a real kernel hacker. I would be
happy to receive more patches from you.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Thanks
Andrew
^ permalink raw reply
* Re: [PATCH RESEND v4 net-next 01/14] net: enetc: extract common helpers for MAC promiscuous mode setting
From: Joe Damato @ 2026-07-20 14:02 UTC (permalink / raw)
To: wei.fang
Cc: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, linux, wei.fang, chleroy,
maxime.chevallier, imx, netdev, linux-kernel, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20260720014317.1059359-2-wei.fang@oss.nxp.com>
On Mon, Jul 20, 2026 at 09:43:03AM +0800, wei.fang@oss.nxp.com wrote:
> From: Wei Fang <wei.fang@nxp.com>
>
> The PSIPMMR (Port Station Interface Promiscuous MAC Mode Register) in
> ENETC v4 has the same bit layout as the PSIPMR register in ENETC v1: bit
> n controls unicast promiscuous mode for SI n, and bit (n + 16) controls
> multicast promiscuous mode for SI n. The only difference between the two
> hardware generations is the register address offset.
>
> Since the register functionality is identical, the MAC promiscuous mode
> setting code can be shared between ENETC v1 and v4 drivers.
>
> Rename ENETC_PSIPMR to ENETC_PSIPMMR in enetc_hw.h to match the actual
> register name used in the reference manual, and extract two new common
> helper functions, enetc_set_si_uc_promisc() and
> enetc_set_si_mc_promisc(), into enetc_pf_common.c. These helpers select
> the correct register offset based on the hardware revision via
> is_enetc_rev1().
>
> Remove the v4-specific enetc4_pf_set_si_mac_promisc() function from
> enetc4_pf.c and the duplicate PSIPMMR_SI_MAC_UP/MP macro definitions
> from enetc4_hw.h, as they are now superseded by the shared code.
>
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
> .../net/ethernet/freescale/enetc/enetc4_hw.h | 2 -
> .../net/ethernet/freescale/enetc/enetc4_pf.c | 21 +--------
> .../ethernet/freescale/enetc/enetc_ethtool.c | 2 +-
> .../net/ethernet/freescale/enetc/enetc_hw.h | 7 +--
> .../net/ethernet/freescale/enetc/enetc_pf.c | 11 ++---
> .../freescale/enetc/enetc_pf_common.c | 44 +++++++++++++++++++
> .../freescale/enetc/enetc_pf_common.h | 2 +
> 7 files changed, 56 insertions(+), 33 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h
> index f18437556a0e..6a8f2ed56017 100644
> --- a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h
> +++ b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h
> @@ -69,8 +69,6 @@
>
> /* Port Station interface promiscuous MAC mode register */
> #define ENETC4_PSIPMMR 0x200
> -#define PSIPMMR_SI_MAC_UP(a) BIT(a) /* a = SI index */
> -#define PSIPMMR_SI_MAC_MP(a) BIT((a) + 16)
[...]
> diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
> index bf99b65d7598..66bfda60da9c 100644
> --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h
> +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
> @@ -180,9 +180,10 @@ enum enetc_bdr_type {TX, RX};
> #define ENETC_PMR_PSPEED_1000M BIT(9)
> #define ENETC_PMR_PSPEED_2500M BIT(10)
> #define ENETC_PSR 0x0004 /* RO */
> -#define ENETC_PSIPMR 0x0018
> -#define ENETC_PSIPMR_SET_UP(n) BIT(n) /* n = SI index */
> -#define ENETC_PSIPMR_SET_MP(n) BIT((n) + 16)
> +#define ENETC_PSIPMMR 0x0018
> +#define PSIPMMR_SI_MAC_UP(n) BIT(n) /* n = SI index */
> +#define PSIPMMR_SI_MAC_MP(n) BIT((n) + 16)
I probably would have fixed the leading spaces when copying/pasting the macro,
but that seems like a nit.
I read the rest of the code a few times and it looks right to me.
Reviewed-by: Joe Damato <joe@dama.to>
^ permalink raw reply
* Re: [Intel-wired-lan] [ANN] E830 NIC join intel's netdev-ci
From: Pielech, Adrian @ 2026-07-20 14:03 UTC (permalink / raw)
To: Paul Menzel
Cc: kuba, netdev, Przemyslaw Kitszel, intel-wired-lan,
Anthony L Nguyen, pabeni, davem, edumazet, horms, andrew+netdev
In-Reply-To: <b4d17a58-f6e5-48c4-9bf0-230ca710ea2d@molgen.mpg.de>
On 7/10/2026 2:06 PM, Paul Menzel wrote:
> Dear Adrian,
>
>
> Am 10.07.26 um 13:20 schrieb Pielech, Adrian:
>
>> I'm pleased to announce that E830-XXVDA2 NIC from Intel Ethernet
>> E830 product line joins the suite that tests ice driver against net-
>> next-hw branch.
>
> Great news! Thank you for sharing this on the list.
>
> I am curious: Is there an overview of the test lab?
>
>
> Kind regards,
>
> Paul
Hi Paul,
Long time to reply but...
Tested NICs provide metadata with exact card identification including FW
details.
All NICs are running on PCI-Express 4.0 controller, powered by Intel
Xeon 2nd generation CPU.
Regards,
Adrian
^ permalink raw reply
* [PATCH bpf v2] veth: convert frag_list skbs before running XDP
From: Matt Fleming @ 2026-07-20 14:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Lorenzo Bianconi,
Toke Høiland-Jørgensen, bpf, netdev, stable,
kernel-team, Matt Fleming
From: Matt Fleming <mfleming@cloudflare.com>
A frag_list skb can reach veth with data_len set but nr_frags zero.
veth_convert_skb_to_xdp_buff() only converts skbs that are shared,
locked, have frags[], or do not have enough headroom. It later uses
skb_is_nonlinear() to decide whether to set XDP_FLAGS_HAS_FRAGS and
xdp_frags_size.
That exposes frag_list data to XDP as if it were stored in frags[], but
frags[] is empty. AF_XDP copy mode can then trust the bogus XDP fragment
metadata, walk an empty fragment entry, and crash in memcpy() from
__xsk_rcv().
Route non-linear skbs through skb_pp_cow_data() before exposing them to
XDP, and only advertise XDP frags when the resulting skb has frags[].
skb_copy_bits() already handles frag_list input, and skb_pp_cow_data()
builds frags[] output with skb_add_rx_frag(), which is the
representation XDP multi-buffer expects.
Fixes: 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb")
Cc: stable@vger.kernel.org
Signed-off-by: Matt Fleming <mfleming@cloudflare.com>
---
Changes in v2:
- Use skb_is_nonlinear() in veth_convert_skb_to_xdp_buff().
- Move the skb_pp_cow_data() comment into kerneldoc.
drivers/net/veth.c | 4 ++--
net/core/skbuff.c | 16 ++++++++++------
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index 1c5142149175..00e34afd858e 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -756,7 +756,7 @@ static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,
u32 frame_sz;
if (skb_shared(skb) || skb_head_is_locked(skb) ||
- skb_shinfo(skb)->nr_frags ||
+ skb_is_nonlinear(skb) ||
skb_headroom(skb) < XDP_PACKET_HEADROOM) {
if (skb_pp_cow_data(rq->page_pool, pskb, XDP_PACKET_HEADROOM))
goto drop;
@@ -771,7 +771,7 @@ static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,
xdp_prepare_buff(xdp, skb->head, skb_headroom(skb),
skb_headlen(skb), true);
- if (skb_is_nonlinear(skb)) {
+ if (skb_shinfo(skb)->nr_frags) {
skb_shinfo(skb)->xdp_frags_size = skb->data_len;
xdp_buff_set_frags_flag(xdp);
} else {
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 18dabb4e9cfa..66f57131633a 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -927,6 +927,16 @@ static void skb_clone_fraglist(struct sk_buff *skb)
skb_get(list);
}
+/**
+ * skb_pp_cow_data() - copy skb data into page-pool backed storage
+ * @pool: page pool to allocate from
+ * @pskb: pointer to skb pointer, replaced with the copied skb on success
+ * @headroom: headroom to reserve in the copied skb
+ *
+ * skb_copy_bits() handles both frags[] and frag_list input. If the copied
+ * skb remains non-linear, it uses frags[], which is the representation used
+ * by XDP multi-buffer.
+ */
int skb_pp_cow_data(struct page_pool *pool, struct sk_buff **pskb,
unsigned int headroom)
{
@@ -936,12 +946,6 @@ int skb_pp_cow_data(struct page_pool *pool, struct sk_buff **pskb,
int err, i, head_off;
void *data;
- /* XDP does not support fraglist so we need to linearize
- * the skb.
- */
- if (skb_has_frag_list(skb))
- return -EOPNOTSUPP;
-
max_head_size = SKB_WITH_OVERHEAD(PAGE_SIZE - headroom);
if (skb->len > max_head_size + MAX_SKB_FRAGS * PAGE_SIZE)
return -ENOMEM;
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net-next v2 08/10] net: dsa: microchip: add KSZ8463 tail tag handling
From: Simon Horman @ 2026-07-20 14:07 UTC (permalink / raw)
To: bastien.curutchet
Cc: Simon Horman, woojung.huh, UNGLinuxDriver, andrew, olteanv, davem,
edumazet, kuba, pabeni, richardcochran, linux, maxime.chevallier,
pascal.eberhard, miquel.raynal, thomas.petazzoni, netdev,
linux-kernel
In-Reply-To: <20260713-ksz-new-ptp-v2-8-7209c05058ef@bootlin.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
https://sashiko.dev/#/patchset/20260713-ksz-new-ptp-v2-1-7209c05058ef@bootlin.com?part=8
---
--- Patch [8]: [PATCH net-next v2 08/10] net: dsa: microchip: add KSZ8463 tail tag handling ---
commit bc95662a9488a7827d30f83890008a818f0a5228
Author: Bastien Curutchet (Schneider Electric) <bastien.curutchet@bootlin.com>
net: dsa: microchip: add KSZ8463 tail tag handling
KSZ8463 uses the KSZ9893 DSA TAG driver, but uses reserved fields in the PTP
header for timestamps instead of the tail tag. This commit adds a
KSZ8463-specific DSA_TAG driver to handle KSZ8463 timestamps.
> diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c
> index f8b40437c5fa4..477f2997c1a34 100644
> --- a/net/dsa/tag_ksz.c
> +++ b/net/dsa/tag_ksz.c
[ ... ]
> +static struct sk_buff *ksz8463_rcv(struct sk_buff *skb, struct net_device *dev)
> +{
> + unsigned int len = KSZ_EGRESS_TAG_LEN;
> + struct ptp_header *ptp_hdr;
> + unsigned int ptp_class;
> + unsigned int port;
> + ktime_t ts;
> + u8 *tag;
> +
> + if (skb_linearize(skb)) {
> + kfree_skb(skb);
> + return NULL;
> + }
> +
> + /* Tag decoding */
> + tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN;
> + port = tag[0] & KSZ8463_TAIL_TAG_EG_PORT_M;
> +
> + __skb_push(skb, ETH_HLEN);
> + ptp_class = ptp_classify_raw(skb);
> + __skb_pull(skb, ETH_HLEN);
> + if (ptp_class == PTP_CLASS_NONE)
> + goto common_rcv;
> +
> + ptp_hdr = ptp_parse_header(skb, ptp_class);
> + if (ptp_hdr) {
> + ts = ksz_decode_tstamp(get_unaligned_be32(&ptp_hdr->reserved2));
> + KSZ_SKB_CB(skb)->tstamp = ts;
> + ptp_hdr->reserved2 = 0;
> + skb->ip_summed = CHECKSUM_NONE;
[Severity: High]
Does this unconditionally modify the payload of all PTP packets?
If a PTPv1 packet is received, ptp_classify_raw() identifies it as PTP, but
the PTPv1 header structure is different. Blindly casting and zeroing
ptp_hdr->reserved2 aliases bytes 16-19, which corrupts the subdomain string.
Should this check that ptp_class includes PTP_CLASS_V2?
Additionally, altering the UDP payload without updating the UDP checksum and
setting skb->ip_summed = CHECKSUM_NONE forces the network stack to verify the
now-invalid checksum, which causes the packet to be dropped.
> + }
[Severity: High]
Can this leak uninitialized memory to userspace if a truncated PTP packet is
received?
If ptp_parse_header() returns NULL, KSZ_SKB_CB(skb)->tstamp is left
uninitialized. Later, when the DSA core calls dsa_skb_defer_rx_timestamp(),
this uninitialized value is read and exposed to userspace:
drivers/net/dsa/microchip/ksz_ptp.c:ksz_port_rxtstamp() {
tstamp = KSZ_SKB_CB(skb)->tstamp;
memset(hwtstamps, 0, sizeof(*hwtstamps));
hwtstamps->hwtstamp = ksz_tstamp_reconstruct(dev, tstamp);
...
}
> +
> +common_rcv:
> + return ksz_common_rcv(skb, dev, port, len);
> +}
^ permalink raw reply
* Re: [PATCH v2] idpf: disable PCIe PTM on probe failure and removal
From: Myeonghun Pak @ 2026-07-20 14:08 UTC (permalink / raw)
To: Tony Nguyen
Cc: Przemek Kitszel, intel-wired-lan, Milena Olech, Emil Tantilov,
Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, netdev, linux-kernel, Ijae Kim
In-Reply-To: <37756e55-5feb-4f5c-9400-55a9762f9a77@intel.com>
You are right, thanks for catching this. I missed that pci_disable_ptm()
now decrements dev->ptm_enable_cnt unconditionally and recurses upstream,
so calling it after a failed enable would corrupt the counts of parents
shared with other endpoints.
v3 will guard both call sites with pcie_ptm_enabled(), which idpf already
uses in idpf_ptp_set_caps(), so no new adapter field is needed. v2 also
needs a rebase since pci_enable_ptm() lost its granularity argument.
Thanks,
Myeonghun
2026년 7월 18일 (토) 오전 5:21, Tony Nguyen <anthony.l.nguyen@intel.com>님이 작성:
>
>
>
> On 7/15/2026 12:43 AM, Myeonghun Pak wrote:
> > idpf_probe() enables PCIe Precision Time Measurement with
> > pci_enable_ptm(pdev, NULL), which programs the PTM control bits and sets
> > pdev->ptm_enabled when the bus/controller supports it.
> >
> > If a later probe step fails, the error path releases the allocated
> > workqueues and adapter memory without disabling PTM. The remove path has
> > the same imbalance when a successfully probed device is detached. In
> > both cases, the PCI core's software PTM state and the device's PTM control
> > bits remain set with no bound driver.
> >
> > Add pci_disable_ptm() to the common probe unwind after the PTM enable and
> > to idpf_remove(). pci_disable_ptm() is a no-op when PTM was not enabled,
> > so the non-fatal pci_enable_ptm() failure remains safe.
> > pcim_enable_device() only arranges for pci_disable_device() and does not
> > undo the PTM enable.
> >
> > Fixes: 8d5e12c5921c ("idpf: add initial PTP support")
> > Co-developed-by: Ijae Kim <ae878000@gmail.com>
> > Signed-off-by: Ijae Kim <ae878000@gmail.com>
> > Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
> > ---
> > Changes in v2:
> > - Disable PTM in the probe error path, as requested by Emil Tantilov.
> >
> > drivers/net/ethernet/intel/idpf/idpf_main.c | 7 +++++--
> > 1 file changed, 5 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c b/drivers/net/ethernet/intel/idpf/idpf_main.c
> > index 0dd741dcfc..f08ce13c20 100644
> > --- a/drivers/net/ethernet/intel/idpf/idpf_main.c
> > +++ b/drivers/net/ethernet/intel/idpf/idpf_main.c
> > @@ -159,6 +159,7 @@ static void idpf_remove(struct pci_dev *pdev)
> > mutex_destroy(&adapter->queue_lock);
> > mutex_destroy(&adapter->vc_buf_lock);
> >
> > + pci_disable_ptm(pdev);
>
> Sashiko says:
>
> If pci_enable_ptm() failed earlier during idpf_probe() because it was
> unsupported by the bus or controller, does this unconditional call to
> pci_disable_ptm() corrupt the upstream PTM enable counts?
> Looking at pci_disable_ptm(), it does not check if the local device was
> successfully enabled. It climbs the PCIe tree and erroneously decrements
> atomic counters like ptm_enable_cnt on parent switches.
> Could this eventually break PTM for other downstream devices sharing the
> same
> switch if a parent's count is incorrectly decremented to -1?
>
> > pci_set_drvdata(pdev, NULL);
> > kfree(adapter);
> > }
> > @@ -266,7 +267,7 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
> > if (err) {
> > pci_err(pdev, "DMA configuration failed: %pe\n", ERR_PTR(err));
> >
> > - goto err_free;
> > + goto err_disable_ptm;
> > }
> >
> > pci_set_master(pdev);
> > @@ -279,7 +280,7 @@ static int idpf_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
> > if (!adapter->init_wq) {
> > dev_err(dev, "Failed to allocate init workqueue\n");
> > err = -ENOMEM;
> > - goto err_free;
> > + goto err_disable_ptm;
> > }
> >
> > adapter->serv_wq = alloc_workqueue("%s-%s-service",
> > @@ -366,6 +367,8 @@ err_mbx_wq_alloc:
> > destroy_workqueue(adapter->serv_wq);
> > err_serv_wq_alloc:
> > destroy_workqueue(adapter->init_wq);
> > +err_disable_ptm:
> > + pci_disable_ptm(pdev);
>
> Like the issue in idpf_remove(), does this error path unconditionally
> disable
> PTM even if the earlier enable attempt failed?
> If a subsequent probe step fails (such as dma_set_mask_and_coherent or
> workqueue allocation), this path is taken regardless of whether
> pci_enable_ptm() succeeded.
> Would it make sense to track the PTM state in the adapter struct, and only
> call pci_disable_ptm() when it was successfully enabled?
>
> > err_free:
> > kfree(adapter);
> > return err;
>
^ permalink raw reply
* Re: [PATCH net-next v2 10/10] net: dsa: microchip: add two-steps PTP support for KSZ8463
From: Simon Horman @ 2026-07-20 14:07 UTC (permalink / raw)
To: bastien.curutchet
Cc: Simon Horman, woojung.huh, UNGLinuxDriver, andrew, olteanv, davem,
edumazet, kuba, pabeni, richardcochran, linux, maxime.chevallier,
pascal.eberhard, miquel.raynal, thomas.petazzoni, netdev,
linux-kernel
In-Reply-To: <20260713-ksz-new-ptp-v2-10-7209c05058ef@bootlin.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
https://sashiko.dev/#/patchset/20260713-ksz-new-ptp-v2-1-7209c05058ef@bootlin.com?part=10
---
--- Patch [10]: [PATCH net-next v2 10/10] net: dsa: microchip: add two-steps PTP support for KSZ8463 ---
commit 36964d81014567278bc46ae45085179f4779f40a
Author: Bastien Curutchet (Schneider Electric) <bastien.curutchet@bootlin.com>
Subject: net: dsa: microchip: add two-steps PTP support for KSZ8463
This patch adds L2 two-step PTP support for the KSZ8463 switch. It
implements the get_ts_info and port_hwtstamp_set callbacks and tracks
the last transmitted event type to read the correct timestamp register.
> diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c
> index be6b8240ac03d..1cd2940c7befa 100644
> --- a/drivers/net/dsa/microchip/ksz_ptp.c
> +++ b/drivers/net/dsa/microchip/ksz_ptp.c
[ ... ]
> +static int ksz8463_set_hwtstamp_config(struct ksz_device *dev,
> + struct ksz_port *prt,
> + struct kernel_hwtstamp_config *config)
> +{
> + const u16 *regs = dev->info->regs;
> + int ret;
> +
> + if (config->flags)
> + return -EINVAL;
> +
> + switch (config->tx_type) {
> + case HWTSTAMP_TX_OFF:
> + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = false;
> + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = false;
> + prt->hwts_tx_en = false;
> + break;
> + case HWTSTAMP_TX_ON:
> + prt->ptpmsg_irq[KSZ8463_SYNC_MSG].ts_en = true;
> + prt->ptpmsg_irq[KSZ8463_XDREQ_PDRES_MSG].ts_en = true;
> + prt->hwts_tx_en = true;
> +
> + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_1STEP, 0);
> + if (ret)
> + return ret;
[Severity: Medium]
If the hardware register configuration fails, does the port software state
remain enabled?
In ksz8463_set_hwtstamp_config(), prt->hwts_tx_en and the ts_en flag are
set to true before attempting to write to the hardware register. If the
hardware write fails, the function returns an error without reverting the
software state. Could this lead to subsequent PTP packets being intercepted
but never hardware-timestamped?
[ ... ]
> +static void ksz8463_set_pdelayresp_flag(struct ksz_port *prt,
> + struct sk_buff *skb)
> +{
> + struct ptp_header *hdr;
> + unsigned int type;
> + u8 ptp_msg_type;
> +
> + if (!ksz_is_ksz8463(prt->ksz_dev))
> + return;
> +
> + if (skb_linearize(skb))
> + return;
[Severity: Medium]
If skb_linearize() fails, it returns early without updating
prt->last_tx_is_pdelayresp. Will the caller ignore this failure and
proceed to transmit the packet anyway?
When the hardware interrupt fires, ksz_read_ts() could use the stale flag
from a previous packet to calculate the register offset, resulting in
reading an incorrect timestamp.
> +
> + type = ptp_classify_raw(skb);
> + if (type == PTP_CLASS_NONE)
> + return;
> +
> + hdr = ptp_parse_header(skb, type);
> + if (!hdr)
> + return;
> +
> + ptp_msg_type = ptp_get_msgtype(hdr, type);
> + prt->last_tx_is_pdelayresp = (ptp_msg_type == PTP_MSGTYPE_PDELAY_RESP);
> +}
> +
> void ksz_port_deferred_xmit(struct kthread_work *work)
> {
> struct ksz_deferred_xmit_work *xmit_work = work_to_xmit_work(work);
[ ... ]
> reinit_completion(&prt->tstamp_msg_comp);
>
> + ksz8463_set_pdelayresp_flag(prt, skb);
> +
> dsa_enqueue_skb(skb, skb->dev);
>
> ksz_ptp_txtstamp_skb(dev, prt, clone);
[Severity: High]
This is a pre-existing issue, but does ksz_ptp_txtstamp_skb() leak the
cloned SKB if the wait for the TX timestamp times out?
In ksz_ptp_txtstamp_skb(), the driver blocks waiting for the hardware TX
timestamp interrupt. If the timeout expires, the function returns early
without freeing the clone:
static void ksz_ptp_txtstamp_skb(...) {
...
ret = wait_for_completion_timeout(&prt->tstamp_msg_comp, ...);
if (!ret)
return;
}
The caller ksz_port_deferred_xmit() then frees the work item, leaving the
cloned SKB permanently leaked. Could this lead to a system memory leak?
[Severity: High]
Does the shared TX path intercept unsupported network layers for the
KSZ8463?
The commit message notes that IPv4 and IPv6 layers aren't supported by the
hardware. However, the shared ksz_port_txtstamp() relies on
ptp_classify_raw() to intercept packets, which does not filter out
IPv4/IPv6 PTP packets.
If a user sends an IPv4/IPv6 PTP packet, does the driver intercept and queue
it for transmission, only to needlessly block the transmission worker for
100ms because the hardware ignores it and no interrupt will fire?
[Severity: High]
This is a pre-existing issue, but can dereferencing the PTP header without
verifying linearity cause page faults?
In the shared ksz_port_txtstamp() path, ptp_parse_header() is called to
retrieve the PTP header, which only verifies that the header fits within
the total SKB length.
Dereferencing this pointer in ptp_get_msgtype() without first calling
skb_linearize() or pskb_may_pull() could result in reading past the
linear allocation if the packet is non-linear.
[ ... ]
> static int ksz_read_ts(struct ksz_port *port, u16 reg, u32 *ts)
> {
> - return ksz_read32(port->ksz_dev, reg, ts);
> + u16 ts_reg = reg;
> +
> + /**
> + * On KSZ8463 DREQ and DRESP timestamps share one interrupt line
> + * so we have to check the nature of the latest event sent to know
> + * where the timestamp is located
> + */
> + if (ksz_is_ksz8463(port->ksz_dev) && port->last_tx_is_pdelayresp)
> + ts_reg += KSZ8463_DRESP_TS_OFFSET;
> +
> + return ksz_read32(port->ksz_dev, ts_reg, ts);
> }
[Severity: High]
Is KSZ8463_DRESP_TS_OFFSET erroneously added to the SYNC_TS register
address here?
The ksz_read_ts() function is used for both the DREQ interrupt and the SYNC
interrupt. If a SYNC_MSG interrupt is processed while
port->last_tx_is_pdelayresp happens to be true, the offset appears to be
unconditionally added to the ts_reg. Does this cause the driver to read
from an invalid hardware register offset?
[Severity: High]
This is a pre-existing issue, but can late TX timestamp interrupts
mistakenly complete the wait for a subsequent, unrelated packet?
If a packet transmission times out, the worker thread moves on to queue the
next packet and reinitializes the completion. In ksz_ptp_msg_thread_fn(),
the interrupt handler calls complete() without verifying sequence IDs:
if (ptpmsg_irq->ts_en) {
...
complete(&port->tstamp_msg_comp);
}
If a delayed interrupt from the first packet arrives, could it falsely
wake up the worker waiting for the second packet, improperly assigning
the first packet's timestamp to the second?
^ permalink raw reply
* Re: [PATCH 0/3] SM8450 IPA support
From: Konrad Dybcio @ 2026-07-20 14:18 UTC (permalink / raw)
To: Alex Elder, Esteban Urrutia, Bjorn Andersson, Konrad Dybcio,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alex Elder
Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <8309e9c7-a110-478b-8cd4-c002070d2b4c@riscstar.com>
On 7/8/26 9:49 PM, Alex Elder wrote:
> On 6/23/26 8:57 PM, Esteban Urrutia wrote:
>> On 6/23/26 11:56 AM, Alex Elder wrote:
>>> I assume you have implemented this based on what you found in
>>> some downstream code. And if so, could you please indicate
>>> where to find that (so I can do some cross-referencing myself).
>>> I no longer have access to any Qualcomm internal documentation.
[...]
>> It's worth mentioning that between SM8450 and SM8475, IPA SRAM size is
>> different, so I used the smaller SRAM size to support SM8475 as well. Hence
>> the reason why I included SM8475's downstream device tree as well.
I can't find information that would confirm that. Where do you see
this difference?
Konrad
^ permalink raw reply
* Re: [PATCH 0/3] SM8450 IPA support
From: Konrad Dybcio @ 2026-07-20 14:21 UTC (permalink / raw)
To: Esteban Urrutia, Alex Elder, Bjorn Andersson, Konrad Dybcio,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alex Elder
Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <3e70d77e-6bec-4e16-ae88-a4f5161f182e@proton.me>
On 7/8/26 10:45 PM, Esteban Urrutia wrote:
> On 7/8/26 3:49 PM, Alex Elder wrote:
>> This was interesting to see. It's something I should probably
>> document better. Most everything maps to the downstream code,
>> but it's not always completely obvious how, because the upstream
>> driver has evolved substantially.
>
> On a personal note, I'm surprised to see someone not from Qualcomm
> maintaining this driver. It must be hard, so kudos.
>
>> This means that the SRAM size (ipa_mem_data->smem_size) should
>> possibly be defined in devicetree (as the IMEM address and size
>> now are).
>>
>> The SMEM region is used for "IPA filter tables", and access to
>> it is shared between the AP and the modem. Unlike the other
>> (host) memory regions, the size used is *not* included in the
>> ipa_init_modem_driver_req message that communicates from the
>> AP to the modem where the regions are, and their sizes.
>>
>> So it's possible that the size used must actually match what
>> is expected by both the AP and modem. If that is the case,
>> using the smaller size might have problems on whichever
>> platform (SM8450?) expects the larger one.
>>
>> So I'm not sure whether using the smaller size for both
>> platforms is OK; someone from Qualcomm might be able to
>> answer that question.
>
> I actually went ahead and reviewed downstream device trees I found on
> GitHub (1) which contain both SM8450 and SM8475 device trees looking for
> the qcom,ipa-q6-smem-size property, which would correspond to the SRAM
> size, and to my surprise, this was set to 0x9000 for both SoCs.
> Most likely the commit I got the SRAM information from (2) never made it
> to production devices.
OK you meant the qcom,ipa-q6-smem-size property. I can't find what
it corresponds to, the name of the associated IOMMU stream
unfortunately doesn't hint at anything useful either..
Konrad
^ permalink raw reply
* [PATCH net-next v2 0/6] Deliver TLS control records to kernel read_sock consumers
From: Chuck Lever @ 2026-07-20 14:27 UTC (permalink / raw)
To: Jakub Kicinski, Paolo Abeni, Simon Horman, John Fastabend,
Sabrina Dubroca, Shuah Khan, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Chuck Lever
Cc: netdev, kernel-tls-handshake, linux-kselftest, linux-nfs,
Chuck Lever
When kTLS is active, kernel consumers of the read_sock API cannot
see TLS control records: tls_sw_read_sock() rejects Alerts and
Handshake records with -EINVAL. A consumer that needs those records
falls back to sock_recvmsg() with ancillary buffers, which is why
NFSD's svcsock runs a MSG_CTRUNC recovery dance on every receive.
This series adds a read_sock_rectype proto_ops method that delivers
non-data records through a separate callback, then converts its first
consumer, NFSD's svcsock, to use it. NFS client (xprtsock), NVMe
target, and NVMe host are meant to move onto the same mechanism in
later series. The existing read_sock data path is unchanged.
The improvement is more than cosmetic. All of these consumers need
to handle TLS Alert messages efficiently and securely (in particular,
for KeyUpdate). A few design points worth mentioning:
- The new sk_read_rectype_actor_t callback does not share the data
actor's byte-counting contract. It returns 0 to consume a record or
a negative value to requeue it and stop delivery. The return is
never a byte count. svc_tcp_rectype_actor() relies on this,
returning 0 to consume every non-data record while stopping the
read loop through desc->count on a fatal alert.
- The no-data cap (patch 1) is a prerequisite, not a stand-alone fix.
Once control records reach read_sock, a record carrying no payload
stops advancing the caller's read descriptor, so a peer streaming
such records would pin the socket lock and the kernel receive
context for as long as the flood lasts. Bounding consecutive
no-data records supplies the return boundary a system call would
otherwise provide. The cap is scoped to tls_sw_read_sock() alone:
splice and recvmsg run in the caller's own context, reschedule, and
drop the lock on return, so they need nothing.
- The svcsock conversion is split so the new path can be reviewed
against the old. The old path is then removed by the last patch
in the series.
- read_sock_rectype has no direct userspace entry point, so its
selftest coverage (patch 4) drives the shared decryption and
rx_list delivery pipeline through the recvmsg and splice paths
instead.
---
Changes in v2:
- Renamed the read_sock_cmsg proto_ops method to read_sock_rectype.
- Non-data records now use a record-type actor, not RFC cmsg-style.
- Bound no-data records so they can't pin the socket lock (new patch 1).
- Added selftests for data/control record interleaving (new patch 4).
- Link to v1: https://lore.kernel.org/r/20260217222033.1929211-1-cel@kernel.org
---
Chuck Lever (6):
net/tls: Bound consecutive no-data records in tls_sw_read_sock()
net: Introduce read_sock_rectype proto_ops for control record delivery
tls: Implement read_sock_rectype for kTLS software path
selftests/tls: Add tests for data/control record interleaving
SUNRPC: Use read_sock_rectype for svcsock TCP receives
SUNRPC: Remove sock_recvmsg path from svcsock TCP receives
include/linux/net.h | 28 +++
net/sunrpc/svcsock.c | 381 +++++++++++++++++---------------------
net/tls/tls.h | 3 +
net/tls/tls_main.c | 5 +
net/tls/tls_sw.c | 54 +++++-
tools/testing/selftests/net/tls.c | 298 ++++++++++++++++++++++++++++-
6 files changed, 556 insertions(+), 213 deletions(-)
---
base-commit: 298bb2b8903323f6ef2eab4819a2e477765f0ff1
change-id: 20260327-tcp-read-sock-778a49660ff7
Best regards,
--
Chuck Lever <cel@kernel.org>
^ permalink raw reply
* [PATCH net-next v2 1/6] net/tls: Bound consecutive no-data records in tls_sw_read_sock()
From: Chuck Lever @ 2026-07-20 14:27 UTC (permalink / raw)
To: Jakub Kicinski, Paolo Abeni, Simon Horman, John Fastabend,
Sabrina Dubroca, Shuah Khan, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Chuck Lever
Cc: netdev, kernel-tls-handshake, linux-kselftest, linux-nfs,
Chuck Lever
In-Reply-To: <20260720-tcp-read-sock-v2-0-29545d034f3c@kernel.org>
A record that delivers no payload -- an empty TLS 1.3 data record
today, a control record once read_sock_rectype() lands -- leaves
tls_sw_read_sock() in its loop without advancing the caller's read
descriptor. A peer that streams such records keeps the receive loop
running, and the socket lock held, for as long as the records
arrive.
Cap the number of consecutive no-data records consumed per call. The
count resets on any record that delivers bytes, so a normal stream
is unaffected; a peer supplying only empty records is bounded to
TLS_RX_NODATA_LIMIT iterations before the call returns 0. read_sock
consumers treat that as "no progress, re-poll" rather than EOF, so
the connection stays up and makes progress once real data arrives.
Only tls_sw_read_sock() needs this cap. Its consumers drive the receive
loop from kernel context -- a work item or service thread holding the
socket lock across the whole call with no return to userspace -- so an
unbounded empty-record stream keeps that context and the lock pinned
for as long as the flood lasts. The cap supplies the return boundary
that a system call would otherwise provide. tls_sw_splice_read()
and tls_sw_recvmsg() already have one: they run in the calling task's
context, reschedule while draining the socket backlog (cond_resched()
in __release_sock()), and drop the socket lock when the call returns. A
flood there costs the caller only its own scheduler time, so the cap
would add nothing.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/tls/tls_sw.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index d4afc90fd796..087950ca639c 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -2049,6 +2049,11 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,
goto splice_read_end;
}
+/* Consecutive empty data records deliver no bytes; cap them per
+ * call so a peer streaming them cannot hold the socket lock here.
+ */
+#define TLS_RX_NODATA_LIMIT 16
+
int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
sk_read_actor_t read_actor)
{
@@ -2057,6 +2062,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
struct tls_prot_info *prot = &tls_ctx->prot_info;
struct strp_msg *rxm = NULL;
struct sk_buff *skb = NULL;
+ unsigned int nodata_count = 0;
struct sk_psock *psock;
size_t flushed_at = 0;
bool released = true;
@@ -2122,7 +2128,13 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
* here instead.
*/
if (rxm->full_len == 0) {
+ err = 0;
consume_skb(skb);
+ /* tls_rx_reader_release() announces any parsed record
+ * on exit, so returning 0 here cannot strand it.
+ */
+ if (++nodata_count >= TLS_RX_NODATA_LIMIT)
+ break;
continue;
}
@@ -2133,6 +2145,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
goto read_sock_requeue;
}
copied += used;
+ nodata_count = 0;
if (used < rxm->full_len) {
rxm->offset += used;
rxm->full_len -= used;
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v2 2/6] net: Introduce read_sock_rectype proto_ops for control record delivery
From: Chuck Lever @ 2026-07-20 14:27 UTC (permalink / raw)
To: Jakub Kicinski, Paolo Abeni, Simon Horman, John Fastabend,
Sabrina Dubroca, Shuah Khan, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Chuck Lever
Cc: netdev, kernel-tls-handshake, linux-kselftest, linux-nfs
In-Reply-To: <20260720-tcp-read-sock-v2-0-29545d034f3c@kernel.org>
From: Chuck Lever <chuck.lever@oracle.com>
Kernel TCP consumers that use the read_sock interface
(proto_ops.read_sock) cannot receive TLS control messages (Alerts,
Handshake records) when kTLS is active. The current
tls_sw_read_sock() method rejects non-data records with -EINVAL, and
the sk_read_actor_t callback has no channel for delivering record-
type metadata.
Four kernel subsystems are affected: NFSD (sunrpc svcsock), NFS
client (sunrpc xprtsock), NVMe target (nvmet-tcp), and NVMe host
(nvme-tcp). Each of these either falls back to the sock_recvmsg()
API or lacks TLS alert handling entirely.
A new read_sock_rectype method in struct proto_ops provides a
separate code path that delivers non-data TLS records to a callback,
without changing the behavior seen by existing read_sock consumers.
The new sk_read_rectype_actor_t callback type extends the
sk_read_actor_t signature with a rectype parameter carrying the
protocol-layer record type (for example, TLS_RECORD_TYPE_ALERT). The
record-type callback returns 0 to consume a record or a negative
value to requeue it and stop delivery; unlike the data callback, its
return value does not count bytes.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
include/linux/net.h | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/include/linux/net.h b/include/linux/net.h
index 277188a40c72..7a19a743a617 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -198,6 +198,13 @@ struct sk_buff;
struct proto_accept_arg;
typedef int (*sk_read_actor_t)(read_descriptor_t *, struct sk_buff *,
unsigned int, size_t);
+/* rectype carries the transport record type, for example a
+ * TLS_RECORD_TYPE_* value.
+ */
+typedef int (*sk_read_rectype_actor_t)(read_descriptor_t *,
+ struct sk_buff *,
+ unsigned int, size_t,
+ u8 rectype);
typedef int (*skb_read_actor_t)(struct sock *, struct sk_buff *);
@@ -264,6 +271,27 @@ struct proto_ops {
*/
int (*read_sock)(struct sock *sk, read_descriptor_t *desc,
sk_read_actor_t recv_actor);
+ /*
+ * read_sock_rectype splits delivery across two callbacks:
+ * recv_actor for data records, per the sk_read_actor_t
+ * convention, and rectype_actor for all other records,
+ * with rectype identifying each. A NULL rectype_actor
+ * leaves non-data records pending. rectype_actor returns 0
+ * to consume a record or negative to leave it pending for
+ * redelivery and stop delivery; the negative return is a
+ * backpressure signal, not a fatal error. Both callbacks
+ * report errors and early stop the way recv_actor does:
+ * by setting desc->count to 0 and recording the reason in
+ * desc->error, per the read_descriptor_t convention and
+ * independent of the return value. The return value reports
+ * only data bytes consumed by recv_actor; the caller
+ * detects an error or early stop via desc->count and
+ * desc->error.
+ */
+ int (*read_sock_rectype)(struct sock *sk,
+ read_descriptor_t *desc,
+ sk_read_actor_t recv_actor,
+ sk_read_rectype_actor_t rectype_actor);
/* This is different from read_sock(), it reads an entire skb at a time. */
int (*read_skb)(struct sock *sk, skb_read_actor_t recv_actor);
int (*sendmsg_locked)(struct sock *sk, struct msghdr *msg,
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v2 3/6] tls: Implement read_sock_rectype for kTLS software path
From: Chuck Lever @ 2026-07-20 14:27 UTC (permalink / raw)
To: Jakub Kicinski, Paolo Abeni, Simon Horman, John Fastabend,
Sabrina Dubroca, Shuah Khan, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Chuck Lever
Cc: netdev, kernel-tls-handshake, linux-kselftest, linux-nfs
In-Reply-To: <20260720-tcp-read-sock-v2-0-29545d034f3c@kernel.org>
From: Chuck Lever <chuck.lever@oracle.com>
tls_sw_read_sock() rejects non-data records (alerts, handshake
messages) with -EINVAL. Kernel consumers that need TLS alert
delivery, such as NFSD, NFS client, and NVMe target, must fall back
to the sock_recvmsg() API to receive control messages via CMSG.
Implement the new read_sock_rectype() method for these consumers,
delivering non-data records to a callback through the kTLS software
receive path.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/tls/tls.h | 3 +++
net/tls/tls_main.c | 5 +++++
net/tls/tls_sw.c | 41 ++++++++++++++++++++++++++++++++++++-----
3 files changed, 44 insertions(+), 5 deletions(-)
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 60a37bdaaa25..c21d2a985e13 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -168,6 +168,9 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,
size_t len, unsigned int flags);
int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
sk_read_actor_t read_actor);
+int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,
+ sk_read_actor_t read_actor,
+ sk_read_rectype_actor_t rectype_actor);
int tls_device_sendmsg(struct sock *sk, struct msghdr *msg, size_t size);
void tls_device_splice_eof(struct socket *sock);
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 8c588cdab733..4963e0caf6d5 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -949,12 +949,17 @@ static void build_proto_ops(struct proto_ops ops[TLS_NUM_CONFIG][TLS_NUM_CONFIG]
ops[TLS_BASE][TLS_SW ].splice_read = tls_sw_splice_read;
ops[TLS_BASE][TLS_SW ].poll = tls_sk_poll;
ops[TLS_BASE][TLS_SW ].read_sock = tls_sw_read_sock;
+ ops[TLS_BASE][TLS_SW ].read_sock_rectype = tls_sw_read_sock_rectype;
ops[TLS_SW ][TLS_SW ] = ops[TLS_SW ][TLS_BASE];
ops[TLS_SW ][TLS_SW ].splice_read = tls_sw_splice_read;
ops[TLS_SW ][TLS_SW ].poll = tls_sk_poll;
ops[TLS_SW ][TLS_SW ].read_sock = tls_sw_read_sock;
+ ops[TLS_SW ][TLS_SW ].read_sock_rectype = tls_sw_read_sock_rectype;
+ /* TLS_HW (device offload) RX entries inherit
+ * read_sock{,_rectype} from SW via the struct copies below.
+ */
#ifdef CONFIG_TLS_DEVICE
ops[TLS_HW ][TLS_BASE] = ops[TLS_BASE][TLS_BASE];
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 087950ca639c..af347b5b17fa 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -2054,8 +2054,9 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,
*/
#define TLS_RX_NODATA_LIMIT 16
-int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
- sk_read_actor_t read_actor)
+static int __tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
+ sk_read_actor_t read_actor,
+ sk_read_rectype_actor_t rectype_actor)
{
struct tls_context *tls_ctx = tls_get_ctx(sk);
struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx);
@@ -2115,10 +2116,27 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
tls_rx_rec_done(ctx);
}
- /* read_sock does not support reading control messages */
+ /* Control records (alerts, handshake) reach a consumer
+ * only through rectype_actor; without one, read_sock
+ * rejects them.
+ */
if (tlm->control != TLS_RECORD_TYPE_DATA) {
- err = -EINVAL;
- goto read_sock_requeue;
+ if (!rectype_actor) {
+ err = -EINVAL;
+ goto read_sock_requeue;
+ }
+ err = rectype_actor(desc, skb, rxm->offset,
+ rxm->full_len,
+ tlm->control);
+ if (err < 0)
+ goto read_sock_requeue;
+ err = 0;
+ /* rectype_actor consumes the whole record; no partial path */
+ consume_skb(skb);
+ skb = NULL;
+ if (++nodata_count >= TLS_RX_NODATA_LIMIT)
+ break;
+ continue;
}
/* An empty data record (legal in TLS 1.3) gives a zero
@@ -2164,6 +2182,19 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
goto read_sock_end;
}
+int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
+ sk_read_actor_t read_actor)
+{
+ return __tls_sw_read_sock(sk, desc, read_actor, NULL);
+}
+
+int tls_sw_read_sock_rectype(struct sock *sk, read_descriptor_t *desc,
+ sk_read_actor_t read_actor,
+ sk_read_rectype_actor_t rectype_actor)
+{
+ return __tls_sw_read_sock(sk, desc, read_actor, rectype_actor);
+}
+
bool tls_sw_sock_is_readable(struct sock *sk)
{
struct tls_context *tls_ctx = tls_get_ctx(sk);
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v2 4/6] selftests/tls: Add tests for data/control record interleaving
From: Chuck Lever @ 2026-07-20 14:27 UTC (permalink / raw)
To: Jakub Kicinski, Paolo Abeni, Simon Horman, John Fastabend,
Sabrina Dubroca, Shuah Khan, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Chuck Lever
Cc: netdev, kernel-tls-handshake, linux-kselftest, linux-nfs
In-Reply-To: <20260720-tcp-read-sock-v2-0-29545d034f3c@kernel.org>
From: Chuck Lever <chuck.lever@oracle.com>
The new read_sock_rectype proto_ops method delivers interleaved
data and control TLS records to kernel consumers through
separate callbacks. The existing selftest coverage for these
interleaving patterns is limited: data_control_data only
peeks, and splice_cmsg_to_pipe tests a single control record
in isolation.
Add seven tests that exercise the record patterns
read_sock_rectype is designed to handle:
- splice_data_cmsg_data: data-control-data via splice, with
the control record drained through recvmsg between the two
splice calls
- splice_multi_cmsg_data: data-control-control-data with
distinct content types, verifying that each control record
is independently drainable with its type preserved, and
splice resumes afterward
- recv_data_cmsg_data: complete consumption of a
data-control-data sequence through recv and recvmsg
- peek_cmsg_after_data: peek at an interleaved control
record after the preceding data record has been consumed,
then consume it
- cmsg_before_data: control record as the first record in the
stream, followed by data
- mixed_control_types: two different control record types
(distinct content_type values) interleaved with data,
verifying type preservation through delivery
- data_cmsg_eof: trailing control record followed by
connection close, verifying the receiver drains the
control record and then observes EOF
The fixture teardown is also updated to skip closing fd
when a test has already closed it (as data_cmsg_eof does).
These tests exercise the record decryption and rx_list
delivery pipeline shared by the recvmsg, splice, and
read_sock paths. read_sock_rectype itself is a kernel-internal
API without a direct userspace entry point, so the tests
validate through the userspace-accessible paths.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
tools/testing/selftests/net/tls.c | 298 +++++++++++++++++++++++++++++++++++++-
1 file changed, 297 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index cbdd3ea28b99..8136306b5caa 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -427,7 +427,8 @@ FIXTURE_SETUP(tls)
FIXTURE_TEARDOWN(tls)
{
- close(self->fd);
+ if (self->fd >= 0)
+ close(self->fd);
close(self->cfd);
}
@@ -897,6 +898,114 @@ TEST_F(tls, splice_dec_cmsg_to_pipe)
EXPECT_EQ(memcmp(test_str, buf, send_len), 0);
}
+/* Verify splice handles data-control-data: splice reads the data
+ * records successfully while the intervening control record must
+ * be drained via recvmsg before splice can continue.
+ */
+TEST_F(tls, splice_data_cmsg_data)
+{
+ char mem_send[TLS_PAYLOAD_MAX_LEN];
+ char mem_recv[TLS_PAYLOAD_MAX_LEN];
+ int send_len = 4096;
+ char *ctrl_str = "control";
+ int ctrl_len = strlen(ctrl_str) + 1;
+ char ctrl_buf[8];
+ int p[2];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ memrnd(mem_send, sizeof(mem_send));
+
+ ASSERT_GE(pipe(p), 0);
+
+ /* Send: data, control, data */
+ EXPECT_EQ(send(self->fd, mem_send, send_len, 0), send_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl_str, ctrl_len, 0),
+ ctrl_len);
+ EXPECT_EQ(send(self->fd, &mem_send[send_len], send_len, 0), send_len);
+
+ /* Splice first data record */
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+ EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+ EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0);
+
+ /* Splice hits control record, fails */
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /* Drain the control record via recvmsg */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ ctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),
+ ctrl_len);
+ EXPECT_EQ(memcmp(ctrl_str, ctrl_buf, ctrl_len), 0);
+
+ /* Splice second data record */
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+ EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+ EXPECT_EQ(memcmp(&mem_send[send_len], mem_recv, send_len), 0);
+}
+
+/* Verify that multiple consecutive control records between data
+ * records can each be drained individually, and splice resumes
+ * afterward. The two control records use different content types
+ * to verify type preservation across the splice boundary.
+ */
+TEST_F(tls, splice_multi_cmsg_data)
+{
+ char mem_send[TLS_PAYLOAD_MAX_LEN];
+ char mem_recv[TLS_PAYLOAD_MAX_LEN];
+ int send_len = 4096;
+ char *ctrl1 = "alert1";
+ char *ctrl2 = "alert2";
+ int ctrl_len = strlen(ctrl1) + 1;
+ char ctrl_buf[7];
+ int p[2];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ memrnd(mem_send, sizeof(mem_send));
+
+ ASSERT_GE(pipe(p), 0);
+
+ /* Send: data, control(100), control(200), data */
+ EXPECT_EQ(send(self->fd, mem_send, send_len, 0), send_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl1, ctrl_len, 0), ctrl_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 200, ctrl2, ctrl_len, 0), ctrl_len);
+ EXPECT_EQ(send(self->fd, &mem_send[send_len], send_len, 0), send_len);
+
+ /* Splice first data */
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+ EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+ EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0);
+
+ /* Splice fails on first control record */
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /* Drain first control (type 100) */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ ctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),
+ ctrl_len);
+ EXPECT_EQ(memcmp(ctrl1, ctrl_buf, ctrl_len), 0);
+
+ /* Splice fails on second control record */
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /* Drain second control (type 200) */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 200,
+ ctrl_buf, sizeof(ctrl_buf), MSG_WAITALL),
+ ctrl_len);
+ EXPECT_EQ(memcmp(ctrl2, ctrl_buf, ctrl_len), 0);
+
+ /* Splice second data */
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, send_len, 0), send_len);
+ EXPECT_EQ(read(p[0], mem_recv, send_len), send_len);
+ EXPECT_EQ(memcmp(&mem_send[send_len], mem_recv, send_len), 0);
+}
+
TEST_F(tls, recv_and_splice)
{
int send_len = TLS_PAYLOAD_MAX_LEN;
@@ -1682,6 +1791,193 @@ TEST_F(tls, data_control_data)
EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_PEEK), send_len);
}
+/* Fully consume a data-control-data sequence. The existing
+ * data_control_data test only peeks; this exercises complete
+ * record delivery through recv and recvmsg.
+ */
+TEST_F(tls, recv_data_cmsg_data)
+{
+ char *data1 = "first_data";
+ char *ctrl = "ctrl_msg";
+ char *data2 = "second_data";
+ int d1_len = strlen(data1) + 1;
+ int c_len = strlen(ctrl) + 1;
+ int d2_len = strlen(data2) + 1;
+ char buf[20];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ EXPECT_EQ(send(self->fd, data1, d1_len, 0), d1_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+ EXPECT_EQ(send(self->fd, data2, d2_len, 0), d2_len);
+
+ /* First data record */
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d1_len);
+ EXPECT_EQ(memcmp(buf, data1, d1_len), 0);
+
+ /* recv without cmsg buffer fails on control record */
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+ EXPECT_EQ(errno, EIO);
+
+ /* Drain control via recvmsg */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ buf, sizeof(buf), MSG_WAITALL), c_len);
+ EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+ /* Second data record */
+ EXPECT_EQ(recv(self->cfd, buf, d2_len, MSG_WAITALL), d2_len);
+ EXPECT_EQ(memcmp(buf, data2, d2_len), 0);
+}
+
+/* Peek at an interleaved control record after the preceding data
+ * record has been consumed, then consume it. MSG_PEEK exposes the
+ * control record's type without consuming it.
+ */
+TEST_F(tls, peek_cmsg_after_data)
+{
+ char *data = "leading";
+ char *ctrl = "middle";
+ char *tail = "trailing";
+ int d_len = strlen(data) + 1;
+ int c_len = strlen(ctrl) + 1;
+ int t_len = strlen(tail) + 1;
+ char buf[20];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ EXPECT_EQ(send(self->fd, data, d_len, 0), d_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+ EXPECT_EQ(send(self->fd, tail, t_len, 0), t_len);
+
+ /* Consume leading data */
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d_len);
+ EXPECT_EQ(memcmp(buf, data, d_len), 0);
+
+ /* Peek at the control record */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ buf, sizeof(buf), MSG_PEEK), c_len);
+ EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+ /* Consume the control record */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ buf, sizeof(buf), 0), c_len);
+ EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+ /* Trailing data */
+ EXPECT_EQ(recv(self->cfd, buf, t_len, MSG_WAITALL), t_len);
+ EXPECT_EQ(memcmp(buf, tail, t_len), 0);
+}
+
+/* Control record as the first record in the stream, followed by
+ * data. The control record must be drained before the data record
+ * becomes available.
+ */
+TEST_F(tls, cmsg_before_data)
+{
+ char *ctrl = "alert";
+ char *data = "payload";
+ int c_len = strlen(ctrl) + 1;
+ int d_len = strlen(data) + 1;
+ char buf[20];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+ EXPECT_EQ(send(self->fd, data, d_len, 0), d_len);
+
+ /* recv without cmsg fails */
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+ EXPECT_EQ(errno, EIO);
+
+ /* Drain control via recvmsg */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ buf, sizeof(buf), MSG_WAITALL), c_len);
+ EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+ /* Data follows */
+ EXPECT_EQ(recv(self->cfd, buf, d_len, MSG_WAITALL), d_len);
+ EXPECT_EQ(memcmp(buf, data, d_len), 0);
+}
+
+/* Two different control record types interleaved with data.
+ * Each control record is delivered with its own type preserved;
+ * verify both types arrive intact.
+ */
+TEST_F(tls, mixed_control_types)
+{
+ char *data1 = "data1";
+ char *ctrl1 = "handshake";
+ char *ctrl2 = "alert_msg";
+ char *data2 = "data2";
+ int d1_len = strlen(data1) + 1;
+ int c1_len = strlen(ctrl1) + 1;
+ int c2_len = strlen(ctrl2) + 1;
+ int d2_len = strlen(data2) + 1;
+ char buf[20];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ EXPECT_EQ(send(self->fd, data1, d1_len, 0), d1_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl1, c1_len, 0), c1_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 200, ctrl2, c2_len, 0), c2_len);
+ EXPECT_EQ(send(self->fd, data2, d2_len, 0), d2_len);
+
+ /* First data */
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d1_len);
+ EXPECT_EQ(memcmp(buf, data1, d1_len), 0);
+
+ /* First control (type 100) */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ buf, sizeof(buf), MSG_WAITALL), c1_len);
+ EXPECT_EQ(memcmp(buf, ctrl1, c1_len), 0);
+
+ /* Second control (type 200) */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 200,
+ buf, sizeof(buf), MSG_WAITALL), c2_len);
+ EXPECT_EQ(memcmp(buf, ctrl2, c2_len), 0);
+
+ /* Second data */
+ EXPECT_EQ(recv(self->cfd, buf, d2_len, MSG_WAITALL), d2_len);
+ EXPECT_EQ(memcmp(buf, data2, d2_len), 0);
+}
+
+/* Trailing control record with no data following it. The sender
+ * closes the connection after the control record; the receiver
+ * drains the control and then observes EOF.
+ */
+TEST_F(tls, data_cmsg_eof)
+{
+ char *data = "payload";
+ char *ctrl = "final";
+ int d_len = strlen(data) + 1;
+ int c_len = strlen(ctrl) + 1;
+ char buf[20];
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ EXPECT_EQ(send(self->fd, data, d_len, 0), d_len);
+ EXPECT_EQ(tls_send_cmsg(self->fd, 100, ctrl, c_len, 0), c_len);
+ EXPECT_EQ(close(self->fd), 0);
+ self->fd = -1;
+
+ /* Consume data */
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), MSG_WAITALL), d_len);
+ EXPECT_EQ(memcmp(buf, data, d_len), 0);
+
+ /* Drain trailing control */
+ EXPECT_EQ(tls_recv_cmsg(_metadata, self->cfd, 100,
+ buf, sizeof(buf), 0), c_len);
+ EXPECT_EQ(memcmp(buf, ctrl, c_len), 0);
+
+ /* Next recv returns EOF */
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), 0);
+}
+
TEST_F(tls, shutdown)
{
char const *test_str = "test_read";
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v2 5/6] SUNRPC: Use read_sock_rectype for svcsock TCP receives
From: Chuck Lever @ 2026-07-20 14:27 UTC (permalink / raw)
To: Jakub Kicinski, Paolo Abeni, Simon Horman, John Fastabend,
Sabrina Dubroca, Shuah Khan, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Chuck Lever
Cc: netdev, kernel-tls-handshake, linux-kselftest, linux-nfs
In-Reply-To: <20260720-tcp-read-sock-v2-0-29545d034f3c@kernel.org>
From: Chuck Lever <chuck.lever@oracle.com>
The svcsock TCP receive path uses sock_recvmsg() with ancillary data
buffers to detect TLS alerts when kTLS is active. This CMSG-based
approach requires a MSG_CTRUNC recovery dance on every receive and
cannot deliver control records through the read_sock interface.
When the socket provides a read_sock_rectype method (now set by
kTLS), svc_tcp_recvfrom() now dispatches to a new
svc_tcp_recvfrom_readsock() path. Two actor callbacks handle the
data:
svc_tcp_recv_actor() parses the RPC record byte stream directly from
skbs. Fragment header bytes fill sk_marker first; subsequent body
bytes are copied into rq_pages at the position tracked by
sk_datalen. When the last fragment of a complete RPC message
arrives, the actor sets desc->count to zero, stopping the read loop.
svc_tcp_rectype_actor() handles non-data TLS records. For fatal
alerts, the transport is marked for deferred close and the read loop
is stopped via desc->count. All non-data records are consumed by
returning 0 so that subsequent data records remain deliverable.
Sockets without read_sock_rectype (plain TCP, non-kTLS) continue to
use the existing sock_recvmsg() path unchanged. A follow-up patch
retires that path in favor of read_sock.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/svcsock.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 244 insertions(+)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 50e5e7f5b762..e40931d11491 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -1134,6 +1134,247 @@ static void svc_tcp_fragment_received(struct svc_sock *svsk)
svsk->sk_marker = xdr_zero;
}
+/*
+ * read_sock_rectype data actor: receives decrypted application data
+ * from the TLS layer, parsing the RPC record stream (fragment
+ * headers and message bodies) and assembling complete RPC messages
+ * into rqstp->rq_pages.
+ */
+static int svc_tcp_recv_actor(read_descriptor_t *desc,
+ struct sk_buff *skb,
+ unsigned int offset, size_t len)
+{
+ struct svc_rqst *rqstp = desc->arg.data;
+ struct svc_sock *svsk =
+ container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
+ size_t reclen, received, want, take, done, n;
+ size_t consumed = 0;
+
+ if (!desc->count)
+ return 0;
+
+ if (svsk->sk_tcplen < sizeof(rpc_fraghdr)) {
+ want = sizeof(rpc_fraghdr) - svsk->sk_tcplen;
+ n = min(want, len);
+
+ if (skb_copy_bits(skb, offset,
+ (char *)&svsk->sk_marker +
+ svsk->sk_tcplen, n))
+ goto fault;
+ svsk->sk_tcplen += n;
+ offset += n;
+ len -= n;
+ consumed += n;
+
+ if (svsk->sk_tcplen < sizeof(rpc_fraghdr))
+ return consumed;
+
+ trace_svcsock_marker(&svsk->sk_xprt, svsk->sk_marker);
+ if (svc_sock_reclen(svsk) + svsk->sk_datalen >
+ svsk->sk_xprt.xpt_server->sv_max_mesg) {
+ net_notice_ratelimited("svc: %s oversized RPC fragment (%u octets) from %pISpc\n",
+ svsk->sk_xprt.xpt_server->sv_name,
+ svc_sock_reclen(svsk),
+ (struct sockaddr *)&svsk->sk_xprt.xpt_remote);
+ desc->error = -EMSGSIZE;
+ desc->count = 0;
+ return consumed;
+ }
+ }
+
+ reclen = svc_sock_reclen(svsk);
+ received = svsk->sk_tcplen - sizeof(rpc_fraghdr);
+ want = reclen - received;
+ take = min(want, len);
+ done = 0;
+
+ while (done < take) {
+ unsigned int pg = svsk->sk_datalen >> PAGE_SHIFT;
+ unsigned int pg_off = svsk->sk_datalen & (PAGE_SIZE - 1);
+ size_t chunk = min(take - done,
+ PAGE_SIZE - (size_t)pg_off);
+
+ if (skb_copy_bits(skb, offset,
+ page_address(rqstp->rq_pages[pg]) + pg_off,
+ chunk))
+ goto fault;
+ flush_dcache_page(rqstp->rq_pages[pg]);
+ offset += chunk;
+ done += chunk;
+ svsk->sk_datalen += chunk;
+ }
+ svsk->sk_tcplen += take;
+ consumed += take;
+
+ if (svsk->sk_tcplen - sizeof(rpc_fraghdr) >= reclen) {
+ if (svc_sock_final_rec(svsk))
+ desc->count = 0;
+ else
+ svc_tcp_fragment_received(svsk);
+ }
+
+ return consumed;
+
+fault:
+ desc->error = -EFAULT;
+ desc->count = 0;
+ return consumed;
+}
+
+/*
+ * read_sock_rectype non-data record actor: receives non-data TLS
+ * records (alerts, handshake messages) and translates them into
+ * transport-level actions.
+ *
+ * Returns 0 to consume the record and allow the TLS layer to
+ * continue delivering subsequent records. A negative return
+ * causes the TLS layer to requeue the skb on its rx_list,
+ * blocking all further record delivery on this connection.
+ */
+static int svc_tcp_rectype_actor(read_descriptor_t *desc,
+ struct sk_buff *skb,
+ unsigned int offset, size_t len,
+ u8 rectype)
+{
+ struct svc_rqst *rqstp = desc->arg.data;
+ struct svc_sock *svsk =
+ container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
+
+ switch (rectype) {
+ case TLS_RECORD_TYPE_ALERT: {
+ u8 alert[2] = {}, level, description;
+ struct kvec kvec = {
+ .iov_base = alert,
+ .iov_len = sizeof(alert),
+ };
+ struct msghdr msg = {};
+
+ if (skb_copy_bits(skb, offset, alert, min(len, sizeof(alert))))
+ break;
+ iov_iter_kvec(&msg.msg_iter, ITER_DEST, &kvec, 1, sizeof(alert));
+ tls_alert_recv(svsk->sk_sk, &msg, &level, &description);
+ if (level == TLS_ALERT_LEVEL_FATAL) {
+ svc_xprt_deferred_close(&svsk->sk_xprt);
+ desc->error = -ENOTCONN;
+ desc->count = 0;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int svc_tcp_recvfrom_readsock(struct svc_rqst *rqstp)
+{
+ struct svc_sock *svsk =
+ container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
+ struct svc_serv *serv = svsk->sk_xprt.xpt_server;
+ struct sock *sk = svsk->sk_sk;
+ read_descriptor_t desc = {
+ .arg.data = rqstp,
+ };
+ ssize_t len;
+ __be32 *p;
+ __be32 calldir;
+
+ clear_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
+
+ svc_tcp_restore_pages(svsk, rqstp);
+ rqstp->rq_arg.head[0].iov_base = page_address(rqstp->rq_pages[0]);
+
+ desc.count = serv->sv_max_mesg;
+ lock_sock(sk);
+ len = svsk->sk_sock->ops->read_sock_rectype(sk, &desc,
+ svc_tcp_recv_actor,
+ svc_tcp_rectype_actor);
+ release_sock(sk);
+
+ if (desc.error < 0) {
+ len = desc.error;
+ goto err_discard;
+ }
+ if (desc.count != 0) {
+ if (len > 0)
+ set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
+ goto err_incomplete;
+ }
+
+ if (svsk->sk_datalen < 8)
+ goto err_nuts;
+
+ rqstp->rq_arg.len = svsk->sk_datalen;
+ rqstp->rq_arg.page_base = 0;
+ if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0].iov_len) {
+ rqstp->rq_arg.head[0].iov_len = rqstp->rq_arg.len;
+ rqstp->rq_arg.page_len = 0;
+ } else {
+ rqstp->rq_arg.page_len = rqstp->rq_arg.len -
+ rqstp->rq_arg.head[0].iov_len;
+ }
+
+ rqstp->rq_xprt_ctxt = NULL;
+ rqstp->rq_prot = IPPROTO_TCP;
+ if (test_bit(XPT_LOCAL, &svsk->sk_xprt.xpt_flags))
+ set_bit(RQ_LOCAL, &rqstp->rq_flags);
+ else
+ clear_bit(RQ_LOCAL, &rqstp->rq_flags);
+
+ p = (__be32 *)rqstp->rq_arg.head[0].iov_base;
+ calldir = p[1];
+ if (calldir)
+ len = receive_cb_reply(svsk, rqstp);
+
+ /* Reset TCP read info */
+ svsk->sk_datalen = 0;
+ svc_tcp_fragment_received(svsk);
+
+ if (len < 0)
+ goto error;
+
+ trace_svcsock_tcp_recv(&svsk->sk_xprt, rqstp->rq_arg.len);
+ svc_xprt_copy_addrs(rqstp, &svsk->sk_xprt);
+ if (serv->sv_stats)
+ serv->sv_stats->nettcpcnt++;
+
+ svc_sock_secure_port(rqstp);
+ set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
+ svc_xprt_received(rqstp->rq_xprt);
+ return rqstp->rq_arg.len;
+
+err_incomplete:
+ svc_tcp_save_pages(svsk, rqstp);
+ if (len < 0 && len != -EAGAIN)
+ goto err_delete;
+ if (svsk->sk_tcplen >= sizeof(rpc_fraghdr))
+ trace_svcsock_tcp_recv_short(&svsk->sk_xprt,
+ svc_sock_reclen(svsk),
+ svsk->sk_tcplen - sizeof(rpc_fraghdr));
+ goto err_noclose;
+error:
+ if (len != -EAGAIN)
+ goto err_delete;
+ trace_svcsock_tcp_recv_eagain(&svsk->sk_xprt, 0);
+ goto err_noclose;
+err_nuts:
+ svsk->sk_datalen = 0;
+ goto err_delete;
+err_discard:
+ /*
+ * Clear sk_datalen so the teardown-time
+ * svc_tcp_clear_pages() does not walk the emptied
+ * svsk->sk_pages[].
+ */
+ svsk->sk_datalen = 0;
+err_delete:
+ trace_svcsock_tcp_recv_err(&svsk->sk_xprt, len);
+ svc_xprt_deferred_close(&svsk->sk_xprt);
+err_noclose:
+ svc_xprt_received(rqstp->rq_xprt);
+ return 0;
+}
+
/**
* svc_tcp_recvfrom - Receive data from a TCP socket
* @rqstp: request structure into which to receive an RPC Call
@@ -1162,6 +1403,9 @@ static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
__be32 *p;
__be32 calldir;
+ if (svsk->sk_sock->ops->read_sock_rectype)
+ return svc_tcp_recvfrom_readsock(rqstp);
+
clear_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
len = svc_tcp_read_marker(svsk, rqstp);
if (len < 0)
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v2 6/6] SUNRPC: Remove sock_recvmsg path from svcsock TCP receives
From: Chuck Lever @ 2026-07-20 14:28 UTC (permalink / raw)
To: Jakub Kicinski, Paolo Abeni, Simon Horman, John Fastabend,
Sabrina Dubroca, Shuah Khan, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Chuck Lever
Cc: netdev, kernel-tls-handshake, linux-kselftest, linux-nfs
In-Reply-To: <20260720-tcp-read-sock-v2-0-29545d034f3c@kernel.org>
From: Chuck Lever <chuck.lever@oracle.com>
The svcsock TCP receive path maintains two code paths: one
using read_sock/read_sock_rectype and a legacy path using
sock_recvmsg. Plain TCP sockets already provide read_sock
(tcp_read_sock) in their proto_ops, so a single
read_sock-based receive path handles all cases relevant to
NFSD, using read_sock_rectype under kTLS and read_sock
otherwise.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/svcsock.c | 329 ++++-----------------------------------------------
1 file changed, 26 insertions(+), 303 deletions(-)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index e40931d11491..9b9e0da9e73c 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -8,15 +8,6 @@
* evenly when servicing a single client. May need to modify the
* svc_xprt_enqueue procedure...
*
- * TCP support is largely untested and may be a little slow. The problem
- * is that we currently do two separate recvfrom's, one for the 4-byte
- * record length, and the second for the actual record. This could possibly
- * be improved by always reading a minimum size of around 100 bytes and
- * tucking any superfluous bytes away in a temporary store. Still, that
- * leaves write requests out in the rain. An alternative may be to peek at
- * the first skb in the queue, and if it matches the next TCP sequence
- * number, to extract the record marker. Yuck.
- *
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
*/
@@ -238,138 +229,6 @@ static int svc_one_sock_name(struct svc_sock *svsk, char *buf, int remaining)
return len;
}
-static int
-svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
- struct cmsghdr *cmsg, int ret)
-{
- u8 content_type = tls_get_record_type(sock->sk, cmsg);
- u8 level, description;
-
- switch (content_type) {
- case 0:
- break;
- case TLS_RECORD_TYPE_DATA:
- /* TLS sets EOR at the end of each application data
- * record, even though there might be more frames
- * waiting to be decrypted.
- */
- msg->msg_flags &= ~MSG_EOR;
- break;
- case TLS_RECORD_TYPE_ALERT:
- tls_alert_recv(sock->sk, msg, &level, &description);
- ret = (level == TLS_ALERT_LEVEL_FATAL) ?
- -ENOTCONN : -EAGAIN;
- break;
- default:
- /* discard this record type */
- ret = -EAGAIN;
- }
- return ret;
-}
-
-static int
-svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
-{
- union {
- struct cmsghdr cmsg;
- u8 buf[CMSG_SPACE(sizeof(u8))];
- } u;
- u8 alert[2];
- struct kvec alert_kvec = {
- .iov_base = alert,
- .iov_len = sizeof(alert),
- };
- struct msghdr msg = {
- .msg_flags = *msg_flags,
- .msg_control = &u,
- .msg_controllen = sizeof(u),
- };
- int ret;
-
- iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
- alert_kvec.iov_len);
- ret = sock_recvmsg(sock, &msg, MSG_DONTWAIT);
- if (ret > 0 &&
- tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT) {
- iov_iter_revert(&msg.msg_iter, ret);
- ret = svc_tcp_sock_process_cmsg(sock, &msg, &u.cmsg, -EAGAIN);
- }
- return ret;
-}
-
-static int
-svc_tcp_sock_recvmsg(struct svc_sock *svsk, struct msghdr *msg)
-{
- int ret;
- struct socket *sock = svsk->sk_sock;
-
- ret = sock_recvmsg(sock, msg, MSG_DONTWAIT);
- if (msg->msg_flags & MSG_CTRUNC) {
- msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR);
- if (ret == 0 || ret == -EIO)
- ret = svc_tcp_sock_recv_cmsg(sock, &msg->msg_flags);
- }
- return ret;
-}
-
-#if ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE
-static void svc_flush_bvec(const struct bio_vec *bvec, size_t size, size_t seek)
-{
- struct bvec_iter bi = {
- .bi_size = size + seek,
- };
- struct bio_vec bv;
-
- bvec_iter_advance(bvec, &bi, seek & PAGE_MASK);
- for_each_bvec(bv, bvec, bi, bi)
- flush_dcache_page(bv.bv_page);
-}
-#else
-static inline void svc_flush_bvec(const struct bio_vec *bvec, size_t size,
- size_t seek)
-{
-}
-#endif
-
-/*
- * Read from @rqstp's transport socket. The incoming message fills whole
- * pages in @rqstp's rq_pages array until the last page of the message
- * has been received into a partial page.
- */
-static ssize_t svc_tcp_read_msg(struct svc_rqst *rqstp, size_t buflen,
- size_t seek)
-{
- struct svc_sock *svsk =
- container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
- struct bio_vec *bvec = rqstp->rq_bvec;
- struct msghdr msg = { NULL };
- unsigned int i;
- ssize_t len;
- size_t t;
-
- clear_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
-
- for (i = 0, t = 0; t < buflen; i++, t += PAGE_SIZE)
- bvec_set_page(&bvec[i], rqstp->rq_pages[i], PAGE_SIZE, 0);
-
- iov_iter_bvec(&msg.msg_iter, ITER_DEST, bvec, i, buflen);
- if (seek) {
- iov_iter_advance(&msg.msg_iter, seek);
- buflen -= seek;
- }
- len = svc_tcp_sock_recvmsg(svsk, &msg);
- if (len > 0)
- svc_flush_bvec(bvec, len, seek);
-
- /* If we read a full record, then assume there may be more
- * data to read (stream based sockets only!)
- */
- if (len == buflen)
- set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
-
- return len;
-}
-
/*
* Set socket snd and rcv buffer lengths
*/
@@ -1048,50 +907,6 @@ static void svc_tcp_clear_pages(struct svc_sock *svsk)
svsk->sk_datalen = 0;
}
-/*
- * Receive fragment record header into sk_marker.
- */
-static ssize_t svc_tcp_read_marker(struct svc_sock *svsk,
- struct svc_rqst *rqstp)
-{
- ssize_t want, len;
-
- /* If we haven't gotten the record length yet,
- * get the next four bytes.
- */
- if (svsk->sk_tcplen < sizeof(rpc_fraghdr)) {
- struct msghdr msg = { NULL };
- struct kvec iov;
-
- want = sizeof(rpc_fraghdr) - svsk->sk_tcplen;
- iov.iov_base = ((char *)&svsk->sk_marker) + svsk->sk_tcplen;
- iov.iov_len = want;
- iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, want);
- len = svc_tcp_sock_recvmsg(svsk, &msg);
- if (len < 0)
- return len;
- svsk->sk_tcplen += len;
- if (len < want) {
- /* call again to read the remaining bytes */
- goto err_short;
- }
- trace_svcsock_marker(&svsk->sk_xprt, svsk->sk_marker);
- if (svc_sock_reclen(svsk) + svsk->sk_datalen >
- svsk->sk_xprt.xpt_server->sv_max_mesg)
- goto err_too_large;
- }
- return svc_sock_reclen(svsk);
-
-err_too_large:
- net_notice_ratelimited("svc: %s oversized RPC fragment (%u octets) from %pISpc\n",
- svsk->sk_xprt.xpt_server->sv_name,
- svc_sock_reclen(svsk),
- (struct sockaddr *)&svsk->sk_xprt.xpt_remote);
- svc_xprt_deferred_close(&svsk->sk_xprt);
-err_short:
- return -EAGAIN;
-}
-
static int receive_cb_reply(struct svc_sock *svsk, struct svc_rqst *rqstp)
{
struct rpc_xprt *bc_xprt = svsk->sk_xprt.xpt_bc_xprt;
@@ -1135,10 +950,10 @@ static void svc_tcp_fragment_received(struct svc_sock *svsk)
}
/*
- * read_sock_rectype data actor: receives decrypted application data
- * from the TLS layer, parsing the RPC record stream (fragment
- * headers and message bodies) and assembling complete RPC messages
- * into rqstp->rq_pages.
+ * read_sock data actor: receives application data from the
+ * transport socket, parsing the RPC record stream (fragment
+ * headers and message bodies) and assembling complete RPC
+ * messages into rqstp->rq_pages.
*/
static int svc_tcp_recv_actor(read_descriptor_t *desc,
struct sk_buff *skb,
@@ -1266,7 +1081,21 @@ static int svc_tcp_rectype_actor(read_descriptor_t *desc,
return 0;
}
-static int svc_tcp_recvfrom_readsock(struct svc_rqst *rqstp)
+/**
+ * svc_tcp_recvfrom - Receive data from a TCP socket
+ * @rqstp: request structure into which to receive an RPC Call
+ *
+ * Called in a loop when XPT_DATA has been set.
+ *
+ * Returns:
+ * On success, the number of bytes in a received RPC Call, or
+ * %0 if a complete RPC Call message was not ready to return
+ *
+ * The zero return case handles partial receives and callback Replies.
+ * The state of a partial receive is preserved in the svc_sock for
+ * the next call to svc_tcp_recvfrom.
+ */
+static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
{
struct svc_sock *svsk =
container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
@@ -1286,9 +1115,13 @@ static int svc_tcp_recvfrom_readsock(struct svc_rqst *rqstp)
desc.count = serv->sv_max_mesg;
lock_sock(sk);
- len = svsk->sk_sock->ops->read_sock_rectype(sk, &desc,
- svc_tcp_recv_actor,
- svc_tcp_rectype_actor);
+ if (svsk->sk_sock->ops->read_sock_rectype)
+ len = svsk->sk_sock->ops->read_sock_rectype(sk, &desc,
+ svc_tcp_recv_actor,
+ svc_tcp_rectype_actor);
+ else
+ len = svsk->sk_sock->ops->read_sock(sk, &desc,
+ svc_tcp_recv_actor);
release_sock(sk);
if (desc.error < 0) {
@@ -1375,116 +1208,6 @@ static int svc_tcp_recvfrom_readsock(struct svc_rqst *rqstp)
return 0;
}
-/**
- * svc_tcp_recvfrom - Receive data from a TCP socket
- * @rqstp: request structure into which to receive an RPC Call
- *
- * Called in a loop when XPT_DATA has been set.
- *
- * Read the 4-byte stream record marker, then use the record length
- * in that marker to set up exactly the resources needed to receive
- * the next RPC message into @rqstp.
- *
- * Returns:
- * On success, the number of bytes in a received RPC Call, or
- * %0 if a complete RPC Call message was not ready to return
- *
- * The zero return case handles partial receives and callback Replies.
- * The state of a partial receive is preserved in the svc_sock for
- * the next call to svc_tcp_recvfrom.
- */
-static int svc_tcp_recvfrom(struct svc_rqst *rqstp)
-{
- struct svc_sock *svsk =
- container_of(rqstp->rq_xprt, struct svc_sock, sk_xprt);
- struct svc_serv *serv = svsk->sk_xprt.xpt_server;
- size_t want, base;
- ssize_t len;
- __be32 *p;
- __be32 calldir;
-
- if (svsk->sk_sock->ops->read_sock_rectype)
- return svc_tcp_recvfrom_readsock(rqstp);
-
- clear_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
- len = svc_tcp_read_marker(svsk, rqstp);
- if (len < 0)
- goto error;
-
- base = svc_tcp_restore_pages(svsk, rqstp);
- want = len - (svsk->sk_tcplen - sizeof(rpc_fraghdr));
- len = svc_tcp_read_msg(rqstp, base + want, base);
- if (len >= 0) {
- trace_svcsock_tcp_recv(&svsk->sk_xprt, len);
- svsk->sk_tcplen += len;
- svsk->sk_datalen += len;
- }
- if (len != want || !svc_sock_final_rec(svsk))
- goto err_incomplete;
- if (svsk->sk_datalen < 8)
- goto err_nuts;
-
- rqstp->rq_arg.len = svsk->sk_datalen;
- rqstp->rq_arg.page_base = 0;
- if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0].iov_len) {
- rqstp->rq_arg.head[0].iov_len = rqstp->rq_arg.len;
- rqstp->rq_arg.page_len = 0;
- } else
- rqstp->rq_arg.page_len = rqstp->rq_arg.len - rqstp->rq_arg.head[0].iov_len;
-
- rqstp->rq_xprt_ctxt = NULL;
- rqstp->rq_prot = IPPROTO_TCP;
- if (test_bit(XPT_LOCAL, &svsk->sk_xprt.xpt_flags))
- set_bit(RQ_LOCAL, &rqstp->rq_flags);
- else
- clear_bit(RQ_LOCAL, &rqstp->rq_flags);
-
- p = (__be32 *)rqstp->rq_arg.head[0].iov_base;
- calldir = p[1];
- if (calldir)
- len = receive_cb_reply(svsk, rqstp);
-
- /* Reset TCP read info */
- svsk->sk_datalen = 0;
- svc_tcp_fragment_received(svsk);
-
- if (len < 0)
- goto error;
-
- svc_xprt_copy_addrs(rqstp, &svsk->sk_xprt);
- if (serv->sv_stats)
- serv->sv_stats->nettcpcnt++;
-
- svc_sock_secure_port(rqstp);
- svc_xprt_received(rqstp->rq_xprt);
- return rqstp->rq_arg.len;
-
-err_incomplete:
- svc_tcp_save_pages(svsk, rqstp);
- if (len < 0 && len != -EAGAIN)
- goto err_delete;
- if (len == want)
- svc_tcp_fragment_received(svsk);
- else
- trace_svcsock_tcp_recv_short(&svsk->sk_xprt,
- svc_sock_reclen(svsk),
- svsk->sk_tcplen - sizeof(rpc_fraghdr));
- goto err_noclose;
-error:
- if (len != -EAGAIN)
- goto err_delete;
- trace_svcsock_tcp_recv_eagain(&svsk->sk_xprt, 0);
- goto err_noclose;
-err_nuts:
- svsk->sk_datalen = 0;
-err_delete:
- trace_svcsock_tcp_recv_err(&svsk->sk_xprt, len);
- svc_xprt_deferred_close(&svsk->sk_xprt);
-err_noclose:
- svc_xprt_received(rqstp->rq_xprt);
- return 0; /* record not complete */
-}
-
/*
* MSG_SPLICE_PAGES is used exclusively to reduce the number of
* copy operations in this path. Therefore the caller must ensure
--
2.54.0
^ permalink raw reply related
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