Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH v10 rdma-next] RDMA: Change capability fields in ib_device_attr from int to u32
From: Leon Romanovsky @ 2026-07-12  8:50 UTC (permalink / raw)
  To: Erni Sri Satya Vennela
  Cc: Jason Gunthorpe, mkalderon, zyjzyj2000, sagi, mgurtovoy,
	haris.iqbal, jinpu.wang, bvanassche, kbusch, Jens Axboe,
	Christoph Hellwig, kch, smfrench, linkinjeon, metze, tom, cel,
	jlayton, neil, okorniev, Dai.Ngo, trondmy, anna, achender, davem,
	edumazet, kuba, pabeni, horms, kees, michaelgur, edwards, phaddad,
	eadavis, yishaih, kalesh-anakkur.purayil, andriy.shevchenko, clm,
	ebadger, linux-rdma, linux-kernel, target-devel, linux-nvme,
	linux-cifs, samba-technical, linux-nfs, netdev, rds-devel,
	Jason Gunthorpe
In-Reply-To: <20260709055211.2498307-1-ernis@linux.microsoft.com>

On Wed, Jul 08, 2026 at 10:51:29PM -0700, Erni Sri Satya Vennela wrote:
> The capability counter fields in struct ib_device_attr are declared
> as signed int, but these values are inherently non-negative. Drivers
> maintain their cached caps as u32 and assign them directly into these
> int fields; if a cap exceeds INT_MAX the implicit narrowing yields a
> negative value visible to the IB core.
> 
> Change the signed int capability fields to u32 to match the
> underlying nature of the data. Also update consumers across the IB
> core, ULPs, NVMe-oF target, RDS, and NFS/RDMA so the new u32 values
> are not forced back through signed int or u8 via min()/min_t() or
> narrowing local variables.
> 
> The nvmet-rdma consumer of max_srq clamps it against
> ib_device.num_comp_vectors, which stays a signed int, so that site
> uses min_t() instead of min() to handle the signed/unsigned mismatch.
> 
> Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
> Acked-by: Stefan Metzmacher <metze@samba.org> # smbdirect
> ---
> Changes in v10:
> * Convert max_srq to u32. Use min_t() against the still-signed ib_device.num_comp_vectors.
> * Update commit message.
> * Change rdma_restrack_count() to return u32 and make fill_res_info()'s curr and max u64.
> Changes in v9:
> * Switch the srq_size module parameter accessors to param_get_uint and
>   kstrtouint()/param_set_uint() so they match the now-unsigned
>   nvmet_rdma_srq_size variable.
> Changes in v8:
> * Convert the remaining non-negative counter fields max_ee_rd_atom,
>   max_ee_init_rd_atom, max_ee, max_rdd, max_raw_ipv6_qp and max_srq_wr
>   to u32; keep max_srq as int (its consumer compares it against
>   ib_device.num_comp_vectors, still int).
> * Drop all remaining min_t() where plain min() now works.
> * Make the srq_size module parameters unsigned int so the srq_size min()
>   stays a plain min().
> * Replace the ternary-inside-min() with the simpler "if (x) x--;".
> * Reorder the send_queue_depth min() to min(value, CONST) to match the
>   sibling site.
> * Restore reverse xmas-tree declaration order.
> * Collapse the min()/min3() assignments that now fit onto a single line
>   within 100 columns.
> * Print the now-u32 fields with %u instead of %d.
> Changes in v7:
> * Drop min_t() in all sites where a plain min() (or min3()) works
>   cleanly
> * Guard nvme/host/rdma.c num_inline_segments computation against a
>   device reporting max_send_sge == 0, so the u32 subtract
>   cannot wrap to UINT_MAX.
> * Use %u when printing the newly-u32 capability fields
>   in diagnostic messages.
> Changes in v6:
> * Fix subject prefix: net-next -> rdma-next.
> Changes in v5:
> * Add U8_MAX clamps in iser_verbs, nvme/host, nvme/target, isert,
> * rds/ib_cm, smbdirect/connect and smbdirect/accept where u32 capability
>   fields were directly narrowed into u8 rdma_conn_param fields without
>   clamping.
> * Guard the inline_sge_count calculation in nvmet_rdma_find_get_device()
>   to prevent u32 underflow when both max_sge_rd and max_recv_sge are
> zero.
> * Expand type migration to 9 additional fields (max_mw, max_raw_ethy_qp,
>   max_mcast_grp, max_mcast_qp_attach, max_total_mcast_qp_attach, max_ah,
>   max_srq, max_srq_wr, max_srq_sge)
> * Fix min_t(int,...) in svc_rdma_transport; min_t(u32,...) in ipoib,
>   srpt, nvme/target, rds/ib, rtrs-clt, rtrs-srv, xprtrdma/verbsdd.
> * Fix frwr_ops.c u32 underflow guard (reorder check before subtraction)
> * Change sc_max_send_sges to unsigned int, inline_sge_count to u32
> * Fix %d -> %u in rxe_qp, rxe_srq, ipoib_cm, ib_isert,
> * svc_rdma_transport
> * Update commit message.
> Changes in v4:
> * Drop clamping the values in mana_ib_query_device, instead update
>   the props values from int to u32.
> Changes in v3:
> * Drop clamping from mana_ib_gd_query_adapter_caps(). The internal u32
>   caps cache does not need to be clamped.
> * Move all clamping exclusively to mana_ib_query_device(), which is the
>   only place the cached u32 values are narrowed into the signed int
>   fields of struct ib_device_attr.
> * Reframe commit message: this is a u32-to-int type boundary fix, not a
>   CVM/untrusted-hardware hardening patch.
> Changes in v2:
> * Update patch title.
> ---
>  drivers/infiniband/core/cq.c               |  3 +-
>  drivers/infiniband/core/nldev.c            |  3 +-
>  drivers/infiniband/core/restrack.c         |  2 +-
>  drivers/infiniband/hw/qedr/verbs.c         |  2 +-
>  drivers/infiniband/sw/rxe/rxe_qp.c         | 22 ++++-----
>  drivers/infiniband/sw/rxe/rxe_srq.c        | 16 +++----
>  drivers/infiniband/ulp/ipoib/ipoib_cm.c    | 10 ++---
>  drivers/infiniband/ulp/ipoib/ipoib_verbs.c |  3 +-
>  drivers/infiniband/ulp/iser/iser_verbs.c   |  5 +--
>  drivers/infiniband/ulp/isert/ib_isert.c    |  7 ++-
>  drivers/infiniband/ulp/rtrs/rtrs-clt.c     | 11 ++---
>  drivers/infiniband/ulp/rtrs/rtrs-srv.c     | 11 ++---
>  drivers/infiniband/ulp/srp/ib_srp.c        |  2 +-
>  drivers/infiniband/ulp/srpt/ib_srpt.c      | 21 +++++----
>  drivers/nvme/host/rdma.c                   |  8 ++--
>  drivers/nvme/target/rdma.c                 | 26 ++++++-----
>  fs/smb/smbdirect/accept.c                  |  5 ++-
>  fs/smb/smbdirect/connect.c                 |  5 ++-
>  fs/smb/smbdirect/connection.c              |  8 ++--
>  include/linux/sunrpc/svc_rdma.h            |  4 +-
>  include/rdma/ib_verbs.h                    | 52 +++++++++++-----------
>  include/rdma/restrack.h                    |  2 +-
>  net/rds/ib.c                               | 10 ++---
>  net/rds/ib_cm.c                            | 10 ++---
>  net/sunrpc/xprtrdma/frwr_ops.c             |  7 +--
>  net/sunrpc/xprtrdma/svc_rdma_transport.c   |  5 +--
>  net/sunrpc/xprtrdma/verbs.c                |  2 +-
>  27 files changed, 129 insertions(+), 133 deletions(-)

This patch touches too many areas to delay it further. Let's merge it
now and improve it later, if needed.

Thanks

^ permalink raw reply

* Re: [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
From: Maxime Chevallier @ 2026-07-12  8:23 UTC (permalink / raw)
  To: contact, Linus Walleij, Alvin Šipraga, Andrew Lunn,
	Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King
  Cc: Mieczyslaw Nalewaj, Luiz Angelo Daros de Luca, netdev,
	linux-kernel
In-Reply-To: <20260711-rtl8367s-sgmii-v6-1-88f7944ddca7@c127.dev>

Hi Johan,

On 7/12/26 06:31, Johan Alvarado via B4 Relay wrote:
> From: Johan Alvarado <contact@c127.dev>
> 
> The RTL8367S can mux its embedded SerDes to external interface 1,
> which is typically used to connect the switch to a CPU port. The chip
> info table already declares SGMII as a supported interface mode for
> this chip, but the driver only implements RGMII so far.
> 
> Implement SGMII support as a phylink PCS, with the configuration
> sequence derived from the GPL-licensed Realtek rtl8367c vendor driver
> as distributed in the Mercusys MR80X GPL code drop:
> 
>  - Add accessors for the SerDes indirect access registers (SDS_INDACS),
>    through which the SerDes internal registers are reached.
> 
>  - Register a phylink_pcs for the SerDes, selected from mac_select_pcs
>    for the SGMII interface, so the SerDes handling lives in the PCS
>    operations rather than in the MAC operations.
> 
>  - Probe the SerDes tuning variant from the chip option register once
>    at setup. The vendor driver keeps two sets of SerDes tuning
>    parameters and selects between them based on this option; only the
>    variant for a non-zero option (which all RTL8367S parts seen so far
>    report) has been validated on hardware, so the SerDes interface
>    modes are only advertised in that case. An unsupported variant thus
>    fails at phylink validation time instead of at link configuration
>    time.
> 
>  - Keep the embedded DW8051 microcontroller in reset and disabled. The
>    vendor driver loads firmware into it to manage the SerDes link, but
>    analysis of that firmware shows it only duplicates the link
>    management phylink already performs: it polls the port status and
>    writes the external interface force registers behind the driver's
>    back.
> 
>  - Clear the line rate bypass bit for the external interface, tune the
>    SerDes with the vendor-prescribed parameters, mux the SerDes to MAC8
>    in SGMII mode and only then take the SerDes out of reset, as the
>    vendor driver does.
> 
>  - After deasserting the SerDes reset, reset the SerDes data path via
>    the SerDes BMCR register to flush the FIFOs and resync the PLL.
>    This mirrors what the vendor firmware does right after deasserting
>    the SerDes reset, and ensures a clean link state from cold boot.
> 
>  - Force the SGMII link parameters (link, speed, duplex) in the SDS_MISC
>    register from pcs_link_up(). SGMII in-band autonegotiation is not
>    implemented, so only fixed-link and conventional PHY setups are
>    supported, just like RGMII. This is reported to phylink through
>    pcs_inband_caps() returning LINK_INBAND_DISABLE, so phylink never
>    selects an in-band-enabled negotiation mode for this PCS.
> 
>  - Program the SerDes pause enables in SDS_MISC from the resolved
>    pause modes when forcing the MAC external interface in mac_link_up,
>    as the vendor driver does, rather than leaving whatever state the
>    boot firmware left there. Flow control testing shows these bits,
>    not the MAC force pause bits, gate pause on the SerDes external
>    interface. This is done in the MAC layer because pcs_link_up()
>    carries no pause information.
> 
>  - Implement pcs_get_state() by reading the link status from the
>    SerDes, with the forced speed and duplex read back from SDS_MISC.
>    Although the supported fixed-link and conventional PHY setups do not
>    use it, the PCS owns the SerDes link state, and phylink consults
>    pcs_get_state() to track the physical link when operating in in-band
>    mode with autonegotiation disabled. The SerDes has no link interrupt
>    wired up, so the PCS sets its poll flag.
> 
> Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to
> the SoC over SGMII.
> 
> Suggested-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
> Suggested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
> Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
> Signed-off-by: Johan Alvarado <contact@c127.dev>

Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>

Maxime


^ permalink raw reply

* Re: [PATCH net] net/af_iucv: fix NULL deref in afiucv_hs_callback_syn()
From: Hidayathulla Khan I @ 2026-07-12  7:56 UTC (permalink / raw)
  To: davem, edumazet, kuba, pabeni
  Cc: horms, linux-s390, netdev, linux-kernel, wintera, twinkler,
	heiko.carstens, gor, agordeev, borntraeger, svens
In-Reply-To: <20260709191732.124092-1-hidayath@linux.ibm.com>

Thanks for the Sashiko AI review: On the findings it raised.

Finding 1: iucv_sock_kill() returns early unless SOCK_ZAPPED is set,
and the flag is never set on a freshly allocated child socket, so the
child socket and its pinned net_device leak on the error paths. I had 
already
spotted this leak (in afiucv_hs_callback_syn() and iucv_callback_connreq())
and Alexandra Winter and I are looking into it.

Both NULL deref and child sock leak come from the same root cause,
the child socket is allocated before the listen-state and accept-queue 
checks.
I will address them together in v2 by allocating the child socket only 
after the
listen-state and accept-queue checks, so the refused path has nothing to
release (no NULL to guard and no child socket to free).

And on the transmit-failure path release the already-constructed
child socket directly (dev_put, unlink, put the last reference) instead of
relying on iucv_sock_kill().

Finding 2: missing sock_hold on the afiucv_hs_rcv() lookup. Agreed.
Bryam Vargas has already submitted a patch for this.

The other findings look valid too. I will follow up on them separately.

Thanks,
Hidayath Khan

On 10/07/26 12:47 am, Hidayath Khan wrote:
> afiucv_hs_callback_syn() allocates the child socket with GFP_ATOMIC.
> If the allocation fails, nsk is NULL.
>
> The connection-refused path is entered when the listen state check
> fails, the accept backlog is full, or nsk is NULL. The code
> unconditionally calls iucv_sock_kill(nsk) in that path.
>
> iucv_sock_kill() does not accept a NULL socket pointer and immediately
> dereferences sk via sock_flag(sk, SOCK_ZAPPED). When nsk is NULL,
> calling iucv_sock_kill(nsk) results in a NULL pointer dereference.
>
> Only call iucv_sock_kill() when a child socket was successfully
> allocated.
>
> Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport")
> Cc: stable@vger.kernel.org
> Reviewed-by: Alexandra Winter <wintera@linux.ibm.com>
> Signed-off-by: Hidayath Khan <hidayath@linux.ibm.com>
> ---
>   net/iucv/af_iucv.c | 3 ++-
>   1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c
> index fed240b453bd..f5b1ec44b6ae 100644
> --- a/net/iucv/af_iucv.c
> +++ b/net/iucv/af_iucv.c
> @@ -1872,7 +1872,8 @@ static int afiucv_hs_callback_syn(struct sock *sk, struct sk_buff *skb)
>   		afiucv_swap_src_dest(skb);
>   		trans_hdr->flags = AF_IUCV_FLAG_SYN | AF_IUCV_FLAG_FIN;
>   		err = dev_queue_xmit(skb);
> -		iucv_sock_kill(nsk);
> +		if (nsk)
> +			iucv_sock_kill(nsk);
>   		bh_unlock_sock(sk);
>   		goto out;
>   	}
>
> base-commit: 262b2eac463d880a664cf92af1107b4f9d84ad37

^ permalink raw reply

* Re: [PATCH net-next v2 3/3] net: ipv4: clear dev->ip_ptr before destroying inetdev
From: Ido Schimmel @ 2026-07-12  7:20 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Andrew Lunn, David Ahern, Elad Nachman,
	Eric Dumazet, Jakub Kicinski, Johannes Berg, Paolo Abeni,
	Simon Horman, linux-kernel, linux-wireless, netdev,
	Kuniyuki Iwashima
In-Reply-To: <20260711005405.2861680-4-yuyanghuang@google.com>

On Sat, Jul 11, 2026 at 09:54:04AM +0900, Yuyang Huang wrote:
> To prevent RCU readers from accessing a partially destroyed in_device,
> clear dev->ip_ptr early in inetdev_destroy() before freeing the
> multicast list and individual IP addresses. This aligns the IPv4 teardown
> sequence with the IPv6 implementation.
> 
> Cc: Ido Schimmel <idosch@nvidia.com>
> Cc: Kuniyuki Iwashima <kuniyu@google.com>
> Signed-off-by: Yuyang Huang <yuyanghuang@google.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

^ permalink raw reply

* Re: VXLAN FDB nexthop groups: per-nexthop UDP dst port / VNI feasibility
From: Ido Schimmel @ 2026-07-12  7:05 UTC (permalink / raw)
  To: Jack Ma
  Cc: David Ahern, Roopa Prabhu, netdev@vger.kernel.org,
	Nikolay Aleksandrov
In-Reply-To: <BL3PR17MB6067BF1D5802A6B1822452EEE5FD2@BL3PR17MB6067.namprd17.prod.outlook.com>

On Fri, Jul 10, 2026 at 05:30:40AM +0000, Jack Ma wrote:
> Thanks very much for any pointers.

I already commented here:

https://lore.kernel.org/netdev/20260709102247.GA1536940@shredder/

^ permalink raw reply

* Re: Questions about resilient next-hop groups (6.18.35)
From: Ido Schimmel @ 2026-07-12  7:02 UTC (permalink / raw)
  To: Froehlich, Dominik; +Cc: netdev@vger.kernel.org, petrm, dsahern
In-Reply-To: <AS2PR02MB95821AC8417044B6529728DAE2FE2@AS2PR02MB9582.eurprd02.prod.outlook.com>

On Thu, Jul 09, 2026 at 02:46:47PM +0000, Froehlich, Dominik wrote:
> Hello everyone,
> 
> I am running kernel 6.18.35 and I have the following multi path route setup:
> 
> 
> 10.181.0.0/16 nhid 400
>       nexthop dev bond0-ip6tnl0 weight 1
>       nexthop dev bond0-ip6tnl1 weight 1
> 100.80.0.0/12 nhid 400
>       nexthop dev bond0-ip6tnl0 weight 1
>       nexthop dev bond0-ip6tnl1 weight 1
> 100.112.0.0/13 nhid 400
>       nexthop dev bond0-ip6tnl0 weight 1
>       nexthop dev bond0-ip6tnl1 weight 1
> 
> 
> The links are ip6tnl devices sending traffic over an IPv6 tunnel to the other end.
> 
> I am using L4 ECMP hash policy:
> 
> ~ # sysctl net.ipv4.fib_multipath_hash_policy
> net.ipv4.fib_multipath_hash_policy = 1
> ~ # sysctl net.ipv6.fib_multipath_hash_policy
> net.ipv6.fib_multipath_hash_policy = 1
> 
> Here are the next hop definitions:
> 
> id 400 group 4000,128/4001,128 type resilient buckets 1024 idle_timer 60 unbalanced_timer 0 unbalanced_time 0
> id 4000 dev bond0-ip6tnl0 scope host
> id 4001 dev bond0-ip6tnl1 scope host
> 
> 
> So I have two equal weighted next hops with 512 buckets each.
> 
> Now I am running a TCP connection in a shell on a host in the 10.181.0.0/16 network over one of the next hops that continuously produces traffic to keep its bucket busy:
> 
> while true; do 
>   sleep 0.1
>   date
> done 
> 
> 
> Thu Jul  9 13:27:03 UTC 2026
> Thu Jul  9 13:27:03 UTC 2026
> Thu Jul  9 13:27:03 UTC 2026
> Thu Jul  9 13:27:04 UTC 2026
> (…)
> 
> This roughly sends 10 pkts/s.
> Now I want to drain next hop 4000 which is where my traffic is flowing:
> 
> 
> ip nexthop replace id 400 group 4000,1/4001,255 type resilient buckets 1024 idle_timer 60 unbalanced_timer 0
> 
> Assigning it a weight of 1 should reduce the number of buckets assigned to next hop 4000 to 4 and move idle buckets to next hop 4001.
> 
> However, what I am seeing is that the connection is immediately terminated.
> 
> 
> When I am changing the weights more slowly it kind of works:
> 
> ~ # ip nexthop replace id 400 group 4000,128/4001,128 type resilient idle_timer 60 unbalanced_timer 0
> ~ # ip nexthop replace id 400 group 4000,64/4001,192 type resilient idle_timer 60 unbalanced_timer 0
> ~ # ip nexthop replace id 400 group 4000,5/4001,250 type resilient idle_timer 60 unbalanced_timer 0
> ~ # ip nexthop replace id 400 group 4000,1/4001,255 type resilient idle_timer 60 unbalanced_timer 0
> 
> I can then see the buckets reduced to just 4 over time as expected:
> 
> Every 1.0s: ip nexthop bucket list nhid 4000                                                                                                                               2026-07-09 13:44:22
> 
> id 400 index 447 idle_time 135.51 nhid 4000
> id 400 index 631 idle_time 117.56 nhid 4000
> id 400 index 720 idle_time 73.11 nhid 4000
> id 400 index 742 idle_time 136.19 nhid 4000
> 
> 
> My guess is that my connection is in bucket index 720 as it has the lowest idle_time reported. 
> 
> My questions:
> - Why is the idle_time reported so high? There should be packets flowing through that bucket every 0.1 seconds, so I was expecting the idle_time to be near zero.
> - Why does my connection get cut immediately when draining rapidly but kind of stays alive when changing weights more slowly? Shouldn't it be a busy (undrainable) bucket either way?
> Bucket nr 720 already has idle_time higher than 60 so I think it would be migrated on any further weight changes - which would disrupt the existing connection again.
> - When / how is the idle_time of a bucket updated? The docs mention that “Every time a packet is forwarded through a bucket, this timer is updated to current jiffies value.” I found that the bucket idle_time is refreshed more frequently when fever buckets are used. But why does the number of buckets even matter here? Fewer buckets mean more flows per bucket, I get that. But if there is at least one busy connection in a bucket, shouldn't that be enough to flag it busy?

IIUC you are using resilient nexthop groups with locally originated
traffic when these groups are meant to be used with forwarded traffic.
TCP caches the route lookup result in the socket and uses the cached dst
entry instead of performing a route lookup each time, so the routing
layer is bypassed and the nexthop bucket is not marked as used.

You can try to force a route lookup by flushing the cache, but it's a
hack and not something I recommend doing in production (it affects the
entire system, not only the flow that you care about):

echo 1 > /proc/sys/net/ipv4/route/flush

^ permalink raw reply

* [PATCH net v2] net: stmmac: enable the MAC on link up for all supported speeds
From: vadik likholetov @ 2026-07-12  6:55 UTC (permalink / raw)
  To: netdev
  Cc: maxime.chevallier, andrew, andrew+netdev, davem, edumazet, kuba,
	pabeni, thierry.reding, jonathanh, vbhadram, linux-tegra,
	linux-kernel
In-Reply-To: <20260710120908.3731591-1-vadikas@gmail.com>

stmmac_mac_link_down() clears the MAC's transmit and receive enable bits.
stmmac_mac_link_up() is expected to set them again through
stmmac_mac_set(..., true), but it first switches on the negotiated speed
and returns early for a speed the switch does not list. The MAC is then
left gated off.

The speed selection is split into three switches, keyed on the interface.
The generic branch -- taken for everything that is neither USXGMII nor
XLGMII, so including PHY_INTERFACE_MODE_10GBASER -- lists only SPEED_2500,
SPEED_1000, SPEED_100 and SPEED_10.

MGBE on Tegra234 runs 10GBASE-R into an Aquantia AQR113C. That PHY does
rate matching, so phylink_link_up() replaces the media speed with the
MAC-side interface speed before calling into the MAC:

	case RATE_MATCH_PAUSE:
		speed = phylink_interface_max_speed(link_state.interface);
		duplex = DUPLEX_FULL;

The driver is therefore called as

	stmmac_mac_link_up(interface=10GBASER, speed=10000, duplex=1)

which falls through to "default: return;". The interface stops passing
traffic after the first link flap.

The failure is easy to misread. The link still comes up, because the PHY
is polled over MDIO and needs no MAC, so the interface reports carrier 1
at the media speed. The DMA is untouched, so its start bits stay set and
descriptors are still consumed. Only the MAC itself is gated off: the
receiver counts nothing (mmc_rx_framecount_gb stops advancing, RE is 0)
and nothing reaches the wire (TE is 0). The interface survives boot only
because stmmac_hw_setup(), called from ndo_open, enables the MAC
unconditionally -- so the problem appears only once the cable has been
unplugged and plugged back in, and "ip link set dev <ethX> down && ip
link set dev <ethX> up" appears to fix it.

The interface is not what the speed bits depend on: with the single
exception of 2.5G, which is selected through the XGMII block on USXGMII
and through the regular speed bits otherwise, each speed maps to one
field of struct mac_link. The per-interface switches are speed
validation, and phylink already validates the speed against
priv->hw->link.caps. So collapse the three switches into one keyed on the
speed alone, keeping the interface test only for the 2.5G case. This
covers 10G on 10GBASE-R, and equally 5G, and 1G/100/10 on USXGMII, all of
which hit "default: return;" today.

A core that does not support a speed leaves the corresponding mac_link
field at 0, and phylink will not offer it that speed in the first place.
For dwxgmac2 at 10G, link.xgmii.speed10000 is XGMAC_CONFIG_SS_10000,
which is 0 and is the correct speed selection for a 10GBASE-R MAC: ctrl
then equals old_ctrl, the register write is skipped, and execution
reaches stmmac_mac_set(..., true).

Log an error in the default case, since a speed with no entry here leaves
the MAC disabled and the symptom does not point at the cause.

Fixes: d8ca113724e7 ("net: stmmac: tegra: Add MGBE support")
Suggested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Signed-off-by: vadik likholetov <vadikas@gmail.com>
---
v2:
 - Collapse the three per-interface switches into a single switch on the
   speed, rather than adding SPEED_10000 to the generic branch, which
   left SPEED_5000 and the USXGMII sub-1G speeds broken (Maxime).
 - netdev_err() in the default case (Andrew).

Fixes tag: the missing speeds predate the commit cited above. I picked
d8ca113724e7 because MGBE is the first in-tree user to reach it -- it
needs a 10GBASE-R interface driven by a rate-matching PHY, so that
phylink hands the MAC a 10G speed. Happy to re-target it.

Verified on an AGX Orin devkit (Tegra234 MGBE0 + AQR113C), before and
after, on the same board and cable. MAC registers read with `ethtool -d`,
after a physical unplug and replug:

  stock		MAC_TX_CONFIG 0x00010000 (TE=0)
		MAC_RX_CONFIG 0x3ff022c0 (RE=0)
		rx_packets frozen, DHCP lease lost

  patched	MAC_TX_CONFIG 0x00010001 (TE=1)
		MAC_RX_CONFIG 0x3ff022c1 (RE=1)
		rx_packets keeps climbing, DHCP lease retained

Only the 10GBASE-R path is covered by hardware here; the other speeds are
by inspection. Testing was done with MGBE0 handed to a VM via
vfio-platform, so the driver ran in a guest; the MAC register evidence
above is read from the device itself and the code path is not
virtualisation-specific.

 .../net/ethernet/stmicro/stmmac/stmmac_main.c | 92 ++++++++-----------
 1 file changed, 37 insertions(+), 55 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 2a0d7eff8..fa9f88e51 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -1083,63 +1083,45 @@ static void stmmac_mac_link_up(struct phylink_config *config,
 	old_ctrl = readl(priv->ioaddr + MAC_CTRL_REG);
 	ctrl = old_ctrl & ~priv->hw->link.speed_mask;
 
-	if (interface == PHY_INTERFACE_MODE_USXGMII) {
-		switch (speed) {
-		case SPEED_10000:
-			ctrl |= priv->hw->link.xgmii.speed10000;
-			break;
-		case SPEED_5000:
-			ctrl |= priv->hw->link.xgmii.speed5000;
-			break;
-		case SPEED_2500:
+	switch (speed) {
+	case SPEED_100000:
+		ctrl |= priv->hw->link.xlgmii.speed100000;
+		break;
+	case SPEED_50000:
+		ctrl |= priv->hw->link.xlgmii.speed50000;
+		break;
+	case SPEED_40000:
+		ctrl |= priv->hw->link.xlgmii.speed40000;
+		break;
+	case SPEED_25000:
+		ctrl |= priv->hw->link.xlgmii.speed25000;
+		break;
+	case SPEED_10000:
+		ctrl |= priv->hw->link.xgmii.speed10000;
+		break;
+	case SPEED_5000:
+		ctrl |= priv->hw->link.xgmii.speed5000;
+		break;
+	case SPEED_2500:
+		if (interface == PHY_INTERFACE_MODE_USXGMII)
 			ctrl |= priv->hw->link.xgmii.speed2500;
-			break;
-		default:
-			return;
-		}
-	} else if (interface == PHY_INTERFACE_MODE_XLGMII) {
-		switch (speed) {
-		case SPEED_100000:
-			ctrl |= priv->hw->link.xlgmii.speed100000;
-			break;
-		case SPEED_50000:
-			ctrl |= priv->hw->link.xlgmii.speed50000;
-			break;
-		case SPEED_40000:
-			ctrl |= priv->hw->link.xlgmii.speed40000;
-			break;
-		case SPEED_25000:
-			ctrl |= priv->hw->link.xlgmii.speed25000;
-			break;
-		case SPEED_10000:
-			ctrl |= priv->hw->link.xgmii.speed10000;
-			break;
-		case SPEED_2500:
-			ctrl |= priv->hw->link.speed2500;
-			break;
-		case SPEED_1000:
-			ctrl |= priv->hw->link.speed1000;
-			break;
-		default:
-			return;
-		}
-	} else {
-		switch (speed) {
-		case SPEED_2500:
+		else
 			ctrl |= priv->hw->link.speed2500;
-			break;
-		case SPEED_1000:
-			ctrl |= priv->hw->link.speed1000;
-			break;
-		case SPEED_100:
-			ctrl |= priv->hw->link.speed100;
-			break;
-		case SPEED_10:
-			ctrl |= priv->hw->link.speed10;
-			break;
-		default:
-			return;
-		}
+		break;
+	case SPEED_1000:
+		ctrl |= priv->hw->link.speed1000;
+		break;
+	case SPEED_100:
+		ctrl |= priv->hw->link.speed100;
+		break;
+	case SPEED_10:
+		ctrl |= priv->hw->link.speed10;
+		break;
+	default:
+		netdev_err(priv->dev,
+			   "unsupported speed %d on %s, leaving the MAC disabled\n",
+			   speed, phy_modes(interface));
+		return;
 	}
 
 	if (priv->plat->fix_mac_speed)
-- 
2.53.0


^ permalink raw reply related

* Failed to reinit phy of spacemit-dwmac when reset-gpio is present
From: Inochi Amaoto @ 2026-07-12  4:52 UTC (permalink / raw)
  To: Inochi Amaoto, Andrew Lunn, Eric Dumazet, David S. Miller,
	Jakub Kicinski, Paolo Abeni, Yixun Lan, Maxime Coquelin,
	Alexandre Torgue
  Cc: netdev, linux-riscv, spacemit, linux-stm32, linux-arm-kernel,
	linux-kernel, E Shattow, Han Gao

TL;DR:

The DWMAC on Spacemit K3 is failled to register phy after the reload
the driver module (rmmod then insmod). Because the reset-gpio is
asserted while unloading the driver and is not desserted before reading
the c22 id, which leads to a fault.

Description

In a few days ago, E Shattow reports he has sufferred the a weird issue,
when unloading the spacemit-dwmac driver and then reloading it, he got
the following error:

[   60.713071] mdio_bus stmmac-0: MDIO device at address 1 is missing.

This only occurs when reloading the driver but the first initialization
successed. After some function tracking, I found it is failed at reading
c22 id. The call graph is as the follows:

stmmac_mdio_register
	mdiobus_alloc
	of_mdiobus_register
		__of_mdiobus_register
		__mdiobus_register
		__of_mdiobus_parse_phys
			of_mdiobus_child_is_phy
			of_mdiobus_register_phy
				fwnode_mdiobus_register_phy
					get_phy_device
						get_phy_c22_id

By checking the difference between the initialization process and the
reloading process, I found the reset gpio is asserted in the function
mdiobus_unregister(). And there is no any function desserted this reset
gpio in the loading stage. And in the initialization process, the reset
goio is deasserted.

This bug report is sent as I found it is hard to solve this problem
and ask for help to fix this issue as it is related to the framework
instead of a specific driver.

The possible workaround I found is as the following, just use the phy
id in the compatible string (Confirmed by dlan):
---
--- a/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
+++ b/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
@@ -196,7 +196,8 @@ &eth0 {

 	mdio {
 		phy0: phy@1 {
-			compatible = "ethernet-phy-ieee802.3-c22";
+			compatible = "ethernet-phy-id001c.c916",
+				     "ethernet-phy-ieee802.3-c22";
 			reg = <1>;
 			reset-gpios = <&gpio 0 15 GPIO_ACTIVE_LOW>;
 			reset-assert-us = <10000>;
---
An interesting thing is, moving the reset-gpio to the MDIO bus level
does not solve this problem. Only setting the right phy id can mitigate
the problem.

Regards,
Inochi

^ permalink raw reply

* [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
From: Johan Alvarado via B4 Relay @ 2026-07-12  4:31 UTC (permalink / raw)
  To: Linus Walleij, Alvin Šipraga, Andrew Lunn, Vladimir Oltean,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Russell King
  Cc: Mieczyslaw Nalewaj, Maxime Chevallier, Luiz Angelo Daros de Luca,
	netdev, linux-kernel, Johan Alvarado
In-Reply-To: <20260711-rtl8367s-sgmii-v6-0-88f7944ddca7@c127.dev>

From: Johan Alvarado <contact@c127.dev>

The RTL8367S can mux its embedded SerDes to external interface 1,
which is typically used to connect the switch to a CPU port. The chip
info table already declares SGMII as a supported interface mode for
this chip, but the driver only implements RGMII so far.

Implement SGMII support as a phylink PCS, with the configuration
sequence derived from the GPL-licensed Realtek rtl8367c vendor driver
as distributed in the Mercusys MR80X GPL code drop:

 - Add accessors for the SerDes indirect access registers (SDS_INDACS),
   through which the SerDes internal registers are reached.

 - Register a phylink_pcs for the SerDes, selected from mac_select_pcs
   for the SGMII interface, so the SerDes handling lives in the PCS
   operations rather than in the MAC operations.

 - Probe the SerDes tuning variant from the chip option register once
   at setup. The vendor driver keeps two sets of SerDes tuning
   parameters and selects between them based on this option; only the
   variant for a non-zero option (which all RTL8367S parts seen so far
   report) has been validated on hardware, so the SerDes interface
   modes are only advertised in that case. An unsupported variant thus
   fails at phylink validation time instead of at link configuration
   time.

 - Keep the embedded DW8051 microcontroller in reset and disabled. The
   vendor driver loads firmware into it to manage the SerDes link, but
   analysis of that firmware shows it only duplicates the link
   management phylink already performs: it polls the port status and
   writes the external interface force registers behind the driver's
   back.

 - Clear the line rate bypass bit for the external interface, tune the
   SerDes with the vendor-prescribed parameters, mux the SerDes to MAC8
   in SGMII mode and only then take the SerDes out of reset, as the
   vendor driver does.

 - After deasserting the SerDes reset, reset the SerDes data path via
   the SerDes BMCR register to flush the FIFOs and resync the PLL.
   This mirrors what the vendor firmware does right after deasserting
   the SerDes reset, and ensures a clean link state from cold boot.

 - Force the SGMII link parameters (link, speed, duplex) in the SDS_MISC
   register from pcs_link_up(). SGMII in-band autonegotiation is not
   implemented, so only fixed-link and conventional PHY setups are
   supported, just like RGMII. This is reported to phylink through
   pcs_inband_caps() returning LINK_INBAND_DISABLE, so phylink never
   selects an in-band-enabled negotiation mode for this PCS.

 - Program the SerDes pause enables in SDS_MISC from the resolved
   pause modes when forcing the MAC external interface in mac_link_up,
   as the vendor driver does, rather than leaving whatever state the
   boot firmware left there. Flow control testing shows these bits,
   not the MAC force pause bits, gate pause on the SerDes external
   interface. This is done in the MAC layer because pcs_link_up()
   carries no pause information.

 - Implement pcs_get_state() by reading the link status from the
   SerDes, with the forced speed and duplex read back from SDS_MISC.
   Although the supported fixed-link and conventional PHY setups do not
   use it, the PCS owns the SerDes link state, and phylink consults
   pcs_get_state() to track the physical link when operating in in-band
   mode with autonegotiation disabled. The SerDes has no link interrupt
   wired up, so the PCS sets its poll flag.

Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to
the SoC over SGMII.

Suggested-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
Suggested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
 drivers/net/dsa/realtek/rtl8365mb_main.c | 515 ++++++++++++++++++++++++++++++-
 1 file changed, 511 insertions(+), 4 deletions(-)

diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 5ac091bf93c9..ea03c42d0f1a 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -40,7 +40,8 @@
  * driver has only been tested with a fixed-link, but in principle it should not
  * matter.
  *
- * NOTE: Currently, only the RGMII interface is implemented in this driver.
+ * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this
+ * driver.
  *
  * The interrupt line is asserted on link UP/DOWN events. The driver creates a
  * custom irqchip to handle this interrupt and demultiplex the events by reading
@@ -94,11 +95,13 @@
 #include <linux/bitops.h>
 #include <linux/interrupt.h>
 #include <linux/irqdomain.h>
+#include <linux/mii.h>
 #include <linux/mutex.h>
 #include <linux/of_irq.h>
 #include <linux/regmap.h>
 #include <linux/if_bridge.h>
 #include <linux/if_vlan.h>
+#include <linux/phylink.h>
 
 #include "realtek.h"
 #include "realtek-smi.h"
@@ -129,6 +132,7 @@
 
 /* Chip reset register */
 #define RTL8365MB_CHIP_RESET_REG	0x1322
+#define RTL8365MB_CHIP_RESET_DW8051_MASK	0x0010
 #define RTL8365MB_CHIP_RESET_SW_MASK	0x0002
 #define RTL8365MB_CHIP_RESET_HW_MASK	0x0001
 
@@ -238,6 +242,76 @@
 #define   RTL8365MB_EXT_RGMXF_RXDELAY_MASK	0x0007
 #define   RTL8365MB_EXT_RGMXF_TXDELAY_MASK	0x0008
 
+/* External interface line rate bypass register - one bit per external
+ * interface, indexed by the external port number with port 5 (the first
+ * external port) as the base. Other RTL8367 families index this register
+ * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only
+ * holds for the RTL8367C-style parts this driver supports.
+ */
+#define RTL8365MB_BYPASS_LINE_RATE_REG		0x03F7
+#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port)	BIT((_port) - 5)
+
+/* SerDes indirect access registers */
+#define RTL8365MB_SDS_INDACS_CMD_REG		0x6600
+#define   RTL8365MB_SDS_INDACS_CMD_BUSY_MASK	0x0100
+#define   RTL8365MB_SDS_INDACS_CMD_RUN_MASK	0x0080
+#define   RTL8365MB_SDS_INDACS_CMD_WR_MASK	0x0040
+#define RTL8365MB_SDS_INDACS_ADR_REG		0x6601
+#define RTL8365MB_SDS_INDACS_DATA_REG		0x6602
+
+/* SerDes miscellaneous configuration register */
+#define RTL8365MB_SDS_MISC_REG				0x1D11
+#define   RTL8365MB_SDS_MISC_SGMII_RXFC_MASK		0x4000
+#define   RTL8365MB_SDS_MISC_SGMII_TXFC_MASK		0x2000
+#define   RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK	0x0800
+#define   RTL8365MB_SDS_MISC_SGMII_FDUP_MASK		0x0400
+#define   RTL8365MB_SDS_MISC_SGMII_LINK_MASK		0x0200
+#define   RTL8365MB_SDS_MISC_SGMII_SPD_MASK		0x0180
+#define   RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK	0x0040
+
+/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR
+ * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the
+ * vendor-specific low bits from phase 1 to phase 2, which triggers a data path
+ * reset and PLL resync.
+ */
+#define RTL8365MB_SDS_REG_BMCR			0x0000
+#define   RTL8365MB_SDS_BMCR_DPRST_PHASE1	(BMCR_ANENABLE | BMCR_ISOLATE | 0x1)
+#define   RTL8365MB_SDS_BMCR_DPRST_PHASE2	(BMCR_ANENABLE | BMCR_ISOLATE | 0x3)
+#define RTL8365MB_SDS_REG_NWAY			0x0002
+#define   RTL8365MB_SDS_NWAY_EN_MASK		0x0200
+#define   RTL8365MB_SDS_NWAY_RESTART_MASK	0x0100
+#define RTL8365MB_SDS_REG_RESET			0x0003
+#define   RTL8365MB_SDS_RESET_DEASSERT		0x7106
+#define RTL8365MB_SDS_REG_LINK_STATUS		0x003d
+#define   RTL8365MB_SDS_LINK_STATUS_LINK_MASK	0x0010
+
+/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
+ * which is port 6.
+ */
+#define RTL8365MB_SDS_EXT_INTERFACE_ID		1
+#define RTL8365MB_SDS_EXT_INTERFACE_PORT	6
+
+/* Line rate bypass bit for the SerDes external interface */
+#define RTL8365MB_SDS_BYPASS_LINE_RATE_MASK \
+	RTL8365MB_BYPASS_LINE_RATE_MASK(RTL8365MB_SDS_EXT_INTERFACE_PORT)
+
+/* SerDes tuning parameter variant selector. The vendor driver picks between
+ * two sets of SerDes tuning parameters based on this chip option. Reading it
+ * requires first arming the read by writing a magic key to the arm register,
+ * then disarming it afterwards.
+ */
+#define RTL8365MB_SDS_OPTION_ARM_REG		0x13C0
+#define   RTL8365MB_SDS_OPTION_ARM_KEY		0x0249
+#define RTL8365MB_SDS_OPTION_REG		0x13C1
+
+/* Embedded DW8051 microcontroller control registers. The microcontroller
+ * can run firmware to manage the SerDes link, but this driver keeps it in
+ * reset and disabled: phylink already performs the link management that
+ * the firmware would otherwise do.
+ */
+#define RTL8365MB_MISC_CFG0_REG			0x130C
+#define   RTL8365MB_MISC_CFG0_DW8051_EN_MASK	0x0020
+
 /* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */
 #define RTL8365MB_PORT_SPEED_10M	0
 #define RTL8365MB_PORT_SPEED_100M	1
@@ -551,6 +625,18 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = {
 	{ 0x1D32, 0x0002 },
 };
 
+/* SGMII SerDes tuning parameters, lifted from the vendor driver sources. The
+ * vendor driver keeps two variants of this table and selects between them
+ * based on the chip option register; these are the values for a non-zero
+ * option, which is what RTL8367S parts seen so far report. See
+ * rtl8365mb_sds_probe_option().
+ */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = {
+	{ 0x0480, 0x04D7 }, { 0x0481, 0xF994 }, { 0x0482, 0x2420 },
+	{ 0x0483, 0x6960 }, { 0x0484, 0x9728 }, { 0x0423, 0x9D85 },
+	{ 0x0424, 0xD810 }, { 0x002E, 0x83F2 },
+};
+
 enum rtl8365mb_phy_interface_mode {
 	RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,
 	RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),
@@ -730,6 +816,9 @@ struct rtl8365mb_port {
  * @cpu: CPU tagging and CPU port configuration for this chip
  * @mib_lock: prevent concurrent reads of MIB counters
  * @ports: per-port data
+ * @pcs: PCS for the SerDes external interface
+ * @sds_supported: SerDes tuning parameters match the chip option, so the
+ *                 SerDes interface modes can be advertised
  *
  * Private data for this driver.
  */
@@ -740,8 +829,12 @@ struct rtl8365mb {
 	struct rtl8365mb_cpu cpu;
 	struct mutex mib_lock;
 	struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS];
+	struct phylink_pcs pcs;
+	bool sds_supported;
 };
 
+#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
+
 static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
 {
 	u32 val;
@@ -1042,6 +1135,334 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
 	return 0;
 }
 
+static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)
+{
+	int ret;
+
+	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
+	if (ret)
+		return ret;
+
+	/* The SerDes indirect access engine completes the command within the
+	 * register write transaction, so there is no need to wait or poll for
+	 * completion before the next access, matching the vendor driver.
+	 */
+	return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+			    RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+			    RTL8365MB_SDS_INDACS_CMD_WR_MASK);
+}
+
+static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
+{
+	u32 val;
+	int ret;
+
+	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+			   RTL8365MB_SDS_INDACS_CMD_RUN_MASK);
+	if (ret)
+		return ret;
+
+	/* Wait for the indirect read to complete: the engine clears the BUSY
+	 * bit once the data register holds the result.
+	 */
+	ret = regmap_read_poll_timeout(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+				       val,
+				       !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK),
+				       10, 1000);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val);
+	if (ret)
+		return ret;
+
+	*data = val;
+
+	return 0;
+}
+
+/* The vendor driver selects between two sets of SerDes tuning parameters based
+ * on the chip option register. Only the variant for a non-zero option has been
+ * tested on real hardware - the RTL8367S parts seen so far all report 1. The
+ * variant for option 0 uses different tuning values that cannot be verified,
+ * so probe the option once at setup and only advertise the SerDes interface
+ * modes when the tuning parameters are known to match, so that an unsupported
+ * variant fails at phylink validation time rather than when configuring the
+ * link.
+ */
+static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
+{
+	struct rtl8365mb *mb = priv->chip_data;
+	const struct rtl8365mb_extint *extint;
+	u32 option;
+	int ret;
+	int i;
+
+	/* Nothing to probe if no external interface is wired to the SerDes */
+	for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) {
+		extint = &mb->chip_info->extints[i];
+
+		if (extint->supported_interfaces &
+		    (RTL8365MB_PHY_INTERFACE_MODE_SGMII |
+		     RTL8365MB_PHY_INTERFACE_MODE_HSGMII))
+			break;
+	}
+	if (i == RTL8365MB_MAX_NUM_EXTINTS)
+		return 0;
+
+	ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG,
+			   RTL8365MB_SDS_OPTION_ARM_KEY);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(priv->map, RTL8365MB_SDS_OPTION_REG, &option);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, 0);
+	if (ret)
+		return ret;
+
+	if (option == 0) {
+		dev_warn(priv->dev,
+			 "unsupported SerDes tuning variant (chip option 0), disabling SerDes interface modes\n");
+		return 0;
+	}
+
+	mb->sds_supported = true;
+
+	return 0;
+}
+
+static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+				phy_interface_t interface,
+				const unsigned long *advertising,
+				bool permit_pause_to_mac)
+{
+	const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
+	struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+	struct realtek_priv *priv;
+	u16 val;
+	int ret;
+	int i;
+
+	priv = mb->priv;
+
+	/* Hold the embedded DW8051 microcontroller in reset and keep it
+	 * disabled. The vendor driver loads firmware into it to manage the
+	 * SerDes link, but the firmware only duplicates work that phylink
+	 * already does: it polls the port status and forces the external
+	 * interface configuration in the very registers this driver manages.
+	 * Letting it run would race with phylink.
+	 */
+	ret = regmap_update_bits(priv->map, RTL8365MB_CHIP_RESET_REG,
+				 RTL8365MB_CHIP_RESET_DW8051_MASK,
+				 RTL8365MB_CHIP_RESET_DW8051_MASK);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(priv->map, RTL8365MB_MISC_CFG0_REG,
+				 RTL8365MB_MISC_CFG0_DW8051_EN_MASK, 0);
+	if (ret)
+		return ret;
+
+	/* The vendor driver clears the line rate bypass for all interface
+	 * modes except TMII.
+	 */
+	ret = regmap_update_bits(priv->map, RTL8365MB_BYPASS_LINE_RATE_REG,
+				 RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, 0);
+	if (ret)
+		return ret;
+
+	/* Tune the SerDes with vendor-prescribed parameters */
+	for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) {
+		ret = rtl8365mb_sds_write(priv,
+					  rtl8365mb_sds_jam_sgmii[i].reg,
+					  rtl8365mb_sds_jam_sgmii[i].val);
+		if (ret)
+			return ret;
+	}
+
+	/* Mux the SerDes to MAC8 in SGMII mode */
+	ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+				 RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
+					 RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,
+				 RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK);
+	if (ret)
+		return ret;
+
+	val = RTL8365MB_EXT_PORT_MODE_SGMII
+	      << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
+	ret = regmap_update_bits(priv->map,
+				 RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
+				 RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),
+				 val);
+	if (ret)
+		return ret;
+
+	/* Take the SerDes out of reset. The vendor driver does this only
+	 * after the SerDes mux and the interface mode are configured.
+	 */
+	ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET,
+				  RTL8365MB_SDS_RESET_DEASSERT);
+	if (ret)
+		return ret;
+
+	/* Reset the SerDes data path and resync its PLL, mirroring what the
+	 * vendor firmware does right after deasserting the SerDes reset.
+	 * This flushes the FIFOs and ensures a clean state for the link,
+	 * preventing silent drops and CRC errors.
+	 */
+	ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+				  RTL8365MB_SDS_BMCR_DPRST_PHASE1);
+	if (ret)
+		return ret;
+
+	ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+				  RTL8365MB_SDS_BMCR_DPRST_PHASE2);
+	if (ret)
+		return ret;
+
+	/* Keep SGMII in-band autonegotiation disabled: the link parameters are
+	 * forced from rtl8365mb_pcs_link_up() instead.
+	 */
+	ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val);
+	if (ret)
+		return ret;
+
+	val &= ~RTL8365MB_SDS_NWAY_EN_MASK;
+	val |= RTL8365MB_SDS_NWAY_RESTART_MASK;
+
+	return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val);
+}
+
+static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
+{
+	return interface == PHY_INTERFACE_MODE_SGMII;
+}
+
+static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs,
+					      phy_interface_t interface)
+{
+	/* In-band autonegotiation is not implemented; the link is always
+	 * forced. Report that to phylink so that it never selects an
+	 * in-band-enabled negotiation mode for this PCS.
+	 */
+	return LINK_INBAND_DISABLE;
+}
+
+static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
+				    unsigned int neg_mode,
+				    struct phylink_link_state *state)
+{
+	struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+	struct realtek_priv *priv = mb->priv;
+	u16 status;
+	u32 val;
+	int ret;
+
+	/* In-band autonegotiation is not implemented, so the link parameters are
+	 * forced from rtl8365mb_pcs_link_up(). The real link state must still be
+	 * read from the SerDes itself: the embedded DW8051 microcontroller that
+	 * the vendor firmware uses to poll the SerDes is kept disabled (see
+	 * rtl8365mb_pcs_config()), so the link status register can be read
+	 * directly through the SDS_INDACS window without racing the auto-poll.
+	 */
+	ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status);
+	if (ret) {
+		state->link = false;
+		return;
+	}
+
+	state->link = !!(status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK);
+	state->an_complete = state->link;
+	if (!state->link)
+		return;
+
+	/* The speed and duplex are forced; read them back from the values
+	 * programmed into the SerDes MISC register.
+	 */
+	ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val);
+	if (ret) {
+		state->link = false;
+		return;
+	}
+
+	state->duplex = (val & RTL8365MB_SDS_MISC_SGMII_FDUP_MASK) ?
+				DUPLEX_FULL : DUPLEX_HALF;
+
+	switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) {
+	case RTL8365MB_PORT_SPEED_1000M:
+		state->speed = SPEED_1000;
+		break;
+	case RTL8365MB_PORT_SPEED_100M:
+		state->speed = SPEED_100;
+		break;
+	case RTL8365MB_PORT_SPEED_10M:
+		state->speed = SPEED_10;
+		break;
+	}
+}
+
+static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
+				  unsigned int neg_mode,
+				  phy_interface_t interface, int speed,
+				  int duplex)
+{
+	struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+	struct realtek_priv *priv = mb->priv;
+	u32 mask = RTL8365MB_SDS_MISC_SGMII_FDUP_MASK |
+		   RTL8365MB_SDS_MISC_SGMII_LINK_MASK |
+		   RTL8365MB_SDS_MISC_SGMII_SPD_MASK;
+	u32 val = RTL8365MB_SDS_MISC_SGMII_LINK_MASK;
+	u32 r_speed;
+	int ret;
+
+	if (speed == SPEED_1000) {
+		r_speed = RTL8365MB_PORT_SPEED_1000M;
+	} else if (speed == SPEED_100) {
+		r_speed = RTL8365MB_PORT_SPEED_100M;
+	} else if (speed == SPEED_10) {
+		r_speed = RTL8365MB_PORT_SPEED_10M;
+	} else {
+		dev_err(priv->dev, "unsupported SerDes speed %s\n",
+			phy_speed_to_str(speed));
+		return;
+	}
+
+	val |= FIELD_PREP(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, r_speed);
+
+	if (duplex == DUPLEX_FULL)
+		val |= RTL8365MB_SDS_MISC_SGMII_FDUP_MASK;
+
+	/* pcs_link_up() carries no pause information, so the SerDes flow
+	 * control bits are programmed together with the MAC external interface
+	 * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause
+	 * modes are known.
+	 */
+	ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val);
+	if (ret) {
+		dev_err(priv->dev, "failed to force SerDes link: %pe\n",
+			ERR_PTR(ret));
+		return;
+	}
+}
+
+static const struct phylink_pcs_ops rtl8365mb_pcs_ops = {
+	.pcs_inband_caps = rtl8365mb_pcs_inband_caps,
+	.pcs_config = rtl8365mb_pcs_config,
+	.pcs_get_state = rtl8365mb_pcs_get_state,
+	.pcs_link_up = rtl8365mb_pcs_link_up,
+};
+
 static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
 					  bool link, int speed, int duplex,
 					  bool tx_pause, bool rx_pause)
@@ -1118,6 +1539,8 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
 {
 	const struct rtl8365mb_extint *extint =
 		rtl8365mb_get_port_extint(ds->priv, port);
+	struct realtek_priv *priv = ds->priv;
+	struct rtl8365mb *mb = priv->chip_data;
 
 	config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
 				   MAC_10 | MAC_100 | MAC_1000FD;
@@ -1141,6 +1564,25 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
 
 	if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII)
 		phy_interface_set_rgmii(config->supported_interfaces);
+
+	if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII &&
+	    mb->sds_supported)
+		__set_bit(PHY_INTERFACE_MODE_SGMII,
+			  config->supported_interfaces);
+}
+
+static struct phylink_pcs *
+rtl8365mb_phylink_mac_select_pcs(struct phylink_config *config,
+				 phy_interface_t interface)
+{
+	struct dsa_port *dp = dsa_phylink_to_port(config);
+	struct realtek_priv *priv = dp->ds->priv;
+	struct rtl8365mb *mb = priv->chip_data;
+
+	if (rtl8365mb_interface_is_serdes(interface))
+		return &mb->pcs;
+
+	return NULL;
 }
 
 static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
@@ -1168,6 +1610,12 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
 		return;
 	}
 
+	/* SGMII is handled by the SerDes PCS, configured through the
+	 * phylink_pcs ops, so there is nothing to do here for it.
+	 */
+	if (rtl8365mb_interface_is_serdes(state->interface))
+		return;
+
 	/* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also
 	 * supports
 	 */
@@ -1188,7 +1636,13 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
 	p = &mb->ports[port];
 	cancel_delayed_work_sync(&p->mib_work);
 
-	if (phy_interface_mode_is_rgmii(interface)) {
+	/* phylink has no pcs_link_down callback, so on the SerDes path only the
+	 * MAC external interface force is reset here. Clearing the MAC force is
+	 * enough to bring the link down; the SerDes keeps presenting its last
+	 * forced state until the next pcs_link_up() reprograms it.
+	 */
+	if (phy_interface_mode_is_rgmii(interface) ||
+	    rtl8365mb_interface_is_serdes(interface)) {
 		ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0,
 						     false, false);
 		if (ret)
@@ -1218,14 +1672,51 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
 	p = &mb->ports[port];
 	schedule_delayed_work(&p->mib_work, 0);
 
-	if (phy_interface_mode_is_rgmii(interface)) {
+	/* The SerDes forced link state is programmed by the PCS in
+	 * rtl8365mb_pcs_link_up(); here only the MAC external interface force
+	 * is configured, for both RGMII and SerDes.
+	 */
+	if (phy_interface_mode_is_rgmii(interface) ||
+	    rtl8365mb_interface_is_serdes(interface)) {
 		ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed,
 						     duplex, tx_pause,
 						     rx_pause);
-		if (ret)
+		if (ret) {
 			dev_err(priv->dev,
 				"failed to force mode on port %d: %pe\n", port,
 				ERR_PTR(ret));
+			return;
+		}
+
+		/* The SerDes has its own pause enables; program them from
+		 * the resolved pause modes, as the vendor driver does when
+		 * forcing the link on a SerDes external interface. These
+		 * bits, not the MAC force pause bits, gate pause on the
+		 * SerDes external interface: flow control testing shows
+		 * that pause frames are only emitted with the SerDes TXFC
+		 * bit set, while the MAC force pause bits alone have no
+		 * effect on this port. This is done here rather than in
+		 * rtl8365mb_pcs_link_up() because pcs_link_up() carries no
+		 * pause information.
+		 */
+		if (rtl8365mb_interface_is_serdes(interface)) {
+			u32 val = 0;
+
+			if (tx_pause)
+				val |= RTL8365MB_SDS_MISC_SGMII_TXFC_MASK;
+			if (rx_pause)
+				val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK;
+
+			ret = regmap_update_bits(priv->map,
+						 RTL8365MB_SDS_MISC_REG,
+						 RTL8365MB_SDS_MISC_SGMII_TXFC_MASK |
+							 RTL8365MB_SDS_MISC_SGMII_RXFC_MASK,
+						 val);
+			if (ret)
+				dev_err(priv->dev,
+					"failed to force SerDes pause modes on port %d: %pe\n",
+					port, ERR_PTR(ret));
+		}
 
 		return;
 	}
@@ -2419,6 +2910,14 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
 	mb = priv->chip_data;
 	cpu = &mb->cpu;
 
+	mb->pcs.ops = &rtl8365mb_pcs_ops;
+
+	/* The SerDes has no link interrupt wired up, so phylink must poll the
+	 * PCS for link changes when it tracks the link through pcs_get_state()
+	 * (in-band mode with autonegotiation disabled).
+	 */
+	mb->pcs.poll = true;
+
 	ret = rtl8365mb_reset_chip(priv);
 	if (ret) {
 		dev_err(priv->dev, "failed to reset chip: %pe\n",
@@ -2426,6 +2925,13 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
 		goto out_error;
 	}
 
+	ret = rtl8365mb_sds_probe_option(priv);
+	if (ret) {
+		dev_err(priv->dev, "failed to probe SerDes chip option: %pe\n",
+			ERR_PTR(ret));
+		goto out_error;
+	}
+
 	/* Configure switch to vendor-defined initial state */
 	ret = rtl8365mb_switch_init(priv);
 	if (ret) {
@@ -2658,6 +3164,7 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
 }
 
 static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = {
+	.mac_select_pcs = rtl8365mb_phylink_mac_select_pcs,
 	.mac_config = rtl8365mb_phylink_mac_config,
 	.mac_link_down = rtl8365mb_phylink_mac_link_down,
 	.mac_link_up = rtl8365mb_phylink_mac_link_up,

-- 
2.55.0



^ permalink raw reply related

* [PATCH net-next v6 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
From: Johan Alvarado via B4 Relay @ 2026-07-12  4:31 UTC (permalink / raw)
  To: Linus Walleij, Alvin Šipraga, Andrew Lunn, Vladimir Oltean,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Russell King
  Cc: Mieczyslaw Nalewaj, Maxime Chevallier, Luiz Angelo Daros de Luca,
	netdev, linux-kernel, Johan Alvarado
In-Reply-To: <20260711-rtl8367s-sgmii-v6-0-88f7944ddca7@c127.dev>

From: Johan Alvarado <contact@c127.dev>

In addition to SGMII, the RTL8367S SerDes also supports HSGMII, which
carries 2.5 Gbps with the same signaling as SGMII at 2.5x clock rate.
The chip info table already declares HSGMII as a supported interface
mode for external interface 1.

Extend the SerDes PCS to handle HSGMII, which phylink represents as
2500base-x:

 - Select the HSGMII SerDes tuning parameters and external interface
   mode, and mux the SerDes to MAC8 in HSGMII mode, from pcs_config()
   according to the interface. The parameters are again lifted from the
   GPL-licensed Realtek rtl8367c vendor driver, and again only cover
   the tuning variant for a non-zero chip option, so the mode is gated
   on the option probed at setup.

 - Advertise 2500base-x and MAC_2500FD on ports whose external
   interface supports HSGMII.

 - Accept SPEED_2500 in the forced link configuration. The MAC speed
   field has no 2.5 Gbps value: the rate is determined by the HSGMII
   SerDes configuration, and the vendor driver programs the 1 Gbps
   value here, so do the same.

 - Raise the port 6 ingress and egress rate limiters to their maximum
   at setup time, as the vendor switch init does unconditionally for
   the whole chip family. The chip resets them to 0x1FFFF (~1.048 Gbps
   in units of 8 Kbps), which caps the aggregate HSGMII throughput at
   roughly 1 Gbps. The vendor documentation describes the reset
   default as disabling the limiter, but the cap is real: on an
   RTL8367S-based Mercusys MR85X running an OpenWrt backport of this
   series, several clients on 1 Gbps user ports were limited to about
   1.02 Gbps combined across the HSGMII CPU port until these limiters
   were raised, after which throughput reached about 2 Gbps [1]. The
   related HSGMII scheduler line rate (LINE_RATE_HSG_H) is already set
   to its maximum by the common init jam table.

Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to
the SoC over HSGMII.

Link: https://github.com/openwrt/openwrt/pull/19445#issuecomment-4505613294 [1]
Suggested-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
 drivers/net/dsa/realtek/rtl8365mb_main.c | 134 +++++++++++++++++++++++++++----
 1 file changed, 118 insertions(+), 16 deletions(-)

diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index ea03c42d0f1a..d1ba0cc9426f 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -40,8 +40,8 @@
  * driver has only been tested with a fixed-link, but in principle it should not
  * matter.
  *
- * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this
- * driver.
+ * NOTE: Currently, only the RGMII, SGMII and HSGMII interfaces are implemented
+ * in this driver.
  *
  * The interrupt line is asserted on link UP/DOWN events. The driver creates a
  * custom irqchip to handle this interrupt and demultiplex the events by reading
@@ -251,6 +251,18 @@
 #define RTL8365MB_BYPASS_LINE_RATE_REG		0x03F7
 #define RTL8365MB_BYPASS_LINE_RATE_MASK(_port)	BIT((_port) - 5)
 
+/* Port 6 ingress and egress rate limiter registers. Each limit is a 19-bit
+ * value in units of 8 Kbps, split across a 16-bit LSB register (CTRL0) and a
+ * 3-bit MSB field (CTRL1). The chip resets them to 0x1FFFF; see
+ * rtl8365mb_sds_raise_rate_limits().
+ */
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG	0x00CF
+#define RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG	0x00D0
+#define   RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK	0x0007
+#define RTL8365MB_PORT6_EGRESSBW_CTRL0_REG		0x0398
+#define RTL8365MB_PORT6_EGRESSBW_CTRL1_REG		0x0399
+#define   RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK		0x0007
+
 /* SerDes indirect access registers */
 #define RTL8365MB_SDS_INDACS_CMD_REG		0x6600
 #define   RTL8365MB_SDS_INDACS_CMD_BUSY_MASK	0x0100
@@ -637,6 +649,18 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = {
 	{ 0x0424, 0xD810 }, { 0x002E, 0x83F2 },
 };
 
+/* HSGMII SerDes tuning parameters, lifted from the vendor driver sources. As
+ * with the SGMII table, the vendor driver keeps several variants and selects
+ * one based on the chip option register; these are the values for a non-zero
+ * option, which is what RTL8367S parts seen so far report. See
+ * rtl8365mb_sds_probe_option().
+ */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = {
+	{ 0x0500, 0x82F0 }, { 0x0501, 0xF195 }, { 0x0502, 0x31A2 },
+	{ 0x0503, 0x7960 }, { 0x0504, 0x9728 }, { 0x0423, 0x9D85 },
+	{ 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 },
+};
+
 enum rtl8365mb_phy_interface_mode {
 	RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,
 	RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),
@@ -1242,20 +1266,70 @@ static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
 	return 0;
 }
 
+/* The vendor driver raises the port 6 ingress and egress rate limiters to
+ * their maximum in its switch init, unconditionally for the whole chip
+ * family. The chip reset in rtl8365mb_setup() puts them back to their reset
+ * default of 0x1FFFF, a ~1.048 Gbps limit which caps the aggregate
+ * throughput of an HSGMII CPU port at roughly 1 Gbps. The vendor
+ * documentation describes the reset default as disabling the limiter, but
+ * the cap has been observed on hardware. Raise them likewise, to 0x7FFFF
+ * (~4.19 Gbps, above the HSGMII line rate). The related HSGMII scheduler
+ * line rate register (LINE_RATE_HSG_H, 0x03FA) is already set to its
+ * maximum by the common init jam table.
+ */
+static int rtl8365mb_sds_raise_rate_limits(struct realtek_priv *priv)
+{
+	int ret;
+
+	ret = regmap_write(priv->map, RTL8365MB_INGRESSBW_PORT6_RATE_CTRL0_REG,
+			   0xFFFF);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(priv->map,
+				 RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_REG,
+				 RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK,
+				 RTL8365MB_INGRESSBW_PORT6_RATE_CTRL1_MASK);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL0_REG,
+			   0xFFFF);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(priv->map, RTL8365MB_PORT6_EGRESSBW_CTRL1_REG,
+				  RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK,
+				  RTL8365MB_PORT6_EGRESSBW_CTRL1_MASK);
+}
+
 static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 				phy_interface_t interface,
 				const unsigned long *advertising,
 				bool permit_pause_to_mac)
 {
+	const struct rtl8365mb_jam_tbl_entry *sds_jam;
 	const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
 	struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
 	struct realtek_priv *priv;
+	size_t sds_jam_size;
+	u32 mode;
 	u16 val;
 	int ret;
 	int i;
 
 	priv = mb->priv;
 
+	if (interface == PHY_INTERFACE_MODE_2500BASEX) {
+		sds_jam = rtl8365mb_sds_jam_hsgmii;
+		sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);
+		mode = RTL8365MB_EXT_PORT_MODE_HSGMII;
+	} else {
+		sds_jam = rtl8365mb_sds_jam_sgmii;
+		sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);
+		mode = RTL8365MB_EXT_PORT_MODE_SGMII;
+	}
+
 	/* Hold the embedded DW8051 microcontroller in reset and keep it
 	 * disabled. The vendor driver loads firmware into it to manage the
 	 * SerDes link, but the firmware only duplicates work that phylink
@@ -1283,24 +1357,24 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 		return ret;
 
 	/* Tune the SerDes with vendor-prescribed parameters */
-	for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) {
-		ret = rtl8365mb_sds_write(priv,
-					  rtl8365mb_sds_jam_sgmii[i].reg,
-					  rtl8365mb_sds_jam_sgmii[i].val);
+	for (i = 0; i < sds_jam_size; i++) {
+		ret = rtl8365mb_sds_write(priv, sds_jam[i].reg,
+					  sds_jam[i].val);
 		if (ret)
 			return ret;
 	}
 
-	/* Mux the SerDes to MAC8 in SGMII mode */
+	/* Mux the SerDes to MAC8 in the requested mode */
 	ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
 				 RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
 					 RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,
-				 RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK);
+				 mode == RTL8365MB_EXT_PORT_MODE_SGMII ?
+					 RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
+					 RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK);
 	if (ret)
 		return ret;
 
-	val = RTL8365MB_EXT_PORT_MODE_SGMII
-	      << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
+	val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
 	ret = regmap_update_bits(priv->map,
 				 RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
 				 RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),
@@ -1346,7 +1420,8 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
 
 static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
 {
-	return interface == PHY_INTERFACE_MODE_SGMII;
+	return interface == PHY_INTERFACE_MODE_SGMII ||
+	       interface == PHY_INTERFACE_MODE_2500BASEX;
 }
 
 static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs,
@@ -1401,7 +1476,9 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
 
 	switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) {
 	case RTL8365MB_PORT_SPEED_1000M:
-		state->speed = SPEED_1000;
+		state->speed =
+			state->interface == PHY_INTERFACE_MODE_2500BASEX ?
+				SPEED_2500 : SPEED_1000;
 		break;
 	case RTL8365MB_PORT_SPEED_100M:
 		state->speed = SPEED_100;
@@ -1426,7 +1503,11 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
 	u32 r_speed;
 	int ret;
 
-	if (speed == SPEED_1000) {
+	/* The speed field has no value for 2.5 Gbps: the rate is determined by
+	 * the HSGMII SerDes configuration, and the vendor driver programs the
+	 * 1 Gbps value here.
+	 */
+	if (speed == SPEED_2500 || speed == SPEED_1000) {
 		r_speed = RTL8365MB_PORT_SPEED_1000M;
 	} else if (speed == SPEED_100) {
 		r_speed = RTL8365MB_PORT_SPEED_100M;
@@ -1486,7 +1567,11 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
 		r_rx_pause = rx_pause ? 1 : 0;
 		r_tx_pause = tx_pause ? 1 : 0;
 
-		if (speed == SPEED_1000) {
+		/* The speed field has no value for 2.5 Gbps: the rate is
+		 * determined by the HSGMII SerDes configuration, and the
+		 * vendor driver programs the 1 Gbps value here.
+		 */
+		if (speed == SPEED_2500 || speed == SPEED_1000) {
 			r_speed = RTL8365MB_PORT_SPEED_1000M;
 		} else if (speed == SPEED_100) {
 			r_speed = RTL8365MB_PORT_SPEED_100M;
@@ -1569,6 +1654,13 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
 	    mb->sds_supported)
 		__set_bit(PHY_INTERFACE_MODE_SGMII,
 			  config->supported_interfaces);
+
+	if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII &&
+	    mb->sds_supported) {
+		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
+			  config->supported_interfaces);
+		config->mac_capabilities |= MAC_2500FD;
+	}
 }
 
 static struct phylink_pcs *
@@ -1610,8 +1702,8 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
 		return;
 	}
 
-	/* SGMII is handled by the SerDes PCS, configured through the
-	 * phylink_pcs ops, so there is nothing to do here for it.
+	/* SGMII and 2500base-x are handled by the SerDes PCS, configured
+	 * through the phylink_pcs ops, so nothing to do here for them.
 	 */
 	if (rtl8365mb_interface_is_serdes(state->interface))
 		return;
@@ -2940,6 +3032,16 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
 		goto out_error;
 	}
 
+	if (mb->sds_supported) {
+		ret = rtl8365mb_sds_raise_rate_limits(priv);
+		if (ret) {
+			dev_err(priv->dev,
+				"failed to raise port rate limits: %pe\n",
+				ERR_PTR(ret));
+			goto out_error;
+		}
+	}
+
 	/* Set up cascading IRQs */
 	ret = rtl8365mb_irq_setup(priv);
 	if (ret == -EPROBE_DEFER)

-- 
2.55.0



^ permalink raw reply related

* [PATCH net-next v6 0/2] net: dsa: realtek: rtl8365mb: add SGMII/HSGMII support for RTL8367S
From: Johan Alvarado via B4 Relay @ 2026-07-12  4:31 UTC (permalink / raw)
  To: Linus Walleij, Alvin Šipraga, Andrew Lunn, Vladimir Oltean,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Russell King
  Cc: Mieczyslaw Nalewaj, Maxime Chevallier, Luiz Angelo Daros de Luca,
	netdev, linux-kernel, Johan Alvarado

The RTL8367S is a 5+2 port switch from the same family as the
RTL8365MB-VC already supported by this driver. Its chip info table
entry declares SGMII and HSGMII on external interface 1, but the
driver so far only implements RGMII, leaving boards that wire the
switch to the CPU over the SerDes without a working CPU port.

This series implements both modes. The configuration sequence and the
SerDes tuning parameters are derived from the GPL-licensed Realtek
rtl8367c vendor driver, as distributed in the Mercusys MR80X GPL code
drop, and cross-checked against the real register sequence captured at
runtime by chainloading a custom U-Boot ahead of the stock firmware
and logging the live SerDes accesses on hardware.

The vendor driver brings up the SerDes by loading firmware into the
switch's embedded DW8051 microcontroller. Analysis of that firmware
(by Luiz Angelo Daros de Luca) showed it only performs a SerDes
data-path reset right after the SerDes reset is deasserted, and then
runs a link-polling loop that writes the external interface force
registers -- duplicating, and racing with, the link management phylink
already performs. This series therefore keeps the DW8051 disabled and
performs the one necessary action (the data-path reset via the SerDes
BMCR register) directly in the driver, avoiding both the race and a
dependency on a redistributable firmware blob.

The SerDes is modelled as a phylink PCS: mac_select_pcs() hands the
SerDes interfaces to a phylink_pcs whose pcs_config()/pcs_link_up()
ops own the SerDes register sequence, keeping it out of the MAC
operations. In-band autonegotiation is not implemented; the link is
forced (fixed-link or conventional PHY), as for RGMII, and the PCS
reports this to phylink through pcs_inband_caps().

Patch 1 adds the SerDes indirect access helpers, the PCS and SGMII
(1 Gbps) support. Patch 2 extends the PCS to HSGMII (2.5 Gbps), which
phylink represents as 2500base-x.

Tested on a Mercusys MR80X v2.20 (RTL8367S wired to the SoC over the
SerDes), in both SGMII and HSGMII modes with a fixed-link device tree
description: link bring-up verified across cold boots, warm reboots,
module reloads and link down/up cycles, with sustained traffic and no
CRC/symbol errors. The SerDes pause enables were verified by driving
congestion toward a 100M user port and observing pause frame emission
on the CPU port (dot3OutPauseFrames) toggle with the SDS_MISC
TXFC/RXFC bits. The port 6 rate limiters were verified to be live by
lowering them to 100 Mbps at runtime and observing iperf3 throughput
across the CPU port clamp accordingly in each direction, recovering
once the maximum was restored. The HSGMII link is confirmed running
at 2.5G at the register level (SoC uniphy mode and gmac clocks);
per-direction throughput could not be pushed past ~1 Gbps on this
board because the SoC side is driven by the IPQ5018 SSDK and the
user-facing PHY is 1G, so full 2.5G line-rate throughput remains
unverified on my hardware. Independently, >1 Gbps aggregate HSGMII
throughput (~2 Gbps with multiple clients) has been observed on an
RTL8367S-based Mercusys MR85X running an OpenWrt backport of this
series with the rate limiters raised (see patch 2).

The RTL8367SB also declares SGMII and HSGMII in its chip info entry
and therefore gains both modes as well. The vendor driver drives the
two chips through the same code path, keyed only on the chip option
register (both report chip id 0x6367), so this is expected to work
there too, but I have no RTL8367SB hardware to confirm it.

Changes across versions:

v6:
  - No code changes; resend of v5. The SMTP relay used for v5 rewrote
    the Message-IDs and dropped the References headers, so the patches
    did not thread under the cover letter on lore and could not be
    processed as a series. Reported by Mieczyslaw Nalewaj. This
    version is sent through the kernel.org b4 web endpoint instead.
v5: https://lore.kernel.org/netdev/0100019f488ec83f-cd82d418-999a-40de-b58b-135b4b2aee51-000000@email.amazonses.com/
  - Raise the port 6 ingress and egress rate limiters to their maximum
    at setup time, as the vendor switch init does. The chip resets them
    to 0x1FFFF (~1.048 Gbps in units of 8 Kbps), which caps the
    aggregate HSGMII throughput at roughly 1 Gbps; the cap was
    confirmed on an RTL8367S-based Mercusys MR85X, where raising the
    limiters took multi-client throughput across the HSGMII CPU port
    from ~1.02 Gbps combined to ~2 Gbps (link in patch 2). The related
    HSGMII scheduler line rate (LINE_RATE_HSG_H) is already set to its
    maximum by the common init jam table. Pointed out by Mieczyslaw
    Nalewaj.
  - Drop the PHYLINK_PCS_NEG_INBAND_ENABLED check from pcs_config().
    With pcs_inband_caps() returning LINK_INBAND_DISABLE, phylink can
    never select an in-band-enabled negotiation mode for this PCS, so
    the check was unreachable. Pointed out by Maxime Chevallier.
  - Document the SerDes SDS_MISC pause bits as the functional pause
    enables of the SerDes external interface, after testing flow
    control on hardware: pause frames are only emitted with the SerDes
    TXFC bit set, while the MAC force pause bits alone have no effect
    on this port. Comment-only change, prompted by a question from
    Maxime Chevallier.
v4: https://lore.kernel.org/netdev/20260702204648.276112-1-contact@c127.dev/
  - Drop the chip model name from the driver's NOTE comment; which
    interfaces a given chip exposes is described by its chip_info entry,
    not the file header. Pointed out by Luiz Angelo Daros de Luca.
  - Build the SerDes BMCR data-path-reset values from the standard
    BMCR_ANENABLE | BMCR_ISOLATE bits instead of a bare magic number, so
    the meaning is in the code rather than only in a comment. Pointed
    out by Luiz Angelo Daros de Luca.
  - Use a temporary for the DIGITAL_INTERFACE_SELECT value instead of
    wrapping the expression across the regmap_update_bits() arguments.
    Pointed out by Luiz Angelo Daros de Luca.
  - Reject the untested SerDes tuning variant. The vendor driver keeps
    two sets of SerDes tuning parameters and selects between them based
    on the chip option register (0x13C1); the tables in this series are
    the variant for a non-zero option, which is what the RTL8367S parts
    seen so far report. The option is probed once at setup and the
    SerDes interface modes are only advertised to phylink when the
    tuning parameters match, so an unsupported variant fails at phylink
    validation time instead of when configuring the link. Thanks to
    Luiz Angelo Daros de Luca for pointing out the conditional.
  - Express the external interface line rate bypass bit through a
    parametric macro keyed on the port number (with port 5 as the base),
    instead of an open-coded BIT(interface id) that only matched by
    coincidence; other RTL8367 families index this register differently.
    Suggested by Luiz Angelo Daros de Luca.
  - Drop the arbitrary usleep_range() after each SerDes indirect access.
    SerDes writes are now fire-and-forget and reads poll the self-clearing
    BUSY bit with regmap_read_poll_timeout(), matching the vendor driver,
    which never sleeps. On the MR80X the BUSY bit is never even observed
    set: the access completes within the register transaction. Pointed out
    by Luiz Angelo Daros de Luca; poll approach suggested by Mieczyslaw
    Nalewaj.
  - Drop the always-zero SerDes index argument from the SerDes indirect
    access helpers, along with the INDACS command index field whose
    width was questioned during review; this chip has a single SerDes
    block reachable through this window, so the index served no purpose.
    Raised by Luiz Angelo Daros de Luca.
  - Stop hardcoding external interface 1 with an early -EOPNOTSUPP in
    the SerDes configuration path. The SerDes interface modes are now
    advertised in phylink_get_caps() from the chip_info
    supported_interfaces, and mac_select_pcs() returns the PCS only for
    those modes. Pointed out by Luiz Angelo Daros de Luca.
  - Keep the new register definitions as raw hex masks, matching the
    prevailing style of the file. A file-wide GENMASK/BIT conversion,
    raised by Luiz Angelo Daros de Luca during review, is left for a
    separate cleanup patch so this series stays focused on the feature.
  - Convert the SerDes path to a phylink_pcs, as suggested by Maxime
    Chevallier. The SGMII/HSGMII SerDes handling now lives in
    pcs_config()/pcs_get_state()/pcs_link_up() selected via
    mac_select_pcs(), instead of being driven from the MAC
    mac_config()/mac_link_up()/mac_link_down() operations. This
    separates the MAC and SerDes layers and makes future in-band
    autonegotiation an additive change. No functional change intended
    for the forced-link path; retested on the MR80X v2.20. In-band
    autonegotiation remains unimplemented and is left for a follow-up,
    once hardware is available to validate it.
  - Implement pcs_inband_caps(), returning LINK_INBAND_DISABLE so that
    phylink knows this PCS cannot do in-band autonegotiation and never
    selects an in-band-enabled negotiation mode for it. pcs_config()
    rejects PHYLINK_PCS_NEG_INBAND_ENABLED with -EOPNOTSUPP instead of
    the previous warn-and-force.
  - Program the SerDes pause controls in SDS_MISC from the resolved
    pause modes when forcing the MAC external interface, as the vendor
    driver does, instead of leaving whatever state the boot firmware
    left there. Done in mac_link_up() because pcs_link_up() carries no
    pause information.
  - Set the PCS poll flag: the SerDes has no link interrupt wired up,
    so phylink must poll pcs_get_state() when it tracks the link
    through the PCS (in-band mode with autonegotiation disabled).
  - Report link down from pcs_get_state() if reading back the forced
    speed/duplex fails, rather than reporting link up with a stale
    state.
  - Reword the misleading "disable in-band aneg" comment.
v3: https://lore.kernel.org/netdev/20260613232136.24246-1-contact@c127.dev/
  - Drop the DW8051 firmware loading entirely. Analysis of the vendor
    firmware showed it only duplicates the link management phylink
    already does; the one needed action (SerDes data-path reset via
    the BMCR register) is now performed directly in the driver, with
    the DW8051 kept disabled. This removes the dependency on the
    rtl8367s-sgmii.bin firmware blob, which could not be redistributed
    via linux-firmware (the GPL vendor source ships it as a byte array
    without the corresponding microcode source). Thanks to Luiz Angelo
    Daros de Luca for the firmware analysis.
v2: https://lore.kernel.org/netdev/0100019eb0b1822e-ffc5626c-1b9f-4c8a-8a1a-759a9e665f4f-000000@email.amazonses.com/
  - No code changes; resend because the SMTP provider used for v1
    corrupted the mails and patch 1/2 never reached the list.
v1: https://lore.kernel.org/netdev/aebccaad-eca3-4ea4-99dd-ae7edbc8981b@smtp-relay.sendinblue.com/

Signed-off-by: Johan Alvarado <contact@c127.dev>
---
Johan Alvarado (2):
      net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
      net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S

 drivers/net/dsa/realtek/rtl8365mb_main.c | 619 ++++++++++++++++++++++++++++++-
 1 file changed, 614 insertions(+), 5 deletions(-)
---
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
change-id: 20260711-rtl8367s-sgmii-59bf1b8eac55

Best regards,
--  
Johan Alvarado <contact@c127.dev>



^ permalink raw reply

* [PATCH net 2/2] selftests: ptp: add a regression test for the frequency adjustment overflow
From: Deep Shah @ 2026-07-12  4:09 UTC (permalink / raw)
  To: netdev, Richard Cochran, David S . Miller, Jakub Kicinski,
	Paolo Abeni, Eric Dumazet, Andrew Lunn
  Cc: linux-kernel, Shuah Khan, linux-kselftest, Deep Shah
In-Reply-To: <20260712040922.6403-1-deepshah146@gmail.com>

testptp's -f option stores the requested adjustment as an int ppb and
converts it to scaled ppm, so it cannot express the 64-bit scaled-ppm
values needed to overflow scaled_ppm_to_ppb() and bypass the max_adj
check enforced by ptp_clock_adjtime().

Add a small test that crafts struct timex.freq directly and verifies that
an overflowing frequency adjustment is rejected with -ERANGE.  The test
skips when no frequency-adjustable PTP device is available.

Signed-off-by: Deep Shah <deepshah146@gmail.com>
---
 tools/testing/selftests/ptp/Makefile          |  2 +-
 .../testing/selftests/ptp/ptp_freq_overflow.c | 74 +++++++++++++++++++
 2 files changed, 75 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/ptp/ptp_freq_overflow.c

diff --git a/tools/testing/selftests/ptp/Makefile b/tools/testing/selftests/ptp/Makefile
index 8f57f88ecadd..dd7376cc9bf5 100644
--- a/tools/testing/selftests/ptp/Makefile
+++ b/tools/testing/selftests/ptp/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 CFLAGS += $(KHDR_INCLUDES)
-TEST_GEN_PROGS := testptp
+TEST_GEN_PROGS := testptp ptp_freq_overflow
 LDLIBS += -lrt
 TEST_PROGS = phc.sh
 
diff --git a/tools/testing/selftests/ptp/ptp_freq_overflow.c b/tools/testing/selftests/ptp/ptp_freq_overflow.c
new file mode 100644
index 000000000000..342b22689f67
--- /dev/null
+++ b/tools/testing/selftests/ptp/ptp_freq_overflow.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Regression test for the scaled_ppm_to_ppb() integer overflow that allowed
+ * a crafted clock_adjtime(ADJ_FREQUENCY) to bypass the PTP max_adj check.
+ *
+ * testptp's -f option stores the adjustment as an int ppb and cannot express
+ * the 64-bit scaled-ppm values needed to overflow the conversion, so this
+ * test crafts struct timex.freq directly.
+ */
+#define _GNU_SOURCE
+#define __SANE_USERSPACE_TYPES__
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/timex.h>
+#include <time.h>
+#include <unistd.h>
+#include <linux/ptp_clock.h>
+#include "../kselftest.h"
+
+#define FD_TO_CLOCKID(fd)	((~(clockid_t)(fd) << 3) | 3)
+
+/* clock_adjtime is not available in GLIBC < 2.14 */
+#if !__GLIBC_PREREQ(2, 14)
+#include <sys/syscall.h>
+static int clock_adjtime(clockid_t id, struct timex *tx)
+{
+	return syscall(__NR_clock_adjtime, id, tx);
+}
+#endif
+
+int main(int argc, char *argv[])
+{
+	const char *device = argc > 1 ? argv[1] : "/dev/ptp0";
+	struct ptp_clock_caps caps;
+	struct timex tx = { 0 };
+	clockid_t clkid;
+	int fd, ret;
+
+	ksft_print_header();
+	ksft_set_plan(1);
+
+	if (sizeof(tx.freq) < 8)
+		ksft_exit_skip("the overflow only affects 64-bit kernels\n");
+
+	fd = open(device, O_RDWR);
+	if (fd < 0)
+		ksft_exit_skip("cannot open %s: %s\n", device, strerror(errno));
+
+	clkid = FD_TO_CLOCKID(fd);
+
+	if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps))
+		ksft_exit_skip("PTP_CLOCK_GETCAPS on %s: %s\n", device, strerror(errno));
+	if (!caps.max_adj)
+		ksft_exit_skip("%s does not support frequency adjustment\n", device);
+
+	/*
+	 * (1 + 147573952589676412) * 125 == 2^64 + 9, which overflows s64 in
+	 * scaled_ppm_to_ppb() and wraps the result to a ppb of 0.  A kernel
+	 * that does not detect the overflow lets this absurd frequency past
+	 * the max_adj check; a fixed kernel rejects it with -ERANGE.
+	 */
+	tx.modes = ADJ_FREQUENCY;
+	tx.freq = 147573952589676412LL;
+
+	ret = clock_adjtime(clkid, &tx);
+	ksft_test_result(ret < 0 && errno == ERANGE,
+			 "overflowing frequency adjustment is rejected (ret=%d errno=%d)\n",
+			 ret, ret < 0 ? errno : 0);
+
+	close(fd);
+	ksft_finished();
+}
-- 
2.43.0


^ permalink raw reply related

* [PATCH net 1/2] ptp: reject frequency adjustments that overflow scaled_ppm_to_ppb()
From: Deep Shah @ 2026-07-12  4:09 UTC (permalink / raw)
  To: netdev, Richard Cochran, David S . Miller, Jakub Kicinski,
	Paolo Abeni, Eric Dumazet, Andrew Lunn
  Cc: linux-kernel, Shuah Khan, linux-kselftest, Deep Shah
In-Reply-To: <20260712040922.6403-1-deepshah146@gmail.com>

ptp_clock_adjtime() converts tx->freq to ppb with scaled_ppm_to_ppb()
and rejects the request if it exceeds ops->max_adj.  On 64-bit systems
that conversion computes (1 + ppm) * 125 in s64, which can overflow for
a large tx->freq and wrap the result back into [-max_adj, max_adj].  The
check then passes and the original out-of-range value is handed to
->adjfine().

For example tx->freq = 147573952589676412 makes (1 + ppm) * 125 equal
2^64 + 9, which wraps to ppb == 0 and is accepted.

Reject the request with -ERANGE if either the addition or the
multiplication overflows.  This hardens the max_adj sanity check and is
not a security fix.  It follows up commit 475b92f93216 ("ptp: improve
max_adj check against unreasonable values"), which fixed the analogous
s32 narrowing but not this overflow.

Fixes: d39a743511cd ("ptp: validate the requested frequency adjustment.")
Signed-off-by: Deep Shah <deepshah146@gmail.com>
---
 drivers/ptp/ptp_clock.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index d6f54ccaf93b..f83aa44b0a74 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -9,6 +9,7 @@
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/overflow.h>
 #include <linux/posix-clock.h>
 #include <linux/pps_kernel.h>
 #include <linux/property.h>
@@ -159,7 +160,18 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
 		delta = ktime_to_ns(kt);
 		err = ops->adjtime(ops, delta);
 	} else if (tx->modes & ADJ_FREQUENCY) {
-		long ppb = scaled_ppm_to_ppb(tx->freq);
+		long ppb;
+		s64 tmp;
+
+		/*
+		 * scaled_ppm_to_ppb() multiplies (1 + freq) by 125 in s64;
+		 * reject a ->freq large enough to overflow that, which could
+		 * otherwise wrap the result back into the max_adj range.
+		 */
+		if (check_add_overflow(tx->freq, 1, &tmp) ||
+		    check_mul_overflow(tmp, 125, &tmp))
+			return -ERANGE;
+		ppb = scaled_ppm_to_ppb(tx->freq);
 		if (ppb > ops->max_adj || ppb < -ops->max_adj)
 			return -ERANGE;
 		err = ops->adjfine(ops, tx->freq);
-- 
2.43.0


^ permalink raw reply related

* [PATCH net 0/2] ptp: fix scaled_ppm_to_ppb() overflow bypassing the max_adj check
From: Deep Shah @ 2026-07-12  4:09 UTC (permalink / raw)
  To: netdev, Richard Cochran, David S . Miller, Jakub Kicinski,
	Paolo Abeni, Eric Dumazet, Andrew Lunn
  Cc: linux-kernel, Shuah Khan, linux-kselftest, Deep Shah

ptp_clock_adjtime() validates an ADJ_FREQUENCY request by converting
tx->freq to ppb and comparing it against ops->max_adj. On 64-bit systems
that conversion can overflow s64 and wrap the result back into range, so
a crafted tx->freq bypasses the check and reaches ->adjfine() unclamped.

Patch 1 rejects the overflow in ptp_clock_adjtime().
Patch 2 adds a regression test that crafts struct timex.freq directly
(testptp's int-ppb path cannot express the value) and confirms it is
rejected with -ERANGE.

Deep Shah (2):
  ptp: reject frequency adjustments that overflow scaled_ppm_to_ppb()
  selftests: ptp: add a regression test for the frequency adjustment
    overflow

 drivers/ptp/ptp_clock.c                       | 14 +++-
 tools/testing/selftests/ptp/Makefile          |  2 +-
 .../testing/selftests/ptp/ptp_freq_overflow.c | 74 +++++++++++++++++++
 3 files changed, 88 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/ptp/ptp_freq_overflow.c


base-commit: 3f1f755366687d051174739fb99f7d560202f60b
-- 
2.43.0


^ permalink raw reply

* [PATCH iproute2-next] Fix netdevsim switch_id display truncation
From: Gris Ge @ 2026-07-12  3:45 UTC (permalink / raw)
  To: netdev, David Ahern; +Cc: Gris Ge

Problem:

The `ip -d link show sim0` against of netdevsim interface, the
`switchid` only has 62 chars. The kernel `struct netdev_phys_item_id`
confirmed the switch id is 32 bytes which should produces 64 hex chars.

Root cause:

When `print_linkinfo()` showing `switchid`, it invokes `hexstring_n2a()`
which output hex string to `SPRINT_BUF(b1)` including trailing NULL.
The 32 bytes binary need 65 chars including trailing NULL while
`SPRINT_BUF` is 64 chars.

Fix:

Bump `SPRINT_BSIZE` from 64 to 128.

Signed-off-by: Gris Ge <cnfourt@gmail.com>
---
 include/utils.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/utils.h b/include/utils.h
index f0d45bad..c451baa9 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -39,7 +39,7 @@ extern bool do_all;
 extern int echo_request;
 extern int use_iec;
 
-#define SPRINT_BSIZE 64
+#define SPRINT_BSIZE 128
 #define SPRINT_BUF(x)	char x[SPRINT_BSIZE]
 
 void incomplete_command(void) __attribute__((noreturn));
-- 
2.55.0


^ permalink raw reply related

* [PATCH iproute2-next 0/2] seg6: add support for lookup attribute in SRv6 encap routes
From: Andrea Mayer @ 2026-07-12  2:11 UTC (permalink / raw)
  To: David Ahern, netdev
  Cc: Stephen Hemminger, Nicolas Dichtel, Stefano Salsano,
	Ahmed Abdelsalam, Paolo Lungaroni, Justin Iurman,
	Anthony Doeraene, Andrea Mayer

This series adds the optional "lookup" attribute to seg6 encap routes.
It selects the FIB table used to look up the route for the first SID,
that is the outer IPv6 destination of the encapsulated packet. The
attribute accepts a table number or a table name.

Examples:

  # SID route installed in the underlay table 500
  ip -6 route add fc00::100/128 via fd00::1 dev veth0 table 500

  # encap route in vrf-100; the first SID is looked up in table 500
  ip -6 route add cafe::1/128 vrf vrf-100 \
      encap seg6 mode encap segs fc00::100 lookup 500 dev veth0

  # or if the SID is already handled by the main table
  ip -6 route add cafe::1/128 vrf vrf-100 \
      encap seg6 mode encap segs fc00::100 lookup main dev veth0

The kernel companion series, adding the SEG6_IPTUNNEL_TABLE attribute, is
posted for net-next:

  https://lore.kernel.org/all/20260711162907.6521-1-andrea.mayer@uniroma2.it/T/

The first patch adds the attribute, the second documents it in the
ip-route man page.

Thanks,
Andrea

Andrea Mayer (2):
  seg6: add support for lookup attribute in SRv6 encap routes
  seg6: man: document the lookup attribute for SRv6 encap routes

 ip/iproute.c           |  1 +
 ip/iproute_lwtunnel.c  | 22 +++++++++++++++++++++-
 man/man8/ip-route.8.in | 16 +++++++++++++++-
 3 files changed, 37 insertions(+), 2 deletions(-)

-- 
2.20.1


^ permalink raw reply

* [PATCH iproute2-next 1/2] seg6: add support for lookup attribute in SRv6 encap routes
From: Andrea Mayer @ 2026-07-12  2:11 UTC (permalink / raw)
  To: David Ahern, netdev
  Cc: Stephen Hemminger, Nicolas Dichtel, Stefano Salsano,
	Ahmed Abdelsalam, Paolo Lungaroni, Justin Iurman,
	Anthony Doeraene, Andrea Mayer
In-Reply-To: <20260712021155.7621-1-andrea.mayer@uniroma2.it>

Add support for the new optional "lookup" attribute for seg6 encap
routes. It selects the FIB table for the post-encap SID route lookup
and accepts a table number or a table name.

Examples:

  # SID route installed in the underlay table 500
  ip -6 route add fc00::100/128 via fd00::1 dev veth0 table 500

  # encap route in vrf-100; the first SID is looked up in table 500
  ip -6 route add cafe::1/128 vrf vrf-100 \
      encap seg6 mode encap segs fc00::100 lookup 500 dev veth0

  # or if the SID is already handled by the main table
  ip -6 route add cafe::1/128 vrf vrf-100 \
      encap seg6 mode encap segs fc00::100 lookup main dev veth0

When the attribute is omitted, the post-encap SID route lookup behaves
as before, using the current routing context (e.g. the tables selected
according to the routing policy database).

Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 ip/iproute.c          |  1 +
 ip/iproute_lwtunnel.c | 22 +++++++++++++++++++++-
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/ip/iproute.c b/ip/iproute.c
index 5b9e7ac1..1ce71f78 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -101,6 +101,7 @@ static void usage(void)
 		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 | xfrm ]\n"
 		"ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR | XFRMINFO ]\n"
 		"SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]\n"
+		"           [ lookup TABLEID ]\n"
 		"SEGMODE := [ encap | encap.red | inline | l2encap | l2encap.red ]\n"
 		"SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]\n"
 		"ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 00b4f756..9a1e747c 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -256,6 +256,7 @@ static void print_encap_seg6(FILE *fp, struct rtattr *encap)
 {
 	struct rtattr *tb[SEG6_IPTUNNEL_MAX+1];
 	struct seg6_iptunnel_encap *tuninfo;
+	SPRINT_BUF(b1);
 
 	parse_rtattr_nested(tb, SEG6_IPTUNNEL_MAX, encap);
 
@@ -274,6 +275,12 @@ static void print_encap_seg6(FILE *fp, struct rtattr *encap)
 	}
 
 	print_srh(fp, tuninfo->srh);
+
+	if (tb[SEG6_IPTUNNEL_TABLE]) {
+		print_string(PRINT_ANY, "lookup", "lookup %s ",
+			     rtnl_rttable_n2a(rta_getattr_u32(tb[SEG6_IPTUNNEL_TABLE]),
+					      b1, sizeof(b1)));
+	}
 }
 
 static void print_rpl_srh(FILE *fp, struct ipv6_rpl_sr_hdr *srh)
@@ -955,7 +962,7 @@ static struct ipv6_sr_hdr *parse_srh(char *segbuf, int hmac, bool encap)
 static int parse_encap_seg6(struct rtattr *rta, size_t len, int *argcp,
 			    char ***argvp)
 {
-	int mode_ok = 0, segs_ok = 0, hmac_ok = 0;
+	int mode_ok = 0, segs_ok = 0, hmac_ok = 0, lookup_ok = 0;
 	struct seg6_iptunnel_encap *tuninfo = NULL;
 	struct ipv6_sr_hdr *srh;
 	char **argv = *argvp;
@@ -963,6 +970,7 @@ static int parse_encap_seg6(struct rtattr *rta, size_t len, int *argcp,
 	bool tunsrc = false;
 	inet_prefix saddr;
 	int argc = *argcp;
+	__u32 lookup = 0;
 	int encap = -1;
 	__u32 hmac = 0;
 	int ret = -1;
@@ -1006,6 +1014,13 @@ static int parse_encap_seg6(struct rtattr *rta, size_t len, int *argcp,
 			if (hmac_ok++)
 				duparg2("hmac", *argv);
 			get_u32(&hmac, *argv, 0);
+		} else if (strcmp(*argv, "lookup") == 0) {
+			NEXT_ARG();
+			if (lookup_ok++)
+				duparg2("lookup", *argv);
+			/* note that table 0 is considered an invalid value */
+			if (rtnl_rttable_a2n(&lookup, *argv) || !lookup)
+				invarg("\"lookup\" value is invalid\n", *argv);
 		} else {
 			break;
 		}
@@ -1036,6 +1051,11 @@ static int parse_encap_seg6(struct rtattr *rta, size_t len, int *argcp,
 			goto out;
 	}
 
+	if (lookup) {
+		if (rta_addattr32(rta, len, SEG6_IPTUNNEL_TABLE, lookup))
+			goto out;
+	}
+
 	*argcp = argc + 1;
 	*argvp = argv - 1;
 	ret = 0;
-- 
2.20.1


^ permalink raw reply related

* [PATCH iproute2-next 2/2] seg6: man: document the lookup attribute for SRv6 encap routes
From: Andrea Mayer @ 2026-07-12  2:11 UTC (permalink / raw)
  To: David Ahern, netdev
  Cc: Stephen Hemminger, Nicolas Dichtel, Stefano Salsano,
	Ahmed Abdelsalam, Paolo Lungaroni, Justin Iurman,
	Anthony Doeraene, Andrea Mayer
In-Reply-To: <20260712021155.7621-1-andrea.mayer@uniroma2.it>

Document the new optional "lookup" attribute for seg6 encap routes. It
selects the FIB table for the post-encap SID route lookup and accepts a
table number or a table name.

Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 man/man8/ip-route.8.in | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 9f29fd43..fd04432f 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -246,7 +246,9 @@ throw " | " unreachable " | " prohibit " | " blackhole " | " nat " ]"
 .B segs
 .IR SEGMENTS " [ "
 .B hmac
-.IR KEYID " ]"
+.IR KEYID " ] [ "
+.B lookup
+.IR TABLEID " ]"
 
 .ti -8
 .IR ENCAP_SEG6LOCAL " := "
@@ -910,6 +912,18 @@ configuration and dynamic resolution.
 
 .I KEYID
 - Numerical value in decimal representation. See \fBip-sr\fR(8).
+.sp
+
+.B lookup
+.I TABLEID
+- Table for the post-encap SID route lookup, i.e. looking up the
+route for the first SID.
+.I TABLEID
+is either a number or a string from
+.BR @SYSCONF_USR_DIR@/rt_tables " or " @SYSCONF_ETC_DIR@/rt_tables
+(has precedence if exists); table id 0 is not a valid value.
+When omitted, this lookup uses the current routing context, e.g. tables
+selected according to the routing policy database.
 .in -2
 .sp
 
-- 
2.20.1


^ permalink raw reply related

* [PATCH 13/13 RFC net-next] net: ipv4: make CONFIG_IPV4 boolean
From: Fernando Fernandez Mancera @ 2026-07-12  1:39 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
	Fernando Fernandez Mancera, Eric Biggers, Ard Biesheuvel,
	Dmitry Safonov, linux-kernel
In-Reply-To: <20260712013941.4570-1-fmancera@suse.de>

Change CONFIG_IPV4 from hidden 'def_bool y' into an exposed boolean.
This allows users to explicitly disable IPv4 via Kconfig to build
IPv6-only or pure L2 kernel network stack. The default is kept at 'y' to
prevent breaking existing configurations.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/ipv4/Kconfig | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index 51bf05d9770c..fa30cc407223 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -3,7 +3,15 @@
 # IP configuration
 #
 config IPV4
-	def_bool y
+	bool "The IPv4 protocol"
+	default y
+	help
+	  Support for IP version 4 (IPv4).
+
+	  Disabling this option compiles the kernel networking stack without
+	  IPv4 support, resulting in a IPv6-only or pure L2 system.
+
+	  If unsure, say Y.
 
 config IP_MULTICAST
 	bool "IP: multicasting"
-- 
2.54.0


^ permalink raw reply related

* [PATCH 12/13 RFC net-next] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4
From: Fernando Fernandez Mancera @ 2026-07-12  1:39 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
	Fernando Fernandez Mancera, Pablo Neira Ayuso, Florian Westphal,
	Phil Sutter, netfilter-devel, coreteam, linux-kernel
In-Reply-To: <20260712013941.4570-1-fmancera@suse.de>

To enable compiling the network stack without IPv4, IPv4 specific packet
rerouting logic inside netfilter must be bypassed. Therefore, guard
ip_route_me_harder() with IS_ENABLED(CONFIG_IPV4) so it returns
-EPROTONOSUPPORT when IPv4 is disabled.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/ipv4/netfilter.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/net/ipv4/netfilter.c b/net/ipv4/netfilter.c
index ce9e1bfa4259..9576d91bb6a6 100644
--- a/net/ipv4/netfilter.c
+++ b/net/ipv4/netfilter.c
@@ -21,6 +21,7 @@
 /* route_me_harder function, used by iptable_nat, iptable_mangle + ip_queue */
 int ip_route_me_harder(struct net *net, struct sock *sk, struct sk_buff *skb, unsigned int addr_type)
 {
+#if IS_ENABLED(CONFIG_IPV4)
 	struct net_device *dev = skb_dst_dev(skb);
 	const struct iphdr *iph = ip_hdr(skb);
 	struct rtable *rt;
@@ -85,6 +86,9 @@ int ip_route_me_harder(struct net *net, struct sock *sk, struct sk_buff *skb, un
 		return -ENOMEM;
 
 	return 0;
+#else
+	return -EPROTONOSUPPORT;
+#endif
 }
 EXPORT_SYMBOL(ip_route_me_harder);
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH 11/13 RFC net-next] net: cipso: guard IPv4 packet manipulation functions
From: Fernando Fernandez Mancera @ 2026-07-12  1:39 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
	Fernando Fernandez Mancera, Paul Moore, linux-security-module,
	linux-kernel
In-Reply-To: <20260712013941.4570-1-fmancera@suse.de>

To enable compiling the network stack without IPv4, the CIPSO functions
that manipulate IPv4 options and generate ICMP errors must be bypassed.

Ideally, CIPSO should not be compiled when IPv4 is disabled but
currently it is too integrated within netlabel, so let's just bypassed
the relevant functions.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/ipv4/cipso_ipv4.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/net/ipv4/cipso_ipv4.c b/net/ipv4/cipso_ipv4.c
index a05aa075de1a..17bb723299d7 100644
--- a/net/ipv4/cipso_ipv4.c
+++ b/net/ipv4/cipso_ipv4.c
@@ -1714,6 +1714,7 @@ int cipso_v4_validate(const struct sk_buff *skb, unsigned char **option)
  */
 void cipso_v4_error(struct sk_buff *skb, int error, u32 gateway)
 {
+#if IS_ENABLED(CONFIG_IPV4)
 	struct inet_skb_parm parm;
 	int res;
 
@@ -1738,6 +1739,7 @@ void cipso_v4_error(struct sk_buff *skb, int error, u32 gateway)
 		__icmp_send(skb, ICMP_DEST_UNREACH, ICMP_NET_ANO, 0, &parm);
 	else
 		__icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_ANO, 0, &parm);
+#endif
 }
 
 /**
@@ -2171,6 +2173,7 @@ int cipso_v4_skbuff_setattr(struct sk_buff *skb,
 			    const struct cipso_v4_doi *doi_def,
 			    const struct netlbl_lsm_secattr *secattr)
 {
+#if IS_ENABLED(CONFIG_IPV4)
 	int ret_val;
 	struct iphdr *iph;
 	struct ip_options *opt = &IPCB(skb)->opt;
@@ -2235,6 +2238,9 @@ int cipso_v4_skbuff_setattr(struct sk_buff *skb,
 	ip_send_check(iph);
 
 	return 0;
+#else
+	return -EOPNOTSUPP;
+#endif
 }
 
 /**
@@ -2248,6 +2254,7 @@ int cipso_v4_skbuff_setattr(struct sk_buff *skb,
  */
 int cipso_v4_skbuff_delattr(struct sk_buff *skb)
 {
+#if IS_ENABLED(CONFIG_IPV4)
 	int ret_val, cipso_len, hdr_len_actual, new_hdr_len_actual, new_hdr_len,
 	    hdr_len_delta;
 	struct iphdr *iph;
@@ -2296,6 +2303,9 @@ int cipso_v4_skbuff_delattr(struct sk_buff *skb)
 	ip_send_check(iph);
 
 	return 0;
+#else
+	return -EOPNOTSUPP;
+#endif
 }
 
 /*
-- 
2.54.0


^ permalink raw reply related

* [PATCH 10/13 RFC net-next] net: tunnel: guard IPv4 tunnel functions with CONFIG_IPV4
From: Fernando Fernandez Mancera @ 2026-07-12  1:39 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
	Fernando Fernandez Mancera, linux-kernel
In-Reply-To: <20260712013941.4570-1-fmancera@suse.de>

To enable compiling INET subsystem withou IPv4, IPv4 specific IP tunnel
core functions must be bypassed when the protocol is disabled.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/ipv4/ip_tunnel_core.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c
index d3c677e9bff2..700a759d3c46 100644
--- a/net/ipv4/ip_tunnel_core.c
+++ b/net/ipv4/ip_tunnel_core.c
@@ -52,6 +52,7 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb,
 		   __u8 tos, __u8 ttl, __be16 df, bool xnet,
 		   u16 ipcb_flags)
 {
+#if IS_ENABLED(CONFIG_IPV4)
 	int pkt_len = skb->len - skb_inner_network_offset(skb);
 	struct net *net = dev_net(rt->dst.dev);
 	struct net_device *dev = skb->dev;
@@ -103,6 +104,12 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb,
 	}
 
 	dev_xmit_recursion_dec();
+#else
+	if (skb->dev)
+		DEV_STATS_INC(skb->dev, tx_errors);
+	ip_rt_put(rt);
+	kfree_skb(skb);
+#endif
 }
 EXPORT_SYMBOL_GPL(iptunnel_xmit);
 
@@ -212,6 +219,7 @@ EXPORT_SYMBOL_GPL(iptunnel_handle_offloads);
  */
 static int iptunnel_pmtud_build_icmp(struct sk_buff *skb, int mtu)
 {
+#if IS_ENABLED(CONFIG_IPV4)
 	const struct iphdr *iph;
 	struct icmphdr *icmph;
 	struct iphdr *niph;
@@ -269,6 +277,9 @@ static int iptunnel_pmtud_build_icmp(struct sk_buff *skb, int mtu)
 	skb_reset_mac_header(skb);
 
 	return skb->len;
+#else
+	return -EOPNOTSUPP;
+#endif
 }
 
 /**
-- 
2.54.0


^ permalink raw reply related

* [PATCH 09/13 RFC net-next] net: fib: split common nexthop logic to fib_core.c
From: Fernando Fernandez Mancera @ 2026-07-12  1:39 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
	Fernando Fernandez Mancera, Alexei Starovoitov, Daniel Borkmann,
	Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
	Kuniyuki Iwashima, Pavel Begunkov, Dragos Tatulea, Eric Biggers,
	Ard Biesheuvel, Florian Westphal, Chia-Yu Chang,
	Ilpo Järvinen, Yung Chih Su, Wyatt Feng, linux-kernel, bpf
In-Reply-To: <20260712013941.4570-1-fmancera@suse.de>

To enable compiling INET subsystem without IPv4, shared FIB and nexthop
logic must be accessible to IPv6 when IPv4 routing stack is omitted.

This patch creates fib_core.c and move all the functions for common
nexthop initialization and release from fib_semantics.c to fib_core.c.
Note that fib_core.c is under net/core.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 include/net/ip_fib.h       |  43 +++++++++++
 include/net/route.h        |   6 ++
 net/core/Makefile          |   2 +-
 net/core/fib_core.c        | 146 +++++++++++++++++++++++++++++++++++++
 net/ipv4/Makefile          |   2 +
 net/ipv4/fib_semantics.c   | 142 ------------------------------------
 net/ipv4/netlink.c         |   2 +
 net/ipv4/nexthop.c         |   2 +-
 net/ipv4/sysctl_net_ipv4.c |   2 +
 net/ipv6/ip6_fib.c         |   2 +
 10 files changed, 205 insertions(+), 144 deletions(-)
 create mode 100644 net/core/fib_core.c

diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h
index 0a35355fb0f3..0c56d8690e00 100644
--- a/include/net/ip_fib.h
+++ b/include/net/ip_fib.h
@@ -250,7 +250,13 @@ int call_fib4_notifiers(struct net *net, enum fib_event_type event_type,
 int __net_init fib4_notifier_init(struct net *net);
 void __net_exit fib4_notifier_exit(struct net *net);
 
+#if IS_ENABLED(CONFIG_IPV4)
 void fib_info_notify_update(struct net *net, struct nl_info *info);
+#else
+static inline void fib_info_notify_update(struct net *net, struct nl_info *info)
+{
+}
+#endif
 int fib_notify(struct net *net, struct notifier_block *nb,
 	       struct netlink_ext_ack *extack);
 
@@ -578,10 +584,23 @@ void fib_select_multipath(struct fib_result *res, int hash,
 void fib_select_path(struct net *net, struct fib_result *res,
 		     struct flowi4 *fl4, const struct sk_buff *skb);
 
+#if IS_ENABLED(CONFIG_IPV4)
 int fib_nh_init(struct net *net, struct fib_nh *fib_nh,
 		struct fib_config *cfg, int nh_weight,
 		struct netlink_ext_ack *extack);
 void fib_nh_release(struct net *net, struct fib_nh *fib_nh);
+#else
+static inline int fib_nh_init(struct net *net, struct fib_nh *fib_nh,
+			      struct fib_config *cfg, int gfp,
+			      struct netlink_ext_ack *extack)
+{
+	return -EAFNOSUPPORT;
+}
+
+static inline void fib_nh_release(struct net *net, struct fib_nh *fib_nh)
+{
+}
+#endif
 int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc,
 		       struct nlattr *fc_encap, u16 fc_encap_type,
 		       void *cfg, gfp_t gfp_flags,
@@ -620,7 +639,13 @@ static inline void fib_combine_itag(u32 *itag, const struct fib_result *res)
 #endif
 }
 
+#if IS_ENABLED(CONFIG_IPV4)
 void fib_flush(struct net *net);
+#else
+static inline void fib_flush(struct net *net)
+{
+}
+#endif
 void free_fib_info(struct fib_info *fi);
 
 static inline void fib_info_hold(struct fib_info *fi)
@@ -658,8 +683,26 @@ int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh,
 			  struct fib_dump_filter *filter,
 			  struct netlink_callback *cb);
 
+#if IS_ENABLED(CONFIG_IPV4)
 int fib_nexthop_info(struct sk_buff *skb, const struct fib_nh_common *nh,
 		     u8 rt_family, unsigned char *flags, bool skip_oif);
 int fib_add_nexthop(struct sk_buff *skb, const struct fib_nh_common *nh,
 		    int nh_weight, u8 rt_family, u32 nh_tclassid);
+#else
+static inline int fib_nexthop_info(struct sk_buff *skb,
+				   const struct fib_nh_common *nh,
+				   u8 rt_family, unsigned char *flags,
+				   bool skip_oif)
+{
+	return 0;
+}
+
+static inline int fib_add_nexthop(struct sk_buff *skb,
+				  const struct fib_nh_common *nh,
+				  int nh_weight, u8 rt_family,
+				  u32 nh_tclassid)
+{
+	return 0;
+}
+#endif
 #endif  /* _NET_FIB_H */
diff --git a/include/net/route.h b/include/net/route.h
index 08adb14224a9..29944be291f8 100644
--- a/include/net/route.h
+++ b/include/net/route.h
@@ -128,7 +128,13 @@ extern struct ip_rt_acct __percpu *ip_rt_acct;
 struct in_device;
 
 int ip_rt_init(void);
+#if IS_ENABLED(CONFIG_IPV4)
 void rt_cache_flush(struct net *net);
+#else
+static inline void rt_cache_flush(struct net *net)
+{
+}
+#endif
 void rt_flush_dev(struct net_device *dev);
 
 static inline void inet_sk_init_flowi4(const struct inet_sock *inet,
diff --git a/net/core/Makefile b/net/core/Makefile
index b3fdcb4e355f..63ebcdc6b9f1 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -12,7 +12,7 @@ obj-$(CONFIG_SYSCTL) += sysctl_net_core.o
 obj-y		     += dev.o dev_api.o dev_addr_lists.o dst.o netevent.o \
 			neighbour.o rtnetlink.o utils.o link_watch.o filter.o \
 			sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \
-			fib_notifier.o xdp.o flow_offload.o gro.o \
+			fib_notifier.o fib_core.o xdp.o flow_offload.o gro.o \
 			netdev-genl.o netdev-genl-gen.o netdev_work.o gso.o
 
 obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o
diff --git a/net/core/fib_core.c b/net/core/fib_core.c
new file mode 100644
index 000000000000..93894b5847e9
--- /dev/null
+++ b/net/core/fib_core.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/types.h>
+#include <net/lwtunnel.h>
+#include <net/route.h>
+
+static void rt_fibinfo_free(struct rtable __rcu **rtp)
+{
+	struct rtable *rt = rcu_dereference_protected(*rtp, 1);
+
+	if (!rt)
+		return;
+
+	/* Not even needed : RCU_INIT_POINTER(*rtp, NULL);
+	 * because we waited an RCU grace period before calling
+	 * free_fib_info_rcu()
+	 */
+
+	dst_dev_put(&rt->dst);
+	dst_release_immediate(&rt->dst);
+}
+
+static void free_nh_exceptions(struct fib_nh_common *nhc)
+{
+	struct fnhe_hash_bucket *hash;
+	int i;
+
+	hash = rcu_dereference_protected(nhc->nhc_exceptions, 1);
+	if (!hash)
+		return;
+	for (i = 0; i < FNHE_HASH_SIZE; i++) {
+		struct fib_nh_exception *fnhe;
+
+		fnhe = rcu_dereference_protected(hash[i].chain, 1);
+		while (fnhe) {
+			struct fib_nh_exception *next;
+
+			next = rcu_dereference_protected(fnhe->fnhe_next, 1);
+
+			rt_fibinfo_free(&fnhe->fnhe_rth_input);
+			rt_fibinfo_free(&fnhe->fnhe_rth_output);
+
+			kfree(fnhe);
+
+			fnhe = next;
+		}
+	}
+	kfree(hash);
+}
+
+static void rt_fibinfo_free_cpus(struct rtable __rcu * __percpu *rtp)
+{
+	int cpu;
+
+	if (!rtp)
+		return;
+
+	for_each_possible_cpu(cpu) {
+		struct rtable *rt;
+
+		rt = rcu_dereference_protected(*per_cpu_ptr(rtp, cpu), 1);
+		if (rt) {
+			dst_dev_put(&rt->dst);
+			dst_release_immediate(&rt->dst);
+		}
+	}
+	free_percpu(rtp);
+}
+
+void fib_nh_common_release(struct fib_nh_common *nhc)
+{
+	netdev_put(nhc->nhc_dev, &nhc->nhc_dev_tracker);
+	lwtstate_put(nhc->nhc_lwtstate);
+	rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
+	rt_fibinfo_free(&nhc->nhc_rth_input);
+	free_nh_exceptions(nhc);
+}
+
+int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc,
+		       struct nlattr *encap, u16 encap_type,
+		       void *cfg, gfp_t gfp_flags,
+		       struct netlink_ext_ack *extack)
+{
+	int err;
+
+	nhc->nhc_pcpu_rth_output = alloc_percpu_gfp(struct rtable __rcu *,
+						    gfp_flags);
+	if (!nhc->nhc_pcpu_rth_output)
+		return -ENOMEM;
+
+	if (encap) {
+		struct lwtunnel_state *lwtstate;
+
+		err = lwtunnel_build_state(net, encap_type, encap,
+					   nhc->nhc_family, cfg, &lwtstate,
+					   extack);
+		if (err)
+			goto lwt_failure;
+
+		nhc->nhc_lwtstate = lwtstate_get(lwtstate);
+	}
+
+	return 0;
+
+lwt_failure:
+	rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
+	nhc->nhc_pcpu_rth_output = NULL;
+	return err;
+}
+
+/* Update the PMTU of exceptions when:
+ * - the new MTU of the first hop becomes smaller than the PMTU
+ * - the old MTU was the same as the PMTU, and it limited discovery of
+ *   larger MTUs on the path. With that limit raised, we can now
+ *   discover larger MTUs
+ * A special case is locked exceptions, for which the PMTU is smaller
+ * than the minimal accepted PMTU:
+ * - if the new MTU is greater than the PMTU, don't make any change
+ * - otherwise, unlock and set PMTU
+ */
+void fib_nhc_update_mtu(struct fib_nh_common *nhc, u32 new, u32 orig)
+{
+	struct fnhe_hash_bucket *bucket;
+	int i;
+
+	bucket = rcu_dereference_protected(nhc->nhc_exceptions, 1);
+	if (!bucket)
+		return;
+
+	for (i = 0; i < FNHE_HASH_SIZE; i++) {
+		struct fib_nh_exception *fnhe;
+
+		for (fnhe = rcu_dereference_protected(bucket[i].chain, 1);
+		     fnhe;
+		     fnhe = rcu_dereference_protected(fnhe->fnhe_next, 1)) {
+			if (fnhe->fnhe_mtu_locked) {
+				if (new <= fnhe->fnhe_pmtu) {
+					fnhe->fnhe_pmtu = new;
+					fnhe->fnhe_mtu_locked = false;
+				}
+			} else if (new < fnhe->fnhe_pmtu ||
+				   orig == fnhe->fnhe_pmtu) {
+				fnhe->fnhe_pmtu = new;
+			}
+		}
+	}
+}
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 83c25f52eb58..b540d549f25a 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -19,7 +19,9 @@ obj-$(CONFIG_IPV4) += route.o ip_input.o ip_fragment.o ip_forward.o ip_options.o
 obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
 obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
 obj-$(CONFIG_PROC_FS) += proc.o
+ifeq ($(CONFIG_IPV4),y)
 obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o
+endif
 obj-$(CONFIG_IP_MROUTE) += ipmr.o
 obj-$(CONFIG_IP_MROUTE_COMMON) += ipmr_base.o
 obj-$(CONFIG_NET_IPIP) += ipip.o
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
index 4f3c0740dde9..6ed252e9559e 100644
--- a/net/ipv4/fib_semantics.c
+++ b/net/ipv4/fib_semantics.c
@@ -136,78 +136,6 @@ const struct fib_prop fib_props[RTN_MAX + 1] = {
 	},
 };
 
-static void rt_fibinfo_free(struct rtable __rcu **rtp)
-{
-	struct rtable *rt = rcu_dereference_protected(*rtp, 1);
-
-	if (!rt)
-		return;
-
-	/* Not even needed : RCU_INIT_POINTER(*rtp, NULL);
-	 * because we waited an RCU grace period before calling
-	 * free_fib_info_rcu()
-	 */
-
-	dst_dev_put(&rt->dst);
-	dst_release_immediate(&rt->dst);
-}
-
-static void free_nh_exceptions(struct fib_nh_common *nhc)
-{
-	struct fnhe_hash_bucket *hash;
-	int i;
-
-	hash = rcu_dereference_protected(nhc->nhc_exceptions, 1);
-	if (!hash)
-		return;
-	for (i = 0; i < FNHE_HASH_SIZE; i++) {
-		struct fib_nh_exception *fnhe;
-
-		fnhe = rcu_dereference_protected(hash[i].chain, 1);
-		while (fnhe) {
-			struct fib_nh_exception *next;
-
-			next = rcu_dereference_protected(fnhe->fnhe_next, 1);
-
-			rt_fibinfo_free(&fnhe->fnhe_rth_input);
-			rt_fibinfo_free(&fnhe->fnhe_rth_output);
-
-			kfree(fnhe);
-
-			fnhe = next;
-		}
-	}
-	kfree(hash);
-}
-
-static void rt_fibinfo_free_cpus(struct rtable __rcu * __percpu *rtp)
-{
-	int cpu;
-
-	if (!rtp)
-		return;
-
-	for_each_possible_cpu(cpu) {
-		struct rtable *rt;
-
-		rt = rcu_dereference_protected(*per_cpu_ptr(rtp, cpu), 1);
-		if (rt) {
-			dst_dev_put(&rt->dst);
-			dst_release_immediate(&rt->dst);
-		}
-	}
-	free_percpu(rtp);
-}
-
-void fib_nh_common_release(struct fib_nh_common *nhc)
-{
-	netdev_put(nhc->nhc_dev, &nhc->nhc_dev_tracker);
-	lwtstate_put(nhc->nhc_lwtstate);
-	rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
-	rt_fibinfo_free(&nhc->nhc_rth_input);
-	free_nh_exceptions(nhc);
-}
-
 void fib_nh_release(struct net *net, struct fib_nh *fib_nh)
 {
 #ifdef CONFIG_IP_ROUTE_CLASSID
@@ -607,38 +535,6 @@ static int fib_detect_death(struct fib_info *fi, int order,
 	return 1;
 }
 
-int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc,
-		       struct nlattr *encap, u16 encap_type,
-		       void *cfg, gfp_t gfp_flags,
-		       struct netlink_ext_ack *extack)
-{
-	int err;
-
-	nhc->nhc_pcpu_rth_output = alloc_percpu_gfp(struct rtable __rcu *,
-						    gfp_flags);
-	if (!nhc->nhc_pcpu_rth_output)
-		return -ENOMEM;
-
-	if (encap) {
-		struct lwtunnel_state *lwtstate;
-
-		err = lwtunnel_build_state(net, encap_type, encap,
-					   nhc->nhc_family, cfg, &lwtstate,
-					   extack);
-		if (err)
-			goto lwt_failure;
-
-		nhc->nhc_lwtstate = lwtstate_get(lwtstate);
-	}
-
-	return 0;
-
-lwt_failure:
-	rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
-	nhc->nhc_pcpu_rth_output = NULL;
-	return err;
-}
-
 int fib_nh_init(struct net *net, struct fib_nh *nh,
 		struct fib_config *cfg, int nh_weight,
 		struct netlink_ext_ack *extack)
@@ -1864,44 +1760,6 @@ static int call_fib_nh_notifiers(struct fib_nh *nh,
 	return NOTIFY_DONE;
 }
 
-/* Update the PMTU of exceptions when:
- * - the new MTU of the first hop becomes smaller than the PMTU
- * - the old MTU was the same as the PMTU, and it limited discovery of
- *   larger MTUs on the path. With that limit raised, we can now
- *   discover larger MTUs
- * A special case is locked exceptions, for which the PMTU is smaller
- * than the minimal accepted PMTU:
- * - if the new MTU is greater than the PMTU, don't make any change
- * - otherwise, unlock and set PMTU
- */
-void fib_nhc_update_mtu(struct fib_nh_common *nhc, u32 new, u32 orig)
-{
-	struct fnhe_hash_bucket *bucket;
-	int i;
-
-	bucket = rcu_dereference_protected(nhc->nhc_exceptions, 1);
-	if (!bucket)
-		return;
-
-	for (i = 0; i < FNHE_HASH_SIZE; i++) {
-		struct fib_nh_exception *fnhe;
-
-		for (fnhe = rcu_dereference_protected(bucket[i].chain, 1);
-		     fnhe;
-		     fnhe = rcu_dereference_protected(fnhe->fnhe_next, 1)) {
-			if (fnhe->fnhe_mtu_locked) {
-				if (new <= fnhe->fnhe_pmtu) {
-					fnhe->fnhe_pmtu = new;
-					fnhe->fnhe_mtu_locked = false;
-				}
-			} else if (new < fnhe->fnhe_pmtu ||
-				   orig == fnhe->fnhe_pmtu) {
-				fnhe->fnhe_pmtu = new;
-			}
-		}
-	}
-}
-
 void fib_sync_mtu(struct net_device *dev, u32 orig_mtu)
 {
 	struct hlist_head *head = fib_nh_head(dev);
diff --git a/net/ipv4/netlink.c b/net/ipv4/netlink.c
index b920e1bdcf58..57d4dca16d3e 100644
--- a/net/ipv4/netlink.c
+++ b/net/ipv4/netlink.c
@@ -16,10 +16,12 @@ int rtm_getroute_parse_ip_proto(struct nlattr *attr, u8 *ip_proto, u8 family,
 	case IPPROTO_TCP:
 	case IPPROTO_UDP:
 		return 0;
+#if IS_ENABLED(CONFIG_IPV4)
 	case IPPROTO_ICMP:
 		if (family != AF_INET)
 			break;
 		return 0;
+#endif
 #if IS_ENABLED(CONFIG_IPV6)
 	case IPPROTO_ICMPV6:
 		if (family != AF_INET6)
diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c
index 6205bd57aa85..b113a31f6150 100644
--- a/net/ipv4/nexthop.c
+++ b/net/ipv4/nexthop.c
@@ -1414,7 +1414,7 @@ static bool nexthop_is_good_nh(const struct nexthop *nh)
 
 	switch (nhi->family) {
 	case AF_INET:
-		return ipv4_good_nh(&nhi->fib_nh);
+		return IS_ENABLED(CONFIG_IPV4) && ipv4_good_nh(&nhi->fib_nh);
 	case AF_INET6:
 		return IS_ENABLED(CONFIG_IPV6) && ipv6_good_nh(&nhi->fib6_nh);
 	}
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index ca1180dba1de..9c343c9566b7 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -613,6 +613,7 @@ static struct ctl_table ipv4_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_doulongvec_minmax,
 	},
+#if IS_ENABLED(CONFIG_IPV4)
 	{
 		.procname	= "fib_sync_mem",
 		.data		= &sysctl_fib_sync_mem,
@@ -622,6 +623,7 @@ static struct ctl_table ipv4_table[] = {
 		.extra1		= &sysctl_fib_sync_mem_min,
 		.extra2		= &sysctl_fib_sync_mem_max,
 	},
+#endif
 };
 
 static struct ctl_table ipv4_net_table[] = {
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index a130cdfaebfb..8b5f855c8dcb 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -645,9 +645,11 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
 
 	rcu_read_lock();
 	if (cb->strict_check) {
+#if IS_ENABLED(CONFIG_IPV4)
 		err = ip_valid_fib_dump_req(net, nlh, &arg.filter, cb);
 		if (err < 0)
 			goto unlock;
+#endif
 	} else if (nlmsg_len(nlh) >= sizeof(struct rtmsg)) {
 		struct rtmsg *rtm = nlmsg_data(nlh);
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH 08/13 RFC net-next] net: ping: split IPv4 specific logic into ping_ipv4.c
From: Fernando Fernandez Mancera @ 2026-07-12  1:39 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
	Fernando Fernandez Mancera, Eric Biggers, Kuniyuki Iwashima,
	Ard Biesheuvel, Julian Anastasov, Florian Westphal, Yue Haibing,
	yuan.gao, Jeff Layton, Gustavo A. R. Silva, Kees Cook,
	linux-kernel, bpf
In-Reply-To: <20260712013941.4570-1-fmancera@suse.de>

To enable compiling INET subsystem withot IPv4, ping socket IPv4
specific functions must be isolated from the generic ping
infrastructure.

This patch creates ping_ipv4.c and move all the functions for packet
transmission, error handling and the AF_INET ping_prot definition from
ping.c to the new file. The Makefile is updated to compile ping_ipv4.c
only when CONFIG_IPV4 is enabled.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/ipv4/Makefile    |   2 +-
 net/ipv4/ping.c      | 215 +++--------------------------------------
 net/ipv4/ping_ipv4.c | 225 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 238 insertions(+), 204 deletions(-)
 create mode 100644 net/ipv4/ping_ipv4.c

diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 651835073a29..83c25f52eb58 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -14,7 +14,7 @@ obj-y     := inetpeer.o protocol.o inet_hashtables.o inet_timewait_sock.o \
 obj-$(CONFIG_IPV4) += route.o ip_input.o ip_fragment.o ip_forward.o ip_options.o \
 		      ip_sockglue.o tcp_ipv4.o datagram.o arp.o devinet.o igmp.o \
 		      fib_notifier.o ip_output.o fib_frontend.o fib_semantics.o \
-		      fib_trie.o raw_ipv4.o udp_ipv4.o icmp_ipv4.o
+		      fib_trie.o raw_ipv4.o udp_ipv4.o icmp_ipv4.o ping_ipv4.o
 
 obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
 obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
diff --git a/net/ipv4/ping.c b/net/ipv4/ping.c
index d36f1e273fde..31ee56eeac81 100644
--- a/net/ipv4/ping.c
+++ b/net/ipv4/ping.c
@@ -285,25 +285,13 @@ void ping_close(struct sock *sk, long timeout)
 	sk_common_release(sk);
 }
 
-static int ping_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
-			    int addr_len)
-{
-	/* This check is replicated from __ip4_datagram_connect() and
-	 * intended to prevent BPF program called below from accessing bytes
-	 * that are out of the bound specified by user in addr_len.
-	 */
-	if (addr_len < sizeof(struct sockaddr_in))
-		return -EINVAL;
-
-	return BPF_CGROUP_RUN_PROG_INET4_CONNECT_LOCK(sk, uaddr, &addr_len);
-}
-
 /* Checks the bind address and possibly modifies sk->sk_bound_dev_if. */
 static int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,
 				struct sockaddr_unsized *uaddr, int addr_len)
 {
 	struct net *net = sock_net(sk);
 	if (sk->sk_family == AF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
 		struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;
 		u32 tb_id = RT_TABLE_LOCAL;
 		int chk_addr_ret;
@@ -331,6 +319,9 @@ static int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,
 		     !inet_can_nonlocal_bind(net, isk)))
 			return -EADDRNOTAVAIL;
 
+#else
+		return -EAFNOSUPPORT;
+#endif
 #if IS_ENABLED(CONFIG_IPV6)
 	} else if (sk->sk_family == AF_INET6) {
 		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) uaddr;
@@ -528,6 +519,7 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
 	inet_sock = inet_sk(sk);
 
 	if (skb->protocol == htons(ETH_P_IP)) {
+#if IS_ENABLED(CONFIG_IPV4)
 		switch (type) {
 		default:
 		case ICMP_TIME_EXCEEDED:
@@ -565,6 +557,7 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
 			err = EREMOTEIO;
 			break;
 		}
+#endif
 #if IS_ENABLED(CONFIG_IPV6)
 	} else if (skb->protocol == htons(ETH_P_IPV6)) {
 		harderr = pingv6_ops.icmpv6_err_convert(type, code, &err);
@@ -581,8 +574,10 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
 			goto out;
 	} else {
 		if (family == AF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
 			ip_icmp_error(sk, skb, err, 0 /* no remote port */,
 				      info, (u8 *)icmph);
+#endif
 #if IS_ENABLED(CONFIG_IPV6)
 		} else if (family == AF_INET6) {
 			pingv6_ops.ipv6_icmp_error(sk, skb, err, 0,
@@ -625,21 +620,6 @@ int ping_getfrag(void *from, char *to,
 	return 0;
 }
 
-static int ping_v4_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,
-				       struct flowi4 *fl4)
-{
-	struct sk_buff *skb = skb_peek(&sk->sk_write_queue);
-
-	if (!skb)
-		return 0;
-	pfh->wcheck = csum_partial((char *)&pfh->icmph,
-		sizeof(struct icmphdr), pfh->wcheck);
-	pfh->icmph.checksum = csum_fold(pfh->wcheck);
-	memcpy(icmp_hdr(skb), &pfh->icmph, sizeof(struct icmphdr));
-	skb->ip_summed = CHECKSUM_NONE;
-	return ip_push_pending_frames(sk, fl4);
-}
-
 int ping_common_sendmsg(int family, struct msghdr *msg, size_t len,
 			void *user_icmph, size_t icmph_len)
 {
@@ -685,160 +665,6 @@ int ping_common_sendmsg(int family, struct msghdr *msg, size_t len,
 	return 0;
 }
 
-static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
-{
-	DEFINE_RAW_FLEX(struct ip_options_rcu, opt_copy, opt.__data,
-			IP_OPTIONS_DATA_FIXED_SIZE);
-	struct net *net = sock_net(sk);
-	struct flowi4 fl4;
-	struct inet_sock *inet = inet_sk(sk);
-	struct ipcm_cookie ipc;
-	struct icmphdr user_icmph;
-	struct pingfakehdr pfh;
-	struct rtable *rt = NULL;
-	int free = 0;
-	__be32 saddr, daddr, faddr;
-	u8 scope;
-	int err;
-
-	pr_debug("ping_v4_sendmsg(sk=%p,sk->num=%u)\n", inet, inet->inet_num);
-
-	err = ping_common_sendmsg(AF_INET, msg, len, &user_icmph,
-				  sizeof(user_icmph));
-	if (err)
-		return err;
-
-	/*
-	 *	Get and verify the address.
-	 */
-
-	if (msg->msg_name) {
-		DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
-		if (msg->msg_namelen < sizeof(*usin))
-			return -EINVAL;
-		if (usin->sin_family != AF_INET)
-			return -EAFNOSUPPORT;
-		daddr = usin->sin_addr.s_addr;
-		/* no remote port */
-	} else {
-		if (sk->sk_state != TCP_ESTABLISHED)
-			return -EDESTADDRREQ;
-		daddr = inet->inet_daddr;
-		/* no remote port */
-	}
-
-	ipcm_init_sk(&ipc, inet);
-
-	if (msg->msg_controllen) {
-		err = ip_cmsg_send(sk, msg, &ipc, false);
-		if (unlikely(err)) {
-			kfree(ipc.opt);
-			return err;
-		}
-		if (ipc.opt)
-			free = 1;
-	}
-	if (!ipc.opt) {
-		struct ip_options_rcu *inet_opt;
-
-		rcu_read_lock();
-		inet_opt = rcu_dereference(inet->inet_opt);
-		if (inet_opt) {
-			memcpy(opt_copy, inet_opt,
-			       sizeof(*inet_opt) + inet_opt->opt.optlen);
-			ipc.opt = opt_copy;
-		}
-		rcu_read_unlock();
-	}
-
-	saddr = ipc.addr;
-	ipc.addr = faddr = daddr;
-
-	if (ipc.opt && ipc.opt->opt.srr) {
-		if (!daddr) {
-			err = -EINVAL;
-			goto out_free;
-		}
-		faddr = ipc.opt->opt.faddr;
-	}
-	scope = ip_sendmsg_scope(inet, &ipc, msg);
-
-	if (ipv4_is_multicast(daddr)) {
-		if (!ipc.oif || netif_index_is_l3_master(sock_net(sk), ipc.oif))
-			ipc.oif = READ_ONCE(inet->mc_index);
-		if (!saddr)
-			saddr = READ_ONCE(inet->mc_addr);
-	} else if (!ipc.oif)
-		ipc.oif = READ_ONCE(inet->uc_index);
-
-	flowi4_init_output(&fl4, ipc.oif, ipc.sockc.mark,
-			   ipc.tos & INET_DSCP_MASK, scope,
-			   sk->sk_protocol, inet_sk_flowi_flags(sk), faddr,
-			   saddr, 0, 0, sk_uid(sk));
-
-	fl4.fl4_icmp_type = user_icmph.type;
-	fl4.fl4_icmp_code = user_icmph.code;
-
-	security_sk_classify_flow(sk, flowi4_to_flowi_common(&fl4));
-	rt = ip_route_output_flow(net, &fl4, sk);
-	if (IS_ERR(rt)) {
-		err = PTR_ERR(rt);
-		rt = NULL;
-		if (err == -ENETUNREACH)
-			IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
-		goto out;
-	}
-
-	err = -EACCES;
-	if ((rt->rt_flags & RTCF_BROADCAST) &&
-	    !sock_flag(sk, SOCK_BROADCAST))
-		goto out;
-
-	if (msg->msg_flags & MSG_CONFIRM)
-		goto do_confirm;
-back_from_confirm:
-
-	if (!ipc.addr)
-		ipc.addr = fl4.daddr;
-
-	lock_sock(sk);
-
-	pfh.icmph.type = user_icmph.type; /* already checked */
-	pfh.icmph.code = user_icmph.code; /* ditto */
-	pfh.icmph.checksum = 0;
-	pfh.icmph.un.echo.id = inet->inet_sport;
-	pfh.icmph.un.echo.sequence = user_icmph.un.echo.sequence;
-	pfh.msg = msg;
-	pfh.wcheck = 0;
-	pfh.family = AF_INET;
-
-	err = ip_append_data(sk, &fl4, ping_getfrag, &pfh, len,
-			     sizeof(struct icmphdr), &ipc, &rt,
-			     msg->msg_flags);
-	if (err)
-		ip_flush_pending_frames(sk);
-	else
-		err = ping_v4_push_pending_frames(sk, &pfh, &fl4);
-	release_sock(sk);
-
-out:
-	ip_rt_put(rt);
-out_free:
-	if (free)
-		kfree(ipc.opt);
-	if (!err)
-		return len;
-	return err;
-
-do_confirm:
-	if (msg->msg_flags & MSG_PROBE)
-		dst_confirm_neigh(&rt->dst, &fl4.daddr);
-	if (!(msg->msg_flags & MSG_PROBE) || len)
-		goto back_from_confirm;
-	err = 0;
-	goto out;
-}
-
 int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
 {
 	struct inet_sock *isk = inet_sk(sk);
@@ -875,6 +701,7 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
 
 	/* Copy the address and add cmsg data. */
 	if (family == AF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
 		DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
 
 		if (sin) {
@@ -888,6 +715,7 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
 		if (inet_cmsg_flags(isk))
 			ip_cmsg_recv(msg, skb);
 
+#endif
 #if IS_ENABLED(CONFIG_IPV6)
 	} else if (family == AF_INET6) {
 		struct ipv6hdr *ip6 = ipv6_hdr(skb);
@@ -911,9 +739,11 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
 		if (skb->protocol == htons(ETH_P_IPV6) &&
 		    inet6_sk(sk)->rxopt.all)
 			pingv6_ops.ip6_datagram_recv_specific_ctl(sk, msg, skb);
+#if IS_ENABLED(CONFIG_IPV4)
 		else if (skb->protocol == htons(ETH_P_IP) &&
 			 inet_cmsg_flags(isk))
 			ip_cmsg_recv(msg, skb);
+#endif
 #endif
 	} else {
 		BUG();
@@ -976,27 +806,6 @@ enum skb_drop_reason ping_rcv(struct sk_buff *skb)
 	return SKB_DROP_REASON_NO_SOCKET;
 }
 
-struct proto ping_prot = {
-	.name =		"PING",
-	.owner =	THIS_MODULE,
-	.init =		ping_init_sock,
-	.close =	ping_close,
-	.pre_connect =	ping_pre_connect,
-	.connect =	ip4_datagram_connect,
-	.disconnect =	__udp_disconnect,
-	.setsockopt =	ip_setsockopt,
-	.getsockopt =	ip_getsockopt,
-	.sendmsg =	ping_v4_sendmsg,
-	.recvmsg =	ping_recvmsg,
-	.bind =		ping_bind,
-	.backlog_rcv =	ping_queue_rcv_skb,
-	.release_cb =	ip4_datagram_release_cb,
-	.unhash =	ping_unhash,
-	.get_port =	ping_get_port,
-	.put_port =	ping_unhash,
-	.obj_size =	sizeof(struct inet_sock),
-};
-
 #ifdef CONFIG_PROC_FS
 
 static struct sock *ping_get_first(struct seq_file *seq, int start)
diff --git a/net/ipv4/ping_ipv4.c b/net/ipv4/ping_ipv4.c
new file mode 100644
index 000000000000..09fc1d85846d
--- /dev/null
+++ b/net/ipv4/ping_ipv4.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		"Ping" sockets
+ *
+ *		IPv4 specific functions
+ *
+ *		code split from:
+ *		net/ipv4/ping.c
+ *
+ *		See ping.c for author information
+ */
+
+#include <linux/bpf-cgroup.h>
+#include <linux/in.h>
+#include <linux/socket.h>
+#include <linux/types.h>
+#include <net/ping.h>
+#include <net/udp.h>
+
+static int ping_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
+			    int addr_len)
+{
+	/* This check is replicated from __ip4_datagram_connect() and
+	 * intended to prevent BPF program called below from accessing bytes
+	 * that are out of the bound specified by user in addr_len.
+	 */
+	if (addr_len < sizeof(struct sockaddr_in))
+		return -EINVAL;
+
+	return BPF_CGROUP_RUN_PROG_INET4_CONNECT_LOCK(sk, uaddr, &addr_len);
+}
+
+static int ping_v4_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,
+				       struct flowi4 *fl4)
+{
+	struct sk_buff *skb = skb_peek(&sk->sk_write_queue);
+
+	if (!skb)
+		return 0;
+	pfh->wcheck = csum_partial((char *)&pfh->icmph,
+		sizeof(struct icmphdr), pfh->wcheck);
+	pfh->icmph.checksum = csum_fold(pfh->wcheck);
+	memcpy(icmp_hdr(skb), &pfh->icmph, sizeof(struct icmphdr));
+	skb->ip_summed = CHECKSUM_NONE;
+	return ip_push_pending_frames(sk, fl4);
+}
+
+static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
+{
+	DEFINE_RAW_FLEX(struct ip_options_rcu, opt_copy, opt.__data,
+			IP_OPTIONS_DATA_FIXED_SIZE);
+	struct net *net = sock_net(sk);
+	struct flowi4 fl4;
+	struct inet_sock *inet = inet_sk(sk);
+	struct ipcm_cookie ipc;
+	struct icmphdr user_icmph;
+	struct pingfakehdr pfh;
+	struct rtable *rt = NULL;
+	int free = 0;
+	__be32 saddr, daddr, faddr;
+	u8 scope;
+	int err;
+
+	pr_debug("ping_v4_sendmsg(sk=%p,sk->num=%u)\n", inet, inet->inet_num);
+
+	err = ping_common_sendmsg(AF_INET, msg, len, &user_icmph,
+				  sizeof(user_icmph));
+	if (err)
+		return err;
+
+	/*
+	 *	Get and verify the address.
+	 */
+
+	if (msg->msg_name) {
+		DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
+		if (msg->msg_namelen < sizeof(*usin))
+			return -EINVAL;
+		if (usin->sin_family != AF_INET)
+			return -EAFNOSUPPORT;
+		daddr = usin->sin_addr.s_addr;
+		/* no remote port */
+	} else {
+		if (sk->sk_state != TCP_ESTABLISHED)
+			return -EDESTADDRREQ;
+		daddr = inet->inet_daddr;
+		/* no remote port */
+	}
+
+	ipcm_init_sk(&ipc, inet);
+
+	if (msg->msg_controllen) {
+		err = ip_cmsg_send(sk, msg, &ipc, false);
+		if (unlikely(err)) {
+			kfree(ipc.opt);
+			return err;
+		}
+		if (ipc.opt)
+			free = 1;
+	}
+	if (!ipc.opt) {
+		struct ip_options_rcu *inet_opt;
+
+		rcu_read_lock();
+		inet_opt = rcu_dereference(inet->inet_opt);
+		if (inet_opt) {
+			memcpy(opt_copy, inet_opt,
+			       sizeof(*inet_opt) + inet_opt->opt.optlen);
+			ipc.opt = opt_copy;
+		}
+		rcu_read_unlock();
+	}
+
+	saddr = ipc.addr;
+	ipc.addr = faddr = daddr;
+
+	if (ipc.opt && ipc.opt->opt.srr) {
+		if (!daddr) {
+			err = -EINVAL;
+			goto out_free;
+		}
+		faddr = ipc.opt->opt.faddr;
+	}
+	scope = ip_sendmsg_scope(inet, &ipc, msg);
+
+	if (ipv4_is_multicast(daddr)) {
+		if (!ipc.oif || netif_index_is_l3_master(sock_net(sk), ipc.oif))
+			ipc.oif = READ_ONCE(inet->mc_index);
+		if (!saddr)
+			saddr = READ_ONCE(inet->mc_addr);
+	} else if (!ipc.oif)
+		ipc.oif = READ_ONCE(inet->uc_index);
+
+	flowi4_init_output(&fl4, ipc.oif, ipc.sockc.mark,
+			   ipc.tos & INET_DSCP_MASK, scope,
+			   sk->sk_protocol, inet_sk_flowi_flags(sk), faddr,
+			   saddr, 0, 0, sk_uid(sk));
+
+	fl4.fl4_icmp_type = user_icmph.type;
+	fl4.fl4_icmp_code = user_icmph.code;
+
+	security_sk_classify_flow(sk, flowi4_to_flowi_common(&fl4));
+	rt = ip_route_output_flow(net, &fl4, sk);
+	if (IS_ERR(rt)) {
+		err = PTR_ERR(rt);
+		rt = NULL;
+		if (err == -ENETUNREACH)
+			IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
+		goto out;
+	}
+
+	err = -EACCES;
+	if ((rt->rt_flags & RTCF_BROADCAST) &&
+	    !sock_flag(sk, SOCK_BROADCAST))
+		goto out;
+
+	if (msg->msg_flags & MSG_CONFIRM)
+		goto do_confirm;
+back_from_confirm:
+
+	if (!ipc.addr)
+		ipc.addr = fl4.daddr;
+
+	lock_sock(sk);
+
+	pfh.icmph.type = user_icmph.type; /* already checked */
+	pfh.icmph.code = user_icmph.code; /* ditto */
+	pfh.icmph.checksum = 0;
+	pfh.icmph.un.echo.id = inet->inet_sport;
+	pfh.icmph.un.echo.sequence = user_icmph.un.echo.sequence;
+	pfh.msg = msg;
+	pfh.wcheck = 0;
+	pfh.family = AF_INET;
+
+	err = ip_append_data(sk, &fl4, ping_getfrag, &pfh, len,
+			     sizeof(struct icmphdr), &ipc, &rt,
+			     msg->msg_flags);
+	if (err)
+		ip_flush_pending_frames(sk);
+	else
+		err = ping_v4_push_pending_frames(sk, &pfh, &fl4);
+	release_sock(sk);
+
+out:
+	ip_rt_put(rt);
+out_free:
+	if (free)
+		kfree(ipc.opt);
+	if (!err)
+		return len;
+	return err;
+
+do_confirm:
+	if (msg->msg_flags & MSG_PROBE)
+		dst_confirm_neigh(&rt->dst, &fl4.daddr);
+	if (!(msg->msg_flags & MSG_PROBE) || len)
+		goto back_from_confirm;
+	err = 0;
+	goto out;
+}
+
+struct proto ping_prot = {
+	.name =		"PING",
+	.owner =	THIS_MODULE,
+	.init =		ping_init_sock,
+	.close =	ping_close,
+	.pre_connect =	ping_pre_connect,
+	.connect =	ip4_datagram_connect,
+	.disconnect =	__udp_disconnect,
+	.setsockopt =	ip_setsockopt,
+	.getsockopt =	ip_getsockopt,
+	.sendmsg =	ping_v4_sendmsg,
+	.recvmsg =	ping_recvmsg,
+	.bind =		ping_bind,
+	.backlog_rcv =	ping_queue_rcv_skb,
+	.release_cb =	ip4_datagram_release_cb,
+	.unhash =	ping_unhash,
+	.get_port =	ping_get_port,
+	.put_port =	ping_unhash,
+	.obj_size =	sizeof(struct inet_sock),
+};
-- 
2.54.0


^ permalink raw reply related

* [PATCH 07/13 RFC net-next] net: icmp: split IPv4 specific logic into icmp_ipv4.c
From: Fernando Fernandez Mancera @ 2026-07-12  1:39 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
	Fernando Fernandez Mancera, Ard Biesheuvel, Neal Cardwell,
	Florian Westphal, Eric Biggers, Kuniyuki Iwashima,
	Willem de Bruijn, Christian Brauner, Jeff Layton, Kees Cook,
	Qi Tang, linux-kernel
In-Reply-To: <20260712013941.4570-1-fmancera@suse.de>

To enable compiling INET subsystem withot IPv4, ICMP protocol IPv4
specific functions must be isolated from the generic ICMP protocol
infrastructure.

This patch creates icmp_ipv4.c and move all the functions for packet
processing, error handling, and other from icmp.c to a new file. The
Makefile is updated to compile icmp_ipv4.c only when CONFIG_IPV4 is
enabled.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 include/net/icmp.h   |    1 +
 net/ipv4/Makefile    |    9 +-
 net/ipv4/icmp.c      | 1407 +----------------------------------------
 net/ipv4/icmp_ipv4.c | 1425 ++++++++++++++++++++++++++++++++++++++++++
 net/ipv6/datagram.c  |    2 +
 5 files changed, 1441 insertions(+), 1403 deletions(-)
 create mode 100644 net/ipv4/icmp_ipv4.c

diff --git a/include/net/icmp.h b/include/net/icmp.h
index 935ee13d9ae9..85ad655aea5f 100644
--- a/include/net/icmp.h
+++ b/include/net/icmp.h
@@ -57,6 +57,7 @@ static inline void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __b
 
 int icmp_rcv(struct sk_buff *skb);
 int icmp_err(struct sk_buff *skb, u32 info);
+int icmp_init_ipv4(void);
 int icmp_init(void);
 void icmp_out_count(struct net *net, unsigned char type);
 bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr);
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 48ef3fb3b164..651835073a29 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -8,12 +8,13 @@ obj-y     := inetpeer.o protocol.o inet_hashtables.o inet_timewait_sock.o \
 	     tcp_minisocks.o tcp_cong.o tcp_metrics.o tcp_fastopen.o \
 	     tcp_recovery.o tcp_ulp.o tcp_offload.o tcp_plb.o raw.o udp.o \
 	     udp_offload.o af_inet.o inet_fragment.o ping.o ip_tunnel_core.o \
-	     gre_offload.o metrics.o netlink.o nexthop.o udp_tunnel_stub.o
+	     gre_offload.o metrics.o netlink.o nexthop.o udp_tunnel_stub.o \
+	     icmp.o
 
 obj-$(CONFIG_IPV4) += route.o ip_input.o ip_fragment.o ip_forward.o ip_options.o \
-		      ip_sockglue.o tcp_ipv4.o datagram.o icmp.o arp.o devinet.o \
-		      igmp.o fib_notifier.o ip_output.o fib_frontend.o \
-		      fib_semantics.o fib_trie.o raw_ipv4.o udp_ipv4.o
+		      ip_sockglue.o tcp_ipv4.o datagram.o arp.o devinet.o igmp.o \
+		      fib_notifier.o ip_output.o fib_frontend.o fib_semantics.o \
+		      fib_trie.o raw_ipv4.o udp_ipv4.o icmp_ipv4.o
 
 obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
 obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 23e921d313b3..3c777f6af276 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -96,134 +96,6 @@
 #include <net/l3mdev.h>
 #include <net/addrconf.h>
 #include <net/inet_dscp.h>
-#define CREATE_TRACE_POINTS
-#include <trace/events/icmp.h>
-
-/*
- *	Build xmit assembly blocks
- */
-
-struct icmp_bxm {
-	struct sk_buff *skb;
-	int offset;
-	int data_len;
-
-	struct {
-		struct icmphdr icmph;
-		__be32	       times[3];
-	} data;
-	int head_len;
-
-	/* Must be last as it ends in a flexible-array member. */
-	struct ip_options_rcu replyopts;
-};
-
-/* An array of errno for error messages from dest unreach. */
-/* RFC 1122: 3.2.2.1 States that NET_UNREACH, HOST_UNREACH and SR_FAILED MUST be considered 'transient errs'. */
-
-const struct icmp_err icmp_err_convert[] = {
-	{
-		.errno = ENETUNREACH,	/* ICMP_NET_UNREACH */
-		.fatal = 0,
-	},
-	{
-		.errno = EHOSTUNREACH,	/* ICMP_HOST_UNREACH */
-		.fatal = 0,
-	},
-	{
-		.errno = ENOPROTOOPT	/* ICMP_PROT_UNREACH */,
-		.fatal = 1,
-	},
-	{
-		.errno = ECONNREFUSED,	/* ICMP_PORT_UNREACH */
-		.fatal = 1,
-	},
-	{
-		.errno = EMSGSIZE,	/* ICMP_FRAG_NEEDED */
-		.fatal = 0,
-	},
-	{
-		.errno = EOPNOTSUPP,	/* ICMP_SR_FAILED */
-		.fatal = 0,
-	},
-	{
-		.errno = ENETUNREACH,	/* ICMP_NET_UNKNOWN */
-		.fatal = 1,
-	},
-	{
-		.errno = EHOSTDOWN,	/* ICMP_HOST_UNKNOWN */
-		.fatal = 1,
-	},
-	{
-		.errno = ENONET,	/* ICMP_HOST_ISOLATED */
-		.fatal = 1,
-	},
-	{
-		.errno = ENETUNREACH,	/* ICMP_NET_ANO	*/
-		.fatal = 1,
-	},
-	{
-		.errno = EHOSTUNREACH,	/* ICMP_HOST_ANO */
-		.fatal = 1,
-	},
-	{
-		.errno = ENETUNREACH,	/* ICMP_NET_UNR_TOS */
-		.fatal = 0,
-	},
-	{
-		.errno = EHOSTUNREACH,	/* ICMP_HOST_UNR_TOS */
-		.fatal = 0,
-	},
-	{
-		.errno = EHOSTUNREACH,	/* ICMP_PKT_FILTERED */
-		.fatal = 1,
-	},
-	{
-		.errno = EHOSTUNREACH,	/* ICMP_PREC_VIOLATION */
-		.fatal = 1,
-	},
-	{
-		.errno = EHOSTUNREACH,	/* ICMP_PREC_CUTOFF */
-		.fatal = 1,
-	},
-};
-EXPORT_SYMBOL(icmp_err_convert);
-
-/*
- *	ICMP control array. This specifies what to do with each ICMP.
- */
-
-struct icmp_control {
-	enum skb_drop_reason (*handler)(struct sk_buff *skb);
-	short   error;		/* This ICMP is classed as an error message */
-};
-
-static const struct icmp_control icmp_pointers[NR_ICMP_TYPES+1];
-
-static DEFINE_PER_CPU(struct sock *, ipv4_icmp_sk);
-
-/* Called with BH disabled */
-static inline struct sock *icmp_xmit_lock(struct net *net)
-{
-	struct sock *sk;
-
-	sk = this_cpu_read(ipv4_icmp_sk);
-
-	if (unlikely(!spin_trylock(&sk->sk_lock.slock))) {
-		/* This can happen if the output path signals a
-		 * dst_link_failure() for an outgoing ICMP packet.
-		 */
-		return NULL;
-	}
-	sock_net_set(sk, net);
-	return sk;
-}
-
-static inline void icmp_xmit_unlock(struct sock *sk)
-{
-	sock_net_set(sk, &init_net);
-	spin_unlock(&sk->sk_lock.slock);
-}
 
 /**
  * icmp_global_allow - Are we allowed to send one more ICMP message ?
@@ -274,77 +146,6 @@ void icmp_global_consume(struct net *net)
 		atomic_sub(credits, &net->ipv4.icmp_global_credit);
 }
 
-static bool icmpv4_mask_allow(struct net *net, int type, int code)
-{
-	if (type > NR_ICMP_TYPES)
-		return true;
-
-	/* Don't limit PMTU discovery. */
-	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
-		return true;
-
-	/* Limit if icmp type is enabled in ratemask. */
-	if (!((1 << type) & READ_ONCE(net->ipv4.sysctl_icmp_ratemask)))
-		return true;
-
-	return false;
-}
-
-static bool icmpv4_global_allow(struct net *net, int type, int code,
-				bool *apply_ratelimit)
-{
-	if (icmpv4_mask_allow(net, type, code))
-		return true;
-
-	if (icmp_global_allow(net)) {
-		*apply_ratelimit = true;
-		return true;
-	}
-	__ICMP_INC_STATS(net, ICMP_MIB_RATELIMITGLOBAL);
-	return false;
-}
-
-/*
- *	Send an ICMP frame.
- */
-
-static bool icmpv4_xrlim_allow(struct net *net, struct rtable *rt,
-			       struct flowi4 *fl4, int type, int code,
-			       bool apply_ratelimit)
-{
-	struct dst_entry *dst = &rt->dst;
-	struct inet_peer *peer;
-	struct net_device *dev;
-	int peer_timeout;
-	bool rc = true;
-
-	if (!apply_ratelimit)
-		return true;
-
-	peer_timeout = READ_ONCE(net->ipv4.sysctl_icmp_ratelimit);
-	if (!peer_timeout)
-		goto out;
-
-	/* No rate limit on loopback */
-	rcu_read_lock();
-	dev = dst_dev_rcu(dst);
-	if (dev && (dev->flags & IFF_LOOPBACK))
-		goto out_unlock;
-
-	peer = inet_getpeer_v4(net->ipv4.peers, fl4->daddr,
-			       l3mdev_master_ifindex_rcu(dev));
-	rc = inet_peer_xrlim_allow(peer, peer_timeout);
-
-out_unlock:
-	rcu_read_unlock();
-out:
-	if (!rc)
-		__ICMP_INC_STATS(net, ICMP_MIB_RATELIMITHOST);
-	else
-		icmp_global_consume(net);
-	return rc;
-}
-
 /*
  *	Maintain the counters used in the SNMP statistics for outgoing ICMP
  */
@@ -354,921 +155,6 @@ void icmp_out_count(struct net *net, unsigned char type)
 	ICMP_INC_STATS(net, ICMP_MIB_OUTMSGS);
 }
 
-/*
- *	Checksum each fragment, and on the first include the headers and final
- *	checksum.
- */
-static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd,
-			  struct sk_buff *skb)
-{
-	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
-			IP_OPTIONS_DATA_FIXED_SIZE);
-	__wsum csum;
-
-	icmp_param = from;
-
-	csum = skb_copy_and_csum_bits(icmp_param->skb,
-				      icmp_param->offset + offset,
-				      to, len);
-
-	skb->csum = csum_block_add(skb->csum, csum, odd);
-	if (icmp_param->data.icmph.type <= NR_ICMP_TYPES &&
-	    icmp_pointers[array_index_nospec(icmp_param->data.icmph.type,
-					     NR_ICMP_TYPES + 1)].error)
-		nf_ct_attach(skb, icmp_param->skb);
-	return 0;
-}
-
-static void icmp_push_reply(struct sock *sk,
-			    struct icmp_bxm *icmp_param,
-			    struct flowi4 *fl4,
-			    struct ipcm_cookie *ipc, struct rtable **rt)
-{
-	struct sk_buff *skb;
-
-	if (ip_append_data(sk, fl4, icmp_glue_bits, icmp_param,
-			   icmp_param->data_len+icmp_param->head_len,
-			   icmp_param->head_len,
-			   ipc, rt, MSG_DONTWAIT) < 0) {
-		__ICMP_INC_STATS(sock_net(sk), ICMP_MIB_OUTERRORS);
-		ip_flush_pending_frames(sk);
-	} else if ((skb = skb_peek(&sk->sk_write_queue)) != NULL) {
-		struct icmphdr *icmph = icmp_hdr(skb);
-		__wsum csum;
-		struct sk_buff *skb1;
-
-		csum = csum_partial_copy_nocheck((void *)&icmp_param->data,
-						 (char *)icmph,
-						 icmp_param->head_len);
-		skb_queue_walk(&sk->sk_write_queue, skb1) {
-			csum = csum_add(csum, skb1->csum);
-		}
-		icmph->checksum = csum_fold(csum);
-		skb->ip_summed = CHECKSUM_NONE;
-		ip_push_pending_frames(sk, fl4);
-	}
-}
-
-/*
- *	Driving logic for building and sending ICMP messages.
- */
-
-static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
-{
-	struct rtable *rt = skb_rtable(skb);
-	struct net *net = dev_net_rcu(rt->dst.dev);
-	bool apply_ratelimit = false;
-	struct ipcm_cookie ipc;
-	struct flowi4 fl4;
-	struct sock *sk;
-	__be32 daddr, saddr;
-	u32 mark = IP4_REPLY_MARK(net, skb->mark);
-	int type = icmp_param->data.icmph.type;
-	int code = icmp_param->data.icmph.code;
-
-	if (ip_options_echo(net, &icmp_param->replyopts.opt, skb))
-		return;
-
-	/* Needed by both icmpv4_global_allow and icmp_xmit_lock */
-	local_bh_disable();
-
-	/* is global icmp_msgs_per_sec exhausted ? */
-	if (!icmpv4_global_allow(net, type, code, &apply_ratelimit))
-		goto out_bh_enable;
-
-	sk = icmp_xmit_lock(net);
-	if (!sk)
-		goto out_bh_enable;
-
-	icmp_param->data.icmph.checksum = 0;
-
-	ipcm_init(&ipc);
-	ipc.tos = ip_hdr(skb)->tos;
-	ipc.sockc.mark = mark;
-	daddr = ipc.addr = ip_hdr(skb)->saddr;
-	saddr = fib_compute_spec_dst(skb);
-
-	if (icmp_param->replyopts.opt.optlen) {
-		ipc.opt = &icmp_param->replyopts;
-		if (ipc.opt->opt.srr)
-			daddr = icmp_param->replyopts.opt.faddr;
-	}
-	memset(&fl4, 0, sizeof(fl4));
-	fl4.daddr = daddr;
-	fl4.saddr = saddr;
-	fl4.flowi4_mark = mark;
-	fl4.flowi4_uid = sock_net_uid(net, NULL);
-	fl4.flowi4_dscp = ip4h_dscp(ip_hdr(skb));
-	fl4.flowi4_proto = IPPROTO_ICMP;
-	fl4.flowi4_oif = l3mdev_master_ifindex(skb->dev);
-	security_skb_classify_flow(skb, flowi4_to_flowi_common(&fl4));
-	rt = ip_route_output_key(net, &fl4);
-	if (IS_ERR(rt))
-		goto out_unlock;
-	if (icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
-		icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
-	ip_rt_put(rt);
-out_unlock:
-	icmp_xmit_unlock(sk);
-out_bh_enable:
-	local_bh_enable();
-}
-
-/*
- * The device used for looking up which routing table to use for sending an ICMP
- * error is preferably the source whenever it is set, which should ensure the
- * icmp error can be sent to the source host, else lookup using the routing
- * table of the destination device, else use the main routing table (index 0).
- */
-static struct net_device *icmp_get_route_lookup_dev(struct sk_buff *skb)
-{
-	struct net_device *dev = skb->dev;
-	const struct dst_entry *dst;
-
-	if (dev)
-		return dev;
-	dst = skb_dst(skb);
-	return dst ? dst_dev(dst) : NULL;
-}
-
-static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
-					struct sk_buff *skb_in,
-					const struct iphdr *iph, __be32 saddr,
-					dscp_t dscp, u32 mark, int type,
-					int code, struct icmp_bxm *param)
-{
-	struct net_device *route_lookup_dev;
-	struct dst_entry *dst, *dst2;
-	struct rtable *rt, *rt2;
-	struct flowi4 fl4_dec;
-	int err;
-
-	memset(fl4, 0, sizeof(*fl4));
-	fl4->daddr = (param->replyopts.opt.srr ?
-		      param->replyopts.opt.faddr : iph->saddr);
-	fl4->saddr = saddr;
-	fl4->flowi4_mark = mark;
-	fl4->flowi4_uid = sock_net_uid(net, NULL);
-	fl4->flowi4_dscp = dscp;
-	fl4->flowi4_proto = IPPROTO_ICMP;
-	fl4->fl4_icmp_type = type;
-	fl4->fl4_icmp_code = code;
-	route_lookup_dev = icmp_get_route_lookup_dev(skb_in);
-	fl4->flowi4_oif = l3mdev_master_ifindex(route_lookup_dev);
-
-	security_skb_classify_flow(skb_in, flowi4_to_flowi_common(fl4));
-	rt = ip_route_output_key_hash(net, fl4, skb_in);
-	if (IS_ERR(rt))
-		return rt;
-
-	/* No need to clone since we're just using its address. */
-	rt2 = rt;
-
-	dst = xfrm_lookup(net, &rt->dst,
-			  flowi4_to_flowi(fl4), NULL, 0);
-	rt = dst_rtable(dst);
-	if (!IS_ERR(dst)) {
-		if (rt != rt2)
-			return rt;
-		if (inet_addr_type_dev_table(net, route_lookup_dev,
-					     fl4->daddr) == RTN_LOCAL)
-			return rt;
-	} else if (PTR_ERR(dst) == -EPERM) {
-		rt = NULL;
-	} else {
-		return rt;
-	}
-	err = xfrm_decode_session_reverse(net, skb_in, flowi4_to_flowi(&fl4_dec), AF_INET);
-	if (err)
-		goto relookup_failed;
-
-	if (inet_addr_type_dev_table(net, route_lookup_dev,
-				     fl4_dec.saddr) == RTN_LOCAL) {
-		rt2 = __ip_route_output_key(net, &fl4_dec);
-		if (IS_ERR(rt2))
-			err = PTR_ERR(rt2);
-	} else {
-		struct flowi4 fl4_2 = {};
-		unsigned long orefdst;
-
-		fl4_2.daddr = fl4_dec.saddr;
-		rt2 = ip_route_output_key(net, &fl4_2);
-		if (IS_ERR(rt2)) {
-			err = PTR_ERR(rt2);
-			goto relookup_failed;
-		}
-		/* Ugh! */
-		orefdst = skb_dstref_steal(skb_in);
-		err = ip_route_input(skb_in, fl4_dec.daddr, fl4_dec.saddr,
-				     dscp, rt2->dst.dev) ? -EINVAL : 0;
-
-		dst_release(&rt2->dst);
-		rt2 = skb_rtable(skb_in);
-		/* steal dst entry from skb_in, don't drop refcnt */
-		skb_dstref_steal(skb_in);
-		skb_dstref_restore(skb_in, orefdst);
-
-		/*
-		 * At this point, fl4_dec.daddr should NOT be local (we
-		 * checked fl4_dec.saddr above). However, a race condition
-		 * may occur if the address is added to the interface
-		 * concurrently. In that case, ip_route_input() returns a
-		 * LOCAL route with dst.output=ip_rt_bug, which must not
-		 * be used for output.
-		 */
-		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
-			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
-					     &fl4_dec.daddr, &fl4_dec.saddr);
-			dst_release(&rt2->dst);
-			err = -EINVAL;
-		}
-	}
-
-	if (err)
-		goto relookup_failed;
-
-	dst2 = xfrm_lookup(net, &rt2->dst, flowi4_to_flowi(&fl4_dec), NULL,
-			   XFRM_LOOKUP_ICMP);
-	rt2 = dst_rtable(dst2);
-	if (!IS_ERR(dst2)) {
-		dst_release(&rt->dst);
-		rt = rt2;
-	} else if (PTR_ERR(dst2) == -EPERM) {
-		if (rt)
-			dst_release(&rt->dst);
-		return rt2;
-	} else {
-		err = PTR_ERR(dst2);
-		goto relookup_failed;
-	}
-	return rt;
-
-relookup_failed:
-	if (rt)
-		return rt;
-	return ERR_PTR(err);
-}
-
-struct icmp_ext_iio_addr4_subobj {
-	__be16 afi;
-	__be16 reserved;
-	__be32 addr4;
-};
-
-static unsigned int icmp_ext_iio_len(void)
-{
-	return sizeof(struct icmp_extobj_hdr) +
-		/* ifIndex */
-		sizeof(__be32) +
-		/* Interface Address Sub-Object */
-		sizeof(struct icmp_ext_iio_addr4_subobj) +
-		/* Interface Name Sub-Object. Length must be a multiple of 4
-		 * bytes.
-		 */
-		ALIGN(sizeof(struct icmp_ext_iio_name_subobj), 4) +
-		/* MTU */
-		sizeof(__be32);
-}
-
-static unsigned int icmp_ext_max_len(u8 ext_objs)
-{
-	unsigned int ext_max_len;
-
-	ext_max_len = sizeof(struct icmp_ext_hdr);
-
-	if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
-		ext_max_len += icmp_ext_iio_len();
-
-	return ext_max_len;
-}
-
-static __be32 icmp_ext_iio_addr4_find(const struct net_device *dev)
-{
-	struct in_device *in_dev;
-	struct in_ifaddr *ifa;
-
-	in_dev = __in_dev_get_rcu(dev);
-	if (!in_dev)
-		return 0;
-
-	/* It is unclear from RFC 5837 which IP address should be chosen, but
-	 * it makes sense to choose a global unicast address.
-	 */
-	in_dev_for_each_ifa_rcu(ifa, in_dev) {
-		if (READ_ONCE(ifa->ifa_flags) & IFA_F_SECONDARY)
-			continue;
-		if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
-		    ipv4_is_multicast(ifa->ifa_address))
-			continue;
-		return ifa->ifa_address;
-	}
-
-	return 0;
-}
-
-static void icmp_ext_iio_iif_append(struct net *net, struct sk_buff *skb,
-				    int iif)
-{
-	struct icmp_ext_iio_name_subobj *name_subobj;
-	struct icmp_extobj_hdr *objh;
-	struct net_device *dev;
-	__be32 data;
-
-	if (!iif)
-		return;
-
-	/* Add the fields in the order specified by RFC 5837. */
-	objh = skb_put(skb, sizeof(*objh));
-	objh->class_num = ICMP_EXT_OBJ_CLASS_IIO;
-	objh->class_type = ICMP_EXT_CTYPE_IIO_ROLE(ICMP_EXT_CTYPE_IIO_ROLE_IIF);
-
-	data = htonl(iif);
-	skb_put_data(skb, &data, sizeof(__be32));
-	objh->class_type |= ICMP_EXT_CTYPE_IIO_IFINDEX;
-
-	rcu_read_lock();
-
-	dev = dev_get_by_index_rcu(net, iif);
-	if (!dev)
-		goto out;
-
-	data = icmp_ext_iio_addr4_find(dev);
-	if (data) {
-		struct icmp_ext_iio_addr4_subobj *addr4_subobj;
-
-		addr4_subobj = skb_put_zero(skb, sizeof(*addr4_subobj));
-		addr4_subobj->afi = htons(ICMP_AFI_IP);
-		addr4_subobj->addr4 = data;
-		objh->class_type |= ICMP_EXT_CTYPE_IIO_IPADDR;
-	}
-
-	name_subobj = skb_put_zero(skb, ALIGN(sizeof(*name_subobj), 4));
-	name_subobj->len = ALIGN(sizeof(*name_subobj), 4);
-	netdev_copy_name(dev, name_subobj->name);
-	objh->class_type |= ICMP_EXT_CTYPE_IIO_NAME;
-
-	data = htonl(READ_ONCE(dev->mtu));
-	skb_put_data(skb, &data, sizeof(__be32));
-	objh->class_type |= ICMP_EXT_CTYPE_IIO_MTU;
-
-out:
-	rcu_read_unlock();
-	objh->length = htons(skb_tail_pointer(skb) - (unsigned char *)objh);
-}
-
-static void icmp_ext_objs_append(struct net *net, struct sk_buff *skb,
-				 u8 ext_objs, int iif)
-{
-	if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
-		icmp_ext_iio_iif_append(net, skb, iif);
-}
-
-static struct sk_buff *
-icmp_ext_append(struct net *net, struct sk_buff *skb_in, struct icmphdr *icmph,
-		unsigned int room, int iif)
-{
-	unsigned int payload_len, ext_max_len, ext_len;
-	struct icmp_ext_hdr *ext_hdr;
-	struct sk_buff *skb;
-	u8 ext_objs;
-	int nhoff;
-
-	switch (icmph->type) {
-	case ICMP_DEST_UNREACH:
-	case ICMP_TIME_EXCEEDED:
-	case ICMP_PARAMETERPROB:
-		break;
-	default:
-		return NULL;
-	}
-
-	ext_objs = READ_ONCE(net->ipv4.sysctl_icmp_errors_extension_mask);
-	if (!ext_objs)
-		return NULL;
-
-	ext_max_len = icmp_ext_max_len(ext_objs);
-	if (ICMP_EXT_ORIG_DGRAM_MIN_LEN + ext_max_len > room)
-		return NULL;
-
-	skb = skb_clone(skb_in, GFP_ATOMIC);
-	if (!skb)
-		return NULL;
-
-	nhoff = skb_network_offset(skb);
-	payload_len = min(skb->len - nhoff, ICMP_EXT_ORIG_DGRAM_MIN_LEN);
-
-	if (!pskb_network_may_pull(skb, payload_len))
-		goto free_skb;
-
-	if (pskb_trim(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN) ||
-	    __skb_put_padto(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN, false))
-		goto free_skb;
-
-	if (pskb_expand_head(skb, 0, ext_max_len, GFP_ATOMIC))
-		goto free_skb;
-
-	ext_hdr = skb_put_zero(skb, sizeof(*ext_hdr));
-	ext_hdr->version = ICMP_EXT_VERSION_2;
-
-	icmp_ext_objs_append(net, skb, ext_objs, iif);
-
-	/* Do not send an empty extension structure. */
-	ext_len = skb_tail_pointer(skb) - (unsigned char *)ext_hdr;
-	if (ext_len == sizeof(*ext_hdr))
-		goto free_skb;
-
-	ext_hdr->checksum = ip_compute_csum(ext_hdr, ext_len);
-	/* The length of the original datagram in 32-bit words (RFC 4884). */
-	icmph->un.reserved[1] = ICMP_EXT_ORIG_DGRAM_MIN_LEN / sizeof(u32);
-
-	return skb;
-
-free_skb:
-	consume_skb(skb);
-	return NULL;
-}
-
-/*
- *	Send an ICMP message in response to a situation
- *
- *	RFC 1122: 3.2.2	MUST send at least the IP header and 8 bytes of header.
- *		  MAY send more (we do).
- *			MUST NOT change this header information.
- *			MUST NOT reply to a multicast/broadcast IP address.
- *			MUST NOT reply to a multicast/broadcast MAC address.
- *			MUST reply to only the first fragment.
- */
-
-void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
-		 const struct inet_skb_parm *parm)
-{
-	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
-			IP_OPTIONS_DATA_FIXED_SIZE);
-	struct iphdr *iph;
-	int room;
-	struct rtable *rt = skb_rtable(skb_in);
-	bool apply_ratelimit = false;
-	struct sk_buff *ext_skb;
-	struct ipcm_cookie ipc;
-	struct flowi4 fl4;
-	__be32 saddr;
-	u8  tos;
-	u32 mark;
-	struct net *net;
-	struct sock *sk;
-
-	if (!rt)
-		return;
-
-	rcu_read_lock();
-
-	if (rt->dst.dev)
-		net = dev_net_rcu(rt->dst.dev);
-	else if (skb_in->dev)
-		net = dev_net_rcu(skb_in->dev);
-	else
-		goto out;
-
-	/*
-	 *	Find the original header. It is expected to be valid, of course.
-	 *	Check this, icmp_send is called from the most obscure devices
-	 *	sometimes.
-	 */
-	iph = ip_hdr(skb_in);
-
-	if ((u8 *)iph < skb_in->head ||
-	    (skb_network_header(skb_in) + sizeof(*iph)) >
-	    skb_tail_pointer(skb_in))
-		goto out;
-
-	/*
-	 *	No replies to physical multicast/broadcast
-	 */
-	if (skb_in->pkt_type != PACKET_HOST)
-		goto out;
-
-	/*
-	 *	Now check at the protocol level
-	 */
-	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
-		goto out;
-
-	/*
-	 *	Only reply to fragment 0. We byte re-order the constant
-	 *	mask for efficiency.
-	 */
-	if (iph->frag_off & htons(IP_OFFSET))
-		goto out;
-
-	/*
-	 *	If we send an ICMP error to an ICMP error a mess would result..
-	 */
-	if (icmp_pointers[type].error) {
-		/*
-		 *	We are an error, check if we are replying to an
-		 *	ICMP error
-		 */
-		if (iph->protocol == IPPROTO_ICMP) {
-			u8 _inner_type, *itp;
-
-			itp = skb_header_pointer(skb_in,
-						 skb_network_header(skb_in) +
-						 (iph->ihl << 2) +
-						 offsetof(struct icmphdr,
-							  type) -
-						 skb_in->data,
-						 sizeof(_inner_type),
-						 &_inner_type);
-			if (!itp)
-				goto out;
-
-			/*
-			 *	Assume any unknown ICMP type is an error. This
-			 *	isn't specified by the RFC, but think about it..
-			 */
-			if (*itp > NR_ICMP_TYPES ||
-			    icmp_pointers[*itp].error)
-				goto out;
-		}
-	}
-
-	/* Needed by both icmpv4_global_allow and icmp_xmit_lock */
-	local_bh_disable();
-
-	/* Check global sysctl_icmp_msgs_per_sec ratelimit, unless
-	 * incoming dev is loopback.  If outgoing dev change to not be
-	 * loopback, then peer ratelimit still work (in icmpv4_xrlim_allow)
-	 */
-	if (!(skb_in->dev && (skb_in->dev->flags&IFF_LOOPBACK)) &&
-	      !icmpv4_global_allow(net, type, code, &apply_ratelimit))
-		goto out_bh_enable;
-
-	sk = icmp_xmit_lock(net);
-	if (!sk)
-		goto out_bh_enable;
-
-	/*
-	 *	Construct source address and options.
-	 */
-
-	saddr = iph->daddr;
-	if (!(rt->rt_flags & RTCF_LOCAL)) {
-		struct net_device *dev = NULL;
-
-		rcu_read_lock();
-		if (rt_is_input_route(rt) &&
-		    READ_ONCE(net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr))
-			dev = dev_get_by_index_rcu(net, parm->iif ? parm->iif :
-						   inet_iif(skb_in));
-
-		if (dev)
-			saddr = inet_select_addr(dev, iph->saddr,
-						 RT_SCOPE_LINK);
-		else
-			saddr = 0;
-		rcu_read_unlock();
-	}
-
-	tos = icmp_pointers[type].error ? (RT_TOS(iph->tos) |
-					   IPTOS_PREC_INTERNETCONTROL) :
-					   iph->tos;
-	mark = IP4_REPLY_MARK(net, skb_in->mark);
-
-	if (__ip_options_echo(net, &icmp_param->replyopts.opt, skb_in,
-			      &parm->opt))
-		goto out_unlock;
-
-
-	/*
-	 *	Prepare data for ICMP header.
-	 */
-
-	icmp_param->data.icmph.type	 = type;
-	icmp_param->data.icmph.code	 = code;
-	icmp_param->data.icmph.un.gateway = info;
-	icmp_param->data.icmph.checksum	 = 0;
-	icmp_param->skb	  = skb_in;
-	icmp_param->offset = skb_network_offset(skb_in);
-	ipcm_init(&ipc);
-	ipc.tos = tos;
-	ipc.addr = iph->saddr;
-	ipc.opt = &icmp_param->replyopts;
-	ipc.sockc.mark = mark;
-
-	rt = icmp_route_lookup(net, &fl4, skb_in, iph, saddr,
-			       inet_dsfield_to_dscp(tos), mark, type, code,
-			       icmp_param);
-	if (IS_ERR(rt))
-		goto out_unlock;
-
-	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
-		goto ende;
-
-	/* peer icmp_ratelimit */
-	if (!icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
-		goto ende;
-
-	/* RFC says return as much as we can without exceeding 576 bytes. */
-
-	room = dst4_mtu(&rt->dst);
-	if (room > 576)
-		room = 576;
-	room -= sizeof(struct iphdr) + icmp_param->replyopts.opt.optlen;
-	room -= sizeof(struct icmphdr);
-	/* Guard against tiny mtu. We need to include at least one
-	 * IP network header for this message to make any sense.
-	 */
-	if (room <= (int)sizeof(struct iphdr))
-		goto ende;
-
-	ext_skb = icmp_ext_append(net, skb_in, &icmp_param->data.icmph, room,
-				  parm->iif);
-	if (ext_skb)
-		icmp_param->skb = ext_skb;
-
-	icmp_param->data_len = icmp_param->skb->len - icmp_param->offset;
-	if (icmp_param->data_len > room)
-		icmp_param->data_len = room;
-	icmp_param->head_len = sizeof(struct icmphdr);
-
-	/* if we don't have a source address at this point, fall back to the
-	 * dummy address instead of sending out a packet with a source address
-	 * of 0.0.0.0
-	 */
-	if (!fl4.saddr)
-		fl4.saddr = htonl(INADDR_DUMMY);
-
-	trace_icmp_send(skb_in, type, code);
-
-	icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
-
-	if (ext_skb)
-		consume_skb(ext_skb);
-ende:
-	ip_rt_put(rt);
-out_unlock:
-	icmp_xmit_unlock(sk);
-out_bh_enable:
-	local_bh_enable();
-out:
-	rcu_read_unlock();
-}
-EXPORT_SYMBOL(__icmp_send);
-
-#if IS_ENABLED(CONFIG_NF_NAT)
-#include <net/netfilter/nf_conntrack.h>
-void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
-{
-	struct sk_buff *cloned_skb = NULL;
-	enum ip_conntrack_info ctinfo;
-	enum ip_conntrack_dir dir;
-	struct inet_skb_parm parm;
-	struct nf_conn *ct;
-	__be32 orig_ip;
-
-	memset(&parm, 0, sizeof(parm));
-	ct = nf_ct_get(skb_in, &ctinfo);
-	if (!ct || !(READ_ONCE(ct->status) & IPS_NAT_MASK)) {
-		__icmp_send(skb_in, type, code, info, &parm);
-		return;
-	}
-
-	if (skb_shared(skb_in))
-		skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
-
-	if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
-	    (skb_network_header(skb_in) + sizeof(struct iphdr)) >
-	    skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
-	    skb_network_offset(skb_in) + sizeof(struct iphdr))))
-		goto out;
-
-	orig_ip = ip_hdr(skb_in)->saddr;
-	dir = CTINFO2DIR(ctinfo);
-	ip_hdr(skb_in)->saddr = ct->tuplehash[dir].tuple.src.u3.ip;
-	__icmp_send(skb_in, type, code, info, &parm);
-	ip_hdr(skb_in)->saddr = orig_ip;
-out:
-	consume_skb(cloned_skb);
-}
-EXPORT_SYMBOL(icmp_ndo_send);
-#endif
-
-static void icmp_socket_deliver(struct sk_buff *skb, u32 info)
-{
-	const struct iphdr *iph = (const struct iphdr *)skb->data;
-	const struct net_protocol *ipprot;
-	int protocol = iph->protocol;
-
-	/* Checkin full IP header plus 8 bytes of protocol to
-	 * avoid additional coding at protocol handlers.
-	 */
-	if (!pskb_may_pull(skb, iph->ihl * 4 + 8))
-		goto out;
-
-	/* IPPROTO_RAW sockets are not supposed to receive anything. */
-	if (protocol == IPPROTO_RAW)
-		goto out;
-
-	raw_icmp_error(skb, protocol, info);
-
-	ipprot = rcu_dereference(inet_protos[protocol]);
-	if (ipprot && ipprot->err_handler)
-		ipprot->err_handler(skb, info);
-	return;
-
-out:
-	__ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
-}
-
-static bool icmp_tag_validation(int proto)
-{
-	const struct net_protocol *ipprot;
-	bool ok;
-
-	rcu_read_lock();
-	ipprot = rcu_dereference(inet_protos[proto]);
-	ok = ipprot ? ipprot->icmp_strict_tag_validation : false;
-	rcu_read_unlock();
-	return ok;
-}
-
-/*
- *	Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEEDED, ICMP_QUENCH, and
- *	ICMP_PARAMETERPROB.
- */
-
-static enum skb_drop_reason icmp_unreach(struct sk_buff *skb)
-{
-	enum skb_drop_reason reason = SKB_NOT_DROPPED_YET;
-	const struct iphdr *iph;
-	struct icmphdr *icmph;
-	struct net *net;
-	u32 info = 0;
-
-	net = skb_dst_dev_net_rcu(skb);
-
-	/*
-	 *	Incomplete header ?
-	 * 	Only checks for the IP header, there should be an
-	 *	additional check for longer headers in upper levels.
-	 */
-
-	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
-		goto out_err;
-
-	icmph = icmp_hdr(skb);
-	iph   = (const struct iphdr *)skb->data;
-
-	if (iph->ihl < 5)  { /* Mangled header, drop. */
-		reason = SKB_DROP_REASON_IP_INHDR;
-		goto out_err;
-	}
-
-	switch (icmph->type) {
-	case ICMP_DEST_UNREACH:
-		switch (icmph->code & 15) {
-		case ICMP_NET_UNREACH:
-		case ICMP_HOST_UNREACH:
-		case ICMP_PROT_UNREACH:
-		case ICMP_PORT_UNREACH:
-			break;
-		case ICMP_FRAG_NEEDED:
-			/* for documentation of the ip_no_pmtu_disc
-			 * values please see
-			 * Documentation/networking/ip-sysctl.rst
-			 */
-			switch (READ_ONCE(net->ipv4.sysctl_ip_no_pmtu_disc)) {
-			default:
-				net_dbg_ratelimited("%pI4: fragmentation needed and DF set\n",
-						    &iph->daddr);
-				break;
-			case 2:
-				goto out;
-			case 3:
-				if (!icmp_tag_validation(iph->protocol))
-					goto out;
-				fallthrough;
-			case 0:
-				info = ntohs(icmph->un.frag.mtu);
-			}
-			break;
-		case ICMP_SR_FAILED:
-			net_dbg_ratelimited("%pI4: Source Route Failed\n",
-					    &iph->daddr);
-			break;
-		default:
-			break;
-		}
-		if (icmph->code > NR_ICMP_UNREACH)
-			goto out;
-		break;
-	case ICMP_PARAMETERPROB:
-		info = ntohl(icmph->un.gateway) >> 24;
-		break;
-	case ICMP_TIME_EXCEEDED:
-		__ICMP_INC_STATS(net, ICMP_MIB_INTIMEEXCDS);
-		if (icmph->code == ICMP_EXC_FRAGTIME)
-			goto out;
-		break;
-	}
-
-	/*
-	 *	Throw it at our lower layers
-	 *
-	 *	RFC 1122: 3.2.2 MUST extract the protocol ID from the passed
-	 *		  header.
-	 *	RFC 1122: 3.2.2.1 MUST pass ICMP unreach messages to the
-	 *		  transport layer.
-	 *	RFC 1122: 3.2.2.2 MUST pass ICMP time expired messages to
-	 *		  transport layer.
-	 */
-
-	/*
-	 *	Check the other end isn't violating RFC 1122. Some routers send
-	 *	bogus responses to broadcast frames. If you see this message
-	 *	first check your netmask matches at both ends, if it does then
-	 *	get the other vendor to fix their kit.
-	 */
-
-	if (!READ_ONCE(net->ipv4.sysctl_icmp_ignore_bogus_error_responses) &&
-	    inet_addr_type_dev_table(net, skb->dev, iph->daddr) == RTN_BROADCAST) {
-		net_warn_ratelimited("%pI4 sent an invalid ICMP type %u, code %u error to a broadcast: %pI4 on %s\n",
-				     &ip_hdr(skb)->saddr,
-				     icmph->type, icmph->code,
-				     &iph->daddr, skb->dev->name);
-		goto out;
-	}
-
-	icmp_socket_deliver(skb, info);
-
-out:
-	return reason;
-out_err:
-	__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
-	return reason ?: SKB_DROP_REASON_NOT_SPECIFIED;
-}
-
-
-/*
- *	Handle ICMP_REDIRECT.
- */
-
-static enum skb_drop_reason icmp_redirect(struct sk_buff *skb)
-{
-	if (skb->len < sizeof(struct iphdr)) {
-		__ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
-		return SKB_DROP_REASON_PKT_TOO_SMALL;
-	}
-
-	if (!pskb_may_pull(skb, sizeof(struct iphdr))) {
-		/* there aught to be a stat */
-		return SKB_DROP_REASON_NOMEM;
-	}
-
-	icmp_socket_deliver(skb, ntohl(icmp_hdr(skb)->un.gateway));
-	return SKB_NOT_DROPPED_YET;
-}
-
-/*
- *	Handle ICMP_ECHO ("ping") and ICMP_EXT_ECHO ("PROBE") requests.
- *
- *	RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo
- *		  requests.
- *	RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be
- *		  included in the reply.
- *	RFC 1812: 4.3.3.6 SHOULD have a config option for silently ignoring
- *		  echo requests, MUST have default=NOT.
- *	RFC 8335: 8 MUST have a config option to enable/disable ICMP
- *		  Extended Echo Functionality, MUST be disabled by default
- *	See also WRT handling of options once they are done and working.
- */
-
-static enum skb_drop_reason icmp_echo(struct sk_buff *skb)
-{
-	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
-			IP_OPTIONS_DATA_FIXED_SIZE);
-	struct net *net;
-
-	net = skb_dst_dev_net_rcu(skb);
-	/* should there be an ICMP stat for ignored echos? */
-	if (READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_all))
-		return SKB_NOT_DROPPED_YET;
-
-	icmp_param->data.icmph	   = *icmp_hdr(skb);
-	icmp_param->skb		   = skb;
-	icmp_param->offset	   = 0;
-	icmp_param->data_len	   = skb->len;
-	icmp_param->head_len	   = sizeof(struct icmphdr);
-
-	if (icmp_param->data.icmph.type == ICMP_ECHO)
-		icmp_param->data.icmph.type = ICMP_ECHOREPLY;
-	else if (!icmp_build_probe(skb, &icmp_param->data.icmph))
-		return SKB_NOT_DROPPED_YET;
-
-	icmp_reply(icmp_param, skb);
-	return SKB_NOT_DROPPED_YET;
-}
-
 /*	Helper for icmp_echo and icmpv6_echo_reply.
  *	Searches for net_device that matches PROBE interface identifier
  *		and builds PROBE reply message in icmphdr.
@@ -1339,11 +225,13 @@ bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
 				 iio->ident.addr.ctype3_hdr.addrlen)
 			goto send_mal_query;
 		switch (ntohs(iio->ident.addr.ctype3_hdr.afi)) {
+#if IS_ENABLED(CONFIG_IPV4)
 		case ICMP_AFI_IP:
 			if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in_addr))
 				goto send_mal_query;
 			dev = ip_dev_find(net, iio->ident.addr.ip_addr.ipv4_addr);
 			break;
+#endif
 #if IS_ENABLED(CONFIG_IPV6)
 		case ICMP_AFI_IP6:
 			if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in6_addr))
@@ -1383,169 +271,6 @@ bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
 	return true;
 }
 
-/*
- *	Handle ICMP Timestamp requests.
- *	RFC 1122: 3.2.2.8 MAY implement ICMP timestamp requests.
- *		  SHOULD be in the kernel for minimum random latency.
- *		  MUST be accurate to a few minutes.
- *		  MUST be updated at least at 15Hz.
- */
-static enum skb_drop_reason icmp_timestamp(struct sk_buff *skb)
-{
-	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
-			IP_OPTIONS_DATA_FIXED_SIZE);
-	/*
-	 *	Too short.
-	 */
-	if (skb->len < 4)
-		goto out_err;
-
-	/*
-	 *	Fill in the current time as ms since midnight UT:
-	 */
-	icmp_param->data.times[1] = inet_current_timestamp();
-	icmp_param->data.times[2] = icmp_param->data.times[1];
-
-	BUG_ON(skb_copy_bits(skb, 0, &icmp_param->data.times[0], 4));
-
-	icmp_param->data.icmph	   = *icmp_hdr(skb);
-	icmp_param->data.icmph.type = ICMP_TIMESTAMPREPLY;
-	icmp_param->data.icmph.code = 0;
-	icmp_param->skb		   = skb;
-	icmp_param->offset	   = 0;
-	icmp_param->data_len	   = 0;
-	icmp_param->head_len	   = sizeof(struct icmphdr) + 12;
-	icmp_reply(icmp_param, skb);
-	return SKB_NOT_DROPPED_YET;
-
-out_err:
-	__ICMP_INC_STATS(skb_dst_dev_net_rcu(skb), ICMP_MIB_INERRORS);
-	return SKB_DROP_REASON_PKT_TOO_SMALL;
-}
-
-static enum skb_drop_reason icmp_discard(struct sk_buff *skb)
-{
-	/* pretend it was a success */
-	return SKB_NOT_DROPPED_YET;
-}
-
-/*
- *	Deal with incoming ICMP packets.
- */
-int icmp_rcv(struct sk_buff *skb)
-{
-	enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
-	struct rtable *rt = skb_rtable(skb);
-	struct net *net = dev_net_rcu(rt->dst.dev);
-	struct icmphdr *icmph;
-
-	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
-		struct sec_path *sp = skb_sec_path(skb);
-		int nh;
-
-		if (!(sp && sp->xvec[sp->len - 1]->props.flags &
-				 XFRM_STATE_ICMP)) {
-			reason = SKB_DROP_REASON_XFRM_POLICY;
-			goto drop;
-		}
-
-		if (!pskb_may_pull(skb, sizeof(*icmph) + sizeof(struct iphdr)))
-			goto drop;
-
-		nh = skb_network_offset(skb);
-		skb_set_network_header(skb, sizeof(*icmph));
-
-		if (!xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN,
-						skb)) {
-			reason = SKB_DROP_REASON_XFRM_POLICY;
-			goto drop;
-		}
-
-		skb_set_network_header(skb, nh);
-	}
-
-	__ICMP_INC_STATS(net, ICMP_MIB_INMSGS);
-
-	if (skb_checksum_simple_validate(skb))
-		goto csum_error;
-
-	if (!pskb_pull(skb, sizeof(*icmph)))
-		goto error;
-
-	icmph = icmp_hdr(skb);
-
-	ICMPMSGIN_INC_STATS(net, icmph->type);
-
-	/* Check for ICMP Extended Echo (PROBE) messages */
-	if (icmph->type == ICMP_EXT_ECHO) {
-		/* We can't use icmp_pointers[].handler() because it is an array of
-		 * size NR_ICMP_TYPES + 1 (19 elements) and PROBE has code 42.
-		 */
-		reason = icmp_echo(skb);
-		goto reason_check;
-	}
-
-	/*
-	 *	Parse the ICMP message
-	 */
-
-	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
-		/*
-		 *	RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be
-		 *	  silently ignored (we let user decide with a sysctl).
-		 *	RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently
-		 *	  discarded if to broadcast/multicast.
-		 */
-		if ((icmph->type == ICMP_ECHO ||
-		     icmph->type == ICMP_TIMESTAMP) &&
-		    READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_broadcasts)) {
-			reason = SKB_DROP_REASON_INVALID_PROTO;
-			goto error;
-		}
-		if (icmph->type != ICMP_ECHO &&
-		    icmph->type != ICMP_TIMESTAMP &&
-		    icmph->type != ICMP_ADDRESS &&
-		    icmph->type != ICMP_ADDRESSREPLY) {
-			reason = SKB_DROP_REASON_INVALID_PROTO;
-			goto error;
-		}
-	}
-
-	if (icmph->type == ICMP_EXT_ECHOREPLY ||
-	    icmph->type == ICMP_ECHOREPLY) {
-		reason = ping_rcv(skb);
-		return reason ? NET_RX_DROP : NET_RX_SUCCESS;
-	}
-
-	/*
-	 *	18 is the highest 'known' ICMP type. Anything else is a mystery
-	 *
-	 *	RFC 1122: 3.2.2  Unknown ICMP messages types MUST be silently
-	 *		  discarded.
-	 */
-	if (icmph->type > NR_ICMP_TYPES) {
-		reason = SKB_DROP_REASON_UNHANDLED_PROTO;
-		goto error;
-	}
-
-	reason = icmp_pointers[icmph->type].handler(skb);
-reason_check:
-	if (!reason)  {
-		consume_skb(skb);
-		return NET_RX_SUCCESS;
-	}
-
-drop:
-	kfree_skb_reason(skb, reason);
-	return NET_RX_DROP;
-csum_error:
-	reason = SKB_DROP_REASON_ICMP_CSUM;
-	__ICMP_INC_STATS(net, ICMP_MIB_CSUMERRORS);
-error:
-	__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
-	goto drop;
-}
-
 static bool ip_icmp_error_rfc4884_validate(const struct sk_buff *skb, int off)
 {
 	struct icmp_extobj_hdr *objh, _objh;
@@ -1604,106 +329,6 @@ void ip_icmp_error_rfc4884(const struct sk_buff *skb,
 		out->flags |= SO_EE_RFC4884_FLAG_INVALID;
 }
 
-int icmp_err(struct sk_buff *skb, u32 info)
-{
-	struct iphdr *iph = (struct iphdr *)skb->data;
-	int offset = iph->ihl<<2;
-	struct icmphdr *icmph = (struct icmphdr *)(skb->data + offset);
-	struct net *net = dev_net_rcu(skb->dev);
-	int type = icmp_hdr(skb)->type;
-	int code = icmp_hdr(skb)->code;
-
-	/*
-	 * Use ping_err to handle all icmp errors except those
-	 * triggered by ICMP_ECHOREPLY which sent from kernel.
-	 */
-	if (icmph->type != ICMP_ECHOREPLY) {
-		ping_err(skb, offset, info);
-		return 0;
-	}
-
-	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
-		ipv4_update_pmtu(skb, net, info, 0, IPPROTO_ICMP);
-	else if (type == ICMP_REDIRECT)
-		ipv4_redirect(skb, net, 0, IPPROTO_ICMP);
-
-	return 0;
-}
-
-/*
- *	This table is the definition of how we handle ICMP.
- */
-static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = {
-	[ICMP_ECHOREPLY] = {
-		.handler = ping_rcv,
-	},
-	[1] = {
-		.handler = icmp_discard,
-		.error = 1,
-	},
-	[2] = {
-		.handler = icmp_discard,
-		.error = 1,
-	},
-	[ICMP_DEST_UNREACH] = {
-		.handler = icmp_unreach,
-		.error = 1,
-	},
-	[ICMP_SOURCE_QUENCH] = {
-		.handler = icmp_unreach,
-		.error = 1,
-	},
-	[ICMP_REDIRECT] = {
-		.handler = icmp_redirect,
-		.error = 1,
-	},
-	[6] = {
-		.handler = icmp_discard,
-		.error = 1,
-	},
-	[7] = {
-		.handler = icmp_discard,
-		.error = 1,
-	},
-	[ICMP_ECHO] = {
-		.handler = icmp_echo,
-	},
-	[9] = {
-		.handler = icmp_discard,
-		.error = 1,
-	},
-	[10] = {
-		.handler = icmp_discard,
-		.error = 1,
-	},
-	[ICMP_TIME_EXCEEDED] = {
-		.handler = icmp_unreach,
-		.error = 1,
-	},
-	[ICMP_PARAMETERPROB] = {
-		.handler = icmp_unreach,
-		.error = 1,
-	},
-	[ICMP_TIMESTAMP] = {
-		.handler = icmp_timestamp,
-	},
-	[ICMP_TIMESTAMPREPLY] = {
-		.handler = icmp_discard,
-	},
-	[ICMP_INFO_REQUEST] = {
-		.handler = icmp_discard,
-	},
-	[ICMP_INFO_REPLY] = {
-		.handler = icmp_discard,
-	},
-	[ICMP_ADDRESS] = {
-		.handler = icmp_discard,
-	},
-	[ICMP_ADDRESSREPLY] = {
-		.handler = icmp_discard,
-	},
-};
-
 static int __net_init icmp_sk_init(struct net *net)
 {
 	/* Control parameters for ECHO replies. */
@@ -1742,28 +367,12 @@ static struct pernet_operations __net_initdata icmp_sk_ops = {
 
 int __init icmp_init(void)
 {
-	int err, i;
-
-	for_each_possible_cpu(i) {
-		struct sock *sk;
-
-		err = inet_ctl_sock_create(&sk, PF_INET,
-					   SOCK_RAW, IPPROTO_ICMP, &init_net);
-		if (err < 0)
-			return err;
-
-		per_cpu(ipv4_icmp_sk, i) = sk;
-
-		/* Enough space for 2 64K ICMP packets, including
-		 * sk_buff/skb_shared_info struct overhead.
-		 */
-		sk->sk_sndbuf =	2 * SKB_TRUESIZE(64 * 1024);
+#if IS_ENABLED(CONFIG_IPV4)
+	int err;
 
-		/*
-		 * Speedup sock_wfree()
-		 */
-		sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
-		inet_sk(sk)->pmtudisc = IP_PMTUDISC_DONT;
-	}
+	err = icmp_init_ipv4();
+	if (err)
+		return err;
+#endif
 	return register_pernet_subsys(&icmp_sk_ops);
 }
diff --git a/net/ipv4/icmp_ipv4.c b/net/ipv4/icmp_ipv4.c
new file mode 100644
index 000000000000..321b4cc40a17
--- /dev/null
+++ b/net/ipv4/icmp_ipv4.c
@@ -0,0 +1,1425 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *	INET:	Implementation of the ICMP protocol layer.
+ *
+ *	IPv4 specific functions
+ *
+ *	code split from:
+ *	net/ipv4/icmp.c
+ *
+ *	See icmp.c for author information
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/inetdevice.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/types.h>
+#include <net/icmp.h>
+#include <net/inet_common.h>
+#include <net/ping.h>
+#include <net/protocol.h>
+#include <net/raw.h>
+#include <net/xfrm.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/icmp.h>
+
+/*
+ *	Build xmit assembly blocks
+ */
+
+struct icmp_bxm {
+	struct sk_buff *skb;
+	int offset;
+	int data_len;
+
+	struct {
+		struct icmphdr icmph;
+		__be32	       times[3];
+	} data;
+	int head_len;
+
+	/* Must be last as it ends in a flexible-array member. */
+	struct ip_options_rcu replyopts;
+};
+
+/* An array of errno for error messages from dest unreach. */
+/* RFC 1122: 3.2.2.1 States that NET_UNREACH, HOST_UNREACH and SR_FAILED MUST be considered 'transient errs'. */
+
+const struct icmp_err icmp_err_convert[] = {
+	{
+		.errno = ENETUNREACH,	/* ICMP_NET_UNREACH */
+		.fatal = 0,
+	},
+	{
+		.errno = EHOSTUNREACH,	/* ICMP_HOST_UNREACH */
+		.fatal = 0,
+	},
+	{
+		.errno = ENOPROTOOPT	/* ICMP_PROT_UNREACH */,
+		.fatal = 1,
+	},
+	{
+		.errno = ECONNREFUSED,	/* ICMP_PORT_UNREACH */
+		.fatal = 1,
+	},
+	{
+		.errno = EMSGSIZE,	/* ICMP_FRAG_NEEDED */
+		.fatal = 0,
+	},
+	{
+		.errno = EOPNOTSUPP,	/* ICMP_SR_FAILED */
+		.fatal = 0,
+	},
+	{
+		.errno = ENETUNREACH,	/* ICMP_NET_UNKNOWN */
+		.fatal = 1,
+	},
+	{
+		.errno = EHOSTDOWN,	/* ICMP_HOST_UNKNOWN */
+		.fatal = 1,
+	},
+	{
+		.errno = ENONET,	/* ICMP_HOST_ISOLATED */
+		.fatal = 1,
+	},
+	{
+		.errno = ENETUNREACH,	/* ICMP_NET_ANO	*/
+		.fatal = 1,
+	},
+	{
+		.errno = EHOSTUNREACH,	/* ICMP_HOST_ANO */
+		.fatal = 1,
+	},
+	{
+		.errno = ENETUNREACH,	/* ICMP_NET_UNR_TOS */
+		.fatal = 0,
+	},
+	{
+		.errno = EHOSTUNREACH,	/* ICMP_HOST_UNR_TOS */
+		.fatal = 0,
+	},
+	{
+		.errno = EHOSTUNREACH,	/* ICMP_PKT_FILTERED */
+		.fatal = 1,
+	},
+	{
+		.errno = EHOSTUNREACH,	/* ICMP_PREC_VIOLATION */
+		.fatal = 1,
+	},
+	{
+		.errno = EHOSTUNREACH,	/* ICMP_PREC_CUTOFF */
+		.fatal = 1,
+	},
+};
+EXPORT_SYMBOL(icmp_err_convert);
+
+/*
+ *	ICMP control array. This specifies what to do with each ICMP.
+ */
+
+struct icmp_control {
+	enum skb_drop_reason (*handler)(struct sk_buff *skb);
+	short   error;		/* This ICMP is classed as an error message */
+};
+
+static const struct icmp_control icmp_pointers[NR_ICMP_TYPES+1];
+
+static DEFINE_PER_CPU(struct sock *, ipv4_icmp_sk);
+
+/* Called with BH disabled */
+static inline struct sock *icmp_xmit_lock(struct net *net)
+{
+	struct sock *sk;
+
+	sk = this_cpu_read(ipv4_icmp_sk);
+
+	if (unlikely(!spin_trylock(&sk->sk_lock.slock))) {
+		/* This can happen if the output path signals a
+		 * dst_link_failure() for an outgoing ICMP packet.
+		 */
+		return NULL;
+	}
+	sock_net_set(sk, net);
+	return sk;
+}
+
+static inline void icmp_xmit_unlock(struct sock *sk)
+{
+	sock_net_set(sk, &init_net);
+	spin_unlock(&sk->sk_lock.slock);
+}
+
+/*
+ *	Send an ICMP frame.
+ */
+
+static bool icmpv4_xrlim_allow(struct net *net, struct rtable *rt,
+			       struct flowi4 *fl4, int type, int code,
+			       bool apply_ratelimit)
+{
+	struct dst_entry *dst = &rt->dst;
+	struct inet_peer *peer;
+	struct net_device *dev;
+	int peer_timeout;
+	bool rc = true;
+
+	if (!apply_ratelimit)
+		return true;
+
+	peer_timeout = READ_ONCE(net->ipv4.sysctl_icmp_ratelimit);
+	if (!peer_timeout)
+		goto out;
+
+	/* No rate limit on loopback */
+	rcu_read_lock();
+	dev = dst_dev_rcu(dst);
+	if (dev && (dev->flags & IFF_LOOPBACK))
+		goto out_unlock;
+
+	peer = inet_getpeer_v4(net->ipv4.peers, fl4->daddr,
+			       l3mdev_master_ifindex_rcu(dev));
+	rc = inet_peer_xrlim_allow(peer, peer_timeout);
+
+out_unlock:
+	rcu_read_unlock();
+out:
+	if (!rc)
+		__ICMP_INC_STATS(net, ICMP_MIB_RATELIMITHOST);
+	else
+		icmp_global_consume(net);
+	return rc;
+}
+
+/*
+ *	Checksum each fragment, and on the first include the headers and final
+ *	checksum.
+ */
+static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd,
+			  struct sk_buff *skb)
+{
+	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+			IP_OPTIONS_DATA_FIXED_SIZE);
+	__wsum csum;
+
+	icmp_param = from;
+
+	csum = skb_copy_and_csum_bits(icmp_param->skb,
+				      icmp_param->offset + offset,
+				      to, len);
+
+	skb->csum = csum_block_add(skb->csum, csum, odd);
+	if (icmp_param->data.icmph.type <= NR_ICMP_TYPES &&
+	    icmp_pointers[array_index_nospec(icmp_param->data.icmph.type,
+					     NR_ICMP_TYPES + 1)].error)
+		nf_ct_attach(skb, icmp_param->skb);
+	return 0;
+}
+
+static void icmp_push_reply(struct sock *sk,
+			    struct icmp_bxm *icmp_param,
+			    struct flowi4 *fl4,
+			    struct ipcm_cookie *ipc, struct rtable **rt)
+{
+	struct sk_buff *skb;
+
+	if (ip_append_data(sk, fl4, icmp_glue_bits, icmp_param,
+			   icmp_param->data_len+icmp_param->head_len,
+			   icmp_param->head_len,
+			   ipc, rt, MSG_DONTWAIT) < 0) {
+		__ICMP_INC_STATS(sock_net(sk), ICMP_MIB_OUTERRORS);
+		ip_flush_pending_frames(sk);
+	} else if ((skb = skb_peek(&sk->sk_write_queue)) != NULL) {
+		struct icmphdr *icmph = icmp_hdr(skb);
+		__wsum csum;
+		struct sk_buff *skb1;
+
+		csum = csum_partial_copy_nocheck((void *)&icmp_param->data,
+						 (char *)icmph,
+						 icmp_param->head_len);
+		skb_queue_walk(&sk->sk_write_queue, skb1) {
+			csum = csum_add(csum, skb1->csum);
+		}
+		icmph->checksum = csum_fold(csum);
+		skb->ip_summed = CHECKSUM_NONE;
+		ip_push_pending_frames(sk, fl4);
+	}
+}
+
+static bool icmpv4_mask_allow(struct net *net, int type, int code)
+{
+	if (type > NR_ICMP_TYPES)
+		return true;
+
+	/* Don't limit PMTU discovery. */
+	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
+		return true;
+
+	/* Limit if icmp type is enabled in ratemask. */
+	if (!((1 << type) & READ_ONCE(net->ipv4.sysctl_icmp_ratemask)))
+		return true;
+
+	return false;
+}
+
+static bool icmpv4_global_allow(struct net *net, int type, int code,
+				bool *apply_ratelimit)
+{
+	if (icmpv4_mask_allow(net, type, code))
+		return true;
+
+	if (icmp_global_allow(net)) {
+		*apply_ratelimit = true;
+		return true;
+	}
+	__ICMP_INC_STATS(net, ICMP_MIB_RATELIMITGLOBAL);
+	return false;
+}
+
+static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
+{
+	struct rtable *rt = skb_rtable(skb);
+	struct net *net = dev_net_rcu(rt->dst.dev);
+	bool apply_ratelimit = false;
+	struct ipcm_cookie ipc;
+	struct flowi4 fl4;
+	struct sock *sk;
+	__be32 daddr, saddr;
+	u32 mark = IP4_REPLY_MARK(net, skb->mark);
+	int type = icmp_param->data.icmph.type;
+	int code = icmp_param->data.icmph.code;
+
+	if (ip_options_echo(net, &icmp_param->replyopts.opt, skb))
+		return;
+
+	/* Needed by both icmpv4_global_allow and icmp_xmit_lock */
+	local_bh_disable();
+
+	/* is global icmp_msgs_per_sec exhausted ? */
+	if (!icmpv4_global_allow(net, type, code, &apply_ratelimit))
+		goto out_bh_enable;
+
+	sk = icmp_xmit_lock(net);
+	if (!sk)
+		goto out_bh_enable;
+
+	icmp_param->data.icmph.checksum = 0;
+
+	ipcm_init(&ipc);
+	ipc.tos = ip_hdr(skb)->tos;
+	ipc.sockc.mark = mark;
+	daddr = ipc.addr = ip_hdr(skb)->saddr;
+	saddr = fib_compute_spec_dst(skb);
+
+	if (icmp_param->replyopts.opt.optlen) {
+		ipc.opt = &icmp_param->replyopts;
+		if (ipc.opt->opt.srr)
+			daddr = icmp_param->replyopts.opt.faddr;
+	}
+	memset(&fl4, 0, sizeof(fl4));
+	fl4.daddr = daddr;
+	fl4.saddr = saddr;
+	fl4.flowi4_mark = mark;
+	fl4.flowi4_uid = sock_net_uid(net, NULL);
+	fl4.flowi4_dscp = ip4h_dscp(ip_hdr(skb));
+	fl4.flowi4_proto = IPPROTO_ICMP;
+	fl4.flowi4_oif = l3mdev_master_ifindex(skb->dev);
+	security_skb_classify_flow(skb, flowi4_to_flowi_common(&fl4));
+	rt = ip_route_output_key(net, &fl4);
+	if (IS_ERR(rt))
+		goto out_unlock;
+	if (icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
+		icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
+	ip_rt_put(rt);
+out_unlock:
+	icmp_xmit_unlock(sk);
+out_bh_enable:
+	local_bh_enable();
+}
+
+/*
+ * The device used for looking up which routing table to use for sending an ICMP
+ * error is preferably the source whenever it is set, which should ensure the
+ * icmp error can be sent to the source host, else lookup using the routing
+ * table of the destination device, else use the main routing table (index 0).
+ */
+static struct net_device *icmp_get_route_lookup_dev(struct sk_buff *skb)
+{
+	struct net_device *dev = skb->dev;
+	const struct dst_entry *dst;
+
+	if (dev)
+		return dev;
+	dst = skb_dst(skb);
+	return dst ? dst_dev(dst) : NULL;
+}
+
+static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
+					struct sk_buff *skb_in,
+					const struct iphdr *iph, __be32 saddr,
+					dscp_t dscp, u32 mark, int type,
+					int code, struct icmp_bxm *param)
+{
+	struct net_device *route_lookup_dev;
+	struct dst_entry *dst, *dst2;
+	struct rtable *rt, *rt2;
+	struct flowi4 fl4_dec;
+	int err;
+
+	memset(fl4, 0, sizeof(*fl4));
+	fl4->daddr = (param->replyopts.opt.srr ?
+		      param->replyopts.opt.faddr : iph->saddr);
+	fl4->saddr = saddr;
+	fl4->flowi4_mark = mark;
+	fl4->flowi4_uid = sock_net_uid(net, NULL);
+	fl4->flowi4_dscp = dscp;
+	fl4->flowi4_proto = IPPROTO_ICMP;
+	fl4->fl4_icmp_type = type;
+	fl4->fl4_icmp_code = code;
+	route_lookup_dev = icmp_get_route_lookup_dev(skb_in);
+	fl4->flowi4_oif = l3mdev_master_ifindex(route_lookup_dev);
+
+	security_skb_classify_flow(skb_in, flowi4_to_flowi_common(fl4));
+	rt = ip_route_output_key_hash(net, fl4, skb_in);
+	if (IS_ERR(rt))
+		return rt;
+
+	/* No need to clone since we're just using its address. */
+	rt2 = rt;
+
+	dst = xfrm_lookup(net, &rt->dst,
+			  flowi4_to_flowi(fl4), NULL, 0);
+	rt = dst_rtable(dst);
+	if (!IS_ERR(dst)) {
+		if (rt != rt2)
+			return rt;
+		if (inet_addr_type_dev_table(net, route_lookup_dev,
+					     fl4->daddr) == RTN_LOCAL)
+			return rt;
+	} else if (PTR_ERR(dst) == -EPERM) {
+		rt = NULL;
+	} else {
+		return rt;
+	}
+	err = xfrm_decode_session_reverse(net, skb_in, flowi4_to_flowi(&fl4_dec), AF_INET);
+	if (err)
+		goto relookup_failed;
+
+	if (inet_addr_type_dev_table(net, route_lookup_dev,
+				     fl4_dec.saddr) == RTN_LOCAL) {
+		rt2 = __ip_route_output_key(net, &fl4_dec);
+		if (IS_ERR(rt2))
+			err = PTR_ERR(rt2);
+	} else {
+		struct flowi4 fl4_2 = {};
+		unsigned long orefdst;
+
+		fl4_2.daddr = fl4_dec.saddr;
+		rt2 = ip_route_output_key(net, &fl4_2);
+		if (IS_ERR(rt2)) {
+			err = PTR_ERR(rt2);
+			goto relookup_failed;
+		}
+		/* Ugh! */
+		orefdst = skb_dstref_steal(skb_in);
+		err = ip_route_input(skb_in, fl4_dec.daddr, fl4_dec.saddr,
+				     dscp, rt2->dst.dev) ? -EINVAL : 0;
+
+		dst_release(&rt2->dst);
+		rt2 = skb_rtable(skb_in);
+		/* steal dst entry from skb_in, don't drop refcnt */
+		skb_dstref_steal(skb_in);
+		skb_dstref_restore(skb_in, orefdst);
+
+		/*
+		 * At this point, fl4_dec.daddr should NOT be local (we
+		 * checked fl4_dec.saddr above). However, a race condition
+		 * may occur if the address is added to the interface
+		 * concurrently. In that case, ip_route_input() returns a
+		 * LOCAL route with dst.output=ip_rt_bug, which must not
+		 * be used for output.
+		 */
+		if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
+			net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
+					     &fl4_dec.daddr, &fl4_dec.saddr);
+			dst_release(&rt2->dst);
+			err = -EINVAL;
+		}
+	}
+
+	if (err)
+		goto relookup_failed;
+
+	dst2 = xfrm_lookup(net, &rt2->dst, flowi4_to_flowi(&fl4_dec), NULL,
+			   XFRM_LOOKUP_ICMP);
+	rt2 = dst_rtable(dst2);
+	if (!IS_ERR(dst2)) {
+		dst_release(&rt->dst);
+		rt = rt2;
+	} else if (PTR_ERR(dst2) == -EPERM) {
+		if (rt)
+			dst_release(&rt->dst);
+		return rt2;
+	} else {
+		err = PTR_ERR(dst2);
+		goto relookup_failed;
+	}
+	return rt;
+
+relookup_failed:
+	if (rt)
+		return rt;
+	return ERR_PTR(err);
+}
+
+struct icmp_ext_iio_addr4_subobj {
+	__be16 afi;
+	__be16 reserved;
+	__be32 addr4;
+};
+
+static unsigned int icmp_ext_iio_len(void)
+{
+	return sizeof(struct icmp_extobj_hdr) +
+		/* ifIndex */
+		sizeof(__be32) +
+		/* Interface Address Sub-Object */
+		sizeof(struct icmp_ext_iio_addr4_subobj) +
+		/* Interface Name Sub-Object. Length must be a multiple of 4
+		 * bytes.
+		 */
+		ALIGN(sizeof(struct icmp_ext_iio_name_subobj), 4) +
+		/* MTU */
+		sizeof(__be32);
+}
+
+static unsigned int icmp_ext_max_len(u8 ext_objs)
+{
+	unsigned int ext_max_len;
+
+	ext_max_len = sizeof(struct icmp_ext_hdr);
+
+	if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
+		ext_max_len += icmp_ext_iio_len();
+
+	return ext_max_len;
+}
+
+static __be32 icmp_ext_iio_addr4_find(const struct net_device *dev)
+{
+	struct in_device *in_dev;
+	struct in_ifaddr *ifa;
+
+	in_dev = __in_dev_get_rcu(dev);
+	if (!in_dev)
+		return 0;
+
+	/* It is unclear from RFC 5837 which IP address should be chosen, but
+	 * it makes sense to choose a global unicast address.
+	 */
+	in_dev_for_each_ifa_rcu(ifa, in_dev) {
+		if (READ_ONCE(ifa->ifa_flags) & IFA_F_SECONDARY)
+			continue;
+		if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
+		    ipv4_is_multicast(ifa->ifa_address))
+			continue;
+		return ifa->ifa_address;
+	}
+
+	return 0;
+}
+
+static void icmp_ext_iio_iif_append(struct net *net, struct sk_buff *skb,
+				    int iif)
+{
+	struct icmp_ext_iio_name_subobj *name_subobj;
+	struct icmp_extobj_hdr *objh;
+	struct net_device *dev;
+	__be32 data;
+
+	if (!iif)
+		return;
+
+	/* Add the fields in the order specified by RFC 5837. */
+	objh = skb_put(skb, sizeof(*objh));
+	objh->class_num = ICMP_EXT_OBJ_CLASS_IIO;
+	objh->class_type = ICMP_EXT_CTYPE_IIO_ROLE(ICMP_EXT_CTYPE_IIO_ROLE_IIF);
+
+	data = htonl(iif);
+	skb_put_data(skb, &data, sizeof(__be32));
+	objh->class_type |= ICMP_EXT_CTYPE_IIO_IFINDEX;
+
+	rcu_read_lock();
+
+	dev = dev_get_by_index_rcu(net, iif);
+	if (!dev)
+		goto out;
+
+	data = icmp_ext_iio_addr4_find(dev);
+	if (data) {
+		struct icmp_ext_iio_addr4_subobj *addr4_subobj;
+
+		addr4_subobj = skb_put_zero(skb, sizeof(*addr4_subobj));
+		addr4_subobj->afi = htons(ICMP_AFI_IP);
+		addr4_subobj->addr4 = data;
+		objh->class_type |= ICMP_EXT_CTYPE_IIO_IPADDR;
+	}
+
+	name_subobj = skb_put_zero(skb, ALIGN(sizeof(*name_subobj), 4));
+	name_subobj->len = ALIGN(sizeof(*name_subobj), 4);
+	netdev_copy_name(dev, name_subobj->name);
+	objh->class_type |= ICMP_EXT_CTYPE_IIO_NAME;
+
+	data = htonl(READ_ONCE(dev->mtu));
+	skb_put_data(skb, &data, sizeof(__be32));
+	objh->class_type |= ICMP_EXT_CTYPE_IIO_MTU;
+
+out:
+	rcu_read_unlock();
+	objh->length = htons(skb_tail_pointer(skb) - (unsigned char *)objh);
+}
+
+static void icmp_ext_objs_append(struct net *net, struct sk_buff *skb,
+				 u8 ext_objs, int iif)
+{
+	if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
+		icmp_ext_iio_iif_append(net, skb, iif);
+}
+
+static struct sk_buff *
+icmp_ext_append(struct net *net, struct sk_buff *skb_in, struct icmphdr *icmph,
+		unsigned int room, int iif)
+{
+	unsigned int payload_len, ext_max_len, ext_len;
+	struct icmp_ext_hdr *ext_hdr;
+	struct sk_buff *skb;
+	u8 ext_objs;
+	int nhoff;
+
+	switch (icmph->type) {
+	case ICMP_DEST_UNREACH:
+	case ICMP_TIME_EXCEEDED:
+	case ICMP_PARAMETERPROB:
+		break;
+	default:
+		return NULL;
+	}
+
+	ext_objs = READ_ONCE(net->ipv4.sysctl_icmp_errors_extension_mask);
+	if (!ext_objs)
+		return NULL;
+
+	ext_max_len = icmp_ext_max_len(ext_objs);
+	if (ICMP_EXT_ORIG_DGRAM_MIN_LEN + ext_max_len > room)
+		return NULL;
+
+	skb = skb_clone(skb_in, GFP_ATOMIC);
+	if (!skb)
+		return NULL;
+
+	nhoff = skb_network_offset(skb);
+	payload_len = min(skb->len - nhoff, ICMP_EXT_ORIG_DGRAM_MIN_LEN);
+
+	if (!pskb_network_may_pull(skb, payload_len))
+		goto free_skb;
+
+	if (pskb_trim(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN) ||
+	    __skb_put_padto(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN, false))
+		goto free_skb;
+
+	if (pskb_expand_head(skb, 0, ext_max_len, GFP_ATOMIC))
+		goto free_skb;
+
+	ext_hdr = skb_put_zero(skb, sizeof(*ext_hdr));
+	ext_hdr->version = ICMP_EXT_VERSION_2;
+
+	icmp_ext_objs_append(net, skb, ext_objs, iif);
+
+	/* Do not send an empty extension structure. */
+	ext_len = skb_tail_pointer(skb) - (unsigned char *)ext_hdr;
+	if (ext_len == sizeof(*ext_hdr))
+		goto free_skb;
+
+	ext_hdr->checksum = ip_compute_csum(ext_hdr, ext_len);
+	/* The length of the original datagram in 32-bit words (RFC 4884). */
+	icmph->un.reserved[1] = ICMP_EXT_ORIG_DGRAM_MIN_LEN / sizeof(u32);
+
+	return skb;
+
+free_skb:
+	consume_skb(skb);
+	return NULL;
+}
+
+/*
+ *	Send an ICMP message in response to a situation
+ *
+ *	RFC 1122: 3.2.2	MUST send at least the IP header and 8 bytes of header.
+ *		  MAY send more (we do).
+ *			MUST NOT change this header information.
+ *			MUST NOT reply to a multicast/broadcast IP address.
+ *			MUST NOT reply to a multicast/broadcast MAC address.
+ *			MUST reply to only the first fragment.
+ */
+
+void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
+		 const struct inet_skb_parm *parm)
+{
+	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+			IP_OPTIONS_DATA_FIXED_SIZE);
+	struct iphdr *iph;
+	int room;
+	struct rtable *rt = skb_rtable(skb_in);
+	bool apply_ratelimit = false;
+	struct sk_buff *ext_skb;
+	struct ipcm_cookie ipc;
+	struct flowi4 fl4;
+	__be32 saddr;
+	u8  tos;
+	u32 mark;
+	struct net *net;
+	struct sock *sk;
+
+	if (!rt)
+		return;
+
+	rcu_read_lock();
+
+	if (rt->dst.dev)
+		net = dev_net_rcu(rt->dst.dev);
+	else if (skb_in->dev)
+		net = dev_net_rcu(skb_in->dev);
+	else
+		goto out;
+
+	/*
+	 *	Find the original header. It is expected to be valid, of course.
+	 *	Check this, icmp_send is called from the most obscure devices
+	 *	sometimes.
+	 */
+	iph = ip_hdr(skb_in);
+
+	if ((u8 *)iph < skb_in->head ||
+	    (skb_network_header(skb_in) + sizeof(*iph)) >
+	    skb_tail_pointer(skb_in))
+		goto out;
+
+	/*
+	 *	No replies to physical multicast/broadcast
+	 */
+	if (skb_in->pkt_type != PACKET_HOST)
+		goto out;
+
+	/*
+	 *	Now check at the protocol level
+	 */
+	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
+		goto out;
+
+	/*
+	 *	Only reply to fragment 0. We byte re-order the constant
+	 *	mask for efficiency.
+	 */
+	if (iph->frag_off & htons(IP_OFFSET))
+		goto out;
+
+	/*
+	 *	If we send an ICMP error to an ICMP error a mess would result..
+	 */
+	if (icmp_pointers[type].error) {
+		/*
+		 *	We are an error, check if we are replying to an
+		 *	ICMP error
+		 */
+		if (iph->protocol == IPPROTO_ICMP) {
+			u8 _inner_type, *itp;
+
+			itp = skb_header_pointer(skb_in,
+						 skb_network_header(skb_in) +
+						 (iph->ihl << 2) +
+						 offsetof(struct icmphdr,
+							  type) -
+						 skb_in->data,
+						 sizeof(_inner_type),
+						 &_inner_type);
+			if (!itp)
+				goto out;
+
+			/*
+			 *	Assume any unknown ICMP type is an error. This
+			 *	isn't specified by the RFC, but think about it..
+			 */
+			if (*itp > NR_ICMP_TYPES ||
+			    icmp_pointers[*itp].error)
+				goto out;
+		}
+	}
+
+	/* Needed by both icmpv4_global_allow and icmp_xmit_lock */
+	local_bh_disable();
+
+	/* Check global sysctl_icmp_msgs_per_sec ratelimit, unless
+	 * incoming dev is loopback.  If outgoing dev change to not be
+	 * loopback, then peer ratelimit still work (in icmpv4_xrlim_allow)
+	 */
+	if (!(skb_in->dev && (skb_in->dev->flags&IFF_LOOPBACK)) &&
+	      !icmpv4_global_allow(net, type, code, &apply_ratelimit))
+		goto out_bh_enable;
+
+	sk = icmp_xmit_lock(net);
+	if (!sk)
+		goto out_bh_enable;
+
+	/*
+	 *	Construct source address and options.
+	 */
+
+	saddr = iph->daddr;
+	if (!(rt->rt_flags & RTCF_LOCAL)) {
+		struct net_device *dev = NULL;
+
+		rcu_read_lock();
+		if (rt_is_input_route(rt) &&
+		    READ_ONCE(net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr))
+			dev = dev_get_by_index_rcu(net, parm->iif ? parm->iif :
+						   inet_iif(skb_in));
+
+		if (dev)
+			saddr = inet_select_addr(dev, iph->saddr,
+						 RT_SCOPE_LINK);
+		else
+			saddr = 0;
+		rcu_read_unlock();
+	}
+
+	tos = icmp_pointers[type].error ? (RT_TOS(iph->tos) |
+					   IPTOS_PREC_INTERNETCONTROL) :
+					   iph->tos;
+	mark = IP4_REPLY_MARK(net, skb_in->mark);
+
+	if (__ip_options_echo(net, &icmp_param->replyopts.opt, skb_in,
+			      &parm->opt))
+		goto out_unlock;
+
+
+	/*
+	 *	Prepare data for ICMP header.
+	 */
+
+	icmp_param->data.icmph.type	 = type;
+	icmp_param->data.icmph.code	 = code;
+	icmp_param->data.icmph.un.gateway = info;
+	icmp_param->data.icmph.checksum	 = 0;
+	icmp_param->skb	  = skb_in;
+	icmp_param->offset = skb_network_offset(skb_in);
+	ipcm_init(&ipc);
+	ipc.tos = tos;
+	ipc.addr = iph->saddr;
+	ipc.opt = &icmp_param->replyopts;
+	ipc.sockc.mark = mark;
+
+	rt = icmp_route_lookup(net, &fl4, skb_in, iph, saddr,
+			       inet_dsfield_to_dscp(tos), mark, type, code,
+			       icmp_param);
+	if (IS_ERR(rt))
+		goto out_unlock;
+
+	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
+		goto ende;
+
+	/* peer icmp_ratelimit */
+	if (!icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
+		goto ende;
+
+	/* RFC says return as much as we can without exceeding 576 bytes. */
+
+	room = dst4_mtu(&rt->dst);
+	if (room > 576)
+		room = 576;
+	room -= sizeof(struct iphdr) + icmp_param->replyopts.opt.optlen;
+	room -= sizeof(struct icmphdr);
+	/* Guard against tiny mtu. We need to include at least one
+	 * IP network header for this message to make any sense.
+	 */
+	if (room <= (int)sizeof(struct iphdr))
+		goto ende;
+
+	ext_skb = icmp_ext_append(net, skb_in, &icmp_param->data.icmph, room,
+				  parm->iif);
+	if (ext_skb)
+		icmp_param->skb = ext_skb;
+
+	icmp_param->data_len = icmp_param->skb->len - icmp_param->offset;
+	if (icmp_param->data_len > room)
+		icmp_param->data_len = room;
+	icmp_param->head_len = sizeof(struct icmphdr);
+
+	/* if we don't have a source address at this point, fall back to the
+	 * dummy address instead of sending out a packet with a source address
+	 * of 0.0.0.0
+	 */
+	if (!fl4.saddr)
+		fl4.saddr = htonl(INADDR_DUMMY);
+
+	trace_icmp_send(skb_in, type, code);
+
+	icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
+
+	if (ext_skb)
+		consume_skb(ext_skb);
+ende:
+	ip_rt_put(rt);
+out_unlock:
+	icmp_xmit_unlock(sk);
+out_bh_enable:
+	local_bh_enable();
+out:
+	rcu_read_unlock();
+}
+EXPORT_SYMBOL(__icmp_send);
+
+#if IS_ENABLED(CONFIG_NF_NAT)
+#include <net/netfilter/nf_conntrack.h>
+void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
+{
+	struct sk_buff *cloned_skb = NULL;
+	enum ip_conntrack_info ctinfo;
+	enum ip_conntrack_dir dir;
+	struct inet_skb_parm parm;
+	struct nf_conn *ct;
+	__be32 orig_ip;
+
+	memset(&parm, 0, sizeof(parm));
+	ct = nf_ct_get(skb_in, &ctinfo);
+	if (!ct || !(READ_ONCE(ct->status) & IPS_NAT_MASK)) {
+		__icmp_send(skb_in, type, code, info, &parm);
+		return;
+	}
+
+	if (skb_shared(skb_in))
+		skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
+
+	if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
+	    (skb_network_header(skb_in) + sizeof(struct iphdr)) >
+	    skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
+	    skb_network_offset(skb_in) + sizeof(struct iphdr))))
+		goto out;
+
+	orig_ip = ip_hdr(skb_in)->saddr;
+	dir = CTINFO2DIR(ctinfo);
+	ip_hdr(skb_in)->saddr = ct->tuplehash[dir].tuple.src.u3.ip;
+	__icmp_send(skb_in, type, code, info, &parm);
+	ip_hdr(skb_in)->saddr = orig_ip;
+out:
+	consume_skb(cloned_skb);
+}
+EXPORT_SYMBOL(icmp_ndo_send);
+#endif
+
+static void icmp_socket_deliver(struct sk_buff *skb, u32 info)
+{
+	const struct iphdr *iph = (const struct iphdr *)skb->data;
+	const struct net_protocol *ipprot;
+	int protocol = iph->protocol;
+
+	/* Checkin full IP header plus 8 bytes of protocol to
+	 * avoid additional coding at protocol handlers.
+	 */
+	if (!pskb_may_pull(skb, iph->ihl * 4 + 8))
+		goto out;
+
+	/* IPPROTO_RAW sockets are not supposed to receive anything. */
+	if (protocol == IPPROTO_RAW)
+		goto out;
+
+	raw_icmp_error(skb, protocol, info);
+
+	ipprot = rcu_dereference(inet_protos[protocol]);
+	if (ipprot && ipprot->err_handler)
+		ipprot->err_handler(skb, info);
+	return;
+
+out:
+	__ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
+}
+
+static bool icmp_tag_validation(int proto)
+{
+	const struct net_protocol *ipprot;
+	bool ok;
+
+	rcu_read_lock();
+	ipprot = rcu_dereference(inet_protos[proto]);
+	ok = ipprot ? ipprot->icmp_strict_tag_validation : false;
+	rcu_read_unlock();
+	return ok;
+}
+
+/*
+ *	Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEEDED, ICMP_QUENCH, and
+ *	ICMP_PARAMETERPROB.
+ */
+
+static enum skb_drop_reason icmp_unreach(struct sk_buff *skb)
+{
+	enum skb_drop_reason reason = SKB_NOT_DROPPED_YET;
+	const struct iphdr *iph;
+	struct icmphdr *icmph;
+	struct net *net;
+	u32 info = 0;
+
+	net = skb_dst_dev_net_rcu(skb);
+
+	/*
+	 *	Incomplete header ?
+	 * 	Only checks for the IP header, there should be an
+	 *	additional check for longer headers in upper levels.
+	 */
+
+	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
+		goto out_err;
+
+	icmph = icmp_hdr(skb);
+	iph   = (const struct iphdr *)skb->data;
+
+	if (iph->ihl < 5)  { /* Mangled header, drop. */
+		reason = SKB_DROP_REASON_IP_INHDR;
+		goto out_err;
+	}
+
+	switch (icmph->type) {
+	case ICMP_DEST_UNREACH:
+		switch (icmph->code & 15) {
+		case ICMP_NET_UNREACH:
+		case ICMP_HOST_UNREACH:
+		case ICMP_PROT_UNREACH:
+		case ICMP_PORT_UNREACH:
+			break;
+		case ICMP_FRAG_NEEDED:
+			/* for documentation of the ip_no_pmtu_disc
+			 * values please see
+			 * Documentation/networking/ip-sysctl.rst
+			 */
+			switch (READ_ONCE(net->ipv4.sysctl_ip_no_pmtu_disc)) {
+			default:
+				net_dbg_ratelimited("%pI4: fragmentation needed and DF set\n",
+						    &iph->daddr);
+				break;
+			case 2:
+				goto out;
+			case 3:
+				if (!icmp_tag_validation(iph->protocol))
+					goto out;
+				fallthrough;
+			case 0:
+				info = ntohs(icmph->un.frag.mtu);
+			}
+			break;
+		case ICMP_SR_FAILED:
+			net_dbg_ratelimited("%pI4: Source Route Failed\n",
+					    &iph->daddr);
+			break;
+		default:
+			break;
+		}
+		if (icmph->code > NR_ICMP_UNREACH)
+			goto out;
+		break;
+	case ICMP_PARAMETERPROB:
+		info = ntohl(icmph->un.gateway) >> 24;
+		break;
+	case ICMP_TIME_EXCEEDED:
+		__ICMP_INC_STATS(net, ICMP_MIB_INTIMEEXCDS);
+		if (icmph->code == ICMP_EXC_FRAGTIME)
+			goto out;
+		break;
+	}
+
+	/*
+	 *	Throw it at our lower layers
+	 *
+	 *	RFC 1122: 3.2.2 MUST extract the protocol ID from the passed
+	 *		  header.
+	 *	RFC 1122: 3.2.2.1 MUST pass ICMP unreach messages to the
+	 *		  transport layer.
+	 *	RFC 1122: 3.2.2.2 MUST pass ICMP time expired messages to
+	 *		  transport layer.
+	 */
+
+	/*
+	 *	Check the other end isn't violating RFC 1122. Some routers send
+	 *	bogus responses to broadcast frames. If you see this message
+	 *	first check your netmask matches at both ends, if it does then
+	 *	get the other vendor to fix their kit.
+	 */
+
+	if (!READ_ONCE(net->ipv4.sysctl_icmp_ignore_bogus_error_responses) &&
+	    inet_addr_type_dev_table(net, skb->dev, iph->daddr) == RTN_BROADCAST) {
+		net_warn_ratelimited("%pI4 sent an invalid ICMP type %u, code %u error to a broadcast: %pI4 on %s\n",
+				     &ip_hdr(skb)->saddr,
+				     icmph->type, icmph->code,
+				     &iph->daddr, skb->dev->name);
+		goto out;
+	}
+
+	icmp_socket_deliver(skb, info);
+
+out:
+	return reason;
+out_err:
+	__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
+	return reason ?: SKB_DROP_REASON_NOT_SPECIFIED;
+}
+
+/*
+ *	Handle ICMP_REDIRECT.
+ */
+
+static enum skb_drop_reason icmp_redirect(struct sk_buff *skb)
+{
+	if (skb->len < sizeof(struct iphdr)) {
+		__ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
+		return SKB_DROP_REASON_PKT_TOO_SMALL;
+	}
+
+	if (!pskb_may_pull(skb, sizeof(struct iphdr))) {
+		/* there aught to be a stat */
+		return SKB_DROP_REASON_NOMEM;
+	}
+
+	icmp_socket_deliver(skb, ntohl(icmp_hdr(skb)->un.gateway));
+	return SKB_NOT_DROPPED_YET;
+}
+
+/*
+ *	Handle ICMP_ECHO ("ping") and ICMP_EXT_ECHO ("PROBE") requests.
+ *
+ *	RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo
+ *		  requests.
+ *	RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be
+ *		  included in the reply.
+ *	RFC 1812: 4.3.3.6 SHOULD have a config option for silently ignoring
+ *		  echo requests, MUST have default=NOT.
+ *	RFC 8335: 8 MUST have a config option to enable/disable ICMP
+ *		  Extended Echo Functionality, MUST be disabled by default
+ *	See also WRT handling of options once they are done and working.
+ */
+
+static enum skb_drop_reason icmp_echo(struct sk_buff *skb)
+{
+	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+			IP_OPTIONS_DATA_FIXED_SIZE);
+	struct net *net;
+
+	net = skb_dst_dev_net_rcu(skb);
+	/* should there be an ICMP stat for ignored echos? */
+	if (READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_all))
+		return SKB_NOT_DROPPED_YET;
+
+	icmp_param->data.icmph	   = *icmp_hdr(skb);
+	icmp_param->skb		   = skb;
+	icmp_param->offset	   = 0;
+	icmp_param->data_len	   = skb->len;
+	icmp_param->head_len	   = sizeof(struct icmphdr);
+
+	if (icmp_param->data.icmph.type == ICMP_ECHO)
+		icmp_param->data.icmph.type = ICMP_ECHOREPLY;
+	else if (!icmp_build_probe(skb, &icmp_param->data.icmph))
+		return SKB_NOT_DROPPED_YET;
+
+	icmp_reply(icmp_param, skb);
+	return SKB_NOT_DROPPED_YET;
+}
+
+/*
+ *	Handle ICMP Timestamp requests.
+ *	RFC 1122: 3.2.2.8 MAY implement ICMP timestamp requests.
+ *		  SHOULD be in the kernel for minimum random latency.
+ *		  MUST be accurate to a few minutes.
+ *		  MUST be updated at least at 15Hz.
+ */
+static enum skb_drop_reason icmp_timestamp(struct sk_buff *skb)
+{
+	DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+			IP_OPTIONS_DATA_FIXED_SIZE);
+	/*
+	 *	Too short.
+	 */
+	if (skb->len < 4)
+		goto out_err;
+
+	/*
+	 *	Fill in the current time as ms since midnight UT:
+	 */
+	icmp_param->data.times[1] = inet_current_timestamp();
+	icmp_param->data.times[2] = icmp_param->data.times[1];
+
+	BUG_ON(skb_copy_bits(skb, 0, &icmp_param->data.times[0], 4));
+
+	icmp_param->data.icmph	   = *icmp_hdr(skb);
+	icmp_param->data.icmph.type = ICMP_TIMESTAMPREPLY;
+	icmp_param->data.icmph.code = 0;
+	icmp_param->skb		   = skb;
+	icmp_param->offset	   = 0;
+	icmp_param->data_len	   = 0;
+	icmp_param->head_len	   = sizeof(struct icmphdr) + 12;
+	icmp_reply(icmp_param, skb);
+	return SKB_NOT_DROPPED_YET;
+
+out_err:
+	__ICMP_INC_STATS(skb_dst_dev_net_rcu(skb), ICMP_MIB_INERRORS);
+	return SKB_DROP_REASON_PKT_TOO_SMALL;
+}
+
+static enum skb_drop_reason icmp_discard(struct sk_buff *skb)
+{
+	/* pretend it was a success */
+	return SKB_NOT_DROPPED_YET;
+}
+
+int icmp_err(struct sk_buff *skb, u32 info)
+{
+	struct iphdr *iph = (struct iphdr *)skb->data;
+	int offset = iph->ihl<<2;
+	struct icmphdr *icmph = (struct icmphdr *)(skb->data + offset);
+	struct net *net = dev_net_rcu(skb->dev);
+	int type = icmp_hdr(skb)->type;
+	int code = icmp_hdr(skb)->code;
+
+	/*
+	 * Use ping_err to handle all icmp errors except those
+	 * triggered by ICMP_ECHOREPLY which sent from kernel.
+	 */
+	if (icmph->type != ICMP_ECHOREPLY) {
+		ping_err(skb, offset, info);
+		return 0;
+	}
+
+	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
+		ipv4_update_pmtu(skb, net, info, 0, IPPROTO_ICMP);
+	else if (type == ICMP_REDIRECT)
+		ipv4_redirect(skb, net, 0, IPPROTO_ICMP);
+
+	return 0;
+}
+
+/*
+ *	Deal with incoming ICMP packets.
+ */
+int icmp_rcv(struct sk_buff *skb)
+{
+	enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
+	struct rtable *rt = skb_rtable(skb);
+	struct net *net = dev_net_rcu(rt->dst.dev);
+	struct icmphdr *icmph;
+
+	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
+		struct sec_path *sp = skb_sec_path(skb);
+		int nh;
+
+		if (!(sp && sp->xvec[sp->len - 1]->props.flags &
+				 XFRM_STATE_ICMP)) {
+			reason = SKB_DROP_REASON_XFRM_POLICY;
+			goto drop;
+		}
+
+		if (!pskb_may_pull(skb, sizeof(*icmph) + sizeof(struct iphdr)))
+			goto drop;
+
+		nh = skb_network_offset(skb);
+		skb_set_network_header(skb, sizeof(*icmph));
+
+		if (!xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN,
+						skb)) {
+			reason = SKB_DROP_REASON_XFRM_POLICY;
+			goto drop;
+		}
+
+		skb_set_network_header(skb, nh);
+	}
+
+	__ICMP_INC_STATS(net, ICMP_MIB_INMSGS);
+
+	if (skb_checksum_simple_validate(skb))
+		goto csum_error;
+
+	if (!pskb_pull(skb, sizeof(*icmph)))
+		goto error;
+
+	icmph = icmp_hdr(skb);
+
+	ICMPMSGIN_INC_STATS(net, icmph->type);
+
+	/* Check for ICMP Extended Echo (PROBE) messages */
+	if (icmph->type == ICMP_EXT_ECHO) {
+		/* We can't use icmp_pointers[].handler() because it is an array of
+		 * size NR_ICMP_TYPES + 1 (19 elements) and PROBE has code 42.
+		 */
+		reason = icmp_echo(skb);
+		goto reason_check;
+	}
+
+	/*
+	 *	Parse the ICMP message
+	 */
+
+	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
+		/*
+		 *	RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be
+		 *	  silently ignored (we let user decide with a sysctl).
+		 *	RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently
+		 *	  discarded if to broadcast/multicast.
+		 */
+		if ((icmph->type == ICMP_ECHO ||
+		     icmph->type == ICMP_TIMESTAMP) &&
+		    READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_broadcasts)) {
+			reason = SKB_DROP_REASON_INVALID_PROTO;
+			goto error;
+		}
+		if (icmph->type != ICMP_ECHO &&
+		    icmph->type != ICMP_TIMESTAMP &&
+		    icmph->type != ICMP_ADDRESS &&
+		    icmph->type != ICMP_ADDRESSREPLY) {
+			reason = SKB_DROP_REASON_INVALID_PROTO;
+			goto error;
+		}
+	}
+
+	if (icmph->type == ICMP_EXT_ECHOREPLY ||
+	    icmph->type == ICMP_ECHOREPLY) {
+		reason = ping_rcv(skb);
+		return reason ? NET_RX_DROP : NET_RX_SUCCESS;
+	}
+
+	/*
+	 *	18 is the highest 'known' ICMP type. Anything else is a mystery
+	 *
+	 *	RFC 1122: 3.2.2  Unknown ICMP messages types MUST be silently
+	 *		  discarded.
+	 */
+	if (icmph->type > NR_ICMP_TYPES) {
+		reason = SKB_DROP_REASON_UNHANDLED_PROTO;
+		goto error;
+	}
+
+	reason = icmp_pointers[icmph->type].handler(skb);
+reason_check:
+	if (!reason)  {
+		consume_skb(skb);
+		return NET_RX_SUCCESS;
+	}
+
+drop:
+	kfree_skb_reason(skb, reason);
+	return NET_RX_DROP;
+csum_error:
+	reason = SKB_DROP_REASON_ICMP_CSUM;
+	__ICMP_INC_STATS(net, ICMP_MIB_CSUMERRORS);
+error:
+	__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
+	goto drop;
+}
+
+/*
+ *	This table is the definition of how we handle ICMP.
+ */
+static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = {
+	[ICMP_ECHOREPLY] = {
+		.handler = ping_rcv,
+	},
+	[1] = {
+		.handler = icmp_discard,
+		.error = 1,
+	},
+	[2] = {
+		.handler = icmp_discard,
+		.error = 1,
+	},
+	[ICMP_DEST_UNREACH] = {
+		.handler = icmp_unreach,
+		.error = 1,
+	},
+	[ICMP_SOURCE_QUENCH] = {
+		.handler = icmp_unreach,
+		.error = 1,
+	},
+	[ICMP_REDIRECT] = {
+		.handler = icmp_redirect,
+		.error = 1,
+	},
+	[6] = {
+		.handler = icmp_discard,
+		.error = 1,
+	},
+	[7] = {
+		.handler = icmp_discard,
+		.error = 1,
+	},
+	[ICMP_ECHO] = {
+		.handler = icmp_echo,
+	},
+	[9] = {
+		.handler = icmp_discard,
+		.error = 1,
+	},
+	[10] = {
+		.handler = icmp_discard,
+		.error = 1,
+	},
+	[ICMP_TIME_EXCEEDED] = {
+		.handler = icmp_unreach,
+		.error = 1,
+	},
+	[ICMP_PARAMETERPROB] = {
+		.handler = icmp_unreach,
+		.error = 1,
+	},
+	[ICMP_TIMESTAMP] = {
+		.handler = icmp_timestamp,
+	},
+	[ICMP_TIMESTAMPREPLY] = {
+		.handler = icmp_discard,
+	},
+	[ICMP_INFO_REQUEST] = {
+		.handler = icmp_discard,
+	},
+	[ICMP_INFO_REPLY] = {
+		.handler = icmp_discard,
+	},
+	[ICMP_ADDRESS] = {
+		.handler = icmp_discard,
+	},
+	[ICMP_ADDRESSREPLY] = {
+		.handler = icmp_discard,
+	},
+};
+
+int icmp_init_ipv4(void)
+{
+	int err, i;
+
+	for_each_possible_cpu(i) {
+		struct sock *sk;
+
+		err = inet_ctl_sock_create(&sk, PF_INET,
+					   SOCK_RAW, IPPROTO_ICMP, &init_net);
+		if (err < 0)
+			return err;
+
+		per_cpu(ipv4_icmp_sk, i) = sk;
+
+		/* Enough space for 2 64K ICMP packets, including
+		 * sk_buff/skb_shared_info struct overhead.
+		 */
+		sk->sk_sndbuf =	2 * SKB_TRUESIZE(64 * 1024);
+
+		/*
+		 * Speedup sock_wfree()
+		 */
+		sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
+		inet_sk(sk)->pmtudisc = IP_PMTUDISC_DONT;
+	}
+
+	return 0;
+}
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 8165277629ad..62007dbadf7a 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -205,7 +205,9 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 					     (struct sockaddr_unsized *)&sin,
 					     sizeof(sin));
 
+#if IS_ENABLED(CONFIG_IPV4)
 ipv4_connected:
+#endif
 		if (err)
 			goto out;
 
-- 
2.54.0


^ permalink raw reply related


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