mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 01/16 net-next v3] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack
       [not found] <20260930135334.4739-1-fmancera@suse.de>
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 02/16 net-next v3] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Chuck Lever, Jason Gunthorpe, Leon Romanovsky, Zhu Yanjun,
	Andrew Lunn, Eric Dumazet, Tony Nguyen, Przemek Kitszel,
	Elad Nachman, Saeed Mahameed, Tariq Toukan, Mark Bloch,
	Ido Schimmel, Petr Machata, Jiri Pirko, Edward Cree,
	Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
	Arend van Spriel, Miri Korenblit, Keith Busch, Jens Axboe,
	Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
	Varun Prakash, James E.J. Bottomley, Martin K. Petersen,
	Alexander Viro, Christian Brauner, Jan Kara, David Howells,
	Marc Dionne, Trond Myklebust, Anna Schumaker, Jeff Layton,
	NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, Marek Lindner,
	Simon Wunderlich, Antonio Quartulli, Sven Eckelmann,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Florian Westphal,
	Phil Sutter, Matthieu Baerts, Mat Martineau, Geliang Tang,
	Julian Anastasov, Allison Henderson, Jamal Hadi Salim,
	Marcelo Ricardo Leitner, Xin Long, D. Wythe, Dust Li,
	Sidraya Jayagond, Mahanta Jambigi, Tony Lu, Wen Gu, Jon Maloy,
	Tung Quang Nguyen, Steffen Klassert, Herbert Xu, Casey Schaufler,
	Paul Moore, James Morris, Serge E. Hallyn, Krzysztof Kozlowski,
	Qingfang Deng, Julian Braha, Greg Kroah-Hartman,
	Ethan Nelson-Moore, Eric Biggers, Ard Biesheuvel, Neal Cardwell,
	Kuniyuki Iwashima, Alyssa Ross, linux-rdma, linux-kernel,
	intel-wired-lan, linux-net-drivers, linux-stm32,
	linux-arm-kernel, linux-wireless, brcm80211,
	brcm80211-dev-list.pdl, linux-nvme, linux-scsi, target-devel,
	linux-fsdevel, linux-afs, linux-nfs, b.a.t.m.a.n, bridge,
	netfilter-devel, coreteam, mptcp, lvs-devel, rds-devel,
	linux-sctp, linux-s390, tipc-discussion, linux-security-module

Historically, the IPv4 protocol has been linked to the core INET
subsystem. Because shared infrastructure like the TCP/UDP engine,
routing or INET hashtables live inside net/ipv4/, it has been impossible
to compile a kernel with only IPv6 support.

This patch introduces the CONFIG_IPV4 Kconfig symbol, which is set to
'def_bool y' for now. This does not allow to completely disable the
IPv4 stack yet but it lays the necessary build-system work for that
goal.

In addition, multiple subsystems Kconfig symbols across networking
features like tunnels, drivers and filesystems have been adjusted to
depend on IPv4. Keep on mind that follow-up work is required to convert
the Kconfig symbol to bool and remove the dependency on IPv4 where it
makes sense.

Acked-by: Chuck Lever <cel@kernel.org>
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
Sashiko: Marking the Kconfig symbols to depend on IPV4 is intentional,
plenty of features won't work without IPv4 for now. Follow-up patches
are required to support IPv4=n for those symbols.
---
 drivers/infiniband/Kconfig                    |  2 +-
 drivers/infiniband/sw/rxe/Kconfig             |  2 +-
 drivers/net/Kconfig                           | 23 ++++++++++---------
 drivers/net/ethernet/chelsio/Kconfig          |  2 +-
 drivers/net/ethernet/intel/Kconfig            |  2 ++
 drivers/net/ethernet/marvell/prestera/Kconfig |  1 +
 .../net/ethernet/mellanox/mlx5/core/Kconfig   |  3 ++-
 drivers/net/ethernet/mellanox/mlxsw/Kconfig   |  1 +
 drivers/net/ethernet/qlogic/Kconfig           |  2 ++
 drivers/net/ethernet/rocker/Kconfig           |  1 +
 drivers/net/ethernet/sfc/Kconfig              |  1 +
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |  1 +
 drivers/net/ethernet/via/Kconfig              |  1 +
 drivers/net/ppp/Kconfig                       |  1 +
 .../broadcom/brcm80211/brcmfmac/Kconfig       |  1 +
 drivers/net/wireless/intel/iwlwifi/Kconfig    |  1 +
 drivers/nvme/host/Kconfig                     |  2 +-
 drivers/nvme/target/Kconfig                   |  2 +-
 drivers/scsi/cxgbi/cxgb3i/Kconfig             |  2 +-
 drivers/scsi/cxgbi/cxgb4i/Kconfig             |  2 +-
 drivers/target/iscsi/Kconfig                  |  2 +-
 drivers/target/iscsi/cxgbit/Kconfig           |  2 +-
 fs/Kconfig                                    |  1 +
 fs/afs/Kconfig                                |  2 +-
 fs/nfs/Kconfig                                |  2 +-
 fs/nfsd/Kconfig                               |  2 +-
 net/Kconfig                                   | 12 ++++++----
 net/batman-adv/Kconfig                        |  6 ++---
 net/bridge/Kconfig                            |  2 +-
 net/bridge/netfilter/Kconfig                  |  3 ++-
 net/ipv4/Kconfig                              | 21 ++++++++++++++---
 net/ipv4/Makefile                             | 21 ++++++++---------
 net/ipv4/netfilter/Kconfig                    |  2 +-
 net/ipv6/Kconfig                              | 17 ++++++++++----
 net/ipv6/netfilter/Kconfig                    |  2 +-
 net/l2tp/Kconfig                              |  2 +-
 net/mptcp/Kconfig                             |  2 +-
 net/netfilter/Kconfig                         |  2 +-
 net/netfilter/ipset/Kconfig                   |  2 +-
 net/netfilter/ipvs/Kconfig                    |  2 +-
 net/rds/Kconfig                               |  1 +
 net/rxrpc/Kconfig                             |  2 +-
 net/sched/Kconfig                             |  2 +-
 net/sctp/Kconfig                              |  2 +-
 net/smc/Kconfig                               |  2 +-
 net/sunrpc/Kconfig                            |  2 +-
 net/tipc/Kconfig                              |  1 +
 net/xfrm/Kconfig                              | 10 ++++++--
 security/smack/Kconfig                        |  1 +
 49 files changed, 118 insertions(+), 65 deletions(-)

diff --git a/drivers/infiniband/Kconfig b/drivers/infiniband/Kconfig
index 086195758a8a..e9302002b6ea 100644
--- a/drivers/infiniband/Kconfig
+++ b/drivers/infiniband/Kconfig
@@ -3,7 +3,7 @@ menuconfig INFINIBAND
 	tristate "InfiniBand support"
 	depends on HAS_IOMEM && HAS_DMA
 	depends on NET
-	depends on INET
+	depends on INET && IPV4
 	depends on !ALPHA
 	select DMA_SHARED_BUFFER
 	select IRQ_POLL
diff --git a/drivers/infiniband/sw/rxe/Kconfig b/drivers/infiniband/sw/rxe/Kconfig
index 1ed5b63f8afc..e4272f863944 100644
--- a/drivers/infiniband/sw/rxe/Kconfig
+++ b/drivers/infiniband/sw/rxe/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config RDMA_RXE
 	tristate "Software RDMA over Ethernet (RoCE) driver"
-	depends on INET && PCI && INFINIBAND && 64BIT
+	depends on INET && IPV4 && PCI && INFINIBAND && 64BIT
 	depends on INFINIBAND_VIRT_DMA
 	select NET_UDP_TUNNEL
 	select CRC32
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index ff79c466712d..5f60c3ff182b 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -40,7 +40,7 @@ if NET_CORE
 
 config BONDING
 	tristate "Bonding driver support"
-	depends on INET
+	depends on INET && IPV4
 	depends on TLS || TLS_DEVICE=n
 	help
 	  Say 'Y' or 'M' if you wish to be able to 'bond' multiple Ethernet
@@ -73,7 +73,7 @@ config DUMMY
 
 config WIREGUARD
 	tristate "WireGuard secure network tunnel"
-	depends on NET && INET
+	depends on NET && INET && IPV4
 	select NET_UDP_TUNNEL
 	select DST_CACHE
 	select CRYPTO_LIB_CURVE25519
@@ -102,7 +102,7 @@ config WIREGUARD_DEBUG
 
 config OVPN
 	tristate "OpenVPN data channel offload"
-	depends on NET && INET
+	depends on NET && INET && IPV4
 	select DST_CACHE
 	select NET_UDP_TUNNEL
 	select CRYPTO
@@ -198,7 +198,7 @@ config IPVLAN_L3S
 
 config IPVLAN
 	tristate "IP-VLAN support"
-	depends on INET
+	depends on INET && IPV4
 	help
 	  This allows one to create virtual devices off of a main interface
 	  and packets will be delivered based on the dest L3 (IPv6/IPv4 addr)
@@ -229,7 +229,7 @@ config IPVTAP
 
 config VXLAN
 	tristate "Virtual eXtensible Local Area Network (VXLAN)"
-	depends on INET
+	depends on INET && IPV4
 	select NET_UDP_TUNNEL
 	select GRO_CELLS
 	help
@@ -244,7 +244,7 @@ config VXLAN
 
 config GENEVE
 	tristate "Generic Network Virtualization Encapsulation"
-	depends on INET
+	depends on INET && IPV4
 	select NET_UDP_TUNNEL
 	select GRO_CELLS
 	help
@@ -259,7 +259,7 @@ config GENEVE
 
 config BAREUDP
 	tristate "Bare UDP Encapsulation"
-	depends on INET
+	depends on INET && IPV4
 	select NET_UDP_TUNNEL
 	select GRO_CELLS
 	help
@@ -271,7 +271,7 @@ config BAREUDP
 
 config GTP
 	tristate "GPRS Tunneling Protocol datapath (GTP-U)"
-	depends on INET
+	depends on INET && IPV4
 	select NET_UDP_TUNNEL
 	help
 	  This allows one to create gtp virtual interfaces that provide
@@ -288,7 +288,7 @@ config GTP
 
 config PFCP
 	tristate "Packet Forwarding Control Protocol (PFCP)"
-	depends on INET
+	depends on INET && IPV4
 	select NET_UDP_TUNNEL
 	help
 	  This allows one to create PFCP virtual interfaces that allows to
@@ -301,7 +301,7 @@ config PFCP
 
 config AMT
 	tristate "Automatic Multicast Tunneling (AMT)"
-	depends on INET && IP_MULTICAST
+	depends on INET && IP_MULTICAST && IPV4
 	select NET_UDP_TUNNEL
 	help
 	  This allows one to create AMT(Automatic Multicast Tunneling)
@@ -471,6 +471,7 @@ config NETKIT
 
 config NET_VRF
 	tristate "Virtual Routing and Forwarding (Lite)"
+	depends on IPV4
 	depends on IP_MULTIPLE_TABLES
 	depends on NET_L3_MASTER_DEV
 	depends on IPV6_MULTIPLE_TABLES || IPV6=n
@@ -604,7 +605,7 @@ source "drivers/net/hyperv/Kconfig"
 config NETDEVSIM
 	tristate "Simulated networking device"
 	depends on DEBUG_FS
-	depends on INET
+	depends on INET && IPV4
 	depends on PSAMPLE || PSAMPLE=n
 	depends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n
 	select NET_DEVLINK
diff --git a/drivers/net/ethernet/chelsio/Kconfig b/drivers/net/ethernet/chelsio/Kconfig
index 96d7779cd2f0..c738820688e0 100644
--- a/drivers/net/ethernet/chelsio/Kconfig
+++ b/drivers/net/ethernet/chelsio/Kconfig
@@ -48,7 +48,7 @@ config CHELSIO_T1_1G
 
 config CHELSIO_T3
 	tristate "Chelsio Communications T3 10Gb Ethernet support"
-	depends on PCI && INET
+	depends on PCI && INET && IPV4
 	select FW_LOADER
 	select MDIO
 	help
diff --git a/drivers/net/ethernet/intel/Kconfig b/drivers/net/ethernet/intel/Kconfig
index 780f113986ea..ce1c68e9cdf6 100644
--- a/drivers/net/ethernet/intel/Kconfig
+++ b/drivers/net/ethernet/intel/Kconfig
@@ -195,6 +195,7 @@ config IXGBE_IPSEC
 	bool "IPSec XFRM cryptography-offload acceleration"
 	depends on IXGBE
 	depends on XFRM_OFFLOAD
+	depends on IPV4 || !INET
 	default y
 	select XFRM_ALGO
 	help
@@ -221,6 +222,7 @@ config IXGBEVF_IPSEC
 	bool "IPSec XFRM cryptography-offload acceleration"
 	depends on IXGBEVF
 	depends on XFRM_OFFLOAD
+	depends on IPV4 || !INET
 	default y
 	select XFRM_ALGO
 	help
diff --git a/drivers/net/ethernet/marvell/prestera/Kconfig b/drivers/net/ethernet/marvell/prestera/Kconfig
index f2f7663c3d10..257afbb3c434 100644
--- a/drivers/net/ethernet/marvell/prestera/Kconfig
+++ b/drivers/net/ethernet/marvell/prestera/Kconfig
@@ -7,6 +7,7 @@ config PRESTERA
 	tristate "Marvell Prestera Switch ASICs support"
 	depends on NET_SWITCHDEV && VLAN_8021Q
 	depends on BRIDGE || BRIDGE=n
+	depends on IPV4 || !INET
 	select NET_DEVLINK
 	select PHYLINK
 	help
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/Kconfig b/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
index 925ee25d05b4..99d998aa7e9b 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
+++ b/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
@@ -12,6 +12,7 @@ config MLX5_CORE
 	depends on PTP_1588_CLOCK_OPTIONAL
 	depends on PCI_HYPERV_INTERFACE || !PCI_HYPERV_INTERFACE
 	depends on HWMON || !HWMON
+	depends on IPV4 || !INET
 	help
 	  Core driver for low level functionality of the ConnectX-4 and
 	  Connect-IB cards by Mellanox Technologies.
@@ -28,7 +29,7 @@ config MLX5_FPGA
 
 config MLX5_CORE_EN
 	bool "Mellanox 5th generation network adapters (ConnectX series) Ethernet support"
-	depends on NETDEVICES && ETHERNET && INET && PCI && MLX5_CORE
+	depends on NETDEVICES && ETHERNET && INET && PCI && MLX5_CORE && IPV4
 	select PAGE_POOL
 	select PAGE_POOL_STATS
 	select DIMLIB
diff --git a/drivers/net/ethernet/mellanox/mlxsw/Kconfig b/drivers/net/ethernet/mellanox/mlxsw/Kconfig
index 2229a2694aa5..fb3028600724 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/Kconfig
+++ b/drivers/net/ethernet/mellanox/mlxsw/Kconfig
@@ -53,6 +53,7 @@ config MLXSW_I2C
 
 config MLXSW_SPECTRUM
 	tristate "Mellanox Technologies Spectrum family support"
+	depends on IPV4 || !INET
 	depends on MLXSW_CORE && MLXSW_PCI && NET_SWITCHDEV && VLAN_8021Q
 	depends on PSAMPLE || PSAMPLE=n
 	depends on BRIDGE || BRIDGE=n
diff --git a/drivers/net/ethernet/qlogic/Kconfig b/drivers/net/ethernet/qlogic/Kconfig
index 1203353238e5..aed78311ecaf 100644
--- a/drivers/net/ethernet/qlogic/Kconfig
+++ b/drivers/net/ethernet/qlogic/Kconfig
@@ -29,6 +29,7 @@ config QLA3XXX
 config QLCNIC
 	tristate "QLOGIC QLCNIC 1/10Gb Converged Ethernet NIC Support"
 	depends on PCI
+	depends on IPV4 || !INET
 	select FW_LOADER
 	help
 	  This driver supports QLogic QLE8240 and QLE8242 Converged Ethernet
@@ -68,6 +69,7 @@ config QLCNIC_HWMON
 
 config NETXEN_NIC
 	tristate "NetXen Multi port (1/10) Gigabit Ethernet NIC"
+	depends on IPV4 || !INET
 	depends on PCI
 	select FW_LOADER
 	help
diff --git a/drivers/net/ethernet/rocker/Kconfig b/drivers/net/ethernet/rocker/Kconfig
index 2318811ff75a..2f994bd6f9bd 100644
--- a/drivers/net/ethernet/rocker/Kconfig
+++ b/drivers/net/ethernet/rocker/Kconfig
@@ -19,6 +19,7 @@ if NET_VENDOR_ROCKER
 config ROCKER
 	tristate "Rocker switch driver (EXPERIMENTAL)"
 	depends on PCI && NET_SWITCHDEV && BRIDGE
+	depends on IPV4 || !INET
 	select CRC32
 	help
 	  This driver supports Rocker switch device.
diff --git a/drivers/net/ethernet/sfc/Kconfig b/drivers/net/ethernet/sfc/Kconfig
index e2898ec2e6e9..2bf5c2a185d2 100644
--- a/drivers/net/ethernet/sfc/Kconfig
+++ b/drivers/net/ethernet/sfc/Kconfig
@@ -18,6 +18,7 @@ if NET_VENDOR_SOLARFLARE
 
 config SFC
 	tristate "Solarflare SFC9100/EF100-family support"
+	depends on IPV4 || !INET
 	depends on PCI
 	depends on PTP_1588_CLOCK_OPTIONAL
 	select MDIO
diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index ab3c4cf96423..0b487786dad4 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config STMMAC_ETH
 	tristate "STMicroelectronics Multi-Gigabit Ethernet driver"
+	depends on IPV4 || !INET
 	depends on HAS_IOMEM && HAS_DMA
 	depends on PTP_1588_CLOCK_OPTIONAL
 	depends on ETHTOOL_NETLINK
diff --git a/drivers/net/ethernet/via/Kconfig b/drivers/net/ethernet/via/Kconfig
index 00773f5e4d7e..dbb24d35dfbd 100644
--- a/drivers/net/ethernet/via/Kconfig
+++ b/drivers/net/ethernet/via/Kconfig
@@ -45,6 +45,7 @@ config VIA_RHINE_MMIO
 
 config VIA_VELOCITY
 	tristate "VIA Velocity support"
+	depends on IPV4 || !INET
 	depends on (PCI || (OF_ADDRESS && OF_IRQ))
 	depends on HAS_DMA
 	select CRC32
diff --git a/drivers/net/ppp/Kconfig b/drivers/net/ppp/Kconfig
index 4047f13b83dc..c5200fae5e4a 100644
--- a/drivers/net/ppp/Kconfig
+++ b/drivers/net/ppp/Kconfig
@@ -160,6 +160,7 @@ config PPPOE_HASH_BITS
 
 config PPTP
 	tristate "PPP over IPv4 (PPTP)"
+	depends on IPV4 || !INET
 	depends on NET_IPGRE_DEMUX
 	select PPPOX
 	help
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig
index 32794c1eca23..01a5b3310e1e 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig
@@ -1,6 +1,7 @@
 config BRCMFMAC
 	tristate "Broadcom FullMAC WLAN driver"
 	depends on CFG80211
+	depends on IPV4 || !INET
 	select BRCMUTIL
 	help
 	  This module adds support for wireless adapters based on Broadcom
diff --git a/drivers/net/wireless/intel/iwlwifi/Kconfig b/drivers/net/wireless/intel/iwlwifi/Kconfig
index 153a8368b412..3719fa0727c2 100644
--- a/drivers/net/wireless/intel/iwlwifi/Kconfig
+++ b/drivers/net/wireless/intel/iwlwifi/Kconfig
@@ -2,6 +2,7 @@
 config IWLWIFI
 	tristate "Intel Wireless WiFi Next Gen AGN - Wireless-N/Advanced-N/Ultimate-N (iwlwifi) "
 	depends on PCI && HAS_IOMEM && CFG80211
+	depends on IPV4 || !INET
 	depends on IWLMEI || !IWLMEI
 	select FW_LOADER
 	help
diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig
index 31974c7dd20c..6e119cf475de 100644
--- a/drivers/nvme/host/Kconfig
+++ b/drivers/nvme/host/Kconfig
@@ -82,7 +82,7 @@ config NVME_FC
 
 config NVME_TCP
 	tristate "NVM Express over Fabrics TCP host driver"
-	depends on INET
+	depends on INET && IPV4
 	depends on BLOCK
 	select CRC32
 	select NET_CRC32C
diff --git a/drivers/nvme/target/Kconfig b/drivers/nvme/target/Kconfig
index 69bde270115e..f731a043f6a9 100644
--- a/drivers/nvme/target/Kconfig
+++ b/drivers/nvme/target/Kconfig
@@ -86,7 +86,7 @@ config NVME_TARGET_FCLOOP
 
 config NVME_TARGET_TCP
 	tristate "NVMe over Fabrics TCP target support"
-	depends on INET
+	depends on INET && IPV4
 	depends on NVME_TARGET
 	help
 	  This enables the NVMe TCP target support, which allows exporting NVMe
diff --git a/drivers/scsi/cxgbi/cxgb3i/Kconfig b/drivers/scsi/cxgbi/cxgb3i/Kconfig
index 143e881ec77e..abb4a462cb37 100644
--- a/drivers/scsi/cxgbi/cxgb3i/Kconfig
+++ b/drivers/scsi/cxgbi/cxgb3i/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config SCSI_CXGB3_ISCSI
 	tristate "Chelsio T3 iSCSI support"
-	depends on PCI && INET
+	depends on PCI && INET && IPV4
 	select NETDEVICES
 	select ETHERNET
 	select NET_VENDOR_CHELSIO
diff --git a/drivers/scsi/cxgbi/cxgb4i/Kconfig b/drivers/scsi/cxgbi/cxgb4i/Kconfig
index dd1c8ff36b00..04b3e7b053dc 100644
--- a/drivers/scsi/cxgbi/cxgb4i/Kconfig
+++ b/drivers/scsi/cxgbi/cxgb4i/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config SCSI_CXGB4_ISCSI
 	tristate "Chelsio T4 iSCSI support"
-	depends on PCI && INET
+	depends on PCI && INET && IPV4
 	depends on PTP_1588_CLOCK_OPTIONAL
 	depends on THERMAL || !THERMAL
 	depends on ETHERNET
diff --git a/drivers/target/iscsi/Kconfig b/drivers/target/iscsi/Kconfig
index 70d76f3dd693..362edff80c15 100644
--- a/drivers/target/iscsi/Kconfig
+++ b/drivers/target/iscsi/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config ISCSI_TARGET
 	tristate "SCSI Target Mode Stack"
-	depends on INET
+	depends on INET && IPV4
 	select CRC32
 	select CRYPTO
 	select CRYPTO_HASH
diff --git a/drivers/target/iscsi/cxgbit/Kconfig b/drivers/target/iscsi/cxgbit/Kconfig
index bdeefa75f29e..7a2e585d76d7 100644
--- a/drivers/target/iscsi/cxgbit/Kconfig
+++ b/drivers/target/iscsi/cxgbit/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config ISCSI_TARGET_CXGB4
 	tristate "Chelsio iSCSI target offload driver"
-	depends on ISCSI_TARGET && CHELSIO_T4 && INET
+	depends on ISCSI_TARGET && CHELSIO_T4 && INET && IPV4
 	select CHELSIO_LIB
 	help
 	To compile this driver as module, choose M here: the module
diff --git a/fs/Kconfig b/fs/Kconfig
index e05917adcd60..2f492fa3a3fb 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -358,6 +358,7 @@ config GRACE_PERIOD
 
 config LOCKD
 	tristate
+	depends on IPV4 || !INET
 	depends on FILE_LOCKING
 	select CRC32
 	select GRACE_PERIOD
diff --git a/fs/afs/Kconfig b/fs/afs/Kconfig
index 682bd8ec2c10..e7ed46426f81 100644
--- a/fs/afs/Kconfig
+++ b/fs/afs/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config AFS_FS
 	tristate "Andrew File System support (AFS)"
-	depends on INET
+	depends on INET && IPV4
 	select AF_RXRPC
 	select DNS_RESOLVER
 	select NETFS_SUPPORT
diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig
index 6bb30543eff0..52efd3664d21 100644
--- a/fs/nfs/Kconfig
+++ b/fs/nfs/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config NFS_FS
 	tristate "NFS client support"
-	depends on INET && FILE_LOCKING && MULTIUSER
+	depends on INET && FILE_LOCKING && MULTIUSER && IPV4
 	select CRC32
 	select LOCKD
 	select SUNRPC
diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig
index ffb76761d6a8..153a9918214d 100644
--- a/fs/nfsd/Kconfig
+++ b/fs/nfsd/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config NFSD
 	tristate "NFS server support"
-	depends on INET
+	depends on INET && IPV4
 	depends on FILE_LOCKING
 	depends on FSNOTIFY
 	select CRC32
diff --git a/net/Kconfig b/net/Kconfig
index 76ab44aa439a..ca86f20540dd 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -135,9 +135,12 @@ config INET
 if INET
 source "net/ipv4/Kconfig"
 source "net/ipv6/Kconfig"
-source "net/netlabel/Kconfig"
 source "net/mptcp/Kconfig"
 
+if IPV4
+source "net/netlabel/Kconfig"
+endif # if IPV4
+
 endif # if INET
 
 config NETWORK_SECMARK
@@ -232,6 +235,7 @@ config BRIDGE_NETFILTER
 	depends on BRIDGE
 	depends on NETFILTER && INET
 	depends on NETFILTER_ADVANCED
+	depends on IPV4
 	select NETFILTER_FAMILY_BRIDGE
 	select SKB_EXTENSIONS
 	help
@@ -384,7 +388,7 @@ menu "Network testing"
 
 config NET_PKTGEN
 	tristate "Packet Generator (USE WITH CAUTION)"
-	depends on INET && PROC_FS
+	depends on INET && IPV4 && PROC_FS
 	help
 	  This module will inject preconfigured packets, at a configurable
 	  rate, out of a given interface.  It is used for network interface
@@ -451,7 +455,7 @@ config LWTUNNEL
 
 config LWTUNNEL_BPF
 	bool "Execute BPF program as route nexthop action"
-	depends on LWTUNNEL && INET
+	depends on LWTUNNEL && INET && IPV4
 	default y if LWTUNNEL=y
 	help
 	  Allows to run BPF programs as a nexthop action following a route
@@ -473,7 +477,7 @@ config NET_IEEE8021Q_HELPERS
 
 config NET_SELFTESTS
 	def_tristate PHYLIB
-	depends on PHYLIB && INET
+	depends on PHYLIB && INET && IPV4
 
 config NET_SOCK_MSG
 	bool
diff --git a/net/batman-adv/Kconfig b/net/batman-adv/Kconfig
index 58c408b7a7d9..5c66122c137c 100644
--- a/net/batman-adv/Kconfig
+++ b/net/batman-adv/Kconfig
@@ -33,7 +33,7 @@ config BATMAN_ADV_BATMAN_V
 
 config BATMAN_ADV_BLA
 	bool "Bridge Loop Avoidance"
-	depends on BATMAN_ADV && INET
+	depends on BATMAN_ADV && INET && IPV4
 	select CRC16
 	select NET_CRC32C
 	default y
@@ -46,7 +46,7 @@ config BATMAN_ADV_BLA
 
 config BATMAN_ADV_DAT
 	bool "Distributed ARP Table"
-	depends on BATMAN_ADV && INET
+	depends on BATMAN_ADV && INET && IPV4
 	default y
 	help
 	  This option enables DAT (Distributed ARP Table), a DHT based
@@ -56,7 +56,7 @@ config BATMAN_ADV_DAT
 
 config BATMAN_ADV_MCAST
 	bool "Multicast optimisation"
-	depends on BATMAN_ADV && INET && !(BRIDGE=m && BATMAN_ADV=y)
+	depends on BATMAN_ADV && INET && !(BRIDGE=m && BATMAN_ADV=y) && IPV4
 	default y
 	help
 	  This option enables the multicast optimisation which aims to
diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
index 318715c8fc9b..c7737d3ebac8 100644
--- a/net/bridge/Kconfig
+++ b/net/bridge/Kconfig
@@ -36,7 +36,7 @@ config BRIDGE
 config BRIDGE_IGMP_SNOOPING
 	bool "IGMP/MLD snooping"
 	depends on BRIDGE
-	depends on INET
+	depends on INET && IPV4
 	default y
 	help
 	  If you say Y here, then the Ethernet bridge will be able selectively
diff --git a/net/bridge/netfilter/Kconfig b/net/bridge/netfilter/Kconfig
index 4fd5a6ea26b4..31e388ce503f 100644
--- a/net/bridge/netfilter/Kconfig
+++ b/net/bridge/netfilter/Kconfig
@@ -28,6 +28,7 @@ endif # NF_TABLES_BRIDGE
 config NF_CONNTRACK_BRIDGE
 	tristate "IPv4/IPV6 bridge connection tracking support"
 	depends on NF_CONNTRACK
+	depends on IPV4 || !INET
 	default n
 	help
 	  Connection tracking keeps a record of what packets have passed
@@ -188,7 +189,7 @@ config BRIDGE_EBT_VLAN
 #
 config BRIDGE_EBT_ARPREPLY
 	tristate "ebt: arp reply target support"
-	depends on BRIDGE_NF_EBTABLES && INET
+	depends on BRIDGE_NF_EBTABLES && INET && IPV4
 	help
 	  This option adds the arp reply target, which allows
 	  automatically sending arp replies to arp requests.
diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index 301b47660305..51bf05d9770c 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -2,6 +2,9 @@
 #
 # IP configuration
 #
+config IPV4
+	def_bool y
+
 config IP_MULTICAST
 	bool "IP: multicasting"
 	help
@@ -62,7 +65,7 @@ config IP_FIB_TRIE_STATS
 
 config IP_MULTIPLE_TABLES
 	bool "IP: policy routing"
-	depends on IP_ADVANCED_ROUTER
+	depends on IPV4 && IP_ADVANCED_ROUTER
 	select FIB_RULES
 	help
 	  Normally, a router decides what to do with a received packet based
@@ -105,6 +108,7 @@ config IP_ROUTE_CLASSID
 
 config IP_PNP
 	bool "IP: kernel level autoconfiguration"
+	depends on IPV4
 	help
 	  This enables automatic configuration of IP addresses of devices and
 	  of the routing table during kernel boot, based on either information
@@ -161,6 +165,7 @@ config IP_PNP_RARP
 
 config NET_IPIP
 	tristate "IP: tunneling"
+	depends on IPV4
 	select INET_TUNNEL
 	select NET_IP_TUNNEL
 	help
@@ -185,12 +190,14 @@ config NET_IPGRE_DEMUX
 
 config NET_IP_TUNNEL
 	tristate
+	depends on IPV4
 	select DST_CACHE
 	select GRO_CELLS
 	default n
 
 config NET_IPGRE
 	tristate "IP: GRE tunnels over IP"
+	depends on IPV4
 	depends on NET_IPGRE_DEMUX
 	select NET_IP_TUNNEL
 	help
@@ -219,7 +226,7 @@ config IP_MROUTE_COMMON
 
 config IP_MROUTE
 	bool "IP: multicast routing"
-	depends on IP_MULTICAST
+	depends on IP_MULTICAST && IPV4
 	select IP_MROUTE_COMMON
 	help
 	  This is used if you want your machine to act as a router for IP
@@ -267,6 +274,7 @@ config IP_PIMSM_V2
 
 config SYN_COOKIES
 	bool "IP: TCP syncookie support"
+	depends on IPV4
 	help
 	  Normal TCP/IP networking is open to an attack known as "SYN
 	  flooding". This denial-of-service attack prevents legitimate remote
@@ -303,6 +311,7 @@ config SYN_COOKIES
 
 config NET_IPVTI
 	tristate "Virtual (secure) IP: tunneling"
+	depends on IPV4
 	select INET_TUNNEL
 	select NET_IP_TUNNEL
 	select XFRM
@@ -315,11 +324,13 @@ config NET_IPVTI
 
 config NET_UDP_TUNNEL
 	tristate
+	depends on IPV4
 	select NET_IP_TUNNEL
 	default n
 
 config NET_FOU
 	tristate "IP: Foo (IP protocols) over UDP"
+	depends on IPV4
 	select NET_UDP_TUNNEL
 	help
 	  Foo over UDP allows any IP protocol to be directly encapsulated
@@ -338,6 +349,7 @@ config NET_FOU_IP_TUNNELS
 
 config INET_AH
 	tristate "IP: AH transformation"
+	depends on IPV4
 	select XFRM_AH
 	help
 	  Support for IPsec AH (Authentication Header).
@@ -353,6 +365,7 @@ config INET_AH
 
 config INET_ESP
 	tristate "IP: ESP transformation"
+	depends on IPV4
 	select XFRM_ESP
 	help
 	  Support for IPsec ESP (Encapsulating Security Payload).
@@ -393,6 +406,7 @@ config INET_ESPINTCP
 
 config INET_IPCOMP
 	tristate "IP: IPComp transformation"
+	depends on IPV4
 	select INET_XFRM_TUNNEL
 	select XFRM_IPCOMP
 	help
@@ -747,7 +761,7 @@ config TCP_AO
 	select CRYPTO_LIB_SHA1
 	select CRYPTO_LIB_SHA256
 	select CRYPTO_LIB_UTILS
-	depends on 64BIT # seq-number extension needs WRITE_ONCE(u64)
+	depends on IPV4 && 64BIT # seq-number extension needs WRITE_ONCE(u64)
 	help
 	  TCP-AO specifies the use of stronger Message Authentication Codes (MACs),
 	  protects against replays for long-lived TCP connections, and
@@ -758,6 +772,7 @@ config TCP_AO
 
 config TCP_MD5SIG
 	bool "TCP: MD5 Signature Option support (RFC2385)"
+	depends on IPV4
 	select CRYPTO_LIB_MD5
 	select CRYPTO_LIB_UTILS
 	help
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 06e21c26b76f..8b148be79fa4 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -3,18 +3,17 @@
 # Makefile for the Linux TCP/IP (INET) layer.
 #
 
-obj-y     := route.o inetpeer.o protocol.o \
-	     ip_input.o ip_fragment.o ip_forward.o ip_options.o \
-	     ip_output.o ip_sockglue.o inet_hashtables.o \
-	     inet_timewait_sock.o inet_connection_sock.o \
-	     tcp.o tcp_input.o tcp_output.o tcp_timer.o tcp_ipv4.o \
+obj-y     := inetpeer.o protocol.o inet_hashtables.o inet_timewait_sock.o \
+	     inet_connection_sock.o tcp.o tcp_input.o tcp_output.o tcp_timer.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 datagram.o raw.o udp.o \
-	     udp_offload.o arp.o icmp.o devinet.o af_inet.o igmp.o \
-	     fib_frontend.o fib_semantics.o fib_trie.o fib_notifier.o \
-	     inet_fragment.o ping.o ip_tunnel_core.o gre_offload.o \
-	     metrics.o netlink.o nexthop.o udp_tunnel_stub.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
+
+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
 
 obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
 obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
diff --git a/net/ipv4/netfilter/Kconfig b/net/ipv4/netfilter/Kconfig
index 7dc9772fe2d8..ab4fa6a6d453 100644
--- a/net/ipv4/netfilter/Kconfig
+++ b/net/ipv4/netfilter/Kconfig
@@ -4,7 +4,7 @@
 #
 
 menu "IP: Netfilter Configuration"
-	depends on INET && NETFILTER
+	depends on INET && NETFILTER && IPV4
 
 config NF_DEFRAG_IPV4
 	tristate
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index c3806c6ac96f..f83efc1505cc 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -47,6 +47,7 @@ config IPV6_OPTIMISTIC_DAD
 config INET6_AH
 	tristate "IPv6: AH transformation"
 	select XFRM_AH
+	depends on IPV4
 	help
 	  Support for IPsec AH (Authentication Header).
 
@@ -61,6 +62,7 @@ config INET6_AH
 
 config INET6_ESP
 	tristate "IPv6: ESP transformation"
+	depends on IPV4
 	select XFRM_ESP
 	help
 	  Support for IPsec ESP (Encapsulating Security Payload).
@@ -101,6 +103,7 @@ config INET6_ESPINTCP
 
 config INET6_IPCOMP
 	tristate "IPv6: IPComp transformation"
+	depends on IPV4
 	select INET6_XFRM_TUNNEL
 	select XFRM_IPCOMP
 	help
@@ -111,6 +114,7 @@ config INET6_IPCOMP
 
 config IPV6_MIP6
 	tristate "IPv6: Mobility"
+	depends on IPV4
 	select XFRM
 	help
 	  Support for IPv6 Mobility described in RFC 3775.
@@ -119,6 +123,7 @@ config IPV6_MIP6
 
 config IPV6_ILA
 	tristate "IPv6: Identifier Locator Addressing (ILA)"
+	depends on IPV4
 	depends on NETFILTER
 	select DST_CACHE
 	select LWTUNNEL
@@ -149,6 +154,7 @@ config INET6_TUNNEL
 
 config IPV6_VTI
 	tristate "Virtual (secure) IPv6: tunneling"
+	depends on IPV4
 	select IPV6_TUNNEL
 	select NET_IP_TUNNEL
 	select XFRM
@@ -161,6 +167,7 @@ config IPV6_VTI
 
 config IPV6_SIT
 	tristate "IPv6: IPv6-in-IPv4 tunnel (SIT driver)"
+	depends on IPV4
 	select INET_TUNNEL
 	select NET_IP_TUNNEL
 	select IPV6_NDISC_NODETYPE
@@ -198,6 +205,7 @@ config IPV6_NDISC_NODETYPE
 
 config IPV6_TUNNEL
 	tristate "IPv6: IP-in-IPv6 tunnel (RFC2473)"
+	depends on IPV4
 	select INET6_TUNNEL
 	select DST_CACHE
 	select GRO_CELLS
@@ -209,6 +217,7 @@ config IPV6_TUNNEL
 
 config IPV6_GRE
 	tristate "IPv6: GRE tunnel"
+	depends on IPV4
 	select IPV6_TUNNEL
 	select NET_IP_TUNNEL
 	depends on NET_IPGRE_DEMUX
@@ -256,7 +265,7 @@ config IPV6_SUBTREES
 
 config IPV6_MROUTE
 	bool "IPv6: multicast routing"
-	depends on IPV6
+	depends on IPV4 && IPV6
 	select IP_MROUTE_COMMON
 	help
 	  Support for IPv6 multicast forwarding.
@@ -285,7 +294,7 @@ config IPV6_PIMSM_V2
 
 config IPV6_SEG6_LWTUNNEL
 	bool "IPv6: Segment Routing Header encapsulation support"
-	depends on IPV6
+	depends on IPV4 && IPV6
 	select LWTUNNEL
 	select DST_CACHE
 	select IPV6_MULTIPLE_TABLES
@@ -316,7 +325,7 @@ config IPV6_SEG6_BPF
 
 config IPV6_RPL_LWTUNNEL
 	bool "IPv6: RPL Source Routing Header support"
-	depends on IPV6
+	depends on IPV4 && IPV6
 	select LWTUNNEL
 	select DST_CACHE
 	help
@@ -327,7 +336,7 @@ config IPV6_RPL_LWTUNNEL
 
 config IPV6_IOAM6_LWTUNNEL
 	bool "IPv6: IOAM Pre-allocated Trace insertion support"
-	depends on IPV6
+	depends on IPV4 && IPV6
 	select LWTUNNEL
 	select DST_CACHE
 	help
diff --git a/net/ipv6/netfilter/Kconfig b/net/ipv6/netfilter/Kconfig
index 81daf82ddc2d..f99a2176bf3d 100644
--- a/net/ipv6/netfilter/Kconfig
+++ b/net/ipv6/netfilter/Kconfig
@@ -4,7 +4,7 @@
 #
 
 menu "IPv6: Netfilter Configuration"
-	depends on INET && IPV6 && NETFILTER
+	depends on INET && IPV6 && NETFILTER && IPV4
 
 # old sockopt interface and eval loop
 config IP6_NF_IPTABLES_LEGACY
diff --git a/net/l2tp/Kconfig b/net/l2tp/Kconfig
index 0de178d5baba..e92cf6d94135 100644
--- a/net/l2tp/Kconfig
+++ b/net/l2tp/Kconfig
@@ -5,7 +5,7 @@
 
 menuconfig L2TP
 	tristate "Layer Two Tunneling Protocol (L2TP)"
-	depends on INET
+	depends on INET && IPV4
 	select NET_UDP_TUNNEL
 	help
 	  Layer Two Tunneling Protocol
diff --git a/net/mptcp/Kconfig b/net/mptcp/Kconfig
index be71fc9b4638..c5b660fdda4c 100644
--- a/net/mptcp/Kconfig
+++ b/net/mptcp/Kconfig
@@ -1,7 +1,7 @@
 
 config MPTCP
 	bool "MPTCP: Multipath TCP"
-	depends on INET
+	depends on INET && IPV4
 	select SKB_EXTENSIONS
 	select CRYPTO_LIB_SHA256
 	select CRYPTO_LIB_UTILS
diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
index 09874c26fd13..857af95fbb48 100644
--- a/net/netfilter/Kconfig
+++ b/net/netfilter/Kconfig
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 menu "Core Netfilter Configuration"
-	depends on INET && NETFILTER
+	depends on INET && IPV4 && NETFILTER
 
 config NETFILTER_INGRESS
 	bool "Netfilter ingress support"
diff --git a/net/netfilter/ipset/Kconfig b/net/netfilter/ipset/Kconfig
index 6c4d54758106..e6537c842a45 100644
--- a/net/netfilter/ipset/Kconfig
+++ b/net/netfilter/ipset/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 menuconfig IP_SET
 	tristate "IP set support"
-	depends on INET && NETFILTER
+	depends on INET && IPV4 && NETFILTER
 	select NETFILTER_NETLINK
 	help
 	  This option adds IP set support to the kernel.
diff --git a/net/netfilter/ipvs/Kconfig b/net/netfilter/ipvs/Kconfig
index 7724cb44e6de..fb09218d84d0 100644
--- a/net/netfilter/ipvs/Kconfig
+++ b/net/netfilter/ipvs/Kconfig
@@ -4,7 +4,7 @@
 #
 menuconfig IP_VS
 	tristate "IP virtual server support"
-	depends on INET && NETFILTER
+	depends on INET && NETFILTER && IPV4
 	depends on (NF_CONNTRACK || NF_CONNTRACK=n)
 	help
 	  IP Virtual Server support will let you build a high-performance
diff --git a/net/rds/Kconfig b/net/rds/Kconfig
index f007730aa2bb..af5632b37dd7 100644
--- a/net/rds/Kconfig
+++ b/net/rds/Kconfig
@@ -17,6 +17,7 @@ config RDS_RDMA
 config RDS_TCP
 	tristate "RDS over TCP"
 	depends on RDS
+	depends on IPV4
 	depends on IPV6 || !IPV6
 	help
 	  Allow RDS to use TCP as a transport.
diff --git a/net/rxrpc/Kconfig b/net/rxrpc/Kconfig
index 2cebb666dc07..2029d2d1065f 100644
--- a/net/rxrpc/Kconfig
+++ b/net/rxrpc/Kconfig
@@ -5,7 +5,7 @@
 
 config AF_RXRPC
 	tristate "RxRPC session sockets"
-	depends on INET
+	depends on INET && IPV4
 	select CRYPTO
 	select CRYPTO_LIB_DES if RXKAD
 	select KEYS
diff --git a/net/sched/Kconfig b/net/sched/Kconfig
index 6ddff028b81a..bb782822c3fb 100644
--- a/net/sched/Kconfig
+++ b/net/sched/Kconfig
@@ -807,7 +807,7 @@ config NET_ACT_SKBEDIT
 
 config NET_ACT_CSUM
 	tristate "Checksum Updating"
-	depends on NET_CLS_ACT && INET
+	depends on NET_CLS_ACT && INET && IPV4
 	select NET_CRC32C
 	help
 	  Say Y here to update some common checksum after some direct
diff --git a/net/sctp/Kconfig b/net/sctp/Kconfig
index fc989a3791b3..eb3e2bf1ec3a 100644
--- a/net/sctp/Kconfig
+++ b/net/sctp/Kconfig
@@ -5,7 +5,7 @@
 
 menuconfig IP_SCTP
 	tristate "The SCTP Protocol"
-	depends on INET
+	depends on INET && IPV4
 	select CRYPTO_LIB_SHA1
 	select CRYPTO_LIB_SHA256
 	select CRYPTO_LIB_UTILS
diff --git a/net/smc/Kconfig b/net/smc/Kconfig
index 277ef504bc26..faac6586b848 100644
--- a/net/smc/Kconfig
+++ b/net/smc/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config SMC
 	tristate "SMC socket protocol family"
-	depends on INET && INFINIBAND && DIBS
+	depends on INET && IPV4 && INFINIBAND && DIBS
 	help
 	  SMC-R provides a "sockets over RDMA" solution making use of
 	  RDMA over Converged Ethernet (RoCE) technology to upgrade
diff --git a/net/sunrpc/Kconfig b/net/sunrpc/Kconfig
index e7808e5714dc..58e6f6db3100 100644
--- a/net/sunrpc/Kconfig
+++ b/net/sunrpc/Kconfig
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config SUNRPC
 	tristate
-	depends on MULTIUSER
+	depends on MULTIUSER && IPV4
 
 config SUNRPC_GSS
 	tristate
diff --git a/net/tipc/Kconfig b/net/tipc/Kconfig
index 18f62135e47b..2db479597c79 100644
--- a/net/tipc/Kconfig
+++ b/net/tipc/Kconfig
@@ -30,6 +30,7 @@ config TIPC_MEDIA_IB
 config TIPC_MEDIA_UDP
 	bool "IP/UDP media type support"
 	depends on TIPC
+	depends on IPV4
 	select NET_UDP_TUNNEL
 	default y
 	help
diff --git a/net/xfrm/Kconfig b/net/xfrm/Kconfig
index 4a62817a88f8..203188b05be4 100644
--- a/net/xfrm/Kconfig
+++ b/net/xfrm/Kconfig
@@ -4,7 +4,7 @@
 #
 config XFRM
 	bool
-	depends on INET
+	depends on INET && IPV4
 	select GRO_CELLS
 	select SKB_EXTENSIONS
 
@@ -13,6 +13,7 @@ config XFRM_OFFLOAD
 
 config XFRM_ALGO
 	tristate
+	depends on IPV4
 	select XFRM
 	select CRYPTO
 	select CRYPTO_AEAD
@@ -23,6 +24,7 @@ if INET
 config XFRM_USER
 	tristate "Transformation user configuration interface"
 	select XFRM_ALGO
+	depends on IPV4
 	help
 	  Support for Transformation(XFRM) user configuration interface
 	  like IPsec used by native Linux tools.
@@ -42,7 +44,7 @@ config XFRM_USER_COMPAT
 
 config XFRM_INTERFACE
 	tristate "Transformation virtual interface"
-	depends on XFRM && IPV6
+	depends on XFRM && IPV4 && IPV6
 	help
 	  This provides a virtual interface to route IPsec traffic.
 
@@ -83,6 +85,7 @@ config XFRM_STATISTICS
 # RFC 8221 lists as MUST be implemented.
 config XFRM_AH
 	tristate
+	depends on IPV4
 	select XFRM_ALGO
 	select CRYPTO
 	select CRYPTO_HMAC
@@ -92,6 +95,7 @@ config XFRM_AH
 # algorithms that RFC 8221 lists as MUST be implemented.
 config XFRM_ESP
 	tristate
+	depends on IPV4
 	select XFRM_ALGO
 	select CRYPTO
 	select CRYPTO_AES
@@ -105,12 +109,14 @@ config XFRM_ESP
 
 config XFRM_IPCOMP
 	tristate
+	depends on IPV4
 	select XFRM_ALGO
 	select CRYPTO
 	select CRYPTO_DEFLATE
 
 config NET_KEY
 	tristate "PF_KEY sockets (deprecated)"
+	depends on IPV4
 	select XFRM_ALGO
 	help
 	  PF_KEYv2 socket family, compatible to KAME ones.
diff --git a/security/smack/Kconfig b/security/smack/Kconfig
index 5a8dfad469c3..b4e6d0168bd1 100644
--- a/security/smack/Kconfig
+++ b/security/smack/Kconfig
@@ -3,6 +3,7 @@ config SECURITY_SMACK
 	bool "Simplified Mandatory Access Control Kernel Support"
 	depends on NET
 	depends on INET
+	depends on IPV4
 	depends on SECURITY
 	select NETLABEL
 	select SECURITY_NETWORK
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 02/16 net-next v3] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n
       [not found] <20260930135334.4739-1-fmancera@suse.de>
  2026-09-30 13:52 ` [PATCH 01/16 net-next v3] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 03/16 net-next v3] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
                   ` (13 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, David Ahern, Ido Schimmel, Alexei Starovoitov,
	Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
	Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
	John Fastabend, Stanislav Fomichev, Neal Cardwell,
	Kuniyuki Iwashima, Kory Maincent, Vadim Fedorenko,
	Nicolai Buchwitz, Jiaming Zhang, Petr Machata, linux-kernel, bpf

To enable compiling the core network stack without IPv4, generic
networking code must avoid referencing missing IPv4 symbols.

This patch provides static inline stubs in include/net/ip.h and
include/net/route.h that return a proper error code when IPv4 is
disabled. This behaves as IPv6 code does. In addition, it introduces
guards around IPv4 specific code in generic implementations to prevent
undefined reference errors while linking.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 include/linux/indirect_call_wrapper.h |   8 +-
 include/net/ip.h                      | 111 ++++++++++++++++++++++----
 include/net/route.h                   |   8 ++
 net/core/dev_ioctl.c                  |   3 +
 net/core/filter.c                     |  20 ++---
 net/core/neighbour.c                  |   4 +
 net/ipv4/inet_hashtables.c            |   3 +
 7 files changed, 127 insertions(+), 30 deletions(-)

diff --git a/include/linux/indirect_call_wrapper.h b/include/linux/indirect_call_wrapper.h
index 0e4340ecd857..583efbc03446 100644
--- a/include/linux/indirect_call_wrapper.h
+++ b/include/linux/indirect_call_wrapper.h
@@ -57,16 +57,18 @@
  * builtin, this macro simplify dealing with indirect calls with only ipv4/ipv6
  * alternatives
  */
-#if IS_ENABLED(CONFIG_IPV6)
+#if IS_ENABLED(CONFIG_IPV4) && IS_ENABLED(CONFIG_IPV6)
 #define INDIRECT_CALL_INET(f, f2, f1, ...) \
 	INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__)
-#elif IS_ENABLED(CONFIG_INET)
+#elif IS_ENABLED(CONFIG_IPV4)
 #define INDIRECT_CALL_INET(f, f2, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__)
+#elif IS_ENABLED(CONFIG_IPV6)
+#define INDIRECT_CALL_INET(f, f2, f1, ...) INDIRECT_CALL_1(f, f2, __VA_ARGS__)
 #else
 #define INDIRECT_CALL_INET(f, f2, f1, ...) f(__VA_ARGS__)
 #endif
 
-#if IS_ENABLED(CONFIG_INET)
+#if IS_ENABLED(CONFIG_IPV4)
 #define INDIRECT_CALL_INET_1(f, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__)
 #else
 #define INDIRECT_CALL_INET_1(f, f1, ...) f(__VA_ARGS__)
diff --git a/include/net/ip.h b/include/net/ip.h
index 6f602df72ee6..194a464c443a 100644
--- a/include/net/ip.h
+++ b/include/net/ip.h
@@ -171,8 +171,17 @@ int ip_mr_input(struct sk_buff *skb);
 int ip_mr_output(struct net *net, struct sock *sk, struct sk_buff *skb);
 int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb);
 int ip_mc_output(struct net *net, struct sock *sk, struct sk_buff *skb);
+#if IS_ENABLED(CONFIG_IPV4)
 int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
 		   int (*output)(struct net *, struct sock *, struct sk_buff *));
+#else
+static inline int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
+				 int (*output)(struct net *, struct sock *, struct sk_buff *))
+{
+	kfree_skb(skb);
+	return -EAFNOSUPPORT;
+}
+#endif
 
 struct ip_fraglist_iter {
 	struct sk_buff	*frag;
@@ -218,6 +227,7 @@ int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
 int __ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
 		    __u8 tos);
 void ip_init(void);
+#if IS_ENABLED(CONFIG_IPV4)
 int ip_append_data(struct sock *sk, struct flowi4 *fl4,
 		   int getfrag(void *from, char *to, int offset, int len,
 			       int odd, struct sk_buff *skb),
@@ -225,27 +235,58 @@ int ip_append_data(struct sock *sk, struct flowi4 *fl4,
 		   struct ipcm_cookie *ipc,
 		   struct rtable **rt,
 		   unsigned int flags);
-int ip_generic_getfrag(void *from, char *to, int offset, int len, int odd,
-		       struct sk_buff *skb);
-struct sk_buff *__ip_make_skb(struct sock *sk, struct flowi4 *fl4,
-			      struct sk_buff_head *queue,
-			      struct inet_cork *cork);
-int ip_send_skb(struct net *net, struct sk_buff *skb);
-int ip_push_pending_frames(struct sock *sk, struct flowi4 *fl4);
-void ip_flush_pending_frames(struct sock *sk);
 struct sk_buff *ip_make_skb(struct sock *sk, struct flowi4 *fl4,
 			    int getfrag(void *from, char *to, int offset,
 					int len, int odd, struct sk_buff *skb),
 			    void *from, int length, int transhdrlen,
 			    struct ipcm_cookie *ipc, struct rtable **rtp,
 			    struct inet_cork *cork, unsigned int flags);
-
-int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl);
+struct sk_buff *__ip_make_skb(struct sock *sk, struct flowi4 *fl4,
+			      struct sk_buff_head *queue,
+			      struct inet_cork *cork);
+void ip_flush_pending_frames(struct sock *sk);
 
 static inline struct sk_buff *ip_finish_skb(struct sock *sk, struct flowi4 *fl4)
 {
 	return __ip_make_skb(sk, fl4, &sk->sk_write_queue, &inet_sk(sk)->cork.base);
 }
+#else
+static inline int ip_append_data(struct sock *sk, struct flowi4 *fl4,
+				 int getfrag(void *from, char *to, int offset, int len,
+					     int odd, struct sk_buff *skb),
+				 void *from, int len, int protolen,
+				 struct ipcm_cookie *ipc,
+				 struct rtable **rt,
+				 unsigned int flags)
+{
+	return -EAFNOSUPPORT;
+}
+
+static inline struct sk_buff *ip_make_skb(struct sock *sk, struct flowi4 *fl4,
+					  int getfrag(void *from, char *to, int offset,
+						      int len, int odd, struct sk_buff *skb),
+					  void *from, int length, int transhdrlen,
+					  struct ipcm_cookie *ipc, struct rtable **rtp,
+					  struct inet_cork *cork, unsigned int flags)
+{
+	return ERR_PTR(-EAFNOSUPPORT);
+}
+
+static inline struct sk_buff *ip_finish_skb(struct sock *sk, struct flowi4 *fl4)
+{
+	return ERR_PTR(-EAFNOSUPPORT);
+}
+
+static inline void ip_flush_pending_frames(struct sock *sk)
+{
+}
+#endif
+
+int ip_generic_getfrag(void *from, char *to, int offset, int len, int odd,
+		       struct sk_buff *skb);
+int ip_send_skb(struct net *net, struct sk_buff *skb);
+int ip_push_pending_frames(struct sock *sk, struct flowi4 *fl4);
+int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl);
 
 /* Get the route scope that should be used when sending a packet. */
 static inline u8 ip_sendmsg_scope(const struct inet_sock *inet,
@@ -768,7 +809,7 @@ static inline bool ip_defrag_user_in_between(u32 user,
 }
 
 int ip_defrag(struct net *net, struct sk_buff *skb, u32 user);
-#ifdef CONFIG_INET
+#if IS_ENABLED(CONFIG_IPV4)
 struct sk_buff *ip_check_defrag(struct net *net, struct sk_buff *skb, u32 user);
 #else
 static inline struct sk_buff *ip_check_defrag(struct net *net, struct sk_buff *skb, u32 user)
@@ -813,24 +854,60 @@ int ip_options_rcv_srr(struct sk_buff *skb, struct net_device *dev);
  *	Functions provided by ip_sockglue.c
  */
 
-void ipv4_pktinfo_prepare(const struct sock *sk, struct sk_buff *skb, bool drop_dst);
+#if IS_ENABLED(CONFIG_IPV4)
 void ip_cmsg_recv_offset(struct msghdr *msg, struct sock *sk,
 			 struct sk_buff *skb, int tlen, int offset);
+void ipv4_pktinfo_prepare(const struct sock *sk, struct sk_buff *skb, bool drop_dst);
 int ip_cmsg_send(struct sock *sk, struct msghdr *msg,
 		 struct ipcm_cookie *ipc, bool allow_ipv6);
+int ip_recv_error(struct sock *sk, struct msghdr *msg, int len);
+int ip_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
+		  unsigned int optlen);
+int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
+		  int __user *optlen);
+#else
+static inline void ip_cmsg_recv_offset(struct msghdr *msg, struct sock *sk,
+				       struct sk_buff *skb, int tlen, int offset)
+{
+}
+
+static inline void ipv4_pktinfo_prepare(const struct sock *sk,
+					struct sk_buff *skb,
+					bool drop_dst)
+{
+}
+
+static inline int ip_cmsg_send(struct sock *sk, struct msghdr *msg,
+			       struct ipcm_cookie *ipc, bool allow_ipv6)
+{
+	return 0;
+}
+
+static inline int ip_recv_error(struct sock *sk, struct msghdr *msg, int len)
+{
+	return -EAFNOSUPPORT;
+}
+
+static inline int ip_setsockopt(struct sock *sk, int level, int optname,
+				sockptr_t optval, unsigned int optlen)
+{
+	return -EAFNOSUPPORT;
+}
+
+static inline int ip_getsockopt(struct sock *sk, int level, int optname,
+				char __user *optval, int __user *optlen)
+{
+	return -EAFNOSUPPORT;
+}
+#endif
 DECLARE_STATIC_KEY_FALSE(ip4_min_ttl);
 int do_ip_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
 		     unsigned int optlen);
-int ip_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
-		  unsigned int optlen);
 int do_ip_getsockopt(struct sock *sk, int level, int optname,
 		     sockptr_t optval, sockptr_t optlen);
-int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
-		  int __user *optlen);
 int ip_ra_control(struct sock *sk, unsigned char on,
 		  void (*destructor)(struct sock *));
 
-int ip_recv_error(struct sock *sk, struct msghdr *msg, int len);
 void ip_icmp_error(struct sock *sk, struct sk_buff *skb, int err, __be16 port,
 		   u32 info, u8 *payload);
 void ip_local_error(struct sock *sk, int err, __be32 daddr, __be16 dport,
diff --git a/include/net/route.h b/include/net/route.h
index 6b55de2e4df8..8c21881eb5f4 100644
--- a/include/net/route.h
+++ b/include/net/route.h
@@ -169,8 +169,16 @@ static inline struct rtable *__ip_route_output_key(struct net *net,
 	return ip_route_output_key_hash(net, flp, NULL);
 }
 
+#if IS_ENABLED(CONFIG_IPV4)
 struct rtable *ip_route_output_flow(struct net *, struct flowi4 *flp,
 				    const struct sock *sk);
+#else
+static inline struct rtable *ip_route_output_flow(struct net *, struct flowi4 *flp,
+						  const struct sock *sk)
+{
+	return ERR_PTR(-EAFNOSUPPORT);
+}
+#endif
 struct dst_entry *ipv4_blackhole_route(struct net *net,
 				       struct dst_entry *dst_orig);
 
diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index 164643140a52..a10921aa3e23 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -44,6 +44,9 @@ int dev_ifconf(struct net *net, struct ifconf __user *uifc)
 	size_t size;
 	int len, total = 0, done;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EAFNOSUPPORT;
+
 	/* both the ifconf and the ifreq structures are slightly different */
 	if (in_compat_syscall()) {
 		struct compat_ifconf ifc32;
diff --git a/net/core/filter.c b/net/core/filter.c
index 70dc621672f2..86d531c63683 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -2318,7 +2318,7 @@ static int __bpf_redirect_neigh_v6(struct sk_buff *skb, struct net_device *dev,
 }
 #endif /* CONFIG_IPV6 */
 
-#if IS_ENABLED(CONFIG_INET)
+#if IS_ENABLED(CONFIG_IPV4)
 static int bpf_out_neigh_v4(struct net *net, struct sk_buff *skb,
 			    struct net_device *dev, struct bpf_nh_params *nh)
 {
@@ -2427,7 +2427,7 @@ static int __bpf_redirect_neigh_v4(struct sk_buff *skb, struct net_device *dev,
 	kfree_skb(skb);
 	return NET_XMIT_DROP;
 }
-#endif /* CONFIG_INET */
+#endif /* CONFIG_IPV4 */
 
 static int __bpf_redirect_neigh(struct sk_buff *skb, struct net_device *dev,
 				struct bpf_nh_params *nh)
@@ -5781,7 +5781,7 @@ static int __bpf_setsockopt(struct sock *sk, int level, int optname,
 
 	if (level == SOL_SOCKET)
 		return sol_socket_sockopt(sk, optname, optval, &optlen, false);
-	else if (IS_ENABLED(CONFIG_INET) && level == SOL_IP)
+	else if (IS_ENABLED(CONFIG_IPV4) && level == SOL_IP)
 		return sol_ip_sockopt(sk, optname, optval, &optlen, false);
 	else if (IS_ENABLED(CONFIG_IPV6) && level == SOL_IPV6)
 		return sol_ipv6_sockopt(sk, optname, optval, &optlen, false);
@@ -5818,7 +5818,7 @@ static int __bpf_getsockopt(struct sock *sk, int level, int optname,
 		err = sol_socket_sockopt(sk, optname, optval, &optlen, true);
 	else if (IS_ENABLED(CONFIG_INET) && level == SOL_TCP)
 		err = sol_tcp_sockopt(sk, optname, optval, &optlen, true);
-	else if (IS_ENABLED(CONFIG_INET) && level == SOL_IP)
+	else if (IS_ENABLED(CONFIG_IPV4) && level == SOL_IP)
 		err = sol_ip_sockopt(sk, optname, optval, &optlen, true);
 	else if (IS_ENABLED(CONFIG_IPV6) && level == SOL_IPV6)
 		err = sol_ipv6_sockopt(sk, optname, optval, &optlen, true);
@@ -6346,7 +6346,7 @@ static struct net_device *bpf_fib_vlan_input_dev(struct net_device *dev,
 }
 #endif
 
-#if IS_ENABLED(CONFIG_INET)
+#if IS_ENABLED(CONFIG_IPV4)
 static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params,
 			       u32 flags, bool check_mtu)
 {
@@ -6678,7 +6678,7 @@ BPF_CALL_4(bpf_xdp_fib_lookup, struct xdp_buff *, ctx,
 		return -EINVAL;
 
 	switch (params->family) {
-#if IS_ENABLED(CONFIG_INET)
+#if IS_ENABLED(CONFIG_IPV4)
 	case AF_INET:
 		return bpf_ipv4_fib_lookup(dev_net(ctx->rxq->dev), params,
 					   flags, true);
@@ -6722,7 +6722,7 @@ BPF_CALL_4(bpf_skb_fib_lookup, struct sk_buff *, skb,
 		check_mtu = true;
 
 	switch (params->family) {
-#if IS_ENABLED(CONFIG_INET)
+#if IS_ENABLED(CONFIG_IPV4)
 	case AF_INET:
 		rc = bpf_ipv4_fib_lookup(net, params, flags, check_mtu);
 		break;
@@ -12231,7 +12231,7 @@ BPF_CALL_1(bpf_skc_to_tcp_timewait_sock, struct sock *, sk)
 	BTF_TYPE_EMIT(struct inet_timewait_sock);
 	BTF_TYPE_EMIT(struct tcp_timewait_sock);
 
-#ifdef CONFIG_INET
+#ifdef CONFIG_IPV4
 	if (sk && sk->sk_prot == &tcp_prot && sk->sk_state == TCP_TIME_WAIT)
 		return (unsigned long)sk;
 #endif
@@ -12254,7 +12254,7 @@ const struct bpf_func_proto bpf_skc_to_tcp_timewait_sock_proto = {
 
 BPF_CALL_1(bpf_skc_to_tcp_request_sock, struct sock *, sk)
 {
-#ifdef CONFIG_INET
+#ifdef CONFIG_IPV4
 	if (sk && sk->sk_prot == &tcp_prot && sk->sk_state == TCP_NEW_SYN_RECV)
 		return (unsigned long)sk;
 #endif
@@ -12736,7 +12736,7 @@ __bpf_kfunc int bpf_icmp_send(struct __sk_buff *skb_ctx, int type, int code)
 		return -ENETUNREACH;
 
 	switch (skb->protocol) {
-#if IS_ENABLED(CONFIG_INET)
+#if IS_ENABLED(CONFIG_IPV4)
 	case htons(ETH_P_IP): {
 		struct sk_buff *nskb;
 
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 6ed8eb075146..63c2a1b86118 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -3241,6 +3241,10 @@ int neigh_xmit(int index, struct net_device *dev,
 		if (index == NEIGH_ARP_TABLE) {
 			u32 key = *((u32 *)addr);
 
+			if (!IS_ENABLED(CONFIG_IPV4)) {
+				rcu_read_unlock();
+				goto out_kfree_skb;
+			}
 			neigh = __ipv4_neigh_lookup_noref(dev, key);
 		} else {
 			neigh = __neigh_lookup_noref(tbl, addr, dev);
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index 5abcb0debb27..ae9f45368571 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -596,6 +596,9 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row,
 	struct sock *sk2;
 	spinlock_t *lock;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EAFNOSUPPORT;
+
 	if (rcu_lookup) {
 		sk_nulls_for_each(sk2, node, &head->chain) {
 			if (sk2->sk_hash != hash ||
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 03/16 net-next v3] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic
       [not found] <20260930135334.4739-1-fmancera@suse.de>
  2026-09-30 13:52 ` [PATCH 01/16 net-next v3] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 02/16 net-next v3] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 04/16 net-next v3] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
                   ` (12 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, David Ahern, Ido Schimmel, Kees Cook,
	Kuniyuki Iwashima, Willem de Bruijn, Jiayuan Chen, Jeff Layton,
	Qi Tang, linux-kernel

To enable compiling the INET subsystem without IPv4, shared generic
utilities must be relocated and IPv4 socket logic must be guarded for
CONFIG_IPV4.

This patch moves the generic ip_generec_getfrag() from ip_output.c to
af_inet.c. It also introduces CONFIG_IPV4 guards around af_inet.c to
reject IPv4-specific ioctls, protocol registrations and bind requests.
The same guard is added to reject IPv4-mapped IPv6.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/ipv4/af_inet.c   | 122 ++++++++++++++++++++++++++++++++-----------
 net/ipv4/ip_output.c |  18 -------
 net/ipv6/af_inet6.c  |   5 ++
 net/ipv6/datagram.c  |   4 +-
 4 files changed, 99 insertions(+), 50 deletions(-)

diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index d9421ac38d78..a9d46a178f9a 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -129,6 +129,28 @@
 int disable_ipv6_mod;
 EXPORT_SYMBOL(disable_ipv6_mod);
 
+/* Keep the function here for now as it is generic, it should be moved
+ * to a common L3 place
+ */
+int
+ip_generic_getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb)
+{
+	struct msghdr *msg = from;
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		if (!copy_from_iter_full(to, len, &msg->msg_iter))
+			return -EFAULT;
+	} else {
+		__wsum csum = 0;
+
+		if (!csum_and_copy_from_iter_full(to, len, &csum, &msg->msg_iter))
+			return -EFAULT;
+		skb->csum = csum_block_add(skb->csum, csum, odd);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(ip_generic_getfrag);
+
 /* The inetsw table contains everything that inet_create needs to
  * build a new socket.
  */
@@ -426,7 +448,8 @@ int inet_release(struct socket *sock)
 			BPF_CGROUP_RUN_PROG_INET_SOCK_RELEASE(sk);
 
 		/* Applications forget to leave groups before exiting */
-		ip_mc_drop_socket(sk);
+		if (IS_ENABLED(CONFIG_IPV4))
+			ip_mc_drop_socket(sk);
 
 		/* If linger is set, we don't return until the close
 		 * is complete.  Otherwise we return immediately. The
@@ -486,6 +509,9 @@ int __inet_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len,
 	u32 tb_id = RT_TABLE_LOCAL;
 	int err;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EAFNOSUPPORT;
+
 	if (addr->sin_family != AF_INET) {
 		/* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
 		 * only if s_addr is INADDR_ANY.
@@ -968,6 +994,9 @@ int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 	struct ifreq ifr;
 	struct rtentry rt;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EOPNOTSUPP;
+
 	switch (cmd) {
 	case SIOCADDRT:
 	case SIOCDELRT:
@@ -1023,6 +1052,9 @@ static int inet_compat_routing_ioctl(struct sock *sk, unsigned int cmd,
 	compat_uptr_t rtdev;
 	struct rtentry rt;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EOPNOTSUPP;
+
 	if (copy_from_user(&rt.rt_dst, &ur->rt_dst,
 			3 * sizeof(struct sockaddr)) ||
 	    get_user(rt.rt_flags, &ur->rt_flags) ||
@@ -1201,6 +1233,9 @@ void inet_register_protosw(struct inet_protosw *p)
 	int protocol = p->protocol;
 	struct list_head *last_perm;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return;
+
 	spin_lock_bh(&inetsw_lock);
 
 	if (p->type >= SOCK_MAX)
@@ -1243,6 +1278,9 @@ EXPORT_SYMBOL(inet_register_protosw);
 
 void inet_unregister_protosw(struct inet_protosw *p)
 {
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return;
+
 	if (INET_PROTOSW_PERMANENT & p->flags) {
 		pr_err("Attempt to unregister permanent protocol %d\n",
 		       p->protocol);
@@ -1267,6 +1305,9 @@ static int inet_sk_reselect_saddr(struct sock *sk)
 	struct ip_options_rcu *inet_opt;
 	int err;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EAFNOSUPPORT;
+
 	inet_opt = rcu_dereference_protected(inet->inet_opt,
 					     lockdep_sock_is_held(sk));
 	if (inet_opt && inet_opt->opt.srr)
@@ -1371,6 +1412,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,
 	int ihl;
 	int id;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return ERR_PTR(-EPROTONOSUPPORT);
+
 	skb_reset_network_header(skb);
 	nhoff = skb_network_header(skb) - skb_mac_header(skb);
 	if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
@@ -1876,14 +1920,12 @@ static struct packet_type ip_packet_type __read_mostly = {
 	.list_func = ip_list_rcv,
 };
 
-static int __init inet_init(void)
+static int __init inet4_init(void)
 {
 	struct inet_protosw *q;
 	struct list_head *r;
 	int rc;
 
-	sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
-
 	raw_hashinfo_init(&raw_v4_hashinfo);
 
 	rc = proto_register(&tcp_prot, 1);
@@ -1959,6 +2001,45 @@ static int __init inet_init(void)
 
 	ip_init();
 
+	return 0;
+out_unregister_raw_proto:
+	proto_unregister(&raw_prot);
+out_unregister_udp_proto:
+	proto_unregister(&udp_prot);
+out_unregister_tcp_proto:
+	proto_unregister(&tcp_prot);
+out:
+	return rc;
+}
+
+static void __init inet4_late_init(void)
+{
+	/*
+	 *	Initialise the multicast router
+	 */
+#if defined(CONFIG_IP_MROUTE)
+	if (ip_mr_init())
+		pr_crit("%s: Cannot init ipv4 mroute\n", __func__);
+#endif
+	ipv4_proc_init();
+
+	ipfrag_init();
+
+	dev_add_pack(&ip_packet_type);
+}
+
+static int __init inet_init(void)
+{
+	int rc;
+
+	sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
+
+	if (IS_ENABLED(CONFIG_IPV4)) {
+		rc = inet4_init();
+		if (rc)
+			return rc;
+	}
+
 	/* Initialise per-cpu ipv4 mibs */
 	if (init_ipv4_mibs())
 		panic("%s: Cannot init ipv4 mibs\n", __func__);
@@ -1980,35 +2061,20 @@ static int __init inet_init(void)
 	if (icmp_init() < 0)
 		panic("Failed to create the ICMP control socket.\n");
 
-	/*
-	 *	Initialise the multicast router
-	 */
-#if defined(CONFIG_IP_MROUTE)
-	if (ip_mr_init())
-		pr_crit("%s: Cannot init ipv4 mroute\n", __func__);
-#endif
-
 	if (init_inet_pernet_ops())
 		pr_crit("%s: Cannot init ipv4 inet pernet ops\n", __func__);
 
-	ipv4_proc_init();
-
-	ipfrag_init();
+#ifdef CONFIG_PROC_FS
+	if (ip_misc_proc_init())
+		pr_crit("%s: Cannot init ipv4 misc proc\n", __func__);
+#endif
 
-	dev_add_pack(&ip_packet_type);
+	if (IS_ENABLED(CONFIG_IPV4))
+		inet4_late_init();
 
 	ip_tunnel_core_init();
 
-	rc = 0;
-out:
-	return rc;
-out_unregister_raw_proto:
-	proto_unregister(&raw_prot);
-out_unregister_udp_proto:
-	proto_unregister(&udp_prot);
-out_unregister_tcp_proto:
-	proto_unregister(&tcp_prot);
-	goto out;
+	return 0;
 }
 
 fs_initcall(inet_init);
@@ -2028,12 +2094,8 @@ static int __init ipv4_proc_init(void)
 		goto out_udp;
 	if (ping_proc_init())
 		goto out_ping;
-	if (ip_misc_proc_init())
-		goto out_misc;
 out:
 	return rc;
-out_misc:
-	ping_proc_exit();
 out_ping:
 	udp4_proc_exit();
 out_udp:
diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index a24cc8ee11d3..4c6b34e70990 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -932,24 +932,6 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
 }
 EXPORT_SYMBOL(ip_do_fragment);
 
-int
-ip_generic_getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb)
-{
-	struct msghdr *msg = from;
-
-	if (skb->ip_summed == CHECKSUM_PARTIAL) {
-		if (!copy_from_iter_full(to, len, &msg->msg_iter))
-			return -EFAULT;
-	} else {
-		__wsum csum = 0;
-		if (!csum_and_copy_from_iter_full(to, len, &csum, &msg->msg_iter))
-			return -EFAULT;
-		skb->csum = csum_block_add(skb->csum, csum, odd);
-	}
-	return 0;
-}
-EXPORT_SYMBOL(ip_generic_getfrag);
-
 static int __ip_append_data(struct sock *sk,
 			    struct flowi4 *fl4,
 			    struct sk_buff_head *queue,
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index f0efdc13baf4..f220eaab085b 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -302,6 +302,11 @@ int __inet6_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len,
 		struct net_device *dev = NULL;
 		int chk_addr_ret;
 
+		if (!IS_ENABLED(CONFIG_IPV4)) {
+			err = -EADDRNOTAVAIL;
+			goto out;
+		}
+
 		/* Binding to v4-mapped address on a v6-only socket
 		 * makes no sense
 		 */
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 191c9733ff9f..079d1951405b 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -153,7 +153,7 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 	int			err;
 
 	if (usin->sin6_family == AF_INET) {
-		if (ipv6_only_sock(sk))
+		if (!IS_ENABLED(CONFIG_IPV4) || ipv6_only_sock(sk))
 			return -EAFNOSUPPORT;
 		err = __ip4_datagram_connect(sk, uaddr, addr_len);
 		goto ipv4_connected;
@@ -186,7 +186,7 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 	if (addr_type & IPV6_ADDR_MAPPED) {
 		struct sockaddr_in sin;
 
-		if (ipv6_only_sock(sk)) {
+		if (!IS_ENABLED(CONFIG_IPV4) || ipv6_only_sock(sk)) {
 			err = -ENETUNREACH;
 			goto out;
 		}
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 04/16 net-next v3] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (2 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 03/16 net-next v3] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 05/16 net-next v3] ipv4: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
                   ` (11 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, John Fastabend,
	Jakub Sitnicki, Jiayuan Chen, Willem de Bruijn, linux-kernel,
	bpf

As the tcp_ipv4.c file isn't compile anymore with CONFIG_IPV4=n, the
generic TCP function must be moved out of tcp_ipv4.c file and they
should be placed in tcp.c file.

As part of this migration, rename tcp_v4_destroy_sock() to
tcp_destroy_sock() to reflect its shared usage by IPv4 and IPv6.

In addition, guard IPv4 code around IPv6 TCP implementation. This
ensures that IPv4-mapped IPv6 sockects are rejected with a proper error
code when IPv4 stack is disabled.

Please note that this commit also addresses how /proc/net/sockstat
output is handled for all protocols. The implementation now force the
inuse attrtibute to be zero when CONFIG_IPV4=n but the shared stats are
represented correctly.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 include/net/tcp.h               |   16 +-
 net/ipv4/inet_connection_sock.c |    2 +-
 net/ipv4/proc.c                 |   45 +-
 net/ipv4/tcp.c                  | 1300 +++++++++++++++++++++++++++++-
 net/ipv4/tcp_bpf.c              |    3 +-
 net/ipv4/tcp_ipv4.c             | 1307 +------------------------------
 net/ipv6/tcp_ipv6.c             |   23 +-
 7 files changed, 1363 insertions(+), 1333 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 96a1a23e47fa..04d487a19b17 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -2437,7 +2437,7 @@ struct tcp_iter_state {
 extern struct request_sock_ops tcp_request_sock_ops;
 extern struct request_sock_ops tcp6_request_sock_ops;
 
-void tcp_v4_destroy_sock(struct sock *sk);
+void tcp_destroy_sock(struct sock *sk);
 
 struct sk_buff *tcp_gso_segment(struct sk_buff *skb,
 				netdev_features_t features);
@@ -2855,6 +2855,20 @@ struct sk_psock;
 #ifdef CONFIG_BPF_SYSCALL
 int tcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore);
 void tcp_bpf_clone(const struct sock *sk, struct sock *newsk);
+
+union bpf_tcp_iter_batch_item {
+	struct sock *sk;
+	__u64 cookie;
+};
+
+struct bpf_tcp_iter_state {
+	struct tcp_iter_state state;
+	unsigned int cur_sk;
+	unsigned int end_sk;
+	unsigned int max_sk;
+	union bpf_tcp_iter_batch_item *batch;
+};
+
 #ifdef CONFIG_BPF_STREAM_PARSER
 struct strparser;
 int tcp_bpf_strp_read_sock(struct strparser *strp, read_descriptor_t *desc,
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index c688c454422a..94ade387ad81 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -1384,7 +1384,7 @@ static void inet_child_forget(struct sock *sk, struct request_sock *req,
 		 * an inbound pkt destined for child is
 		 * blocked by sock lock in tcp_v4_rcv().
 		 * Also to satisfy an assertion in
-		 * tcp_v4_destroy_sock().
+		 * tcp_destroy_sock().
 		 */
 		RCU_INIT_POINTER(tcp_sk(child)->fastopen_rsk, NULL);
 	}
diff --git a/net/ipv4/proc.c b/net/ipv4/proc.c
index bfc06d1713ec..169e0dd0d213 100644
--- a/net/ipv4/proc.c
+++ b/net/ipv4/proc.c
@@ -29,6 +29,7 @@
  */
 #include <linux/types.h>
 #include <net/net_namespace.h>
+#include <net/aligned_data.h>
 #include <net/icmp.h>
 #include <net/protocol.h>
 #include <net/tcp.h>
@@ -50,25 +51,33 @@
  */
 static int sockstat_seq_show(struct seq_file *seq, void *v)
 {
+	int tcp_inuse = 0, udp_inuse = 0, raw_inuse = 0;
 	struct net *net = seq->private;
+	long tcp_mem, udp_mem;
 	int orphans, sockets;
 
 	orphans = tcp_orphan_count_sum();
-	sockets = proto_sockets_allocated_sum_positive(&tcp_prot);
+	sockets = percpu_counter_sum_positive(&tcp_sockets_allocated);
+	tcp_mem = max(0L, atomic_long_read(&net_aligned_data.tcp_memory_allocated));
+	udp_mem = max(0L, atomic_long_read(&net_aligned_data.udp_memory_allocated));
+
+	if (IS_ENABLED(CONFIG_IPV4)) {
+		tcp_inuse = sock_prot_inuse_get(net, &tcp_prot);
+		udp_inuse = sock_prot_inuse_get(net, &udp_prot);
+		raw_inuse = sock_prot_inuse_get(net, &raw_prot);
+	}
 
 	socket_seq_show(seq);
 	seq_printf(seq, "TCP: inuse %d orphan %d tw %d alloc %d mem %ld\n",
-		   sock_prot_inuse_get(net, &tcp_prot), orphans,
+		   tcp_inuse, orphans,
 		   refcount_read(&net->ipv4.tcp_death_row.tw_refcount) - 1,
-		   sockets, proto_memory_allocated(&tcp_prot));
-	seq_printf(seq, "UDP: inuse %d mem %ld\n",
-		   sock_prot_inuse_get(net, &udp_prot),
-		   proto_memory_allocated(&udp_prot));
-	seq_printf(seq, "RAW: inuse %d\n",
-		   sock_prot_inuse_get(net, &raw_prot));
-	seq_printf(seq,  "FRAG: inuse %u memory %lu\n",
-		   atomic_read(&net->ipv4.fqdir->rhashtable.nelems),
-		   frag_mem_limit(net->ipv4.fqdir));
+		   sockets, tcp_mem);
+	seq_printf(seq, "UDP: inuse %d mem %ld\n", udp_inuse, udp_mem);
+	seq_printf(seq, "RAW: inuse %d\n", raw_inuse);
+	if (IS_ENABLED(CONFIG_IPV4))
+		seq_printf(seq,  "FRAG: inuse %u memory %lu\n",
+			   atomic_read(&net->ipv4.fqdir->rhashtable.nelems),
+			   frag_mem_limit(net->ipv4.fqdir));
 	return 0;
 }
 
@@ -406,7 +415,7 @@ static int snmp_seq_show_ipstats(struct seq_file *seq, void *v)
 	return 0;
 }
 
-static int snmp_seq_show_tcp_udp(struct seq_file *seq, void *v)
+static int snmp_seq_show_tcp_udp(struct seq_file *seq, void *v, bool first)
 {
 	const int udp_cnt = ARRAY_SIZE(snmp4_udp_list);
 	const int tcp_cnt = ARRAY_SIZE(snmp4_tcp_list);
@@ -416,7 +425,7 @@ static int snmp_seq_show_tcp_udp(struct seq_file *seq, void *v)
 
 	memset(buff, 0, tcp_cnt * sizeof(unsigned long));
 
-	seq_puts(seq, "\nTcp:");
+	seq_puts(seq, first ? "Tcp:" : "\nTcp:");
 	for (i = 0; i < tcp_cnt; i++)
 		seq_printf(seq, " %s", snmp4_tcp_list[i].name);
 
@@ -450,12 +459,14 @@ static int snmp_seq_show_tcp_udp(struct seq_file *seq, void *v)
 
 static int snmp_seq_show(struct seq_file *seq, void *v)
 {
-	snmp_seq_show_ipstats(seq, v);
+	if (IS_ENABLED(CONFIG_IPV4)) {
+		snmp_seq_show_ipstats(seq, v);
 
-	icmp_put(seq);	/* RFC 2011 compatibility */
-	icmpmsg_put(seq);
+		icmp_put(seq);	/* RFC 2011 compatibility */
+		icmpmsg_put(seq);
+	}
 
-	snmp_seq_show_tcp_udp(seq, v);
+	snmp_seq_show_tcp_udp(seq, v, !IS_ENABLED(CONFIG_IPV4));
 
 	return 0;
 }
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 3ac485685279..1f0fc46136ea 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -267,6 +267,8 @@
 #include <linux/errqueue.h>
 #include <linux/static_key.h>
 #include <linux/btf.h>
+#include <linux/proc_fs.h>
+#include <linux/skbuff_ref.h>
 
 #include <net/icmp.h>
 #include <net/inet_common.h>
@@ -308,6 +310,10 @@ DEFINE_STATIC_KEY_FALSE(tcp_have_smc);
 EXPORT_SYMBOL(tcp_have_smc);
 #endif
 
+struct inet_hashinfo tcp_hashinfo;
+
+static DEFINE_MUTEX(tcp_exit_batch_mutex);
+
 /*
  * Current number of TCP sockets.
  */
@@ -1458,6 +1464,1115 @@ int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 }
 EXPORT_SYMBOL(tcp_sendmsg);
 
+#ifdef CONFIG_PROC_FS
+/* Proc filesystem TCP sock list dumping. */
+
+static unsigned short seq_file_family(const struct seq_file *seq);
+
+static bool seq_sk_match(struct seq_file *seq, const struct sock *sk)
+{
+	unsigned short family = seq_file_family(seq);
+
+	/* AF_UNSPEC is used as a match all */
+	return ((family == AF_UNSPEC || family == sk->sk_family) &&
+		net_eq(sock_net(sk), seq_file_net(seq)));
+}
+
+/* Find a non empty bucket (starting from st->bucket)
+ * and return the first sk from it.
+ */
+static void *listening_get_first(struct seq_file *seq)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct tcp_iter_state *st = seq->private;
+
+	st->offset = 0;
+	for (; st->bucket <= hinfo->lhash2_mask; st->bucket++) {
+		struct inet_listen_hashbucket *ilb2;
+		struct hlist_nulls_node *node;
+		struct sock *sk;
+
+		ilb2 = &hinfo->lhash2[st->bucket];
+		if (hlist_nulls_empty(&ilb2->nulls_head))
+			continue;
+
+		spin_lock(&ilb2->lock);
+		sk_nulls_for_each(sk, node, &ilb2->nulls_head) {
+			if (seq_sk_match(seq, sk))
+				return sk;
+		}
+		spin_unlock(&ilb2->lock);
+	}
+
+	return NULL;
+}
+
+/* Find the next sk of "cur" within the same bucket (i.e. st->bucket).
+ * If "cur" is the last one in the st->bucket,
+ * call listening_get_first() to return the first sk of the next
+ * non empty bucket.
+ */
+static void *listening_get_next(struct seq_file *seq, void *cur)
+{
+	struct tcp_iter_state *st = seq->private;
+	struct inet_listen_hashbucket *ilb2;
+	struct hlist_nulls_node *node;
+	struct inet_hashinfo *hinfo;
+	struct sock *sk = cur;
+
+	++st->num;
+	++st->offset;
+
+	sk = sk_nulls_next(sk);
+	sk_nulls_for_each_from(sk, node) {
+		if (seq_sk_match(seq, sk))
+			return sk;
+	}
+
+	hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	ilb2 = &hinfo->lhash2[st->bucket];
+	spin_unlock(&ilb2->lock);
+	++st->bucket;
+	return listening_get_first(seq);
+}
+
+static void *listening_get_idx(struct seq_file *seq, loff_t *pos)
+{
+	struct tcp_iter_state *st = seq->private;
+	void *rc;
+
+	st->bucket = 0;
+	st->offset = 0;
+	rc = listening_get_first(seq);
+
+	while (rc && *pos) {
+		rc = listening_get_next(seq, rc);
+		--*pos;
+	}
+	return rc;
+}
+
+static inline bool empty_bucket(struct inet_hashinfo *hinfo,
+				const struct tcp_iter_state *st)
+{
+	return hlist_nulls_empty(&hinfo->ehash[st->bucket].chain);
+}
+
+/*
+ * Get first established socket starting from bucket given in st->bucket.
+ * If st->bucket is zero, the very first socket in the hash is returned.
+ */
+static void *established_get_first(struct seq_file *seq)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct tcp_iter_state *st = seq->private;
+
+	st->offset = 0;
+	for (; st->bucket <= hinfo->ehash_mask; ++st->bucket) {
+		struct sock *sk;
+		struct hlist_nulls_node *node;
+		spinlock_t *lock = inet_ehash_lockp(hinfo, st->bucket);
+
+		cond_resched();
+
+		/* Lockless fast path for the common case of empty buckets */
+		if (empty_bucket(hinfo, st))
+			continue;
+
+		spin_lock_bh(lock);
+		sk_nulls_for_each(sk, node, &hinfo->ehash[st->bucket].chain) {
+			if (seq_sk_match(seq, sk))
+				return sk;
+		}
+		spin_unlock_bh(lock);
+	}
+
+	return NULL;
+}
+
+static void *established_get_next(struct seq_file *seq, void *cur)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct tcp_iter_state *st = seq->private;
+	struct hlist_nulls_node *node;
+	struct sock *sk = cur;
+
+	++st->num;
+	++st->offset;
+
+	sk = sk_nulls_next(sk);
+
+	sk_nulls_for_each_from(sk, node) {
+		if (seq_sk_match(seq, sk))
+			return sk;
+	}
+
+	spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
+	++st->bucket;
+	return established_get_first(seq);
+}
+
+static void *established_get_idx(struct seq_file *seq, loff_t pos)
+{
+	struct tcp_iter_state *st = seq->private;
+	void *rc;
+
+	st->bucket = 0;
+	rc = established_get_first(seq);
+
+	while (rc && pos) {
+		rc = established_get_next(seq, rc);
+		--pos;
+	}
+	return rc;
+}
+
+static void *tcp_get_idx(struct seq_file *seq, loff_t pos)
+{
+	void *rc;
+	struct tcp_iter_state *st = seq->private;
+
+	st->state = TCP_SEQ_STATE_LISTENING;
+	rc	  = listening_get_idx(seq, &pos);
+
+	if (!rc) {
+		st->state = TCP_SEQ_STATE_ESTABLISHED;
+		rc	  = established_get_idx(seq, pos);
+	}
+
+	return rc;
+}
+
+static void *tcp_seek_last_pos(struct seq_file *seq)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct tcp_iter_state *st = seq->private;
+	int bucket = st->bucket;
+	int offset = st->offset;
+	int orig_num = st->num;
+	void *rc = NULL;
+
+	switch (st->state) {
+	case TCP_SEQ_STATE_LISTENING:
+		if (st->bucket > hinfo->lhash2_mask)
+			break;
+		rc = listening_get_first(seq);
+		while (offset-- && rc && bucket == st->bucket)
+			rc = listening_get_next(seq, rc);
+		if (rc)
+			break;
+		st->bucket = 0;
+		st->state = TCP_SEQ_STATE_ESTABLISHED;
+		fallthrough;
+	case TCP_SEQ_STATE_ESTABLISHED:
+		if (st->bucket > hinfo->ehash_mask)
+			break;
+		rc = established_get_first(seq);
+		while (offset-- && rc && bucket == st->bucket)
+			rc = established_get_next(seq, rc);
+	}
+
+	st->num = orig_num;
+
+	return rc;
+}
+
+void *tcp_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	struct tcp_iter_state *st = seq->private;
+	void *rc;
+
+	if (*pos && *pos == st->last_pos) {
+		rc = tcp_seek_last_pos(seq);
+		if (rc)
+			goto out;
+	}
+
+	st->state = TCP_SEQ_STATE_LISTENING;
+	st->num = 0;
+	st->bucket = 0;
+	st->offset = 0;
+	rc = *pos ? tcp_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
+
+out:
+	st->last_pos = *pos;
+	return rc;
+}
+
+void *tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	struct tcp_iter_state *st = seq->private;
+	void *rc = NULL;
+
+	if (v == SEQ_START_TOKEN) {
+		rc = tcp_get_idx(seq, 0);
+		goto out;
+	}
+
+	switch (st->state) {
+	case TCP_SEQ_STATE_LISTENING:
+		rc = listening_get_next(seq, v);
+		if (!rc) {
+			st->state = TCP_SEQ_STATE_ESTABLISHED;
+			st->bucket = 0;
+			st->offset = 0;
+			rc	  = established_get_first(seq);
+		}
+		break;
+	case TCP_SEQ_STATE_ESTABLISHED:
+		rc = established_get_next(seq, v);
+		break;
+	}
+out:
+	++*pos;
+	st->last_pos = *pos;
+	return rc;
+}
+
+void tcp_seq_stop(struct seq_file *seq, void *v)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct tcp_iter_state *st = seq->private;
+
+	switch (st->state) {
+	case TCP_SEQ_STATE_LISTENING:
+		if (v != SEQ_START_TOKEN)
+			spin_unlock(&hinfo->lhash2[st->bucket].lock);
+		break;
+	case TCP_SEQ_STATE_ESTABLISHED:
+		if (v)
+			spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
+		break;
+	}
+}
+
+#ifdef CONFIG_BPF_SYSCALL
+struct bpf_iter__tcp {
+	__bpf_md_ptr(struct bpf_iter_meta *, meta);
+	__bpf_md_ptr(struct sock_common *, sk_common);
+	uid_t uid __aligned(8);
+};
+
+static int tcp_prog_seq_show(struct bpf_prog *prog, struct bpf_iter_meta *meta,
+			     struct sock_common *sk_common, uid_t uid)
+{
+	struct bpf_iter__tcp ctx;
+
+	meta->seq_num--;  /* skip SEQ_START_TOKEN */
+	ctx.meta = meta;
+	ctx.sk_common = sk_common;
+	ctx.uid = uid;
+	return bpf_iter_run_prog(prog, &ctx);
+}
+
+static void bpf_iter_tcp_put_batch(struct bpf_tcp_iter_state *iter)
+{
+	union bpf_tcp_iter_batch_item *item;
+	unsigned int cur_sk = iter->cur_sk;
+	__u64 cookie;
+
+	/* Remember the cookies of the sockets we haven't seen yet, so we can
+	 * pick up where we left off next time around.
+	 */
+	while (cur_sk < iter->end_sk) {
+		item = &iter->batch[cur_sk++];
+		cookie = sock_gen_cookie(item->sk);
+		sock_gen_put(item->sk);
+		item->cookie = cookie;
+	}
+}
+
+static int bpf_iter_tcp_realloc_batch(struct bpf_tcp_iter_state *iter,
+				      unsigned int new_batch_sz, gfp_t flags)
+{
+	union bpf_tcp_iter_batch_item *new_batch;
+
+	new_batch = kvmalloc_objs(*new_batch, new_batch_sz,
+				   flags | __GFP_NOWARN);
+	if (!new_batch)
+		return -ENOMEM;
+
+	memcpy(new_batch, iter->batch, sizeof(*iter->batch) * iter->end_sk);
+	kvfree(iter->batch);
+	iter->batch = new_batch;
+	iter->max_sk = new_batch_sz;
+
+	return 0;
+}
+
+static struct sock *bpf_iter_tcp_resume_bucket(struct sock *first_sk,
+					       union bpf_tcp_iter_batch_item *cookies,
+					       int n_cookies)
+{
+	struct hlist_nulls_node *node;
+	struct sock *sk;
+	int i;
+
+	for (i = 0; i < n_cookies; i++) {
+		sk = first_sk;
+		sk_nulls_for_each_from(sk, node)
+			if (cookies[i].cookie == atomic64_read(&sk->sk_cookie))
+				return sk;
+	}
+
+	return NULL;
+}
+
+static struct sock *bpf_iter_tcp_resume_listening(struct seq_file *seq)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct tcp_iter_state *st = &iter->state;
+	unsigned int find_cookie = iter->cur_sk;
+	unsigned int end_cookie = iter->end_sk;
+	int resume_bucket = st->bucket;
+	struct sock *sk;
+
+	if (end_cookie && find_cookie == end_cookie)
+		++st->bucket;
+
+	sk = listening_get_first(seq);
+	iter->cur_sk = 0;
+	iter->end_sk = 0;
+
+	if (sk && st->bucket == resume_bucket && end_cookie) {
+		sk = bpf_iter_tcp_resume_bucket(sk, &iter->batch[find_cookie],
+						end_cookie - find_cookie);
+		if (!sk) {
+			spin_unlock(&hinfo->lhash2[st->bucket].lock);
+			++st->bucket;
+			sk = listening_get_first(seq);
+		}
+	}
+
+	return sk;
+}
+
+static struct sock *bpf_iter_tcp_resume_established(struct seq_file *seq)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct tcp_iter_state *st = &iter->state;
+	unsigned int find_cookie = iter->cur_sk;
+	unsigned int end_cookie = iter->end_sk;
+	int resume_bucket = st->bucket;
+	struct sock *sk;
+
+	if (end_cookie && find_cookie == end_cookie)
+		++st->bucket;
+
+	sk = established_get_first(seq);
+	iter->cur_sk = 0;
+	iter->end_sk = 0;
+
+	if (sk && st->bucket == resume_bucket && end_cookie) {
+		sk = bpf_iter_tcp_resume_bucket(sk, &iter->batch[find_cookie],
+						end_cookie - find_cookie);
+		if (!sk) {
+			spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
+			++st->bucket;
+			sk = established_get_first(seq);
+		}
+	}
+
+	return sk;
+}
+
+static struct sock *bpf_iter_tcp_resume(struct seq_file *seq)
+{
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct tcp_iter_state *st = &iter->state;
+	struct sock *sk = NULL;
+
+	switch (st->state) {
+	case TCP_SEQ_STATE_LISTENING:
+		sk = bpf_iter_tcp_resume_listening(seq);
+		if (sk)
+			break;
+		st->bucket = 0;
+		st->state = TCP_SEQ_STATE_ESTABLISHED;
+		fallthrough;
+	case TCP_SEQ_STATE_ESTABLISHED:
+		sk = bpf_iter_tcp_resume_established(seq);
+		break;
+	}
+
+	return sk;
+}
+
+static unsigned int bpf_iter_tcp_listening_batch(struct seq_file *seq,
+						 struct sock **start_sk)
+{
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct hlist_nulls_node *node;
+	unsigned int expected = 1;
+	struct sock *sk;
+
+	sock_hold(*start_sk);
+	iter->batch[iter->end_sk++].sk = *start_sk;
+
+	sk = sk_nulls_next(*start_sk);
+	*start_sk = NULL;
+	sk_nulls_for_each_from(sk, node) {
+		if (seq_sk_match(seq, sk)) {
+			if (iter->end_sk < iter->max_sk) {
+				sock_hold(sk);
+				iter->batch[iter->end_sk++].sk = sk;
+			} else if (!*start_sk) {
+				/* Remember where we left off. */
+				*start_sk = sk;
+			}
+			expected++;
+		}
+	}
+
+	return expected;
+}
+
+static unsigned int bpf_iter_tcp_established_batch(struct seq_file *seq,
+						   struct sock **start_sk)
+{
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct hlist_nulls_node *node;
+	struct sock *sk = *start_sk;
+	unsigned int expected = 0;
+
+	*start_sk = NULL;
+	sk_nulls_for_each_from(sk, node) {
+		if (!seq_sk_match(seq, sk))
+			continue;
+		expected++;
+		if (iter->end_sk < iter->max_sk) {
+			/* reqsk_queue_hash_req() inserts with sk_refcnt == 0
+			 * and refcount_set()s it after the bucket lock drops.
+			 */
+			if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
+				continue;
+			iter->batch[iter->end_sk++].sk = sk;
+		} else if (!*start_sk) {
+			/* Remember where we left off. */
+			*start_sk = sk;
+		}
+	}
+
+	return expected;
+}
+
+static unsigned int bpf_iter_fill_batch(struct seq_file *seq,
+					struct sock **start_sk)
+{
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct tcp_iter_state *st = &iter->state;
+
+	if (st->state == TCP_SEQ_STATE_LISTENING)
+		return bpf_iter_tcp_listening_batch(seq, start_sk);
+	else
+		return bpf_iter_tcp_established_batch(seq, start_sk);
+}
+
+static void bpf_iter_tcp_unlock_bucket(struct seq_file *seq)
+{
+	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct tcp_iter_state *st = &iter->state;
+
+	if (st->state == TCP_SEQ_STATE_LISTENING)
+		spin_unlock(&hinfo->lhash2[st->bucket].lock);
+	else
+		spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
+}
+
+static struct sock *bpf_iter_tcp_batch(struct seq_file *seq)
+{
+	struct bpf_tcp_iter_state *iter = seq->private;
+	unsigned int expected;
+	struct sock *sk;
+	int err;
+
+again:
+	sk = bpf_iter_tcp_resume(seq);
+	if (!sk)
+		return NULL; /* Done */
+
+	expected = bpf_iter_fill_batch(seq, &sk);
+	if (likely(!sk))
+		goto done;
+
+	/* Batch size was too small. */
+	bpf_iter_tcp_unlock_bucket(seq);
+	bpf_iter_tcp_put_batch(iter);
+	err = bpf_iter_tcp_realloc_batch(iter, expected * 3 / 2,
+					 GFP_USER);
+	if (err) {
+		iter->cur_sk = 0;
+		iter->end_sk = 0;
+		return ERR_PTR(err);
+	}
+
+	sk = bpf_iter_tcp_resume(seq);
+	if (!sk)
+		return NULL; /* Done */
+
+	expected = bpf_iter_fill_batch(seq, &sk);
+	if (likely(!sk))
+		goto done;
+
+	/* Batch size was still too small. Hold onto the lock while we try
+	 * again with a larger batch to make sure the current bucket's size
+	 * does not change in the meantime.
+	 */
+	err = bpf_iter_tcp_realloc_batch(iter, expected, GFP_NOWAIT);
+	if (err) {
+		bpf_iter_tcp_unlock_bucket(seq);
+		return ERR_PTR(err);
+	}
+
+	bpf_iter_fill_batch(seq, &sk);
+	WARN_ON_ONCE(sk);
+done:
+	bpf_iter_tcp_unlock_bucket(seq);
+	if (unlikely(!iter->end_sk)) {
+		++iter->state.bucket;
+		goto again;
+	}
+	return iter->batch[0].sk;
+}
+
+static void *bpf_iter_tcp_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	/* bpf iter does not support lseek, so it always
+	 * continue from where it was stop()-ped.
+	 */
+	if (*pos)
+		return bpf_iter_tcp_batch(seq);
+
+	return SEQ_START_TOKEN;
+}
+
+static void *bpf_iter_tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct tcp_iter_state *st = &iter->state;
+	struct sock *sk;
+
+	/* Whenever seq_next() is called, the iter->cur_sk is
+	 * done with seq_show(), so advance to the next sk in
+	 * the batch.
+	 */
+	if (iter->cur_sk < iter->end_sk) {
+		/* Keeping st->num consistent in tcp_iter_state.
+		 * bpf_iter_tcp does not use st->num.
+		 * meta.seq_num is used instead.
+		 */
+		st->num++;
+		sock_gen_put(iter->batch[iter->cur_sk++].sk);
+	}
+
+	if (iter->cur_sk < iter->end_sk)
+		sk = iter->batch[iter->cur_sk].sk;
+	else
+		sk = bpf_iter_tcp_batch(seq);
+
+	++*pos;
+	/* Keeping st->last_pos consistent in tcp_iter_state.
+	 * bpf iter does not do lseek, so st->last_pos always equals to *pos.
+	 */
+	st->last_pos = *pos;
+	return sk;
+}
+
+static int bpf_iter_tcp_seq_show(struct seq_file *seq, void *v)
+{
+	struct bpf_iter_meta meta;
+	struct bpf_prog *prog;
+	struct sock *sk = v;
+	uid_t uid;
+	int ret;
+
+	if (v == SEQ_START_TOKEN)
+		return 0;
+
+	if (sk_fullsock(sk))
+		lock_sock(sk);
+
+	if (unlikely(sk_unhashed(sk))) {
+		ret = SEQ_SKIP;
+		goto unlock;
+	}
+
+	if (sk->sk_state == TCP_TIME_WAIT) {
+		uid = 0;
+	} else if (sk->sk_state == TCP_NEW_SYN_RECV) {
+		const struct request_sock *req = v;
+
+		uid = from_kuid_munged(seq_user_ns(seq),
+				       sk_uid(req->rsk_listener));
+	} else {
+		uid = from_kuid_munged(seq_user_ns(seq), sk_uid(sk));
+	}
+
+	meta.seq = seq;
+	prog = bpf_iter_get_info(&meta, false);
+	ret = tcp_prog_seq_show(prog, &meta, v, uid);
+
+unlock:
+	if (sk_fullsock(sk))
+		release_sock(sk);
+	return ret;
+}
+
+static void bpf_iter_tcp_seq_stop(struct seq_file *seq, void *v)
+{
+	struct bpf_tcp_iter_state *iter = seq->private;
+	struct bpf_iter_meta meta;
+	struct bpf_prog *prog;
+
+	if (!v) {
+		meta.seq = seq;
+		prog = bpf_iter_get_info(&meta, true);
+		if (prog)
+			(void)tcp_prog_seq_show(prog, &meta, v, 0);
+	}
+
+	if (iter->cur_sk < iter->end_sk)
+		bpf_iter_tcp_put_batch(iter);
+}
+
+static const struct seq_operations bpf_iter_tcp_seq_ops = {
+	.show		= bpf_iter_tcp_seq_show,
+	.start		= bpf_iter_tcp_seq_start,
+	.next		= bpf_iter_tcp_seq_next,
+	.stop		= bpf_iter_tcp_seq_stop,
+};
+#endif /* CONFIG_BPF_SYSCALL */
+
+static unsigned short seq_file_family(const struct seq_file *seq)
+{
+	const struct tcp_seq_afinfo *afinfo;
+
+#ifdef CONFIG_BPF_SYSCALL
+	/* Iterated from bpf_iter.  Let the bpf prog to filter instead. */
+	if (seq->op == &bpf_iter_tcp_seq_ops)
+		return AF_UNSPEC;
+#endif
+
+	/* Iterated from proc fs */
+	afinfo = pde_data(file_inode(seq->file));
+	return afinfo->family;
+}
+#endif /* CONFIG_PROC_FS */
+
+#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
+DEFINE_BPF_ITER_FUNC(tcp, struct bpf_iter_meta *meta,
+		     struct sock_common *sk_common, uid_t uid)
+
+#define INIT_BATCH_SZ 16
+
+static int bpf_iter_init_tcp(void *priv_data, struct bpf_iter_aux_info *aux)
+{
+	struct bpf_tcp_iter_state *iter = priv_data;
+	int err;
+
+	err = bpf_iter_init_seq_net(priv_data, aux);
+	if (err)
+		return err;
+
+	err = bpf_iter_tcp_realloc_batch(iter, INIT_BATCH_SZ, GFP_USER);
+	if (err) {
+		bpf_iter_fini_seq_net(priv_data);
+		return err;
+	}
+
+	return 0;
+}
+
+static void bpf_iter_fini_tcp(void *priv_data)
+{
+	struct bpf_tcp_iter_state *iter = priv_data;
+
+	bpf_iter_fini_seq_net(priv_data);
+	kvfree(iter->batch);
+}
+
+static const struct bpf_iter_seq_info tcp_seq_info = {
+	.seq_ops		= &bpf_iter_tcp_seq_ops,
+	.init_seq_private	= bpf_iter_init_tcp,
+	.fini_seq_private	= bpf_iter_fini_tcp,
+	.seq_priv_size		= sizeof(struct bpf_tcp_iter_state),
+};
+
+static const struct bpf_func_proto *
+bpf_iter_tcp_get_func_proto(enum bpf_func_id func_id,
+			    const struct bpf_prog *prog)
+{
+	switch (func_id) {
+	case BPF_FUNC_setsockopt:
+		return &bpf_sk_setsockopt_proto;
+	case BPF_FUNC_getsockopt:
+		return &bpf_sk_getsockopt_proto;
+	default:
+		return NULL;
+	}
+}
+
+static struct bpf_iter_reg tcp_reg_info = {
+	.target			= "tcp",
+	.ctx_arg_info_size	= 1,
+	.ctx_arg_info		= {
+		{ offsetof(struct bpf_iter__tcp, sk_common),
+		  PTR_TO_BTF_ID_OR_NULL | PTR_TRUSTED },
+	},
+	.get_func_proto		= bpf_iter_tcp_get_func_proto,
+	.seq_info		= &tcp_seq_info,
+};
+
+static void __init bpf_iter_register(void)
+{
+	tcp_reg_info.ctx_arg_info[0].btf_id = btf_sock_ids[BTF_SOCK_TYPE_SOCK_COMMON];
+	if (bpf_iter_reg_target(&tcp_reg_info))
+		pr_warn("Warning: could not register bpf iterator tcp\n");
+}
+
+#endif
+
+static void tcp_release_user_frags(struct sock *sk)
+{
+#ifdef CONFIG_PAGE_POOL
+	unsigned long index;
+	void *netmem;
+
+	xa_for_each(&sk->sk_user_frags, index, netmem)
+		WARN_ON_ONCE(!napi_pp_put_page((__force netmem_ref)netmem));
+#endif
+}
+
+void tcp_destroy_sock(struct sock *sk)
+{
+	struct tcp_sock *tp = tcp_sk(sk);
+
+	tcp_release_user_frags(sk);
+
+	xa_destroy(&sk->sk_user_frags);
+
+	trace_tcp_destroy_sock(sk);
+
+	tcp_clear_xmit_timers(sk);
+
+	tcp_cleanup_congestion_control(sk);
+
+	tcp_cleanup_ulp(sk);
+
+	/* Cleanup up the write buffer. */
+	tcp_write_queue_purge(sk);
+
+	/* Check if we want to disable active TFO */
+	tcp_fastopen_active_disable_ofo_check(sk);
+
+	/* Cleans up our, hopefully empty, out_of_order_queue. */
+	skb_rbtree_purge(&tp->out_of_order_queue);
+
+	/* Clean up a referenced TCP bind bucket. */
+	if (inet_csk(sk)->icsk_bind_hash)
+		inet_put_port(sk);
+
+	BUG_ON(rcu_access_pointer(tp->fastopen_rsk));
+
+	/* If socket is aborted during connect operation */
+	tcp_free_fastopen_req(tp);
+	tcp_fastopen_destroy_cipher(sk);
+	tcp_saved_syn_free(tp);
+
+	sk_sockets_allocated_dec(sk);
+}
+
+int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
+{
+	int reuse = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_tw_reuse);
+	const struct inet_timewait_sock *tw = inet_twsk(sktw);
+	const struct tcp_timewait_sock *tcptw = tcp_twsk(sktw);
+	struct tcp_sock *tp = tcp_sk(sk);
+	int ts_recent_stamp;
+	u32 reuse_thresh;
+
+	if (READ_ONCE(tw->tw_substate) == TCP_FIN_WAIT2)
+		reuse = 0;
+
+	if (reuse == 2) {
+		/* Still does not detect *everything* that goes through
+		 * lo, since we require a loopback src or dst address
+		 * or direct binding to 'lo' interface.
+		 */
+		bool loopback = false;
+
+		if (tw->tw_bound_dev_if == LOOPBACK_IFINDEX)
+			loopback = true;
+#if IS_ENABLED(CONFIG_IPV6)
+		if (tw->tw_family == AF_INET6) {
+			if (ipv6_addr_loopback(&tw->tw_v6_daddr) ||
+			    ipv6_addr_v4mapped_loopback(&tw->tw_v6_daddr) ||
+			    ipv6_addr_loopback(&tw->tw_v6_rcv_saddr) ||
+			    ipv6_addr_v4mapped_loopback(&tw->tw_v6_rcv_saddr))
+				loopback = true;
+		} else
+#endif
+		{
+			if (ipv4_is_loopback(tw->tw_daddr) ||
+			    ipv4_is_loopback(tw->tw_rcv_saddr))
+				loopback = true;
+		}
+		if (!loopback)
+			reuse = 0;
+	}
+
+	/* With PAWS, it is safe from the viewpoint
+	 * of data integrity. Even without PAWS it is safe provided sequence
+	 * spaces do not overlap i.e. at data rates <= 80Mbit/sec.
+	 *
+	 * Actually, the idea is close to VJ's one, only timestamp cache is
+	 * held not per host, but per port pair and TW bucket is used as state
+	 * holder.
+	 *
+	 * If TW bucket has been already destroyed we fall back to VJ's scheme
+	 * and use initial timestamp retrieved from peer table.
+	 */
+	ts_recent_stamp = READ_ONCE(tcptw->tw_ts_recent_stamp);
+	reuse_thresh = READ_ONCE(tw->tw_entry_stamp) +
+		       READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_tw_reuse_delay);
+	if (ts_recent_stamp &&
+	    (!twp || (reuse && time_after32(tcp_clock_ms(), reuse_thresh)))) {
+		/* inet_twsk_hashdance_schedule() sets sk_refcnt after putting twsk
+		 * and releasing the bucket lock.
+		 */
+		if (unlikely(!refcount_inc_not_zero(&sktw->sk_refcnt)))
+			return 0;
+
+		/* In case of repair and re-using TIME-WAIT sockets we still
+		 * want to be sure that it is safe as above but honor the
+		 * sequence numbers and time stamps set as part of the repair
+		 * process.
+		 *
+		 * Without this check re-using a TIME-WAIT socket with TCP
+		 * repair would accumulate a -1 on the repair assigned
+		 * sequence number. The first time it is reused the sequence
+		 * is -1, the second time -2, etc. This fixes that issue
+		 * without appearing to create any others.
+		 */
+		if (likely(!tp->repair)) {
+			u32 seq = tcptw->tw_snd_nxt + 65535 + 2;
+
+			if (!seq)
+				seq = 1;
+			WRITE_ONCE(tp->write_seq, seq);
+			tp->rx_opt.ts_recent	   = READ_ONCE(tcptw->tw_ts_recent);
+			tp->rx_opt.ts_recent_stamp = ts_recent_stamp;
+		}
+
+		return 1;
+	}
+
+	return 0;
+}
+
+enum skb_drop_reason tcp_add_backlog(struct sock *sk, struct sk_buff *skb)
+{
+	u32 tail_gso_size, tail_gso_segs;
+	struct skb_shared_info *shinfo;
+	const struct tcphdr *th;
+	struct tcphdr *thtail;
+	struct sk_buff *tail;
+	unsigned int hdrlen;
+	bool fragstolen;
+	u32 gso_segs;
+	u32 gso_size;
+	u64 limit;
+	int delta;
+	int err;
+
+	/* In case all data was pulled from skb frags (in __pskb_pull_tail()),
+	 * we can fix skb->truesize to its real value to avoid future drops.
+	 * This is valid because skb is not yet charged to the socket.
+	 * It has been noticed pure SACK packets were sometimes dropped
+	 * (if cooked by drivers without copybreak feature).
+	 */
+	skb_condense(skb);
+
+	tcp_cleanup_skb(skb);
+
+	if (unlikely(tcp_checksum_complete(skb))) {
+		bh_unlock_sock(sk);
+		trace_tcp_bad_csum(skb);
+		__TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS);
+		__TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
+		return SKB_DROP_REASON_TCP_CSUM;
+	}
+
+	/* Attempt coalescing to last skb in backlog, even if we are
+	 * above the limits.
+	 * This is okay because skb capacity is limited to MAX_SKB_FRAGS.
+	 */
+	th = (const struct tcphdr *)skb->data;
+	hdrlen = th->doff * 4;
+
+	tail = sk->sk_backlog.tail;
+	if (!tail)
+		goto no_coalesce;
+	thtail = (struct tcphdr *)tail->data;
+
+	if (TCP_SKB_CB(tail)->end_seq != TCP_SKB_CB(skb)->seq ||
+	    TCP_SKB_CB(tail)->ip_dsfield != TCP_SKB_CB(skb)->ip_dsfield ||
+	    ((TCP_SKB_CB(tail)->tcp_flags |
+	      TCP_SKB_CB(skb)->tcp_flags) & (TCPHDR_SYN | TCPHDR_RST | TCPHDR_URG)) ||
+	    !((TCP_SKB_CB(tail)->tcp_flags &
+	      TCP_SKB_CB(skb)->tcp_flags) & TCPHDR_ACK) ||
+	    ((TCP_SKB_CB(tail)->tcp_flags ^
+	      TCP_SKB_CB(skb)->tcp_flags) &
+	     (TCPHDR_ECE | TCPHDR_CWR | TCPHDR_AE)) ||
+	    !tcp_skb_can_collapse_rx(tail, skb) ||
+	    thtail->doff != th->doff ||
+	    memcmp(thtail + 1, th + 1, hdrlen - sizeof(*th)) ||
+	    /* prior to PSP Rx policy check, retain exact PSP metadata */
+	    psp_skb_coalesce_diff(tail, skb))
+		goto no_coalesce;
+
+	__skb_pull(skb, hdrlen);
+
+	shinfo = skb_shinfo(skb);
+	gso_size = shinfo->gso_size ?: skb->len;
+	gso_segs = shinfo->gso_segs ?: 1;
+
+	shinfo = skb_shinfo(tail);
+	tail_gso_size = shinfo->gso_size ?: (tail->len - hdrlen);
+	tail_gso_segs = shinfo->gso_segs ?: 1;
+
+	if (skb_try_coalesce(tail, skb, &fragstolen, &delta)) {
+		TCP_SKB_CB(tail)->end_seq = TCP_SKB_CB(skb)->end_seq;
+
+		if (likely(!before(TCP_SKB_CB(skb)->ack_seq, TCP_SKB_CB(tail)->ack_seq))) {
+			TCP_SKB_CB(tail)->ack_seq = TCP_SKB_CB(skb)->ack_seq;
+			thtail->window = th->window;
+		}
+
+		/* We have to update both TCP_SKB_CB(tail)->tcp_flags and
+		 * thtail->fin, so that the fast path in tcp_rcv_established()
+		 * is not entered if we append a packet with a FIN.
+		 * SYN, RST, URG are not present.
+		 * ACK is set on both packets.
+		 * PSH : we do not really care in TCP stack,
+		 *       at least for 'GRO' packets.
+		 */
+		thtail->fin |= th->fin;
+		TCP_SKB_CB(tail)->tcp_flags |= TCP_SKB_CB(skb)->tcp_flags;
+
+		if (TCP_SKB_CB(skb)->has_rxtstamp) {
+			TCP_SKB_CB(tail)->has_rxtstamp = true;
+			tail->tstamp = skb->tstamp;
+			skb_hwtstamps(tail)->hwtstamp = skb_hwtstamps(skb)->hwtstamp;
+		}
+
+		/* Not as strict as GRO. We only need to carry mss max value */
+		shinfo->gso_size = max(gso_size, tail_gso_size);
+		shinfo->gso_segs = min_t(u32, gso_segs + tail_gso_segs, 0xFFFF);
+
+		sk->sk_backlog.len += delta;
+		__NET_INC_STATS(sock_net(sk),
+				LINUX_MIB_TCPBACKLOGCOALESCE);
+		kfree_skb_partial(skb, fragstolen);
+		return SKB_NOT_DROPPED_YET;
+	}
+	__skb_push(skb, hdrlen);
+
+no_coalesce:
+	/* sk->sk_backlog.len is reset only at the end of __release_sock().
+	 * Both sk->sk_backlog.len and sk->sk_rmem_alloc could reach
+	 * sk_rcvbuf in normal conditions.
+	 */
+	limit = ((u64)READ_ONCE(sk->sk_rcvbuf)) << 1;
+
+	limit += ((u32)READ_ONCE(sk->sk_sndbuf)) >> 1;
+
+	/* Only socket owner can try to collapse/prune rx queues
+	 * to reduce memory overhead, so add a little headroom here.
+	 * Few sockets backlog are possibly concurrently non empty.
+	 */
+	limit += 64 * 1024;
+
+	limit = min_t(u64, limit, UINT_MAX);
+
+	err = sk_add_backlog(sk, skb, limit);
+	if (unlikely(err)) {
+		bh_unlock_sock(sk);
+		if (err == -ENOMEM) {
+			__NET_INC_STATS(sock_net(sk), LINUX_MIB_PFMEMALLOCDROP);
+			return SKB_DROP_REASON_PFMEMALLOC;
+		}
+		__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPBACKLOGDROP);
+		return SKB_DROP_REASON_SOCKET_BACKLOG;
+	}
+	return SKB_NOT_DROPPED_YET;
+}
+
+/* TCP-LD (RFC 6069) logic */
+void tcp_ld_RTO_revert(struct sock *sk, u32 seq)
+{
+	struct inet_connection_sock *icsk = inet_csk(sk);
+	struct tcp_sock *tp = tcp_sk(sk);
+	struct sk_buff *skb;
+	s32 remaining;
+	u32 delta_us;
+
+	if (sock_owned_by_user(sk))
+		return;
+
+	if (seq != tp->snd_una  || !icsk->icsk_retransmits ||
+	    !icsk->icsk_backoff)
+		return;
+
+	skb = tcp_rtx_queue_head(sk);
+	if (WARN_ON_ONCE(!skb))
+		return;
+
+	icsk->icsk_backoff--;
+	icsk->icsk_rto = tp->srtt_us ? __tcp_set_rto(tp) : TCP_TIMEOUT_INIT;
+	icsk->icsk_rto = inet_csk_rto_backoff(icsk, tcp_rto_max(sk));
+
+	tcp_mstamp_refresh(tp);
+	delta_us = (u32)(tp->tcp_mstamp - tcp_skb_timestamp_us(skb));
+	remaining = icsk->icsk_rto - usecs_to_jiffies(delta_us);
+
+	if (remaining > 0) {
+		tcp_reset_xmit_timer(sk, ICSK_TIME_RETRANS, remaining, false);
+	} else {
+		/* RTO revert clocked out retransmission.
+		 * Will retransmit now.
+		 */
+		tcp_retransmit_timer(sk);
+	}
+}
+
+/* handle ICMP messages on TCP_NEW_SYN_RECV request sockets */
+void tcp_req_err(struct sock *sk, u32 seq, bool abort)
+{
+	struct request_sock *req = inet_reqsk(sk);
+	struct net *net = sock_net(sk);
+
+	/* ICMPs are not backlogged, hence we cannot get
+	 * an established socket here.
+	 */
+	if (seq != tcp_rsk(req)->snt_isn) {
+		__NET_INC_STATS(net, LINUX_MIB_OUTOFWINDOWICMPS);
+	} else if (abort) {
+		/*
+		 * Still in SYN_RECV, just remove it silently.
+		 * There is no good way to pass the error to the newly
+		 * created socket, and POSIX does not want network
+		 * errors returned from accept().
+		 */
+		inet_csk_reqsk_queue_drop(req->rsk_listener, req);
+		tcp_listendrop(req->rsk_listener);
+	}
+	reqsk_put(req);
+}
+
 void tcp_splice_eof(struct socket *sock)
 {
 	struct sock *sk = sock->sk;
@@ -5291,6 +6406,182 @@ static void __init tcp_struct_check(void)
 	CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcvq_space);
 }
 
+static void __net_exit tcp_sk_exit(struct net *net)
+{
+	if (net->ipv4.tcp_congestion_control)
+		bpf_module_put(net->ipv4.tcp_congestion_control,
+			       net->ipv4.tcp_congestion_control->owner);
+}
+
+static void __net_init tcp_set_hashinfo(struct net *net)
+{
+	struct inet_hashinfo *hinfo;
+	unsigned int ehash_entries;
+	struct net *old_net;
+
+	if (net_eq(net, &init_net))
+		goto fallback;
+
+	old_net = current->nsproxy->net_ns;
+	ehash_entries = READ_ONCE(old_net->ipv4.sysctl_tcp_child_ehash_entries);
+	if (!ehash_entries)
+		goto fallback;
+
+	ehash_entries = roundup_pow_of_two(ehash_entries);
+	hinfo = inet_pernet_hashinfo_alloc(&tcp_hashinfo, ehash_entries);
+	if (!hinfo) {
+		pr_warn("Failed to allocate TCP ehash (entries: %u) "
+			"for a netns, fallback to the global one\n",
+			ehash_entries);
+fallback:
+		hinfo = &tcp_hashinfo;
+		ehash_entries = tcp_hashinfo.ehash_mask + 1;
+	}
+
+	net->ipv4.tcp_death_row.hashinfo = hinfo;
+	net->ipv4.tcp_death_row.sysctl_max_tw_buckets = ehash_entries / 2;
+	net->ipv4.sysctl_max_syn_backlog = max(128U, ehash_entries / 128);
+}
+
+static int __net_init tcp_sk_init(struct net *net)
+{
+	net->ipv4.sysctl_tcp_ecn = TCP_ECN_IN_ECN_OUT_NOECN;
+	net->ipv4.sysctl_tcp_ecn_option = TCP_ACCECN_OPTION_FULL;
+	net->ipv4.sysctl_tcp_ecn_option_beacon = TCP_ACCECN_OPTION_BEACON;
+	net->ipv4.sysctl_tcp_ecn_fallback = 1;
+
+	net->ipv4.sysctl_tcp_base_mss = TCP_BASE_MSS;
+	net->ipv4.sysctl_tcp_min_snd_mss = TCP_MIN_SND_MSS;
+	net->ipv4.sysctl_tcp_probe_threshold = TCP_PROBE_THRESHOLD;
+	net->ipv4.sysctl_tcp_probe_interval = TCP_PROBE_INTERVAL;
+	net->ipv4.sysctl_tcp_mtu_probe_floor = TCP_MIN_SND_MSS;
+
+	net->ipv4.sysctl_tcp_keepalive_time = TCP_KEEPALIVE_TIME;
+	net->ipv4.sysctl_tcp_keepalive_probes = TCP_KEEPALIVE_PROBES;
+	net->ipv4.sysctl_tcp_keepalive_intvl = TCP_KEEPALIVE_INTVL;
+
+	net->ipv4.sysctl_tcp_syn_retries = TCP_SYN_RETRIES;
+	net->ipv4.sysctl_tcp_synack_retries = TCP_SYNACK_RETRIES;
+	net->ipv4.sysctl_tcp_syncookies = 1;
+	net->ipv4.sysctl_tcp_reordering = TCP_FASTRETRANS_THRESH;
+	net->ipv4.sysctl_tcp_retries1 = TCP_RETR1;
+	net->ipv4.sysctl_tcp_retries2 = TCP_RETR2;
+	net->ipv4.sysctl_tcp_orphan_retries = 0;
+	net->ipv4.sysctl_tcp_fin_timeout = TCP_FIN_TIMEOUT;
+	net->ipv4.sysctl_tcp_notsent_lowat = UINT_MAX;
+	net->ipv4.sysctl_tcp_tw_reuse = 2;
+	net->ipv4.sysctl_tcp_tw_reuse_delay = 1 * MSEC_PER_SEC;
+	net->ipv4.sysctl_tcp_no_ssthresh_metrics_save = 1;
+
+	refcount_set(&net->ipv4.tcp_death_row.tw_refcount, 1);
+	tcp_set_hashinfo(net);
+
+	net->ipv4.sysctl_tcp_sack = 1;
+	net->ipv4.sysctl_tcp_window_scaling = 1;
+	net->ipv4.sysctl_tcp_timestamps = 1;
+	net->ipv4.sysctl_tcp_early_retrans = 3;
+	net->ipv4.sysctl_tcp_recovery = TCP_RACK_LOSS_DETECTION;
+	net->ipv4.sysctl_tcp_slow_start_after_idle = 1; /* By default, RFC2861 behavior.  */
+	net->ipv4.sysctl_tcp_retrans_collapse = 1;
+	net->ipv4.sysctl_tcp_max_reordering = 300;
+	net->ipv4.sysctl_tcp_dsack = 1;
+	net->ipv4.sysctl_tcp_app_win = 31;
+	net->ipv4.sysctl_tcp_adv_win_scale = 1;
+	net->ipv4.sysctl_tcp_frto = 2;
+	net->ipv4.sysctl_tcp_moderate_rcvbuf = 1;
+	net->ipv4.sysctl_tcp_rcvbuf_low_rtt = USEC_PER_MSEC;
+	/* This limits the percentage of the congestion window which we
+	 * will allow a single TSO frame to consume.  Building TSO frames
+	 * which are too large can cause TCP streams to be bursty.
+	 */
+	net->ipv4.sysctl_tcp_tso_win_divisor = 3;
+	/* Default TSQ limit of 4 MB */
+	net->ipv4.sysctl_tcp_limit_output_bytes = 4 << 20;
+
+	/* rfc5961 challenge ack rate limiting, per net-ns, disabled by default. */
+	net->ipv4.sysctl_tcp_challenge_ack_limit = INT_MAX;
+
+	net->ipv4.sysctl_tcp_min_tso_segs = 2;
+	net->ipv4.sysctl_tcp_tso_rtt_log = 9;  /* 2^9 = 512 usec */
+	net->ipv4.sysctl_tcp_min_rtt_wlen = 300;
+	net->ipv4.sysctl_tcp_autocorking = 1;
+	net->ipv4.sysctl_tcp_invalid_ratelimit = HZ / 2;
+	net->ipv4.sysctl_tcp_pacing_ss_ratio = 200;
+	net->ipv4.sysctl_tcp_pacing_ca_ratio = 120;
+	if (net != &init_net) {
+		memcpy(net->ipv4.sysctl_tcp_rmem,
+		       init_net.ipv4.sysctl_tcp_rmem,
+		       sizeof(init_net.ipv4.sysctl_tcp_rmem));
+		memcpy(net->ipv4.sysctl_tcp_wmem,
+		       init_net.ipv4.sysctl_tcp_wmem,
+		       sizeof(init_net.ipv4.sysctl_tcp_wmem));
+	}
+	net->ipv4.sysctl_tcp_comp_sack_delay_ns = NSEC_PER_MSEC;
+	net->ipv4.sysctl_tcp_comp_sack_slack_ns = 10 * NSEC_PER_USEC;
+	net->ipv4.sysctl_tcp_comp_sack_nr = 44;
+	net->ipv4.sysctl_tcp_comp_sack_rtt_percent = 33;
+	net->ipv4.sysctl_tcp_backlog_ack_defer = 1;
+	net->ipv4.sysctl_tcp_fastopen = TFO_CLIENT_ENABLE;
+	net->ipv4.sysctl_tcp_fastopen_blackhole_timeout = 0;
+	atomic_set(&net->ipv4.tfo_active_disable_times, 0);
+
+	/* Set default values for PLB */
+	net->ipv4.sysctl_tcp_plb_enabled = 0; /* Disabled by default */
+	net->ipv4.sysctl_tcp_plb_idle_rehash_rounds = 3;
+	net->ipv4.sysctl_tcp_plb_rehash_rounds = 12;
+	net->ipv4.sysctl_tcp_plb_suspend_rto_sec = 60;
+	/* Default congestion threshold for PLB to mark a round is 50% */
+	net->ipv4.sysctl_tcp_plb_cong_thresh = (1 << TCP_PLB_SCALE) / 2;
+
+	/* Reno is always built in */
+	if (!net_eq(net, &init_net) &&
+	    bpf_try_module_get(init_net.ipv4.tcp_congestion_control,
+			       init_net.ipv4.tcp_congestion_control->owner))
+		net->ipv4.tcp_congestion_control = init_net.ipv4.tcp_congestion_control;
+	else
+		net->ipv4.tcp_congestion_control = &tcp_reno;
+
+	net->ipv4.sysctl_tcp_syn_linear_timeouts = 4;
+	net->ipv4.sysctl_tcp_shrink_window = 0;
+
+	net->ipv4.sysctl_tcp_pingpong_thresh = 1;
+	net->ipv4.sysctl_tcp_rto_min_us = jiffies_to_usecs(TCP_RTO_MIN);
+	net->ipv4.sysctl_tcp_rto_max_ms = TCP_RTO_MAX_SEC * MSEC_PER_SEC;
+
+	return 0;
+}
+
+static void __net_exit tcp_sk_exit_batch(struct list_head *net_exit_list)
+{
+	struct net *net;
+
+	/* make sure concurrent calls to tcp_sk_exit_batch from net_cleanup_work
+	 * and failed setup_net error unwinding path are serialized.
+	 *
+	 * tcp_twsk_purge() handles twsk in any dead netns, not just those in
+	 * net_exit_list, the thread that dismantles a particular twsk must
+	 * do so without other thread progressing to refcount_dec_and_test() of
+	 * tcp_death_row.tw_refcount.
+	 */
+	mutex_lock(&tcp_exit_batch_mutex);
+
+	tcp_twsk_purge(net_exit_list);
+
+	list_for_each_entry(net, net_exit_list, exit_list) {
+		inet_pernet_hashinfo_free(net->ipv4.tcp_death_row.hashinfo);
+		WARN_ON_ONCE(!refcount_dec_and_test(&net->ipv4.tcp_death_row.tw_refcount));
+		tcp_fastopen_ctx_destroy(net);
+	}
+
+	mutex_unlock(&tcp_exit_batch_mutex);
+}
+
+static struct pernet_operations tcp_sk_ops __net_initdata =  {
+	.init		= tcp_sk_init,
+	.exit		= tcp_sk_exit,
+	.exit_batch	= tcp_sk_exit_batch,
+};
+
 void __init tcp_init(void)
 {
 	int max_rshare, max_wshare, cnt;
@@ -5385,7 +6676,14 @@ void __init tcp_init(void)
 	pr_info("Hash tables configured (established %u bind %u)\n",
 		tcp_hashinfo.ehash_mask + 1, tcp_hashinfo.bhash_size);
 
-	tcp_v4_init();
+	if (IS_ENABLED(CONFIG_IPV4))
+		tcp_v4_init();
+	if (register_pernet_subsys(&tcp_sk_ops))
+		panic("Failed to create the TCP control socket.\n");
+
+#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
+	bpf_iter_register();
+#endif
 	tcp_metrics_init();
 	BUG_ON(tcp_register_congestion_control(&tcp_reno) != 0);
 	tcp_tsq_work_init();
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index 2e234d155b5e..23748ec9cb9c 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -671,7 +671,8 @@ static void tcp_bpf_check_v6_needs_rebuild(struct proto *ops)
 
 static int __init tcp_bpf_v4_build_proto(void)
 {
-	tcp_bpf_rebuild_protos(tcp_bpf_prots[TCP_BPF_IPV4], &tcp_prot);
+	if (IS_ENABLED(CONFIG_IPV4))
+		tcp_bpf_rebuild_protos(tcp_bpf_prots[TCP_BPF_IPV4], &tcp_prot);
 	return 0;
 }
 late_initcall(tcp_bpf_v4_build_proto);
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index c107b7e44e5b..366ed4df49d6 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -85,7 +85,6 @@
 #include <linux/seq_file.h>
 #include <linux/inetdevice.h>
 #include <linux/btf_ids.h>
-#include <linux/skbuff_ref.h>
 
 #include <crypto/md5.h>
 #include <crypto/utils.h>
@@ -97,14 +96,10 @@ static void tcp_v4_md5_hash_hdr(char *md5_hash, const struct tcp_md5sig_key *key
 				__be32 daddr, __be32 saddr, const struct tcphdr *th);
 #endif
 
-struct inet_hashinfo tcp_hashinfo;
-
 static DEFINE_PER_CPU(struct sock_bh_locked, ipv4_tcp_sk) = {
 	.bh_lock = INIT_LOCAL_LOCK(bh_lock),
 };
 
-static DEFINE_MUTEX(tcp_exit_batch_mutex);
-
 INDIRECT_CALLABLE_SCOPE union tcp_seq_and_ts_off
 tcp_v4_init_seq_and_ts_off(const struct net *net, const struct sk_buff *skb)
 {
@@ -115,93 +110,6 @@ tcp_v4_init_seq_and_ts_off(const struct net *net, const struct sk_buff *skb)
 					 tcp_hdr(skb)->source);
 }
 
-int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
-{
-	int reuse = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_tw_reuse);
-	const struct inet_timewait_sock *tw = inet_twsk(sktw);
-	const struct tcp_timewait_sock *tcptw = tcp_twsk(sktw);
-	struct tcp_sock *tp = tcp_sk(sk);
-	int ts_recent_stamp;
-	u32 reuse_thresh;
-
-	if (READ_ONCE(tw->tw_substate) == TCP_FIN_WAIT2)
-		reuse = 0;
-
-	if (reuse == 2) {
-		/* Still does not detect *everything* that goes through
-		 * lo, since we require a loopback src or dst address
-		 * or direct binding to 'lo' interface.
-		 */
-		bool loopback = false;
-		if (tw->tw_bound_dev_if == LOOPBACK_IFINDEX)
-			loopback = true;
-#if IS_ENABLED(CONFIG_IPV6)
-		if (tw->tw_family == AF_INET6) {
-			if (ipv6_addr_loopback(&tw->tw_v6_daddr) ||
-			    ipv6_addr_v4mapped_loopback(&tw->tw_v6_daddr) ||
-			    ipv6_addr_loopback(&tw->tw_v6_rcv_saddr) ||
-			    ipv6_addr_v4mapped_loopback(&tw->tw_v6_rcv_saddr))
-				loopback = true;
-		} else
-#endif
-		{
-			if (ipv4_is_loopback(tw->tw_daddr) ||
-			    ipv4_is_loopback(tw->tw_rcv_saddr))
-				loopback = true;
-		}
-		if (!loopback)
-			reuse = 0;
-	}
-
-	/* With PAWS, it is safe from the viewpoint
-	   of data integrity. Even without PAWS it is safe provided sequence
-	   spaces do not overlap i.e. at data rates <= 80Mbit/sec.
-
-	   Actually, the idea is close to VJ's one, only timestamp cache is
-	   held not per host, but per port pair and TW bucket is used as state
-	   holder.
-
-	   If TW bucket has been already destroyed we fall back to VJ's scheme
-	   and use initial timestamp retrieved from peer table.
-	 */
-	ts_recent_stamp = READ_ONCE(tcptw->tw_ts_recent_stamp);
-	reuse_thresh = READ_ONCE(tw->tw_entry_stamp) +
-		       READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_tw_reuse_delay);
-	if (ts_recent_stamp &&
-	    (!twp || (reuse && time_after32(tcp_clock_ms(), reuse_thresh)))) {
-		/* inet_twsk_hashdance_schedule() sets sk_refcnt after putting twsk
-		 * and releasing the bucket lock.
-		 */
-		if (unlikely(!refcount_inc_not_zero(&sktw->sk_refcnt)))
-			return 0;
-
-		/* In case of repair and re-using TIME-WAIT sockets we still
-		 * want to be sure that it is safe as above but honor the
-		 * sequence numbers and time stamps set as part of the repair
-		 * process.
-		 *
-		 * Without this check re-using a TIME-WAIT socket with TCP
-		 * repair would accumulate a -1 on the repair assigned
-		 * sequence number. The first time it is reused the sequence
-		 * is -1, the second time -2, etc. This fixes that issue
-		 * without appearing to create any others.
-		 */
-		if (likely(!tp->repair)) {
-			u32 seq = tcptw->tw_snd_nxt + 65535 + 2;
-
-			if (!seq)
-				seq = 1;
-			WRITE_ONCE(tp->write_seq, seq);
-			tp->rx_opt.ts_recent	   = READ_ONCE(tcptw->tw_ts_recent);
-			tp->rx_opt.ts_recent_stamp = ts_recent_stamp;
-		}
-
-		return 1;
-	}
-
-	return 0;
-}
-
 static int tcp_v4_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 			      int addr_len)
 {
@@ -409,69 +317,6 @@ static void do_redirect(struct sk_buff *skb, struct sock *sk)
 		dst->ops->redirect(dst, sk, skb);
 }
 
-
-/* handle ICMP messages on TCP_NEW_SYN_RECV request sockets */
-void tcp_req_err(struct sock *sk, u32 seq, bool abort)
-{
-	struct request_sock *req = inet_reqsk(sk);
-	struct net *net = sock_net(sk);
-
-	/* ICMPs are not backlogged, hence we cannot get
-	 * an established socket here.
-	 */
-	if (seq != tcp_rsk(req)->snt_isn) {
-		__NET_INC_STATS(net, LINUX_MIB_OUTOFWINDOWICMPS);
-	} else if (abort) {
-		/*
-		 * Still in SYN_RECV, just remove it silently.
-		 * There is no good way to pass the error to the newly
-		 * created socket, and POSIX does not want network
-		 * errors returned from accept().
-		 */
-		inet_csk_reqsk_queue_drop(req->rsk_listener, req);
-		tcp_listendrop(req->rsk_listener);
-	}
-	reqsk_put(req);
-}
-
-/* TCP-LD (RFC 6069) logic */
-void tcp_ld_RTO_revert(struct sock *sk, u32 seq)
-{
-	struct inet_connection_sock *icsk = inet_csk(sk);
-	struct tcp_sock *tp = tcp_sk(sk);
-	struct sk_buff *skb;
-	s32 remaining;
-	u32 delta_us;
-
-	if (sock_owned_by_user(sk))
-		return;
-
-	if (seq != tp->snd_una  || !icsk->icsk_retransmits ||
-	    !icsk->icsk_backoff)
-		return;
-
-	skb = tcp_rtx_queue_head(sk);
-	if (WARN_ON_ONCE(!skb))
-		return;
-
-	icsk->icsk_backoff--;
-	icsk->icsk_rto = tp->srtt_us ? __tcp_set_rto(tp) : TCP_TIMEOUT_INIT;
-	icsk->icsk_rto = inet_csk_rto_backoff(icsk, tcp_rto_max(sk));
-
-	tcp_mstamp_refresh(tp);
-	delta_us = (u32)(tp->tcp_mstamp - tcp_skb_timestamp_us(skb));
-	remaining = icsk->icsk_rto - usecs_to_jiffies(delta_us);
-
-	if (remaining > 0) {
-		tcp_reset_xmit_timer(sk, ICSK_TIME_RETRANS, remaining, false);
-	} else {
-		/* RTO revert clocked out retransmission.
-		 * Will retransmit now.
-		 */
-		tcp_retransmit_timer(sk);
-	}
-}
-
 /*
  * This routine is called by the ICMP module when it gets some
  * sort of error condition.  If err < 0 then the socket should
@@ -1898,144 +1743,6 @@ int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
 	goto discard;
 }
 
-enum skb_drop_reason tcp_add_backlog(struct sock *sk, struct sk_buff *skb)
-{
-	u32 tail_gso_size, tail_gso_segs;
-	struct skb_shared_info *shinfo;
-	const struct tcphdr *th;
-	struct tcphdr *thtail;
-	struct sk_buff *tail;
-	unsigned int hdrlen;
-	bool fragstolen;
-	u32 gso_segs;
-	u32 gso_size;
-	u64 limit;
-	int delta;
-	int err;
-
-	/* In case all data was pulled from skb frags (in __pskb_pull_tail()),
-	 * we can fix skb->truesize to its real value to avoid future drops.
-	 * This is valid because skb is not yet charged to the socket.
-	 * It has been noticed pure SACK packets were sometimes dropped
-	 * (if cooked by drivers without copybreak feature).
-	 */
-	skb_condense(skb);
-
-	tcp_cleanup_skb(skb);
-
-	if (unlikely(tcp_checksum_complete(skb))) {
-		bh_unlock_sock(sk);
-		trace_tcp_bad_csum(skb);
-		__TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS);
-		__TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
-		return SKB_DROP_REASON_TCP_CSUM;
-	}
-
-	/* Attempt coalescing to last skb in backlog, even if we are
-	 * above the limits.
-	 * This is okay because skb capacity is limited to MAX_SKB_FRAGS.
-	 */
-	th = (const struct tcphdr *)skb->data;
-	hdrlen = th->doff * 4;
-
-	tail = sk->sk_backlog.tail;
-	if (!tail)
-		goto no_coalesce;
-	thtail = (struct tcphdr *)tail->data;
-
-	if (TCP_SKB_CB(tail)->end_seq != TCP_SKB_CB(skb)->seq ||
-	    TCP_SKB_CB(tail)->ip_dsfield != TCP_SKB_CB(skb)->ip_dsfield ||
-	    ((TCP_SKB_CB(tail)->tcp_flags |
-	      TCP_SKB_CB(skb)->tcp_flags) & (TCPHDR_SYN | TCPHDR_RST | TCPHDR_URG)) ||
-	    !((TCP_SKB_CB(tail)->tcp_flags &
-	      TCP_SKB_CB(skb)->tcp_flags) & TCPHDR_ACK) ||
-	    ((TCP_SKB_CB(tail)->tcp_flags ^
-	      TCP_SKB_CB(skb)->tcp_flags) &
-	     (TCPHDR_ECE | TCPHDR_CWR | TCPHDR_AE)) ||
-	    !tcp_skb_can_collapse_rx(tail, skb) ||
-	    thtail->doff != th->doff ||
-	    memcmp(thtail + 1, th + 1, hdrlen - sizeof(*th)) ||
-	    /* prior to PSP Rx policy check, retain exact PSP metadata */
-	    psp_skb_coalesce_diff(tail, skb))
-		goto no_coalesce;
-
-	__skb_pull(skb, hdrlen);
-
-	shinfo = skb_shinfo(skb);
-	gso_size = shinfo->gso_size ?: skb->len;
-	gso_segs = shinfo->gso_segs ?: 1;
-
-	shinfo = skb_shinfo(tail);
-	tail_gso_size = shinfo->gso_size ?: (tail->len - hdrlen);
-	tail_gso_segs = shinfo->gso_segs ?: 1;
-
-	if (skb_try_coalesce(tail, skb, &fragstolen, &delta)) {
-		TCP_SKB_CB(tail)->end_seq = TCP_SKB_CB(skb)->end_seq;
-
-		if (likely(!before(TCP_SKB_CB(skb)->ack_seq, TCP_SKB_CB(tail)->ack_seq))) {
-			TCP_SKB_CB(tail)->ack_seq = TCP_SKB_CB(skb)->ack_seq;
-			thtail->window = th->window;
-		}
-
-		/* We have to update both TCP_SKB_CB(tail)->tcp_flags and
-		 * thtail->fin, so that the fast path in tcp_rcv_established()
-		 * is not entered if we append a packet with a FIN.
-		 * SYN, RST, URG are not present.
-		 * ACK is set on both packets.
-		 * PSH : we do not really care in TCP stack,
-		 *       at least for 'GRO' packets.
-		 */
-		thtail->fin |= th->fin;
-		TCP_SKB_CB(tail)->tcp_flags |= TCP_SKB_CB(skb)->tcp_flags;
-
-		if (TCP_SKB_CB(skb)->has_rxtstamp) {
-			TCP_SKB_CB(tail)->has_rxtstamp = true;
-			tail->tstamp = skb->tstamp;
-			skb_hwtstamps(tail)->hwtstamp = skb_hwtstamps(skb)->hwtstamp;
-		}
-
-		/* Not as strict as GRO. We only need to carry mss max value */
-		shinfo->gso_size = max(gso_size, tail_gso_size);
-		shinfo->gso_segs = min_t(u32, gso_segs + tail_gso_segs, 0xFFFF);
-
-		sk->sk_backlog.len += delta;
-		__NET_INC_STATS(sock_net(sk),
-				LINUX_MIB_TCPBACKLOGCOALESCE);
-		kfree_skb_partial(skb, fragstolen);
-		return SKB_NOT_DROPPED_YET;
-	}
-	__skb_push(skb, hdrlen);
-
-no_coalesce:
-	/* sk->sk_backlog.len is reset only at the end of __release_sock().
-	 * Both sk->sk_backlog.len and sk->sk_rmem_alloc could reach
-	 * sk_rcvbuf in normal conditions.
-	 */
-	limit = ((u64)READ_ONCE(sk->sk_rcvbuf)) << 1;
-
-	limit += ((u32)READ_ONCE(sk->sk_sndbuf)) >> 1;
-
-	/* Only socket owner can try to collapse/prune rx queues
-	 * to reduce memory overhead, so add a little headroom here.
-	 * Few sockets backlog are possibly concurrently non empty.
-	 */
-	limit += 64 * 1024;
-
-	limit = min_t(u64, limit, UINT_MAX);
-
-	err = sk_add_backlog(sk, skb, limit);
-	if (unlikely(err)) {
-		bh_unlock_sock(sk);
-		if (err == -ENOMEM) {
-			__NET_INC_STATS(sock_net(sk), LINUX_MIB_PFMEMALLOCDROP);
-			return SKB_DROP_REASON_PFMEMALLOC;
-		}
-		__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPBACKLOGDROP);
-		return SKB_DROP_REASON_SOCKET_BACKLOG;
-	}
-	return SKB_NOT_DROPPED_YET;
-}
-
 static void tcp_v4_restore_cb(struct sk_buff *skb)
 {
 	memmove(IPCB(skb), &TCP_SKB_CB(skb)->header.h4,
@@ -2409,338 +2116,9 @@ static int tcp_v4_init_sock(struct sock *sk)
 	return 0;
 }
 
-static void tcp_release_user_frags(struct sock *sk)
-{
-#ifdef CONFIG_PAGE_POOL
-	unsigned long index;
-	void *netmem;
-
-	xa_for_each(&sk->sk_user_frags, index, netmem)
-		WARN_ON_ONCE(!napi_pp_put_page((__force netmem_ref)netmem));
-#endif
-}
-
-void tcp_v4_destroy_sock(struct sock *sk)
-{
-	struct tcp_sock *tp = tcp_sk(sk);
-
-	tcp_release_user_frags(sk);
-
-	xa_destroy(&sk->sk_user_frags);
-
-	trace_tcp_destroy_sock(sk);
-
-	tcp_clear_xmit_timers(sk);
-
-	tcp_cleanup_congestion_control(sk);
-
-	tcp_cleanup_ulp(sk);
-
-	/* Cleanup up the write buffer. */
-	tcp_write_queue_purge(sk);
-
-	/* Check if we want to disable active TFO */
-	tcp_fastopen_active_disable_ofo_check(sk);
-
-	/* Cleans up our, hopefully empty, out_of_order_queue. */
-	skb_rbtree_purge(&tp->out_of_order_queue);
-
-	/* Clean up a referenced TCP bind bucket. */
-	if (inet_csk(sk)->icsk_bind_hash)
-		inet_put_port(sk);
-
-	BUG_ON(rcu_access_pointer(tp->fastopen_rsk));
-
-	/* If socket is aborted during connect operation */
-	tcp_free_fastopen_req(tp);
-	tcp_fastopen_destroy_cipher(sk);
-	tcp_saved_syn_free(tp);
-
-	sk_sockets_allocated_dec(sk);
-}
-
 #ifdef CONFIG_PROC_FS
 /* Proc filesystem TCP sock list dumping. */
 
-static unsigned short seq_file_family(const struct seq_file *seq);
-
-static bool seq_sk_match(struct seq_file *seq, const struct sock *sk)
-{
-	unsigned short family = seq_file_family(seq);
-
-	/* AF_UNSPEC is used as a match all */
-	return ((family == AF_UNSPEC || family == sk->sk_family) &&
-		net_eq(sock_net(sk), seq_file_net(seq)));
-}
-
-/* Find a non empty bucket (starting from st->bucket)
- * and return the first sk from it.
- */
-static void *listening_get_first(struct seq_file *seq)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct tcp_iter_state *st = seq->private;
-
-	st->offset = 0;
-	for (; st->bucket <= hinfo->lhash2_mask; st->bucket++) {
-		struct inet_listen_hashbucket *ilb2;
-		struct hlist_nulls_node *node;
-		struct sock *sk;
-
-		ilb2 = &hinfo->lhash2[st->bucket];
-		if (hlist_nulls_empty(&ilb2->nulls_head))
-			continue;
-
-		spin_lock(&ilb2->lock);
-		sk_nulls_for_each(sk, node, &ilb2->nulls_head) {
-			if (seq_sk_match(seq, sk))
-				return sk;
-		}
-		spin_unlock(&ilb2->lock);
-	}
-
-	return NULL;
-}
-
-/* Find the next sk of "cur" within the same bucket (i.e. st->bucket).
- * If "cur" is the last one in the st->bucket,
- * call listening_get_first() to return the first sk of the next
- * non empty bucket.
- */
-static void *listening_get_next(struct seq_file *seq, void *cur)
-{
-	struct tcp_iter_state *st = seq->private;
-	struct inet_listen_hashbucket *ilb2;
-	struct hlist_nulls_node *node;
-	struct inet_hashinfo *hinfo;
-	struct sock *sk = cur;
-
-	++st->num;
-	++st->offset;
-
-	sk = sk_nulls_next(sk);
-	sk_nulls_for_each_from(sk, node) {
-		if (seq_sk_match(seq, sk))
-			return sk;
-	}
-
-	hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	ilb2 = &hinfo->lhash2[st->bucket];
-	spin_unlock(&ilb2->lock);
-	++st->bucket;
-	return listening_get_first(seq);
-}
-
-static void *listening_get_idx(struct seq_file *seq, loff_t *pos)
-{
-	struct tcp_iter_state *st = seq->private;
-	void *rc;
-
-	st->bucket = 0;
-	st->offset = 0;
-	rc = listening_get_first(seq);
-
-	while (rc && *pos) {
-		rc = listening_get_next(seq, rc);
-		--*pos;
-	}
-	return rc;
-}
-
-static inline bool empty_bucket(struct inet_hashinfo *hinfo,
-				const struct tcp_iter_state *st)
-{
-	return hlist_nulls_empty(&hinfo->ehash[st->bucket].chain);
-}
-
-/*
- * Get first established socket starting from bucket given in st->bucket.
- * If st->bucket is zero, the very first socket in the hash is returned.
- */
-static void *established_get_first(struct seq_file *seq)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct tcp_iter_state *st = seq->private;
-
-	st->offset = 0;
-	for (; st->bucket <= hinfo->ehash_mask; ++st->bucket) {
-		struct sock *sk;
-		struct hlist_nulls_node *node;
-		spinlock_t *lock = inet_ehash_lockp(hinfo, st->bucket);
-
-		cond_resched();
-
-		/* Lockless fast path for the common case of empty buckets */
-		if (empty_bucket(hinfo, st))
-			continue;
-
-		spin_lock_bh(lock);
-		sk_nulls_for_each(sk, node, &hinfo->ehash[st->bucket].chain) {
-			if (seq_sk_match(seq, sk))
-				return sk;
-		}
-		spin_unlock_bh(lock);
-	}
-
-	return NULL;
-}
-
-static void *established_get_next(struct seq_file *seq, void *cur)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct tcp_iter_state *st = seq->private;
-	struct hlist_nulls_node *node;
-	struct sock *sk = cur;
-
-	++st->num;
-	++st->offset;
-
-	sk = sk_nulls_next(sk);
-
-	sk_nulls_for_each_from(sk, node) {
-		if (seq_sk_match(seq, sk))
-			return sk;
-	}
-
-	spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
-	++st->bucket;
-	return established_get_first(seq);
-}
-
-static void *established_get_idx(struct seq_file *seq, loff_t pos)
-{
-	struct tcp_iter_state *st = seq->private;
-	void *rc;
-
-	st->bucket = 0;
-	rc = established_get_first(seq);
-
-	while (rc && pos) {
-		rc = established_get_next(seq, rc);
-		--pos;
-	}
-	return rc;
-}
-
-static void *tcp_get_idx(struct seq_file *seq, loff_t pos)
-{
-	void *rc;
-	struct tcp_iter_state *st = seq->private;
-
-	st->state = TCP_SEQ_STATE_LISTENING;
-	rc	  = listening_get_idx(seq, &pos);
-
-	if (!rc) {
-		st->state = TCP_SEQ_STATE_ESTABLISHED;
-		rc	  = established_get_idx(seq, pos);
-	}
-
-	return rc;
-}
-
-static void *tcp_seek_last_pos(struct seq_file *seq)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct tcp_iter_state *st = seq->private;
-	int bucket = st->bucket;
-	int offset = st->offset;
-	int orig_num = st->num;
-	void *rc = NULL;
-
-	switch (st->state) {
-	case TCP_SEQ_STATE_LISTENING:
-		if (st->bucket > hinfo->lhash2_mask)
-			break;
-		rc = listening_get_first(seq);
-		while (offset-- && rc && bucket == st->bucket)
-			rc = listening_get_next(seq, rc);
-		if (rc)
-			break;
-		st->bucket = 0;
-		st->state = TCP_SEQ_STATE_ESTABLISHED;
-		fallthrough;
-	case TCP_SEQ_STATE_ESTABLISHED:
-		if (st->bucket > hinfo->ehash_mask)
-			break;
-		rc = established_get_first(seq);
-		while (offset-- && rc && bucket == st->bucket)
-			rc = established_get_next(seq, rc);
-	}
-
-	st->num = orig_num;
-
-	return rc;
-}
-
-void *tcp_seq_start(struct seq_file *seq, loff_t *pos)
-{
-	struct tcp_iter_state *st = seq->private;
-	void *rc;
-
-	if (*pos && *pos == st->last_pos) {
-		rc = tcp_seek_last_pos(seq);
-		if (rc)
-			goto out;
-	}
-
-	st->state = TCP_SEQ_STATE_LISTENING;
-	st->num = 0;
-	st->bucket = 0;
-	st->offset = 0;
-	rc = *pos ? tcp_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
-
-out:
-	st->last_pos = *pos;
-	return rc;
-}
-
-void *tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
-{
-	struct tcp_iter_state *st = seq->private;
-	void *rc = NULL;
-
-	if (v == SEQ_START_TOKEN) {
-		rc = tcp_get_idx(seq, 0);
-		goto out;
-	}
-
-	switch (st->state) {
-	case TCP_SEQ_STATE_LISTENING:
-		rc = listening_get_next(seq, v);
-		if (!rc) {
-			st->state = TCP_SEQ_STATE_ESTABLISHED;
-			st->bucket = 0;
-			st->offset = 0;
-			rc	  = established_get_first(seq);
-		}
-		break;
-	case TCP_SEQ_STATE_ESTABLISHED:
-		rc = established_get_next(seq, v);
-		break;
-	}
-out:
-	++*pos;
-	st->last_pos = *pos;
-	return rc;
-}
-
-void tcp_seq_stop(struct seq_file *seq, void *v)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct tcp_iter_state *st = seq->private;
-
-	switch (st->state) {
-	case TCP_SEQ_STATE_LISTENING:
-		if (v != SEQ_START_TOKEN)
-			spin_unlock(&hinfo->lhash2[st->bucket].lock);
-		break;
-	case TCP_SEQ_STATE_ESTABLISHED:
-		if (v)
-			spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
-		break;
-	}
-}
-
 static void get_openreq4(const struct request_sock *req,
 			 struct seq_file *f, int i)
 {
@@ -2876,434 +2254,6 @@ static int tcp4_seq_show(struct seq_file *seq, void *v)
 	return 0;
 }
 
-#ifdef CONFIG_BPF_SYSCALL
-union bpf_tcp_iter_batch_item {
-	struct sock *sk;
-	__u64 cookie;
-};
-
-struct bpf_tcp_iter_state {
-	struct tcp_iter_state state;
-	unsigned int cur_sk;
-	unsigned int end_sk;
-	unsigned int max_sk;
-	union bpf_tcp_iter_batch_item *batch;
-};
-
-struct bpf_iter__tcp {
-	__bpf_md_ptr(struct bpf_iter_meta *, meta);
-	__bpf_md_ptr(struct sock_common *, sk_common);
-	uid_t uid __aligned(8);
-};
-
-static int tcp_prog_seq_show(struct bpf_prog *prog, struct bpf_iter_meta *meta,
-			     struct sock_common *sk_common, uid_t uid)
-{
-	struct bpf_iter__tcp ctx;
-
-	meta->seq_num--;  /* skip SEQ_START_TOKEN */
-	ctx.meta = meta;
-	ctx.sk_common = sk_common;
-	ctx.uid = uid;
-	return bpf_iter_run_prog(prog, &ctx);
-}
-
-static void bpf_iter_tcp_put_batch(struct bpf_tcp_iter_state *iter)
-{
-	union bpf_tcp_iter_batch_item *item;
-	unsigned int cur_sk = iter->cur_sk;
-	__u64 cookie;
-
-	/* Remember the cookies of the sockets we haven't seen yet, so we can
-	 * pick up where we left off next time around.
-	 */
-	while (cur_sk < iter->end_sk) {
-		item = &iter->batch[cur_sk++];
-		cookie = sock_gen_cookie(item->sk);
-		sock_gen_put(item->sk);
-		item->cookie = cookie;
-	}
-}
-
-static int bpf_iter_tcp_realloc_batch(struct bpf_tcp_iter_state *iter,
-				      unsigned int new_batch_sz, gfp_t flags)
-{
-	union bpf_tcp_iter_batch_item *new_batch;
-
-	new_batch = kvmalloc_objs(*new_batch, new_batch_sz,
-				  flags | __GFP_NOWARN);
-	if (!new_batch)
-		return -ENOMEM;
-
-	memcpy(new_batch, iter->batch, sizeof(*iter->batch) * iter->end_sk);
-	kvfree(iter->batch);
-	iter->batch = new_batch;
-	iter->max_sk = new_batch_sz;
-
-	return 0;
-}
-
-static struct sock *bpf_iter_tcp_resume_bucket(struct sock *first_sk,
-					       union bpf_tcp_iter_batch_item *cookies,
-					       int n_cookies)
-{
-	struct hlist_nulls_node *node;
-	struct sock *sk;
-	int i;
-
-	for (i = 0; i < n_cookies; i++) {
-		sk = first_sk;
-		sk_nulls_for_each_from(sk, node)
-			if (cookies[i].cookie == atomic64_read(&sk->sk_cookie))
-				return sk;
-	}
-
-	return NULL;
-}
-
-static struct sock *bpf_iter_tcp_resume_listening(struct seq_file *seq)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct tcp_iter_state *st = &iter->state;
-	unsigned int find_cookie = iter->cur_sk;
-	unsigned int end_cookie = iter->end_sk;
-	int resume_bucket = st->bucket;
-	struct sock *sk;
-
-	if (end_cookie && find_cookie == end_cookie)
-		++st->bucket;
-
-	sk = listening_get_first(seq);
-	iter->cur_sk = 0;
-	iter->end_sk = 0;
-
-	if (sk && st->bucket == resume_bucket && end_cookie) {
-		sk = bpf_iter_tcp_resume_bucket(sk, &iter->batch[find_cookie],
-						end_cookie - find_cookie);
-		if (!sk) {
-			spin_unlock(&hinfo->lhash2[st->bucket].lock);
-			++st->bucket;
-			sk = listening_get_first(seq);
-		}
-	}
-
-	return sk;
-}
-
-static struct sock *bpf_iter_tcp_resume_established(struct seq_file *seq)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct tcp_iter_state *st = &iter->state;
-	unsigned int find_cookie = iter->cur_sk;
-	unsigned int end_cookie = iter->end_sk;
-	int resume_bucket = st->bucket;
-	struct sock *sk;
-
-	if (end_cookie && find_cookie == end_cookie)
-		++st->bucket;
-
-	sk = established_get_first(seq);
-	iter->cur_sk = 0;
-	iter->end_sk = 0;
-
-	if (sk && st->bucket == resume_bucket && end_cookie) {
-		sk = bpf_iter_tcp_resume_bucket(sk, &iter->batch[find_cookie],
-						end_cookie - find_cookie);
-		if (!sk) {
-			spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
-			++st->bucket;
-			sk = established_get_first(seq);
-		}
-	}
-
-	return sk;
-}
-
-static struct sock *bpf_iter_tcp_resume(struct seq_file *seq)
-{
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct tcp_iter_state *st = &iter->state;
-	struct sock *sk = NULL;
-
-	switch (st->state) {
-	case TCP_SEQ_STATE_LISTENING:
-		sk = bpf_iter_tcp_resume_listening(seq);
-		if (sk)
-			break;
-		st->bucket = 0;
-		st->state = TCP_SEQ_STATE_ESTABLISHED;
-		fallthrough;
-	case TCP_SEQ_STATE_ESTABLISHED:
-		sk = bpf_iter_tcp_resume_established(seq);
-		break;
-	}
-
-	return sk;
-}
-
-static unsigned int bpf_iter_tcp_listening_batch(struct seq_file *seq,
-						 struct sock **start_sk)
-{
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct hlist_nulls_node *node;
-	unsigned int expected = 1;
-	struct sock *sk;
-
-	sock_hold(*start_sk);
-	iter->batch[iter->end_sk++].sk = *start_sk;
-
-	sk = sk_nulls_next(*start_sk);
-	*start_sk = NULL;
-	sk_nulls_for_each_from(sk, node) {
-		if (seq_sk_match(seq, sk)) {
-			if (iter->end_sk < iter->max_sk) {
-				sock_hold(sk);
-				iter->batch[iter->end_sk++].sk = sk;
-			} else if (!*start_sk) {
-				/* Remember where we left off. */
-				*start_sk = sk;
-			}
-			expected++;
-		}
-	}
-
-	return expected;
-}
-
-static unsigned int bpf_iter_tcp_established_batch(struct seq_file *seq,
-						   struct sock **start_sk)
-{
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct hlist_nulls_node *node;
-	struct sock *sk = *start_sk;
-	unsigned int expected = 0;
-
-	*start_sk = NULL;
-	sk_nulls_for_each_from(sk, node) {
-		if (!seq_sk_match(seq, sk))
-			continue;
-		expected++;
-		if (iter->end_sk < iter->max_sk) {
-			/* reqsk_queue_hash_req() inserts with sk_refcnt == 0
-			 * and refcount_set()s it after the bucket lock drops.
-			 */
-			if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
-				continue;
-			iter->batch[iter->end_sk++].sk = sk;
-		} else if (!*start_sk) {
-			/* Remember where we left off. */
-			*start_sk = sk;
-		}
-	}
-
-	return expected;
-}
-
-static unsigned int bpf_iter_fill_batch(struct seq_file *seq,
-					struct sock **start_sk)
-{
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct tcp_iter_state *st = &iter->state;
-
-	if (st->state == TCP_SEQ_STATE_LISTENING)
-		return bpf_iter_tcp_listening_batch(seq, start_sk);
-	else
-		return bpf_iter_tcp_established_batch(seq, start_sk);
-}
-
-static void bpf_iter_tcp_unlock_bucket(struct seq_file *seq)
-{
-	struct inet_hashinfo *hinfo = seq_file_net(seq)->ipv4.tcp_death_row.hashinfo;
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct tcp_iter_state *st = &iter->state;
-
-	if (st->state == TCP_SEQ_STATE_LISTENING)
-		spin_unlock(&hinfo->lhash2[st->bucket].lock);
-	else
-		spin_unlock_bh(inet_ehash_lockp(hinfo, st->bucket));
-}
-
-static struct sock *bpf_iter_tcp_batch(struct seq_file *seq)
-{
-	struct bpf_tcp_iter_state *iter = seq->private;
-	unsigned int expected;
-	struct sock *sk;
-	int err;
-
-again:
-	sk = bpf_iter_tcp_resume(seq);
-	if (!sk)
-		return NULL; /* Done */
-
-	expected = bpf_iter_fill_batch(seq, &sk);
-	if (likely(!sk))
-		goto done;
-
-	/* Batch size was too small. */
-	bpf_iter_tcp_unlock_bucket(seq);
-	bpf_iter_tcp_put_batch(iter);
-	err = bpf_iter_tcp_realloc_batch(iter, expected * 3 / 2,
-					 GFP_USER);
-	if (err) {
-		iter->cur_sk = 0;
-		iter->end_sk = 0;
-		return ERR_PTR(err);
-	}
-
-	sk = bpf_iter_tcp_resume(seq);
-	if (!sk)
-		return NULL; /* Done */
-
-	expected = bpf_iter_fill_batch(seq, &sk);
-	if (likely(!sk))
-		goto done;
-
-	/* Batch size was still too small. Hold onto the lock while we try
-	 * again with a larger batch to make sure the current bucket's size
-	 * does not change in the meantime.
-	 */
-	err = bpf_iter_tcp_realloc_batch(iter, expected, GFP_NOWAIT);
-	if (err) {
-		bpf_iter_tcp_unlock_bucket(seq);
-		return ERR_PTR(err);
-	}
-
-	bpf_iter_fill_batch(seq, &sk);
-	WARN_ON_ONCE(sk);
-done:
-	bpf_iter_tcp_unlock_bucket(seq);
-	if (unlikely(!iter->end_sk)) {
-		++iter->state.bucket;
-		goto again;
-	}
-	return iter->batch[0].sk;
-}
-
-static void *bpf_iter_tcp_seq_start(struct seq_file *seq, loff_t *pos)
-{
-	/* bpf iter does not support lseek, so it always
-	 * continue from where it was stop()-ped.
-	 */
-	if (*pos)
-		return bpf_iter_tcp_batch(seq);
-
-	return SEQ_START_TOKEN;
-}
-
-static void *bpf_iter_tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
-{
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct tcp_iter_state *st = &iter->state;
-	struct sock *sk;
-
-	/* Whenever seq_next() is called, the iter->cur_sk is
-	 * done with seq_show(), so advance to the next sk in
-	 * the batch.
-	 */
-	if (iter->cur_sk < iter->end_sk) {
-		/* Keeping st->num consistent in tcp_iter_state.
-		 * bpf_iter_tcp does not use st->num.
-		 * meta.seq_num is used instead.
-		 */
-		st->num++;
-		sock_gen_put(iter->batch[iter->cur_sk++].sk);
-	}
-
-	if (iter->cur_sk < iter->end_sk)
-		sk = iter->batch[iter->cur_sk].sk;
-	else
-		sk = bpf_iter_tcp_batch(seq);
-
-	++*pos;
-	/* Keeping st->last_pos consistent in tcp_iter_state.
-	 * bpf iter does not do lseek, so st->last_pos always equals to *pos.
-	 */
-	st->last_pos = *pos;
-	return sk;
-}
-
-static int bpf_iter_tcp_seq_show(struct seq_file *seq, void *v)
-{
-	struct bpf_iter_meta meta;
-	struct bpf_prog *prog;
-	struct sock *sk = v;
-	uid_t uid;
-	int ret;
-
-	if (v == SEQ_START_TOKEN)
-		return 0;
-
-	if (sk_fullsock(sk))
-		lock_sock(sk);
-
-	if (unlikely(sk_unhashed(sk))) {
-		ret = SEQ_SKIP;
-		goto unlock;
-	}
-
-	if (sk->sk_state == TCP_TIME_WAIT) {
-		uid = 0;
-	} else if (sk->sk_state == TCP_NEW_SYN_RECV) {
-		const struct request_sock *req = v;
-
-		uid = from_kuid_munged(seq_user_ns(seq),
-				       sk_uid(req->rsk_listener));
-	} else {
-		uid = from_kuid_munged(seq_user_ns(seq), sk_uid(sk));
-	}
-
-	meta.seq = seq;
-	prog = bpf_iter_get_info(&meta, false);
-	ret = tcp_prog_seq_show(prog, &meta, v, uid);
-
-unlock:
-	if (sk_fullsock(sk))
-		release_sock(sk);
-	return ret;
-
-}
-
-static void bpf_iter_tcp_seq_stop(struct seq_file *seq, void *v)
-{
-	struct bpf_tcp_iter_state *iter = seq->private;
-	struct bpf_iter_meta meta;
-	struct bpf_prog *prog;
-
-	if (!v) {
-		meta.seq = seq;
-		prog = bpf_iter_get_info(&meta, true);
-		if (prog)
-			(void)tcp_prog_seq_show(prog, &meta, v, 0);
-	}
-
-	if (iter->cur_sk < iter->end_sk)
-		bpf_iter_tcp_put_batch(iter);
-}
-
-static const struct seq_operations bpf_iter_tcp_seq_ops = {
-	.show		= bpf_iter_tcp_seq_show,
-	.start		= bpf_iter_tcp_seq_start,
-	.next		= bpf_iter_tcp_seq_next,
-	.stop		= bpf_iter_tcp_seq_stop,
-};
-#endif
-static unsigned short seq_file_family(const struct seq_file *seq)
-{
-	const struct tcp_seq_afinfo *afinfo;
-
-#ifdef CONFIG_BPF_SYSCALL
-	/* Iterated from bpf_iter.  Let the bpf prog to filter instead. */
-	if (seq->op == &bpf_iter_tcp_seq_ops)
-		return AF_UNSPEC;
-#endif
-
-	/* Iterated from proc fs */
-	afinfo = pde_data(file_inode(seq->file));
-	return afinfo->family;
-}
-
 static const struct seq_operations tcp4_seq_ops = {
 	.show		= tcp4_seq_show,
 	.start		= tcp_seq_start,
@@ -3354,7 +2304,7 @@ struct proto tcp_prot = {
 	.accept			= inet_csk_accept,
 	.ioctl			= tcp_ioctl,
 	.init			= tcp_v4_init_sock,
-	.destroy		= tcp_v4_destroy_sock,
+	.destroy		= tcp_destroy_sock,
 	.shutdown		= tcp_shutdown,
 	.setsockopt		= tcp_setsockopt,
 	.getsockopt		= tcp_getsockopt,
@@ -3397,255 +2347,6 @@ struct proto tcp_prot = {
 };
 EXPORT_SYMBOL(tcp_prot);
 
-static void __net_exit tcp_sk_exit(struct net *net)
-{
-	if (net->ipv4.tcp_congestion_control)
-		bpf_module_put(net->ipv4.tcp_congestion_control,
-			       net->ipv4.tcp_congestion_control->owner);
-}
-
-static void __net_init tcp_set_hashinfo(struct net *net)
-{
-	struct inet_hashinfo *hinfo;
-	unsigned int ehash_entries;
-	struct net *old_net;
-
-	if (net_eq(net, &init_net))
-		goto fallback;
-
-	old_net = current->nsproxy->net_ns;
-	ehash_entries = READ_ONCE(old_net->ipv4.sysctl_tcp_child_ehash_entries);
-	if (!ehash_entries)
-		goto fallback;
-
-	ehash_entries = roundup_pow_of_two(ehash_entries);
-	hinfo = inet_pernet_hashinfo_alloc(&tcp_hashinfo, ehash_entries);
-	if (!hinfo) {
-		pr_warn("Failed to allocate TCP ehash (entries: %u) "
-			"for a netns, fallback to the global one\n",
-			ehash_entries);
-fallback:
-		hinfo = &tcp_hashinfo;
-		ehash_entries = tcp_hashinfo.ehash_mask + 1;
-	}
-
-	net->ipv4.tcp_death_row.hashinfo = hinfo;
-	net->ipv4.tcp_death_row.sysctl_max_tw_buckets = ehash_entries / 2;
-	net->ipv4.sysctl_max_syn_backlog = max(128U, ehash_entries / 128);
-}
-
-static int __net_init tcp_sk_init(struct net *net)
-{
-	net->ipv4.sysctl_tcp_ecn = TCP_ECN_IN_ECN_OUT_NOECN;
-	net->ipv4.sysctl_tcp_ecn_option = TCP_ACCECN_OPTION_FULL;
-	net->ipv4.sysctl_tcp_ecn_option_beacon = TCP_ACCECN_OPTION_BEACON;
-	net->ipv4.sysctl_tcp_ecn_fallback = 1;
-
-	net->ipv4.sysctl_tcp_base_mss = TCP_BASE_MSS;
-	net->ipv4.sysctl_tcp_min_snd_mss = TCP_MIN_SND_MSS;
-	net->ipv4.sysctl_tcp_probe_threshold = TCP_PROBE_THRESHOLD;
-	net->ipv4.sysctl_tcp_probe_interval = TCP_PROBE_INTERVAL;
-	net->ipv4.sysctl_tcp_mtu_probe_floor = TCP_MIN_SND_MSS;
-
-	net->ipv4.sysctl_tcp_keepalive_time = TCP_KEEPALIVE_TIME;
-	net->ipv4.sysctl_tcp_keepalive_probes = TCP_KEEPALIVE_PROBES;
-	net->ipv4.sysctl_tcp_keepalive_intvl = TCP_KEEPALIVE_INTVL;
-
-	net->ipv4.sysctl_tcp_syn_retries = TCP_SYN_RETRIES;
-	net->ipv4.sysctl_tcp_synack_retries = TCP_SYNACK_RETRIES;
-	net->ipv4.sysctl_tcp_syncookies = 1;
-	net->ipv4.sysctl_tcp_reordering = TCP_FASTRETRANS_THRESH;
-	net->ipv4.sysctl_tcp_retries1 = TCP_RETR1;
-	net->ipv4.sysctl_tcp_retries2 = TCP_RETR2;
-	net->ipv4.sysctl_tcp_orphan_retries = 0;
-	net->ipv4.sysctl_tcp_fin_timeout = TCP_FIN_TIMEOUT;
-	net->ipv4.sysctl_tcp_notsent_lowat = UINT_MAX;
-	net->ipv4.sysctl_tcp_tw_reuse = 2;
-	net->ipv4.sysctl_tcp_tw_reuse_delay = 1 * MSEC_PER_SEC;
-	net->ipv4.sysctl_tcp_no_ssthresh_metrics_save = 1;
-
-	refcount_set(&net->ipv4.tcp_death_row.tw_refcount, 1);
-	tcp_set_hashinfo(net);
-
-	net->ipv4.sysctl_tcp_sack = 1;
-	net->ipv4.sysctl_tcp_window_scaling = 1;
-	net->ipv4.sysctl_tcp_timestamps = 1;
-	net->ipv4.sysctl_tcp_early_retrans = 3;
-	net->ipv4.sysctl_tcp_recovery = TCP_RACK_LOSS_DETECTION;
-	net->ipv4.sysctl_tcp_slow_start_after_idle = 1; /* By default, RFC2861 behavior.  */
-	net->ipv4.sysctl_tcp_retrans_collapse = 1;
-	net->ipv4.sysctl_tcp_max_reordering = 300;
-	net->ipv4.sysctl_tcp_dsack = 1;
-	net->ipv4.sysctl_tcp_app_win = 31;
-	net->ipv4.sysctl_tcp_adv_win_scale = 1;
-	net->ipv4.sysctl_tcp_frto = 2;
-	net->ipv4.sysctl_tcp_moderate_rcvbuf = 1;
-	net->ipv4.sysctl_tcp_rcvbuf_low_rtt = USEC_PER_MSEC;
-	/* This limits the percentage of the congestion window which we
-	 * will allow a single TSO frame to consume.  Building TSO frames
-	 * which are too large can cause TCP streams to be bursty.
-	 */
-	net->ipv4.sysctl_tcp_tso_win_divisor = 3;
-	/* Default TSQ limit of 4 MB */
-	net->ipv4.sysctl_tcp_limit_output_bytes = 4 << 20;
-
-	/* rfc5961 challenge ack rate limiting, per net-ns, disabled by default. */
-	net->ipv4.sysctl_tcp_challenge_ack_limit = INT_MAX;
-
-	net->ipv4.sysctl_tcp_min_tso_segs = 2;
-	net->ipv4.sysctl_tcp_tso_rtt_log = 9;  /* 2^9 = 512 usec */
-	net->ipv4.sysctl_tcp_min_rtt_wlen = 300;
-	net->ipv4.sysctl_tcp_autocorking = 1;
-	net->ipv4.sysctl_tcp_invalid_ratelimit = HZ/2;
-	net->ipv4.sysctl_tcp_pacing_ss_ratio = 200;
-	net->ipv4.sysctl_tcp_pacing_ca_ratio = 120;
-	if (net != &init_net) {
-		memcpy(net->ipv4.sysctl_tcp_rmem,
-		       init_net.ipv4.sysctl_tcp_rmem,
-		       sizeof(init_net.ipv4.sysctl_tcp_rmem));
-		memcpy(net->ipv4.sysctl_tcp_wmem,
-		       init_net.ipv4.sysctl_tcp_wmem,
-		       sizeof(init_net.ipv4.sysctl_tcp_wmem));
-	}
-	net->ipv4.sysctl_tcp_comp_sack_delay_ns = NSEC_PER_MSEC;
-	net->ipv4.sysctl_tcp_comp_sack_slack_ns = 10 * NSEC_PER_USEC;
-	net->ipv4.sysctl_tcp_comp_sack_nr = 44;
-	net->ipv4.sysctl_tcp_comp_sack_rtt_percent = 33;
-	net->ipv4.sysctl_tcp_backlog_ack_defer = 1;
-	net->ipv4.sysctl_tcp_fastopen = TFO_CLIENT_ENABLE;
-	net->ipv4.sysctl_tcp_fastopen_blackhole_timeout = 0;
-	atomic_set(&net->ipv4.tfo_active_disable_times, 0);
-
-	/* Set default values for PLB */
-	net->ipv4.sysctl_tcp_plb_enabled = 0; /* Disabled by default */
-	net->ipv4.sysctl_tcp_plb_idle_rehash_rounds = 3;
-	net->ipv4.sysctl_tcp_plb_rehash_rounds = 12;
-	net->ipv4.sysctl_tcp_plb_suspend_rto_sec = 60;
-	/* Default congestion threshold for PLB to mark a round is 50% */
-	net->ipv4.sysctl_tcp_plb_cong_thresh = (1 << TCP_PLB_SCALE) / 2;
-
-	/* Reno is always built in */
-	if (!net_eq(net, &init_net) &&
-	    bpf_try_module_get(init_net.ipv4.tcp_congestion_control,
-			       init_net.ipv4.tcp_congestion_control->owner))
-		net->ipv4.tcp_congestion_control = init_net.ipv4.tcp_congestion_control;
-	else
-		net->ipv4.tcp_congestion_control = &tcp_reno;
-
-	net->ipv4.sysctl_tcp_syn_linear_timeouts = 4;
-	net->ipv4.sysctl_tcp_shrink_window = 0;
-
-	net->ipv4.sysctl_tcp_pingpong_thresh = 1;
-	net->ipv4.sysctl_tcp_rto_min_us = jiffies_to_usecs(TCP_RTO_MIN);
-	net->ipv4.sysctl_tcp_rto_max_ms = TCP_RTO_MAX_SEC * MSEC_PER_SEC;
-
-	return 0;
-}
-
-static void __net_exit tcp_sk_exit_batch(struct list_head *net_exit_list)
-{
-	struct net *net;
-
-	/* make sure concurrent calls to tcp_sk_exit_batch from net_cleanup_work
-	 * and failed setup_net error unwinding path are serialized.
-	 *
-	 * tcp_twsk_purge() handles twsk in any dead netns, not just those in
-	 * net_exit_list, the thread that dismantles a particular twsk must
-	 * do so without other thread progressing to refcount_dec_and_test() of
-	 * tcp_death_row.tw_refcount.
-	 */
-	mutex_lock(&tcp_exit_batch_mutex);
-
-	tcp_twsk_purge(net_exit_list);
-
-	list_for_each_entry(net, net_exit_list, exit_list) {
-		inet_pernet_hashinfo_free(net->ipv4.tcp_death_row.hashinfo);
-		WARN_ON_ONCE(!refcount_dec_and_test(&net->ipv4.tcp_death_row.tw_refcount));
-		tcp_fastopen_ctx_destroy(net);
-	}
-
-	mutex_unlock(&tcp_exit_batch_mutex);
-}
-
-static struct pernet_operations __net_initdata tcp_sk_ops = {
-       .init	   = tcp_sk_init,
-       .exit	   = tcp_sk_exit,
-       .exit_batch = tcp_sk_exit_batch,
-};
-
-#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
-DEFINE_BPF_ITER_FUNC(tcp, struct bpf_iter_meta *meta,
-		     struct sock_common *sk_common, uid_t uid)
-
-#define INIT_BATCH_SZ 16
-
-static int bpf_iter_init_tcp(void *priv_data, struct bpf_iter_aux_info *aux)
-{
-	struct bpf_tcp_iter_state *iter = priv_data;
-	int err;
-
-	err = bpf_iter_init_seq_net(priv_data, aux);
-	if (err)
-		return err;
-
-	err = bpf_iter_tcp_realloc_batch(iter, INIT_BATCH_SZ, GFP_USER);
-	if (err) {
-		bpf_iter_fini_seq_net(priv_data);
-		return err;
-	}
-
-	return 0;
-}
-
-static void bpf_iter_fini_tcp(void *priv_data)
-{
-	struct bpf_tcp_iter_state *iter = priv_data;
-
-	bpf_iter_fini_seq_net(priv_data);
-	kvfree(iter->batch);
-}
-
-static const struct bpf_iter_seq_info tcp_seq_info = {
-	.seq_ops		= &bpf_iter_tcp_seq_ops,
-	.init_seq_private	= bpf_iter_init_tcp,
-	.fini_seq_private	= bpf_iter_fini_tcp,
-	.seq_priv_size		= sizeof(struct bpf_tcp_iter_state),
-};
-
-static const struct bpf_func_proto *
-bpf_iter_tcp_get_func_proto(enum bpf_func_id func_id,
-			    const struct bpf_prog *prog)
-{
-	switch (func_id) {
-	case BPF_FUNC_setsockopt:
-		return &bpf_sk_setsockopt_proto;
-	case BPF_FUNC_getsockopt:
-		return &bpf_sk_getsockopt_proto;
-	default:
-		return NULL;
-	}
-}
-
-static struct bpf_iter_reg tcp_reg_info = {
-	.target			= "tcp",
-	.ctx_arg_info_size	= 1,
-	.ctx_arg_info		= {
-		{ offsetof(struct bpf_iter__tcp, sk_common),
-		  PTR_TO_BTF_ID_OR_NULL | PTR_TRUSTED },
-	},
-	.get_func_proto		= bpf_iter_tcp_get_func_proto,
-	.seq_info		= &tcp_seq_info,
-};
-
-static void __init bpf_iter_register(void)
-{
-	tcp_reg_info.ctx_arg_info[0].btf_id = btf_sock_ids[BTF_SOCK_TYPE_SOCK_COMMON];
-	if (bpf_iter_reg_target(&tcp_reg_info))
-		pr_warn("Warning: could not register bpf iterator tcp\n");
-}
-
-#endif
-
 void __init tcp_v4_init(void)
 {
 	int cpu, res;
@@ -3668,10 +2369,4 @@ void __init tcp_v4_init(void)
 
 		per_cpu(ipv4_tcp_sk.sock, cpu) = sk;
 	}
-	if (register_pernet_subsys(&tcp_sk_ops))
-		panic("Failed to create the TCP control socket.\n");
-
-#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PROC_FS)
-	bpf_iter_register();
-#endif
 }
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index afb4d8e9fa67..07a4222e1712 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -220,7 +220,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 		u32 exthdrlen = icsk->icsk_ext_hdr_len;
 		struct sockaddr_in sin;
 
-		if (ipv6_only_sock(sk))
+		if (!IS_ENABLED(CONFIG_IPV4) || ipv6_only_sock(sk))
 			return -ENETUNREACH;
 
 		sin.sin_family = AF_INET;
@@ -1315,8 +1315,11 @@ u16 tcp_v6_get_syncookie(struct sock *sk, struct ipv6hdr *iph,
 
 static int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb)
 {
-	if (skb->protocol == htons(ETH_P_IP))
+	if (skb->protocol == htons(ETH_P_IP)) {
+		if (!IS_ENABLED(CONFIG_IPV4))
+			goto drop;
 		return tcp_v4_conn_request(sk, skb);
+	}
 
 	if (!ipv6_unicast_destination(skb))
 		goto drop;
@@ -1401,10 +1404,14 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *
 #endif
 	struct flowi6 fl6;
 
-	if (skb->protocol == htons(ETH_P_IP))
+	if (skb->protocol == htons(ETH_P_IP)) {
+		if (!IS_ENABLED(CONFIG_IPV4))
+			return NULL;
 		return tcp_v4_syn_recv_sock(sk, skb, req, dst,
 					    req_unhash, own_req,
 					    tcp_v6_mapped_child_init);
+	}
+
 	ireq = inet_rsk(req);
 
 	if (sk_acceptq_is_full(sk))
@@ -1579,8 +1586,12 @@ int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
 	   tcp_v6_hnd_req and tcp_v6_send_reset().   --ANK
 	 */
 
-	if (skb->protocol == htons(ETH_P_IP))
-		return tcp_v4_do_rcv(sk, skb);
+	if (skb->protocol == htons(ETH_P_IP)) {
+		if (IS_ENABLED(CONFIG_IPV4))
+			return tcp_v4_do_rcv(sk, skb);
+		kfree_skb(skb);
+		return 0;
+	}
 
 	reason = psp_sk_rx_policy_check(sk, skb);
 	if (reason)
@@ -2284,7 +2295,7 @@ struct proto tcpv6_prot = {
 	.accept			= inet_csk_accept,
 	.ioctl			= tcp_ioctl,
 	.init			= tcp_v6_init_sock,
-	.destroy		= tcp_v4_destroy_sock,
+	.destroy		= tcp_destroy_sock,
 	.shutdown		= tcp_shutdown,
 	.setsockopt		= tcp_setsockopt,
 	.getsockopt		= tcp_getsockopt,
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 05/16 net-next v3] ipv4: raw: split IPv4 specific logic into raw_ipv4.c
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (3 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 04/16 net-next v3] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 06/16 net-next v3] ipv4: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, Julian Anastasov, Kuniyuki Iwashima,
	Florian Westphal, Eric Biggers, Willem de Bruijn, David Ahern,
	Runyu Xiao, Michael Bommarito, Jeff Layton, Breno Leitao,
	Gustavo A. R. Silva, linux-kernel

To enable compiling the INET subsystem without IPv4, RAW socket IPv4
specific functions must be isolated from the generic RAW socket
infrastructure, similar to how it is done for TCP.

This patch creates raw_ipv4.c and move all the functions for packet
matching, input/output processing, ICMP filtering and the AF_INET
raw_prot definition from raw.c to the new file. The Makefile is updated
to compile raw_ipv4.c only when CONFIG_IPV4 is enabled.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/ipv4/Makefile   |   2 +-
 net/ipv4/raw.c      | 853 ------------------------------------------
 net/ipv4/raw_diag.c |   3 +-
 net/ipv4/raw_ipv4.c | 884 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 887 insertions(+), 855 deletions(-)
 create mode 100644 net/ipv4/raw_ipv4.c

diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 8b148be79fa4..7ac882ec5d4f 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -13,7 +13,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 icmp.o arp.o devinet.o \
 		      igmp.o fib_notifier.o ip_output.o fib_frontend.o \
-		      fib_semantics.o fib_trie.o
+		      fib_semantics.o fib_trie.o raw_ipv4.o
 
 obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
 obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
index 9d01006d8189..dcd554d92082 100644
--- a/net/ipv4/raw.c
+++ b/net/ipv4/raw.c
@@ -38,7 +38,6 @@
 #include <asm/byteorder.h>
 #include <asm/current.h>
 #include <linux/uaccess.h>
-#include <asm/ioctls.h>
 #include <linux/stddef.h>
 #include <linux/slab.h>
 #include <linux/errno.h>
@@ -48,31 +47,25 @@
 #include <linux/sockios.h>
 #include <linux/socket.h>
 #include <linux/in.h>
-#include <linux/mroute.h>
 #include <linux/netdevice.h>
 #include <linux/in_route.h>
 #include <linux/route.h>
 #include <linux/skbuff.h>
-#include <linux/igmp.h>
 #include <net/net_namespace.h>
 #include <net/dst.h>
 #include <net/sock.h>
 #include <linux/ip.h>
 #include <linux/net.h>
 #include <net/ip.h>
-#include <net/icmp.h>
 #include <net/udp.h>
 #include <net/raw.h>
 #include <net/snmp.h>
 #include <net/tcp_states.h>
 #include <net/inet_common.h>
 #include <net/checksum.h>
-#include <net/xfrm.h>
 #include <linux/rtnetlink.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
-#include <linux/netfilter.h>
-#include <linux/netfilter_ipv4.h>
 #include <linux/compat.h>
 #include <linux/uio.h>
 
@@ -114,824 +107,6 @@ void raw_unhash_sk(struct sock *sk)
 	spin_unlock(&h->lock);
 }
 
-bool raw_v4_match(struct net *net, const struct sock *sk, unsigned short num,
-		  __be32 raddr, __be32 laddr, int dif, int sdif)
-{
-	const struct inet_sock *inet = inet_sk(sk);
-	__be32 daddr, rcv_saddr;
-
-	if (!net_eq(sock_net(sk), net) || inet->inet_num != num)
-		return false;
-
-	daddr = READ_ONCE(inet->inet_daddr);
-	if (daddr && daddr != raddr)
-		return false;
-
-	rcv_saddr = READ_ONCE(inet->inet_rcv_saddr);
-	if (rcv_saddr && rcv_saddr != laddr)
-		return false;
-
-	return raw_sk_bound_dev_eq(net, READ_ONCE(sk->sk_bound_dev_if),
-				   dif, sdif);
-}
-EXPORT_SYMBOL_GPL(raw_v4_match);
-
-/*
- *	0 - deliver
- *	1 - block
- */
-static int icmp_filter(const struct sock *sk, const struct sk_buff *skb)
-{
-	struct icmphdr _hdr;
-	const struct icmphdr *hdr;
-
-	hdr = skb_header_pointer(skb, skb_transport_offset(skb),
-				 sizeof(_hdr), &_hdr);
-	if (!hdr)
-		return 1;
-
-	if (hdr->type < 32) {
-		__u32 data = raw_sk(sk)->filter.data;
-
-		return ((1U << hdr->type) & data) != 0;
-	}
-
-	/* Do not block unknown ICMP types */
-	return 0;
-}
-
-/* IP input processing comes here for RAW socket delivery.
- * Caller owns SKB, so we must make clones.
- *
- * RFC 1122: SHOULD pass TOS value up to the transport layer.
- * -> It does. And not only TOS, but all IP header.
- */
-static int raw_v4_input(struct net *net, struct sk_buff *skb,
-			const struct iphdr *iph, int hash)
-{
-	int sdif = inet_sdif(skb);
-	struct hlist_head *hlist;
-	int dif = inet_iif(skb);
-	int delivered = 0;
-	struct sock *sk;
-
-	hlist = &raw_v4_hashinfo.ht[hash];
-	rcu_read_lock();
-	sk_for_each_rcu(sk, hlist) {
-		if (!raw_v4_match(net, sk, iph->protocol,
-				  iph->saddr, iph->daddr, dif, sdif))
-			continue;
-
-		if (atomic_read(&sk->sk_rmem_alloc) >=
-		    READ_ONCE(sk->sk_rcvbuf)) {
-			sk_drops_inc(sk);
-			continue;
-		}
-
-		delivered = 1;
-		if ((iph->protocol != IPPROTO_ICMP || !icmp_filter(sk, skb)) &&
-		    ip_mc_sf_allow(sk, iph->daddr, iph->saddr,
-				   skb->dev->ifindex, sdif)) {
-			struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);
-
-			/* Not releasing hash table! */
-			if (clone)
-				raw_rcv(sk, clone);
-		}
-	}
-	rcu_read_unlock();
-	return delivered;
-}
-
-int raw_local_deliver(struct sk_buff *skb, int protocol)
-{
-	struct net *net = dev_net(skb->dev);
-
-	return raw_v4_input(net, skb, ip_hdr(skb),
-			    raw_hashfunc(net, protocol));
-}
-
-static void raw_err(struct sock *sk, struct sk_buff *skb, u32 info)
-{
-	struct inet_sock *inet = inet_sk(sk);
-	const int type = icmp_hdr(skb)->type;
-	const int code = icmp_hdr(skb)->code;
-	int harderr = 0;
-	bool recverr;
-	int err = 0;
-
-	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
-		ipv4_sk_update_pmtu(skb, sk, info);
-	else if (type == ICMP_REDIRECT) {
-		ipv4_sk_redirect(skb, sk);
-		return;
-	}
-
-	/* Report error on raw socket, if:
-	   1. User requested ip_recverr.
-	   2. Socket is connected (otherwise the error indication
-	      is useless without ip_recverr and error is hard.
-	 */
-	recverr = inet_test_bit(RECVERR, sk);
-	if (!recverr && sk->sk_state != TCP_ESTABLISHED)
-		return;
-
-	switch (type) {
-	default:
-	case ICMP_TIME_EXCEEDED:
-		err = EHOSTUNREACH;
-		break;
-	case ICMP_SOURCE_QUENCH:
-		return;
-	case ICMP_PARAMETERPROB:
-		err = EPROTO;
-		harderr = 1;
-		break;
-	case ICMP_DEST_UNREACH:
-		err = EHOSTUNREACH;
-		if (code > NR_ICMP_UNREACH)
-			break;
-		if (code == ICMP_FRAG_NEEDED) {
-			harderr = READ_ONCE(inet->pmtudisc) != IP_PMTUDISC_DONT;
-			err = EMSGSIZE;
-		} else {
-			err = icmp_err_convert[code].errno;
-			harderr = icmp_err_convert[code].fatal;
-		}
-	}
-
-	if (recverr) {
-		const struct iphdr *iph = (const struct iphdr *)skb->data;
-		u8 *payload = skb->data + (iph->ihl << 2);
-
-		if (inet_test_bit(HDRINCL, sk))
-			payload = skb->data;
-		ip_icmp_error(sk, skb, err, 0, info, payload);
-	}
-
-	if (recverr || harderr) {
-		sk->sk_err = err;
-		sk_error_report(sk);
-	}
-}
-
-void raw_icmp_error(struct sk_buff *skb, int protocol, u32 info)
-{
-	struct net *net = dev_net(skb->dev);
-	int dif = skb->dev->ifindex;
-	int sdif = inet_sdif(skb);
-	struct hlist_head *hlist;
-	const struct iphdr *iph;
-	struct sock *sk;
-	int hash;
-
-	hash = raw_hashfunc(net, protocol);
-	hlist = &raw_v4_hashinfo.ht[hash];
-
-	rcu_read_lock();
-	sk_for_each_rcu(sk, hlist) {
-		iph = (const struct iphdr *)skb->data;
-		if (!raw_v4_match(net, sk, iph->protocol,
-				  iph->daddr, iph->saddr, dif, sdif))
-			continue;
-		raw_err(sk, skb, info);
-	}
-	rcu_read_unlock();
-}
-
-static int raw_rcv_skb(struct sock *sk, struct sk_buff *skb)
-{
-	enum skb_drop_reason reason;
-
-	/* Charge it to the socket. */
-
-	ipv4_pktinfo_prepare(sk, skb, true);
-	reason = sock_queue_rcv_skb_reason(sk, skb);
-	if (reason) {
-		sk_skb_reason_drop(sk, skb, reason);
-		return NET_RX_DROP;
-	}
-
-	return NET_RX_SUCCESS;
-}
-
-int raw_rcv(struct sock *sk, struct sk_buff *skb)
-{
-	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
-		sk_drops_inc(sk);
-		sk_skb_reason_drop(sk, skb, SKB_DROP_REASON_XFRM_POLICY);
-		return NET_RX_DROP;
-	}
-	nf_reset_ct(skb);
-
-	skb_push(skb, -skb_network_offset(skb));
-
-	raw_rcv_skb(sk, skb);
-	return 0;
-}
-
-static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,
-			   struct msghdr *msg, size_t length,
-			   struct rtable **rtp, unsigned int flags,
-			   const struct sockcm_cookie *sockc)
-{
-	struct inet_sock *inet = inet_sk(sk);
-	struct net *net = sock_net(sk);
-	struct iphdr *iph;
-	struct sk_buff *skb;
-	unsigned int iphlen;
-	int err;
-	struct rtable *rt = *rtp;
-	int hlen, tlen;
-
-	if (length > rt->dst.dev->mtu) {
-		ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
-			       rt->dst.dev->mtu);
-		return -EMSGSIZE;
-	}
-	if (length < sizeof(struct iphdr))
-		return -EINVAL;
-
-	if (flags&MSG_PROBE)
-		goto out;
-
-	hlen = LL_RESERVED_SPACE(rt->dst.dev);
-	tlen = rt->dst.dev->needed_tailroom;
-	skb = sock_alloc_send_skb(sk,
-				  length + hlen + tlen + 15,
-				  flags & MSG_DONTWAIT, &err);
-	if (!skb)
-		goto error;
-	skb_reserve(skb, hlen);
-
-	skb->protocol = htons(ETH_P_IP);
-	skb->priority = sockc->priority;
-	skb->mark = sockc->mark;
-	skb_set_delivery_type_by_clockid(skb, sockc->transmit_time, sk->sk_clockid);
-	skb_dst_set(skb, &rt->dst);
-	*rtp = NULL;
-
-	skb_reset_network_header(skb);
-	iph = ip_hdr(skb);
-	skb_put(skb, length);
-
-	skb->ip_summed = CHECKSUM_NONE;
-
-	skb_setup_tx_timestamp(skb, sockc);
-
-	if (flags & MSG_CONFIRM)
-		skb_set_dst_pending_confirm(skb, 1);
-
-	skb->transport_header = skb->network_header;
-	err = -EFAULT;
-	if (memcpy_from_msg(iph, msg, length))
-		goto error_free;
-
-	iphlen = iph->ihl * 4;
-
-	/*
-	 * We don't want to modify the ip header, but we do need to
-	 * be sure that it won't cause problems later along the network
-	 * stack.  Specifically we want to make sure that iph->ihl is a
-	 * sane value.  If ihl points beyond the length of the buffer passed
-	 * in, reject the frame as invalid
-	 */
-	err = -EINVAL;
-	if (iphlen > length || iphlen < sizeof(*iph))
-		goto error_free;
-
-	if (iphlen >= sizeof(*iph)) {
-		if (!iph->saddr)
-			iph->saddr = fl4->saddr;
-		iph->check   = 0;
-		iph->tot_len = htons(length);
-		if (!iph->id)
-			ip_select_ident(net, skb, NULL);
-
-		iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
-		skb->transport_header += iphlen;
-		if (iph->protocol == IPPROTO_ICMP &&
-		    length >= iphlen + sizeof(struct icmphdr))
-			icmp_out_count(net, ((struct icmphdr *)
-				skb_transport_header(skb))->type);
-	}
-
-	err = NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
-		      net, sk, skb, NULL, rt->dst.dev,
-		      dst_output);
-	if (err > 0)
-		err = net_xmit_errno(err);
-	if (err)
-		goto error;
-out:
-	return 0;
-
-error_free:
-	kfree_skb(skb);
-error:
-	IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
-	if (err == -ENOBUFS && !inet_test_bit(RECVERR, sk))
-		err = 0;
-	return err;
-}
-
-static int raw_probe_proto_opt(struct raw_frag_vec *rfv, struct flowi4 *fl4)
-{
-	int err;
-
-	if (fl4->flowi4_proto != IPPROTO_ICMP)
-		return 0;
-
-	/* We only need the first two bytes. */
-	rfv->hlen = 2;
-
-	err = memcpy_from_msg(rfv->hdr.c, rfv->msg, rfv->hlen);
-	if (err)
-		return err;
-
-	fl4->fl4_icmp_type = rfv->hdr.icmph.type;
-	fl4->fl4_icmp_code = rfv->hdr.icmph.code;
-
-	return 0;
-}
-
-static int raw_getfrag(void *from, char *to, int offset, int len, int odd,
-		       struct sk_buff *skb)
-{
-	struct raw_frag_vec *rfv = from;
-
-	if (offset < rfv->hlen) {
-		int copy = min(rfv->hlen - offset, len);
-
-		if (skb->ip_summed == CHECKSUM_PARTIAL)
-			memcpy(to, rfv->hdr.c + offset, copy);
-		else
-			skb->csum = csum_block_add(
-				skb->csum,
-				csum_partial_copy_nocheck(rfv->hdr.c + offset,
-							  to, copy),
-				odd);
-
-		odd = 0;
-		offset += copy;
-		to += copy;
-		len -= copy;
-
-		if (!len)
-			return 0;
-	}
-
-	offset -= rfv->hlen;
-
-	return ip_generic_getfrag(rfv->msg, to, offset, len, odd, skb);
-}
-
-static int raw_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 inet_sock *inet = inet_sk(sk);
-	struct net *net = sock_net(sk);
-	struct ipcm_cookie ipc;
-	struct rtable *rt = NULL;
-	struct flowi4 fl4;
-	u8 scope;
-	int free = 0;
-	__be32 daddr;
-	__be32 saddr;
-	int uc_index, err;
-	struct raw_frag_vec rfv;
-	int hdrincl;
-
-	err = -EMSGSIZE;
-	if (len > 0xFFFF)
-		goto out;
-
-	hdrincl = inet_test_bit(HDRINCL, sk);
-
-	/*
-	 *	Check the flags.
-	 */
-
-	err = -EOPNOTSUPP;
-	if (msg->msg_flags & MSG_OOB)	/* Mirror BSD error message */
-		goto out;               /* compatibility */
-
-	/*
-	 *	Get and verify the address.
-	 */
-
-	if (msg->msg_namelen) {
-		DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
-		err = -EINVAL;
-		if (msg->msg_namelen < sizeof(*usin))
-			goto out;
-		if (usin->sin_family != AF_INET) {
-			pr_info_once("%s: %s forgot to set AF_INET. Fix it!\n",
-				     __func__, current->comm);
-			err = -EAFNOSUPPORT;
-			if (usin->sin_family)
-				goto out;
-		}
-		daddr = usin->sin_addr.s_addr;
-		/* ANK: I did not forget to get protocol from port field.
-		 * I just do not know, who uses this weirdness.
-		 * IP_HDRINCL is much more convenient.
-		 */
-	} else {
-		err = -EDESTADDRREQ;
-		if (sk->sk_state != TCP_ESTABLISHED)
-			goto out;
-		daddr = inet->inet_daddr;
-	}
-
-	ipcm_init_sk(&ipc, inet);
-	/* Keep backward compat */
-	if (hdrincl)
-		ipc.protocol = IPPROTO_RAW;
-
-	if (msg->msg_controllen) {
-		err = ip_cmsg_send(sk, msg, &ipc, false);
-		if (unlikely(err)) {
-			kfree(ipc.opt);
-			goto out;
-		}
-		if (ipc.opt)
-			free = 1;
-	}
-
-	saddr = ipc.addr;
-	ipc.addr = daddr;
-
-	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();
-	}
-
-	if (ipc.opt) {
-		err = -EINVAL;
-		/* Linux does not mangle headers on raw sockets,
-		 * so that IP options + IP_HDRINCL is non-sense.
-		 */
-		if (hdrincl)
-			goto done;
-		if (ipc.opt->opt.srr) {
-			if (!daddr)
-				goto done;
-			daddr = ipc.opt->opt.faddr;
-		}
-	}
-	scope = ip_sendmsg_scope(inet, &ipc, msg);
-
-	uc_index = READ_ONCE(inet->uc_index);
-	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 = uc_index;
-	} else if (ipv4_is_lbcast(daddr) && uc_index) {
-		/* oif is set, packet is to local broadcast
-		 * and uc_index is set. oif is most likely set
-		 * by sk_bound_dev_if. If uc_index != oif check if the
-		 * oif is an L3 master and uc_index is an L3 slave.
-		 * If so, we want to allow the send using the uc_index.
-		 */
-		if (ipc.oif != uc_index &&
-		    ipc.oif == l3mdev_master_ifindex_by_index(sock_net(sk),
-							      uc_index)) {
-			ipc.oif = uc_index;
-		}
-	}
-
-	flowi4_init_output(&fl4, ipc.oif, ipc.sockc.mark,
-			   ipc.tos & INET_DSCP_MASK, scope,
-			   hdrincl ? ipc.protocol : sk->sk_protocol,
-			   inet_sk_flowi_flags(sk) |
-			    (hdrincl ? FLOWI_FLAG_KNOWN_NH : 0),
-			   daddr, saddr, 0, 0, sk_uid(sk));
-
-	fl4.fl4_icmp_type = 0;
-	fl4.fl4_icmp_code = 0;
-
-	if (!hdrincl) {
-		rfv.msg = msg;
-		rfv.hlen = 0;
-
-		err = raw_probe_proto_opt(&rfv, &fl4);
-		if (err)
-			goto done;
-	}
-
-	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;
-		goto done;
-	}
-
-	err = -EACCES;
-	if (rt->rt_flags & RTCF_BROADCAST && !sock_flag(sk, SOCK_BROADCAST))
-		goto done;
-
-	if (msg->msg_flags & MSG_CONFIRM)
-		goto do_confirm;
-back_from_confirm:
-
-	if (hdrincl)
-		err = raw_send_hdrinc(sk, &fl4, msg, len,
-				      &rt, msg->msg_flags, &ipc.sockc);
-
-	 else {
-		if (!ipc.addr)
-			ipc.addr = fl4.daddr;
-		lock_sock(sk);
-		err = ip_append_data(sk, &fl4, raw_getfrag,
-				     &rfv, len, 0,
-				     &ipc, &rt, msg->msg_flags);
-		if (err)
-			ip_flush_pending_frames(sk);
-		else if (!(msg->msg_flags & MSG_MORE)) {
-			err = ip_push_pending_frames(sk, &fl4);
-			if (err == -ENOBUFS && !inet_test_bit(RECVERR, sk))
-				err = 0;
-		}
-		release_sock(sk);
-	}
-done:
-	if (free)
-		kfree(ipc.opt);
-	ip_rt_put(rt);
-
-out:
-	if (err < 0)
-		return err;
-	return len;
-
-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 done;
-}
-
-static void raw_close(struct sock *sk, long timeout)
-{
-	/*
-	 * Raw sockets may have direct kernel references. Kill them.
-	 */
-	ip_ra_control(sk, 0, NULL);
-
-	sk_common_release(sk);
-}
-
-static void raw_destroy(struct sock *sk)
-{
-	lock_sock(sk);
-	ip_flush_pending_frames(sk);
-	release_sock(sk);
-}
-
-/* This gets rid of all the nasties in af_inet. -DaveM */
-static int raw_bind(struct sock *sk, struct sockaddr_unsized *uaddr,
-		    int addr_len)
-{
-	struct inet_sock *inet = inet_sk(sk);
-	struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;
-	struct net *net = sock_net(sk);
-	u32 tb_id = RT_TABLE_LOCAL;
-	int ret = -EINVAL;
-	int chk_addr_ret;
-
-	lock_sock(sk);
-	if (sk->sk_state != TCP_CLOSE || addr_len < sizeof(struct sockaddr_in))
-		goto out;
-
-	if (sk->sk_bound_dev_if)
-		tb_id = l3mdev_fib_table_by_index(net,
-						  sk->sk_bound_dev_if) ? : tb_id;
-
-	chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);
-
-	ret = -EADDRNOTAVAIL;
-	if (!inet_addr_valid_or_nonlocal(net, inet, addr->sin_addr.s_addr,
-					 chk_addr_ret))
-		goto out;
-
-	inet->inet_saddr = addr->sin_addr.s_addr;
-	WRITE_ONCE(inet->inet_rcv_saddr, addr->sin_addr.s_addr);
-	if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
-		inet->inet_saddr = 0;  /* Use device */
-	sk_dst_reset(sk);
-	ret = 0;
-out:
-	release_sock(sk);
-	return ret;
-}
-
-/*
- *	This should be easy, if there is something there
- *	we return it, otherwise we block.
- */
-
-static int raw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
-		       int flags)
-{
-	struct inet_sock *inet = inet_sk(sk);
-	size_t copied = 0;
-	int err = -EOPNOTSUPP;
-	DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
-	struct sk_buff *skb;
-
-	if (flags & MSG_OOB)
-		goto out;
-
-	if (flags & MSG_ERRQUEUE) {
-		err = ip_recv_error(sk, msg, len);
-		goto out;
-	}
-
-	skb = skb_recv_datagram(sk, flags, &err);
-	if (!skb)
-		goto out;
-
-	copied = skb->len;
-	if (len < copied) {
-		msg->msg_flags |= MSG_TRUNC;
-		copied = len;
-	}
-
-	err = skb_copy_datagram_msg(skb, 0, msg, copied);
-	if (err)
-		goto done;
-
-	sock_recv_cmsgs(msg, sk, skb);
-
-	/* Copy the address. */
-	if (sin) {
-		sin->sin_family = AF_INET;
-		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
-		sin->sin_port = 0;
-		memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
-		msg->msg_namelen = sizeof(*sin);
-	}
-	if (inet_cmsg_flags(inet))
-		ip_cmsg_recv(msg, skb);
-	if (flags & MSG_TRUNC)
-		copied = skb->len;
-done:
-	skb_free_datagram(sk, skb);
-out:
-	if (err)
-		return err;
-	return copied;
-}
-
-static int raw_sk_init(struct sock *sk)
-{
-	struct raw_sock *rp = raw_sk(sk);
-
-	sk->sk_drop_counters = &rp->drop_counters;
-	if (inet_sk(sk)->inet_num == IPPROTO_ICMP)
-		memset(&rp->filter, 0, sizeof(rp->filter));
-	return 0;
-}
-
-static int raw_seticmpfilter(struct sock *sk, sockptr_t optval, int optlen)
-{
-	if (optlen > sizeof(struct icmp_filter))
-		optlen = sizeof(struct icmp_filter);
-	if (copy_from_sockptr(&raw_sk(sk)->filter, optval, optlen))
-		return -EFAULT;
-	return 0;
-}
-
-static int raw_geticmpfilter(struct sock *sk, sockopt_t *opt)
-{
-	int len = opt->optlen;
-
-	if (len < 0)
-		return -EINVAL;
-	if (len > sizeof(struct icmp_filter))
-		len = sizeof(struct icmp_filter);
-	opt->optlen = len;
-	if (copy_to_iter(&raw_sk(sk)->filter, len, &opt->iter_out) != len)
-		return -EFAULT;
-	return 0;
-}
-
-static int do_raw_setsockopt(struct sock *sk, int optname,
-			     sockptr_t optval, unsigned int optlen)
-{
-	if (optname == ICMP_FILTER) {
-		if (inet_sk(sk)->inet_num != IPPROTO_ICMP)
-			return -EOPNOTSUPP;
-		else
-			return raw_seticmpfilter(sk, optval, optlen);
-	}
-	return -ENOPROTOOPT;
-}
-
-static int raw_setsockopt(struct sock *sk, int level, int optname,
-			  sockptr_t optval, unsigned int optlen)
-{
-	if (level != SOL_RAW)
-		return ip_setsockopt(sk, level, optname, optval, optlen);
-	return do_raw_setsockopt(sk, optname, optval, optlen);
-}
-
-static int do_raw_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
-{
-	if (optname == ICMP_FILTER) {
-		if (inet_sk(sk)->inet_num != IPPROTO_ICMP)
-			return -EOPNOTSUPP;
-		else
-			return raw_geticmpfilter(sk, opt);
-	}
-	return -ENOPROTOOPT;
-}
-
-static int raw_getsockopt(struct sock *sk, int level, int optname,
-			  char __user *optval, int __user *optlen)
-{
-	sockopt_t opt;
-	int err;
-
-	if (level != SOL_RAW)
-		return ip_getsockopt(sk, level, optname, optval, optlen);
-
-	err = sockopt_init_user(&opt, optval, optlen);
-	if (err)
-		return err;
-
-	err = do_raw_getsockopt(sk, optname, &opt);
-	if (err)
-		return err;
-
-	if (put_user(opt.optlen, optlen))
-		return -EFAULT;
-
-	return 0;
-}
-
-static int raw_ioctl(struct sock *sk, int cmd, int *karg)
-{
-	switch (cmd) {
-	case SIOCOUTQ: {
-		*karg = sk_wmem_alloc_get(sk);
-		return 0;
-	}
-	case SIOCINQ: {
-		struct sk_buff *skb;
-
-		spin_lock_bh(&sk->sk_receive_queue.lock);
-		skb = skb_peek(&sk->sk_receive_queue);
-		if (skb)
-			*karg = skb->len;
-		else
-			*karg = 0;
-		spin_unlock_bh(&sk->sk_receive_queue.lock);
-		return 0;
-	}
-
-	default:
-#ifdef CONFIG_IP_MROUTE
-		return ipmr_ioctl(sk, cmd, karg);
-#else
-		return -ENOIOCTLCMD;
-#endif
-	}
-}
-
-#ifdef CONFIG_COMPAT
-static int compat_raw_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg)
-{
-	switch (cmd) {
-	case SIOCOUTQ:
-	case SIOCINQ:
-		return -ENOIOCTLCMD;
-	default:
-#ifdef CONFIG_IP_MROUTE
-		return ipmr_compat_ioctl(sk, cmd, compat_ptr(arg));
-#else
-		return -ENOIOCTLCMD;
-#endif
-	}
-}
-#endif
-
 int raw_abort(struct sock *sk, int err)
 {
 	lock_sock(sk);
@@ -945,34 +120,6 @@ int raw_abort(struct sock *sk, int err)
 	return 0;
 }
 
-struct proto raw_prot = {
-	.name		   = "RAW",
-	.owner		   = THIS_MODULE,
-	.close		   = raw_close,
-	.destroy	   = raw_destroy,
-	.connect	   = ip4_datagram_connect,
-	.disconnect	   = __udp_disconnect,
-	.ioctl		   = raw_ioctl,
-	.init		   = raw_sk_init,
-	.setsockopt	   = raw_setsockopt,
-	.getsockopt	   = raw_getsockopt,
-	.sendmsg	   = raw_sendmsg,
-	.recvmsg	   = raw_recvmsg,
-	.bind		   = raw_bind,
-	.backlog_rcv	   = raw_rcv_skb,
-	.release_cb	   = ip4_datagram_release_cb,
-	.hash		   = raw_hash_sk,
-	.unhash		   = raw_unhash_sk,
-	.obj_size	   = sizeof(struct raw_sock),
-	.useroffset	   = offsetof(struct raw_sock, filter),
-	.usersize	   = sizeof_field(struct raw_sock, filter),
-	.h.raw_hash	   = &raw_v4_hashinfo,
-#ifdef CONFIG_COMPAT
-	.compat_ioctl	   = compat_raw_ioctl,
-#endif
-	.diag_destroy	   = raw_abort,
-};
-
 #ifdef CONFIG_PROC_FS
 static struct sock *raw_get_first(struct seq_file *seq, int bucket)
 {
diff --git a/net/ipv4/raw_diag.c b/net/ipv4/raw_diag.c
index 943e5998e0ad..38a1e74af7cd 100644
--- a/net/ipv4/raw_diag.c
+++ b/net/ipv4/raw_diag.c
@@ -40,7 +40,8 @@ static bool raw_lookup(struct net *net, const struct sock *sk,
 	struct inet_diag_req_raw *r = (void *)req;
 
 	if (r->sdiag_family == AF_INET)
-		return raw_v4_match(net, sk, r->sdiag_raw_protocol,
+		return IS_ENABLED(CONFIG_IPV4) &&
+		       raw_v4_match(net, sk, r->sdiag_raw_protocol,
 				    r->id.idiag_dst[0],
 				    r->id.idiag_src[0],
 				    r->id.idiag_if, 0);
diff --git a/net/ipv4/raw_ipv4.c b/net/ipv4/raw_ipv4.c
new file mode 100644
index 000000000000..0419026683b0
--- /dev/null
+++ b/net/ipv4/raw_ipv4.c
@@ -0,0 +1,884 @@
+// 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.
+ *
+ *		RAW - implementation of IP "raw" sockets.
+ *
+ *		IPv4 specific functions
+ *
+ *		code split from:
+ *		net/ipv4/raw.c
+ *
+ *		See raw.c for author information
+ */
+
+#include <asm/ioctls.h>
+#include <linux/igmp.h>
+#include <linux/mroute.h>
+#include <linux/netfilter.h>
+#include <linux/sockios.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+#include <linux/compat.h>
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/raw.h>
+#include <net/sock.h>
+#include <net/udp.h>
+#include <net/xfrm.h>
+
+struct raw_frag_vec {
+	struct msghdr *msg;
+	union {
+		struct icmphdr icmph;
+		char c[1];
+	} hdr;
+	int hlen;
+};
+
+bool raw_v4_match(struct net *net, const struct sock *sk, unsigned short num,
+		  __be32 raddr, __be32 laddr, int dif, int sdif)
+{
+	const struct inet_sock *inet = inet_sk(sk);
+	__be32 daddr, rcv_saddr;
+
+	if (!net_eq(sock_net(sk), net) || inet->inet_num != num)
+		return false;
+
+	daddr = READ_ONCE(inet->inet_daddr);
+	if (daddr && daddr != raddr)
+		return false;
+
+	rcv_saddr = READ_ONCE(inet->inet_rcv_saddr);
+	if (rcv_saddr && rcv_saddr != laddr)
+		return false;
+
+	return raw_sk_bound_dev_eq(net, READ_ONCE(sk->sk_bound_dev_if),
+				   dif, sdif);
+}
+EXPORT_SYMBOL_GPL(raw_v4_match);
+
+/*
+ *	0 - deliver
+ *	1 - block
+ */
+static int icmp_filter(const struct sock *sk, const struct sk_buff *skb)
+{
+	struct icmphdr _hdr;
+	const struct icmphdr *hdr;
+
+	hdr = skb_header_pointer(skb, skb_transport_offset(skb),
+				 sizeof(_hdr), &_hdr);
+	if (!hdr)
+		return 1;
+
+	if (hdr->type < 32) {
+		__u32 data = raw_sk(sk)->filter.data;
+
+		return ((1U << hdr->type) & data) != 0;
+	}
+
+	/* Do not block unknown ICMP types */
+	return 0;
+}
+
+/* IP input processing comes here for RAW socket delivery.
+ * Caller owns SKB, so we must make clones.
+ *
+ * RFC 1122: SHOULD pass TOS value up to the transport layer.
+ * -> It does. And not only TOS, but all IP header.
+ */
+static int raw_v4_input(struct net *net, struct sk_buff *skb,
+			const struct iphdr *iph, int hash)
+{
+	int sdif = inet_sdif(skb);
+	struct hlist_head *hlist;
+	int dif = inet_iif(skb);
+	int delivered = 0;
+	struct sock *sk;
+
+	hlist = &raw_v4_hashinfo.ht[hash];
+	rcu_read_lock();
+	sk_for_each_rcu(sk, hlist) {
+		if (!raw_v4_match(net, sk, iph->protocol,
+				  iph->saddr, iph->daddr, dif, sdif))
+			continue;
+
+		if (atomic_read(&sk->sk_rmem_alloc) >=
+		    READ_ONCE(sk->sk_rcvbuf)) {
+			sk_drops_inc(sk);
+			continue;
+		}
+
+		delivered = 1;
+		if ((iph->protocol != IPPROTO_ICMP || !icmp_filter(sk, skb)) &&
+		    ip_mc_sf_allow(sk, iph->daddr, iph->saddr,
+				   skb->dev->ifindex, sdif)) {
+			struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);
+
+			/* Not releasing hash table! */
+			if (clone)
+				raw_rcv(sk, clone);
+		}
+	}
+	rcu_read_unlock();
+	return delivered;
+}
+
+int raw_local_deliver(struct sk_buff *skb, int protocol)
+{
+	struct net *net = dev_net(skb->dev);
+
+	return raw_v4_input(net, skb, ip_hdr(skb),
+			    raw_hashfunc(net, protocol));
+}
+
+static void raw_err(struct sock *sk, struct sk_buff *skb, u32 info)
+{
+	struct inet_sock *inet = inet_sk(sk);
+	const int type = icmp_hdr(skb)->type;
+	const int code = icmp_hdr(skb)->code;
+	int harderr = 0;
+	bool recverr;
+	int err = 0;
+
+	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
+		ipv4_sk_update_pmtu(skb, sk, info);
+	} else if (type == ICMP_REDIRECT) {
+		ipv4_sk_redirect(skb, sk);
+		return;
+	}
+
+	/* Report error on raw socket, if:
+	 * 1. User requested ip_recverr.
+	 * 2. Socket is connected (otherwise the error indication
+	 *    is useless without ip_recverr and error is hard.
+	 */
+	recverr = inet_test_bit(RECVERR, sk);
+	if (!recverr && sk->sk_state != TCP_ESTABLISHED)
+		return;
+
+	switch (type) {
+	default:
+	case ICMP_TIME_EXCEEDED:
+		err = EHOSTUNREACH;
+		break;
+	case ICMP_SOURCE_QUENCH:
+		return;
+	case ICMP_PARAMETERPROB:
+		err = EPROTO;
+		harderr = 1;
+		break;
+	case ICMP_DEST_UNREACH:
+		err = EHOSTUNREACH;
+		if (code > NR_ICMP_UNREACH)
+			break;
+		if (code == ICMP_FRAG_NEEDED) {
+			harderr = READ_ONCE(inet->pmtudisc) != IP_PMTUDISC_DONT;
+			err = EMSGSIZE;
+		} else {
+			err = icmp_err_convert[code].errno;
+			harderr = icmp_err_convert[code].fatal;
+		}
+	}
+
+	if (recverr) {
+		const struct iphdr *iph = (const struct iphdr *)skb->data;
+		u8 *payload = skb->data + (iph->ihl << 2);
+
+		if (inet_test_bit(HDRINCL, sk))
+			payload = skb->data;
+		ip_icmp_error(sk, skb, err, 0, info, payload);
+	}
+
+	if (recverr || harderr) {
+		sk->sk_err = err;
+		sk_error_report(sk);
+	}
+}
+
+void raw_icmp_error(struct sk_buff *skb, int protocol, u32 info)
+{
+	struct net *net = dev_net(skb->dev);
+	int dif = skb->dev->ifindex;
+	int sdif = inet_sdif(skb);
+	struct hlist_head *hlist;
+	const struct iphdr *iph;
+	struct sock *sk;
+	int hash;
+
+	hash = raw_hashfunc(net, protocol);
+	hlist = &raw_v4_hashinfo.ht[hash];
+
+	rcu_read_lock();
+	sk_for_each_rcu(sk, hlist) {
+		iph = (const struct iphdr *)skb->data;
+		if (!raw_v4_match(net, sk, iph->protocol,
+				  iph->daddr, iph->saddr, dif, sdif))
+			continue;
+		raw_err(sk, skb, info);
+	}
+	rcu_read_unlock();
+}
+
+static int raw_rcv_skb(struct sock *sk, struct sk_buff *skb)
+{
+	enum skb_drop_reason reason;
+
+	/* Charge it to the socket. */
+
+	ipv4_pktinfo_prepare(sk, skb, true);
+	reason = sock_queue_rcv_skb_reason(sk, skb);
+	if (reason) {
+		sk_skb_reason_drop(sk, skb, reason);
+		return NET_RX_DROP;
+	}
+
+	return NET_RX_SUCCESS;
+}
+
+int raw_rcv(struct sock *sk, struct sk_buff *skb)
+{
+	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
+		sk_drops_inc(sk);
+		sk_skb_reason_drop(sk, skb, SKB_DROP_REASON_XFRM_POLICY);
+		return NET_RX_DROP;
+	}
+	nf_reset_ct(skb);
+
+	skb_push(skb, -skb_network_offset(skb));
+
+	raw_rcv_skb(sk, skb);
+	return 0;
+}
+
+static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,
+			   struct msghdr *msg, size_t length,
+			   struct rtable **rtp, unsigned int flags,
+			   const struct sockcm_cookie *sockc)
+{
+	struct inet_sock *inet = inet_sk(sk);
+	struct net *net = sock_net(sk);
+	struct iphdr *iph;
+	struct sk_buff *skb;
+	unsigned int iphlen;
+	int err;
+	struct rtable *rt = *rtp;
+	int hlen, tlen;
+
+	if (length > rt->dst.dev->mtu) {
+		ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
+			       rt->dst.dev->mtu);
+		return -EMSGSIZE;
+	}
+	if (length < sizeof(struct iphdr))
+		return -EINVAL;
+
+	if (flags & MSG_PROBE)
+		goto out;
+
+	hlen = LL_RESERVED_SPACE(rt->dst.dev);
+	tlen = rt->dst.dev->needed_tailroom;
+	skb = sock_alloc_send_skb(sk,
+				  length + hlen + tlen + 15,
+				  flags & MSG_DONTWAIT, &err);
+	if (!skb)
+		goto error;
+	skb_reserve(skb, hlen);
+
+	skb->protocol = htons(ETH_P_IP);
+	skb->priority = sockc->priority;
+	skb->mark = sockc->mark;
+	skb_set_delivery_type_by_clockid(skb, sockc->transmit_time, sk->sk_clockid);
+	skb_dst_set(skb, &rt->dst);
+	*rtp = NULL;
+
+	skb_reset_network_header(skb);
+	iph = ip_hdr(skb);
+	skb_put(skb, length);
+
+	skb->ip_summed = CHECKSUM_NONE;
+
+	skb_setup_tx_timestamp(skb, sockc);
+
+	if (flags & MSG_CONFIRM)
+		skb_set_dst_pending_confirm(skb, 1);
+
+	skb->transport_header = skb->network_header;
+	err = -EFAULT;
+	if (memcpy_from_msg(iph, msg, length))
+		goto error_free;
+
+	iphlen = iph->ihl * 4;
+
+	/*
+	 * We don't want to modify the ip header, but we do need to
+	 * be sure that it won't cause problems later along the network
+	 * stack.  Specifically we want to make sure that iph->ihl is a
+	 * sane value.  If ihl points beyond the length of the buffer passed
+	 * in, reject the frame as invalid
+	 */
+	err = -EINVAL;
+	if (iphlen > length || iphlen < sizeof(*iph))
+		goto error_free;
+
+	if (iphlen >= sizeof(*iph)) {
+		if (!iph->saddr)
+			iph->saddr = fl4->saddr;
+		iph->check   = 0;
+		iph->tot_len = htons(length);
+		if (!iph->id)
+			ip_select_ident(net, skb, NULL);
+
+		iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+		skb->transport_header += iphlen;
+		if (iph->protocol == IPPROTO_ICMP &&
+		    length >= iphlen + sizeof(struct icmphdr))
+			icmp_out_count(net, ((struct icmphdr *)
+				skb_transport_header(skb))->type);
+	}
+
+	err = NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
+		      net, sk, skb, NULL, rt->dst.dev,
+		      dst_output);
+	if (err > 0)
+		err = net_xmit_errno(err);
+	if (err)
+		goto error;
+out:
+	return 0;
+
+error_free:
+	kfree_skb(skb);
+error:
+	IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
+	if (err == -ENOBUFS && !inet_test_bit(RECVERR, sk))
+		err = 0;
+	return err;
+}
+
+static int raw_probe_proto_opt(struct raw_frag_vec *rfv, struct flowi4 *fl4)
+{
+	int err;
+
+	if (fl4->flowi4_proto != IPPROTO_ICMP)
+		return 0;
+
+	/* We only need the first two bytes. */
+	rfv->hlen = 2;
+
+	err = memcpy_from_msg(rfv->hdr.c, rfv->msg, rfv->hlen);
+	if (err)
+		return err;
+
+	fl4->fl4_icmp_type = rfv->hdr.icmph.type;
+	fl4->fl4_icmp_code = rfv->hdr.icmph.code;
+
+	return 0;
+}
+
+static int raw_getfrag(void *from, char *to, int offset, int len, int odd,
+		       struct sk_buff *skb)
+{
+	struct raw_frag_vec *rfv = from;
+
+	if (offset < rfv->hlen) {
+		int copy = min(rfv->hlen - offset, len);
+
+		if (skb->ip_summed == CHECKSUM_PARTIAL)
+			memcpy(to, rfv->hdr.c + offset, copy);
+		else
+			skb->csum = csum_block_add(skb->csum,
+						   csum_partial_copy_nocheck(rfv->hdr.c + offset,
+									     to, copy),
+						    odd);
+
+		odd = 0;
+		offset += copy;
+		to += copy;
+		len -= copy;
+
+		if (!len)
+			return 0;
+	}
+
+	offset -= rfv->hlen;
+
+	return ip_generic_getfrag(rfv->msg, to, offset, len, odd, skb);
+}
+
+static int raw_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 inet_sock *inet = inet_sk(sk);
+	struct net *net = sock_net(sk);
+	struct ipcm_cookie ipc;
+	struct rtable *rt = NULL;
+	struct flowi4 fl4;
+	u8 scope;
+	int free = 0;
+	__be32 daddr;
+	__be32 saddr;
+	int uc_index, err;
+	struct raw_frag_vec rfv;
+	int hdrincl;
+
+	err = -EMSGSIZE;
+	if (len > 0xFFFF)
+		goto out;
+
+	hdrincl = inet_test_bit(HDRINCL, sk);
+
+	/*
+	 *	Check the flags.
+	 */
+
+	err = -EOPNOTSUPP;
+	if (msg->msg_flags & MSG_OOB)	/* Mirror BSD error message */
+		goto out;               /* compatibility */
+
+	/*
+	 *	Get and verify the address.
+	 */
+
+	if (msg->msg_namelen) {
+		DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
+
+		err = -EINVAL;
+		if (msg->msg_namelen < sizeof(*usin))
+			goto out;
+		if (usin->sin_family != AF_INET) {
+			pr_info_once("%s: %s forgot to set AF_INET. Fix it!\n",
+				     __func__, current->comm);
+			err = -EAFNOSUPPORT;
+			if (usin->sin_family)
+				goto out;
+		}
+		daddr = usin->sin_addr.s_addr;
+		/* ANK: I did not forget to get protocol from port field.
+		 * I just do not know, who uses this weirdness.
+		 * IP_HDRINCL is much more convenient.
+		 */
+	} else {
+		err = -EDESTADDRREQ;
+		if (sk->sk_state != TCP_ESTABLISHED)
+			goto out;
+		daddr = inet->inet_daddr;
+	}
+
+	ipcm_init_sk(&ipc, inet);
+	/* Keep backward compat */
+	if (hdrincl)
+		ipc.protocol = IPPROTO_RAW;
+
+	if (msg->msg_controllen) {
+		err = ip_cmsg_send(sk, msg, &ipc, false);
+		if (unlikely(err)) {
+			kfree(ipc.opt);
+			goto out;
+		}
+		if (ipc.opt)
+			free = 1;
+	}
+
+	saddr = ipc.addr;
+	ipc.addr = daddr;
+
+	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();
+	}
+
+	if (ipc.opt) {
+		err = -EINVAL;
+		/* Linux does not mangle headers on raw sockets,
+		 * so that IP options + IP_HDRINCL is non-sense.
+		 */
+		if (hdrincl)
+			goto done;
+		if (ipc.opt->opt.srr) {
+			if (!daddr)
+				goto done;
+			daddr = ipc.opt->opt.faddr;
+		}
+	}
+	scope = ip_sendmsg_scope(inet, &ipc, msg);
+
+	uc_index = READ_ONCE(inet->uc_index);
+	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 = uc_index;
+	} else if (ipv4_is_lbcast(daddr) && uc_index) {
+		/* oif is set, packet is to local broadcast
+		 * and uc_index is set. oif is most likely set
+		 * by sk_bound_dev_if. If uc_index != oif check if the
+		 * oif is an L3 master and uc_index is an L3 slave.
+		 * If so, we want to allow the send using the uc_index.
+		 */
+		if (ipc.oif != uc_index &&
+		    ipc.oif == l3mdev_master_ifindex_by_index(sock_net(sk),
+							      uc_index)) {
+			ipc.oif = uc_index;
+		}
+	}
+
+	flowi4_init_output(&fl4, ipc.oif, ipc.sockc.mark,
+			   ipc.tos & INET_DSCP_MASK, scope,
+			   hdrincl ? ipc.protocol : sk->sk_protocol,
+			   inet_sk_flowi_flags(sk) |
+			    (hdrincl ? FLOWI_FLAG_KNOWN_NH : 0),
+			   daddr, saddr, 0, 0, sk_uid(sk));
+
+	fl4.fl4_icmp_type = 0;
+	fl4.fl4_icmp_code = 0;
+
+	if (!hdrincl) {
+		rfv.msg = msg;
+		rfv.hlen = 0;
+
+		err = raw_probe_proto_opt(&rfv, &fl4);
+		if (err)
+			goto done;
+	}
+
+	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;
+		goto done;
+	}
+
+	err = -EACCES;
+	if (rt->rt_flags & RTCF_BROADCAST && !sock_flag(sk, SOCK_BROADCAST))
+		goto done;
+
+	if (msg->msg_flags & MSG_CONFIRM)
+		goto do_confirm;
+back_from_confirm:
+
+	if (hdrincl) {
+		err = raw_send_hdrinc(sk, &fl4, msg, len,
+				      &rt, msg->msg_flags, &ipc.sockc);
+	} else {
+		if (!ipc.addr)
+			ipc.addr = fl4.daddr;
+		lock_sock(sk);
+		err = ip_append_data(sk, &fl4, raw_getfrag,
+				     &rfv, len, 0,
+				     &ipc, &rt, msg->msg_flags);
+		if (err) {
+			ip_flush_pending_frames(sk);
+		} else if (!(msg->msg_flags & MSG_MORE)) {
+			err = ip_push_pending_frames(sk, &fl4);
+			if (err == -ENOBUFS && !inet_test_bit(RECVERR, sk))
+				err = 0;
+		}
+		release_sock(sk);
+	}
+done:
+	if (free)
+		kfree(ipc.opt);
+	ip_rt_put(rt);
+
+out:
+	if (err < 0)
+		return err;
+	return len;
+
+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 done;
+}
+
+static void raw_close(struct sock *sk, long timeout)
+{
+	/*
+	 * Raw sockets may have direct kernel references. Kill them.
+	 */
+	ip_ra_control(sk, 0, NULL);
+
+	sk_common_release(sk);
+}
+
+static void raw_destroy(struct sock *sk)
+{
+	lock_sock(sk);
+	ip_flush_pending_frames(sk);
+	release_sock(sk);
+}
+
+/* This gets rid of all the nasties in af_inet. -DaveM */
+static int raw_bind(struct sock *sk, struct sockaddr_unsized *uaddr,
+		    int addr_len)
+{
+	struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
+	struct inet_sock *inet = inet_sk(sk);
+	struct net *net = sock_net(sk);
+	u32 tb_id = RT_TABLE_LOCAL;
+	int ret = -EINVAL;
+	int chk_addr_ret;
+
+	lock_sock(sk);
+	if (sk->sk_state != TCP_CLOSE || addr_len < sizeof(struct sockaddr_in))
+		goto out;
+
+	if (sk->sk_bound_dev_if)
+		tb_id = l3mdev_fib_table_by_index(net,
+						  sk->sk_bound_dev_if) ? : tb_id;
+
+	chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);
+
+	ret = -EADDRNOTAVAIL;
+	if (!inet_addr_valid_or_nonlocal(net, inet, addr->sin_addr.s_addr,
+					 chk_addr_ret))
+		goto out;
+
+	inet->inet_saddr = addr->sin_addr.s_addr;
+	WRITE_ONCE(inet->inet_rcv_saddr, addr->sin_addr.s_addr);
+	if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
+		inet->inet_saddr = 0;  /* Use device */
+	sk_dst_reset(sk);
+	ret = 0;
+out:
+	release_sock(sk);
+	return ret;
+}
+
+/*
+ *	This should be easy, if there is something there
+ *	we return it, otherwise we block.
+ */
+
+static int raw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
+		       int flags)
+{
+	struct inet_sock *inet = inet_sk(sk);
+	size_t copied = 0;
+	int err = -EOPNOTSUPP;
+	DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
+	struct sk_buff *skb;
+
+	if (flags & MSG_OOB)
+		goto out;
+
+	if (flags & MSG_ERRQUEUE) {
+		err = ip_recv_error(sk, msg, len);
+		goto out;
+	}
+
+	skb = skb_recv_datagram(sk, flags, &err);
+	if (!skb)
+		goto out;
+
+	copied = skb->len;
+	if (len < copied) {
+		msg->msg_flags |= MSG_TRUNC;
+		copied = len;
+	}
+
+	err = skb_copy_datagram_msg(skb, 0, msg, copied);
+	if (err)
+		goto done;
+
+	sock_recv_cmsgs(msg, sk, skb);
+
+	/* Copy the address. */
+	if (sin) {
+		sin->sin_family = AF_INET;
+		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
+		sin->sin_port = 0;
+		memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
+		msg->msg_namelen = sizeof(*sin);
+	}
+	if (inet_cmsg_flags(inet))
+		ip_cmsg_recv(msg, skb);
+	if (flags & MSG_TRUNC)
+		copied = skb->len;
+done:
+	skb_free_datagram(sk, skb);
+out:
+	if (err)
+		return err;
+	return copied;
+}
+
+static int raw_sk_init(struct sock *sk)
+{
+	struct raw_sock *rp = raw_sk(sk);
+
+	sk->sk_drop_counters = &rp->drop_counters;
+	if (inet_sk(sk)->inet_num == IPPROTO_ICMP)
+		memset(&rp->filter, 0, sizeof(rp->filter));
+	return 0;
+}
+
+static int raw_seticmpfilter(struct sock *sk, sockptr_t optval, int optlen)
+{
+	if (optlen > sizeof(struct icmp_filter))
+		optlen = sizeof(struct icmp_filter);
+	if (copy_from_sockptr(&raw_sk(sk)->filter, optval, optlen))
+		return -EFAULT;
+	return 0;
+}
+
+static int raw_geticmpfilter(struct sock *sk, sockopt_t *opt)
+{
+	int len = opt->optlen;
+
+	if (len < 0)
+		return -EINVAL;
+	if (len > sizeof(struct icmp_filter))
+		len = sizeof(struct icmp_filter);
+	opt->optlen = len;
+	if (copy_to_iter(&raw_sk(sk)->filter, len, &opt->iter_out) != len)
+		return -EFAULT;
+	return 0;
+}
+
+static int do_raw_setsockopt(struct sock *sk, int optname,
+			     sockptr_t optval, unsigned int optlen)
+{
+	if (optname == ICMP_FILTER) {
+		if (inet_sk(sk)->inet_num != IPPROTO_ICMP)
+			return -EOPNOTSUPP;
+		else
+			return raw_seticmpfilter(sk, optval, optlen);
+	}
+	return -ENOPROTOOPT;
+}
+
+static int raw_setsockopt(struct sock *sk, int level, int optname,
+			  sockptr_t optval, unsigned int optlen)
+{
+	if (level != SOL_RAW)
+		return ip_setsockopt(sk, level, optname, optval, optlen);
+	return do_raw_setsockopt(sk, optname, optval, optlen);
+}
+
+static int do_raw_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
+{
+	if (optname == ICMP_FILTER) {
+		if (inet_sk(sk)->inet_num != IPPROTO_ICMP)
+			return -EOPNOTSUPP;
+		else
+			return raw_geticmpfilter(sk, opt);
+	}
+	return -ENOPROTOOPT;
+}
+
+static int raw_getsockopt(struct sock *sk, int level, int optname,
+			  char __user *optval, int __user *optlen)
+{
+	sockopt_t opt;
+	int err;
+
+	if (level != SOL_RAW)
+		return ip_getsockopt(sk, level, optname, optval, optlen);
+
+	err = sockopt_init_user(&opt, optval, optlen);
+	if (err)
+		return err;
+
+	err = do_raw_getsockopt(sk, optname, &opt);
+	if (err)
+		return err;
+
+	if (put_user(opt.optlen, optlen))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int raw_ioctl(struct sock *sk, int cmd, int *karg)
+{
+	switch (cmd) {
+	case SIOCOUTQ: {
+		*karg = sk_wmem_alloc_get(sk);
+		return 0;
+	}
+	case SIOCINQ: {
+		struct sk_buff *skb;
+
+		spin_lock_bh(&sk->sk_receive_queue.lock);
+		skb = skb_peek(&sk->sk_receive_queue);
+		if (skb)
+			*karg = skb->len;
+		else
+			*karg = 0;
+		spin_unlock_bh(&sk->sk_receive_queue.lock);
+		return 0;
+	}
+
+	default:
+#ifdef CONFIG_IP_MROUTE
+		return ipmr_ioctl(sk, cmd, karg);
+#else
+		return -ENOIOCTLCMD;
+#endif
+	}
+}
+
+#ifdef CONFIG_COMPAT
+static int compat_raw_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case SIOCOUTQ:
+	case SIOCINQ:
+		return -ENOIOCTLCMD;
+	default:
+#ifdef CONFIG_IP_MROUTE
+		return ipmr_compat_ioctl(sk, cmd, compat_ptr(arg));
+#else
+		return -ENOIOCTLCMD;
+#endif
+	}
+}
+#endif
+
+struct proto raw_prot = {
+	.name		   = "RAW",
+	.owner		   = THIS_MODULE,
+	.close		   = raw_close,
+	.destroy	   = raw_destroy,
+	.connect	   = ip4_datagram_connect,
+	.disconnect	   = __udp_disconnect,
+	.ioctl		   = raw_ioctl,
+	.init		   = raw_sk_init,
+	.setsockopt	   = raw_setsockopt,
+	.getsockopt	   = raw_getsockopt,
+	.sendmsg	   = raw_sendmsg,
+	.recvmsg	   = raw_recvmsg,
+	.bind		   = raw_bind,
+	.backlog_rcv	   = raw_rcv_skb,
+	.release_cb	   = ip4_datagram_release_cb,
+	.hash		   = raw_hash_sk,
+	.unhash		   = raw_unhash_sk,
+	.obj_size	   = sizeof(struct raw_sock),
+	.useroffset	   = offsetof(struct raw_sock, filter),
+	.usersize	   = sizeof_field(struct raw_sock, filter),
+	.h.raw_hash	   = &raw_v4_hashinfo,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	   = compat_raw_ioctl,
+#endif
+	.diag_destroy	   = raw_abort,
+};
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 06/16 net-next v3] ipv4: udp: split IPv4 specific logic into udp_ipv4.c
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (4 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 05/16 net-next v3] ipv4: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 07/16 net-next v3] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
                   ` (9 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Willem de Bruijn, Eric Dumazet, John Fastabend, Jakub Sitnicki,
	Jiayuan Chen, Eric Biggers, Kuniyuki Iwashima, Neal Cardwell,
	Florian Westphal, Kees Cook, Alice Mikityanska,
	Nikolay Aleksandrov, Gal Pressman, Xuanqiang Luo, linux-kernel,
	bpf

To enable compiling the INET subsystem without IPv4, UDP socket IPv4
specific functions must be isolated from the generic UDP socket
infrastructure, similar to how it is done for TCP.

This patch creates udp_ipv4.c and move all the functions for packet
matching, input/output processing, multicast and the AF_INET udp_prot
from udp.c to the new file. The Makefile is updated to compile
udp_ipv4.c only when CONFIG_IPV4 is enabled.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 include/net/udp.h      |   11 +
 net/ipv4/Makefile      |    2 +-
 net/ipv4/af_inet.c     |   10 +
 net/ipv4/udp.c         | 1817 +---------------------------------------
 net/ipv4/udp_bpf.c     |    5 +-
 net/ipv4/udp_ipv4.c    | 1790 +++++++++++++++++++++++++++++++++++++++
 net/ipv4/udp_offload.c |    3 +
 net/ipv6/udp.c         |   11 +-
 8 files changed, 1861 insertions(+), 1788 deletions(-)
 create mode 100644 net/ipv4/udp_ipv4.c

diff --git a/include/net/udp.h b/include/net/udp.h
index 1fee17274745..9915e9b84c65 100644
--- a/include/net/udp.h
+++ b/include/net/udp.h
@@ -436,9 +436,20 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,
 		       int (*push_pending_frames)(struct sock *));
 struct sock *udp4_lib_lookup(const struct net *net, __be32 saddr, __be16 sport,
 			     __be32 daddr, __be16 dport, int dif);
+#if IS_ENABLED(CONFIG_IPV4)
 struct sock *__udp4_lib_lookup(const struct net *net, __be32 saddr,
 			       __be16 sport, __be32 daddr, __be16 dport,
 			       int dif, int sdif, struct sk_buff *skb);
+#else
+static inline struct sock *__udp4_lib_lookup(const struct net *net,
+					     __be32 saddr, __be16 sport,
+					     __be32 daddr, __be16 dport,
+					     int dif, int sdif,
+					     struct sk_buff *skb)
+{
+	return NULL;
+}
+#endif
 struct sock *udp4_lib_lookup_skb(const struct sk_buff *skb,
 				 __be16 sport, __be16 dport);
 struct sock *udp6_lib_lookup(const struct net *net,
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 7ac882ec5d4f..48ef3fb3b164 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -13,7 +13,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 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
+		      fib_semantics.o fib_trie.o raw_ipv4.o udp_ipv4.o
 
 obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
 obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index a9d46a178f9a..e5222b0346fd 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -886,8 +886,13 @@ int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
 		return -EAGAIN;
 
 	prot = READ_ONCE(sk->sk_prot);
+#if IS_ENABLED(CONFIG_IPV4)
 	return INDIRECT_CALL_2(prot->sendmsg, tcp_sendmsg, udp_sendmsg,
 			       sk, msg, size);
+#else
+	return INDIRECT_CALL_1(prot->sendmsg, tcp_sendmsg,
+			       sk, msg, size);
+#endif
 }
 EXPORT_SYMBOL(inet_sendmsg);
 
@@ -914,8 +919,13 @@ int inet_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
 		sock_rps_record_flow(sk);
 
 	prot = READ_ONCE(sk->sk_prot);
+#if IS_ENABLED(CONFIG_IPV4)
 	return INDIRECT_CALL_2(prot->recvmsg, tcp_recvmsg, udp_recvmsg,
 			       sk, msg, size, flags);
+#else
+	return INDIRECT_CALL_1(prot->recvmsg, tcp_recvmsg,
+			       sk, msg, size, flags);
+#endif
 }
 EXPORT_SYMBOL(inet_recvmsg);
 
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 303d0102519a..b9ae41bbc9d2 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -348,62 +348,6 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,
 	return error;
 }
 
-static int udp_v4_get_port(struct sock *sk, unsigned short snum)
-{
-	unsigned int hash2_nulladdr =
-		ipv4_portaddr_hash(sock_net(sk), htonl(INADDR_ANY), snum);
-	unsigned int hash2_partial =
-		ipv4_portaddr_hash(sock_net(sk), inet_sk(sk)->inet_rcv_saddr, 0);
-
-	/* precompute partial secondary hash */
-	udp_sk(sk)->udp_portaddr_hash = hash2_partial;
-	return udp_lib_get_port(sk, snum, hash2_nulladdr);
-}
-
-static __always_inline int
-compute_score(struct sock *sk, const struct net *net,
-	      __be32 saddr, __be16 sport, __be32 daddr,
-	      unsigned short hnum, int dif, int sdif)
-{
-	int score;
-	struct inet_sock *inet;
-	bool dev_match;
-
-	if (!net_eq(sock_net(sk), net) ||
-	    udp_sk(sk)->udp_port_hash != hnum ||
-	    ipv6_only_sock(sk))
-		return -1;
-
-	if (sk->sk_rcv_saddr != daddr)
-		return -1;
-
-	score = (sk->sk_family == PF_INET) ? 2 : 1;
-
-	inet = inet_sk(sk);
-	if (inet->inet_daddr) {
-		if (inet->inet_daddr != saddr)
-			return -1;
-		score += 4;
-	}
-
-	if (inet->inet_dport) {
-		if (inet->inet_dport != sport)
-			return -1;
-		score += 4;
-	}
-
-	dev_match = udp_sk_bound_dev_eq(net, sk->sk_bound_dev_if,
-					dif, sdif);
-	if (!dev_match)
-		return -1;
-	if (sk->sk_bound_dev_if)
-		score += 4;
-
-	if (READ_ONCE(sk->sk_incoming_cpu) == raw_smp_processor_id())
-		score++;
-	return score;
-}
-
 u32 udp_ehashfn(const struct net *net, const __be32 laddr, const __u16 lport,
 		const __be32 faddr, const __be16 fport)
 {
@@ -413,117 +357,7 @@ u32 udp_ehashfn(const struct net *net, const __be32 laddr, const __u16 lport,
 			      udp_ehash_secret + net_hash_mix(net));
 }
 
-/**
- * udp4_lib_lookup1() - Simplified lookup using primary hash (destination port)
- * @net:	Network namespace
- * @saddr:	Source address, network order
- * @sport:	Source port, network order
- * @daddr:	Destination address, network order
- * @hnum:	Destination port, host order
- * @dif:	Destination interface index
- * @sdif:	Destination bridge port index, if relevant
- * @udptable:	Set of UDP hash tables
- *
- * Simplified lookup to be used as fallback if no sockets are found due to a
- * potential race between (receive) address change, and lookup happening before
- * the rehash operation. This function ignores SO_REUSEPORT groups while scoring
- * result sockets, because if we have one, we don't need the fallback at all.
- *
- * Called under rcu_read_lock().
- *
- * Return: socket with highest matching score if any, NULL if none
- */
-static struct sock *udp4_lib_lookup1(const struct net *net,
-				     __be32 saddr, __be16 sport,
-				     __be32 daddr, unsigned int hnum,
-				     int dif, int sdif,
-				     const struct udp_table *udptable)
-{
-	unsigned int slot = udp_hashfn(net, hnum, udptable->mask);
-	struct udp_hslot *hslot = &udptable->hash[slot];
-	struct sock *sk, *result = NULL;
-	int score, badness = 0;
-
-	sk_for_each_rcu(sk, &hslot->head) {
-		score = compute_score(sk, net,
-				      saddr, sport, daddr, hnum, dif, sdif);
-		if (score > badness) {
-			result = sk;
-			badness = score;
-		}
-	}
-
-	return result;
-}
-
-/* called with rcu_read_lock() */
-static struct sock *udp4_lib_lookup2(const struct net *net,
-				     __be32 saddr, __be16 sport,
-				     __be32 daddr, unsigned int hnum,
-				     int dif, int sdif,
-				     struct udp_hslot *hslot2,
-				     struct sk_buff *skb)
-{
-	struct sock *sk, *result;
-	int score, badness;
-	bool need_rescore;
-
-	result = NULL;
-	badness = 0;
-	udp_portaddr_for_each_entry_rcu(sk, &hslot2->head) {
-		need_rescore = false;
-rescore:
-		score = compute_score(need_rescore ? result : sk, net, saddr,
-				      sport, daddr, hnum, dif, sdif);
-		if (score > badness) {
-			badness = score;
-
-			if (need_rescore)
-				continue;
-
-			if (sk->sk_state == TCP_ESTABLISHED) {
-				result = sk;
-				continue;
-			}
-
-			result = inet_lookup_reuseport(net, sk, skb, sizeof(struct udphdr),
-						       saddr, sport, daddr, hnum, udp_ehashfn);
-			if (!result) {
-				result = sk;
-				continue;
-			}
-
-			/* Fall back to scoring if group has connections */
-			if (!reuseport_has_conns(sk))
-				return result;
-
-			/* Reuseport logic returned an error, keep original score. */
-			if (IS_ERR(result))
-				continue;
-
-			/* compute_score is too long of a function to be
-			 * inlined twice here, and calling it uninlined
-			 * here yields measurable overhead for some
-			 * workloads. Work around it by jumping
-			 * backwards to rescore 'result'.
-			 */
-			need_rescore = true;
-			goto rescore;
-		}
-	}
-	return result;
-}
-
 #if IS_ENABLED(CONFIG_BASE_SMALL)
-static struct sock *udp4_lib_lookup4(const struct net *net,
-				     __be32 saddr, __be16 sport,
-				     __be32 daddr, unsigned int hnum,
-				     int dif, int sdif,
-				     struct udp_table *udptable)
-{
-	return NULL;
-}
-
 static void udp_rehash4(struct udp_table *udptable, struct sock *sk,
 			u16 newhash4)
 {
@@ -533,42 +367,6 @@ static void udp_unhash4(struct udp_table *udptable, struct sock *sk)
 {
 }
 #else /* !CONFIG_BASE_SMALL */
-static struct sock *udp4_lib_lookup4(const struct net *net,
-				     __be32 saddr, __be16 sport,
-				     __be32 daddr, unsigned int hnum,
-				     int dif, int sdif,
-				     struct udp_table *udptable)
-{
-	const __portpair ports = INET_COMBINED_PORTS(sport, hnum);
-	const struct hlist_nulls_node *node;
-	struct udp_hslot *hslot4;
-	unsigned int hash4, slot;
-	struct udp_sock *up;
-	struct sock *sk;
-
-	hash4 = udp_ehashfn(net, daddr, hnum, saddr, sport);
-	slot = hash4 & udptable->mask;
-	hslot4 = &udptable->hash4[slot];
-	INET_ADDR_COOKIE(acookie, saddr, daddr);
-
-begin:
-	/* SLAB_TYPESAFE_BY_RCU not used, so we don't need to touch sk_refcnt */
-	udp_lrpa_for_each_entry_rcu(up, node, &hslot4->nulls_head) {
-		sk = (struct sock *)up;
-		if (inet_match(net, sk, acookie, ports, dif, sdif))
-			return sk;
-	}
-
-	/* if the nulls value we got at the end of this lookup is not the
-	 * expected one, we must restart lookup. We probably met an item that
-	 * was moved to another chain due to rehash.
-	 */
-	if (get_nulls_value(node) != slot)
-		goto begin;
-
-	return NULL;
-}
-
 /* udp_rehash4() only checks hslot4, and hash4_cnt is not processed. */
 static void udp_rehash4(struct udp_table *udptable, struct sock *sk,
 			u16 newhash4)
@@ -655,155 +453,8 @@ void udp_lib_hash4(struct sock *sk, u16 hash)
 
 	spin_unlock_bh(&hslot->lock);
 }
-
-/* call with sock lock */
-void udp4_hash4(struct sock *sk)
-{
-	struct net *net = sock_net(sk);
-	unsigned int hash;
-
-	if (sk_unhashed(sk) || sk->sk_rcv_saddr == htonl(INADDR_ANY))
-		return;
-
-	hash = udp_ehashfn(net, sk->sk_rcv_saddr, sk->sk_num,
-			   sk->sk_daddr, sk->sk_dport);
-
-	udp_lib_hash4(sk, hash);
-}
 #endif /* CONFIG_BASE_SMALL */
 
-/* UDP is nearly always wildcards out the wazoo, it makes no sense to try
- * harder than this. -DaveM
- */
-struct sock *__udp4_lib_lookup(const struct net *net, __be32 saddr,
-			       __be16 sport, __be32 daddr, __be16 dport,
-			       int dif, int sdif, struct sk_buff *skb)
-{
-	struct udp_table *udptable = net->ipv4.udp_table;
-	unsigned short hnum = ntohs(dport);
-	struct udp_hslot *hslot2;
-	struct sock *result, *sk;
-	unsigned int hash2;
-
-	hash2 = ipv4_portaddr_hash(net, daddr, hnum);
-	hslot2 = udp_hashslot2(udptable, hash2);
-
-	if (udp_has_hash4(hslot2)) {
-		result = udp4_lib_lookup4(net, saddr, sport, daddr, hnum,
-					  dif, sdif, udptable);
-		if (result) /* udp4_lib_lookup4 return sk or NULL */
-			return result;
-	}
-
-	/* Lookup connected or non-wildcard socket */
-	result = udp4_lib_lookup2(net, saddr, sport,
-				  daddr, hnum, dif, sdif,
-				  hslot2, skb);
-	if (!IS_ERR_OR_NULL(result) && result->sk_state == TCP_ESTABLISHED)
-		goto done;
-
-	/* Lookup redirect from BPF */
-	if (static_branch_unlikely(&bpf_sk_lookup_enabled)) {
-		sk = inet_lookup_run_sk_lookup(net, IPPROTO_UDP, skb, sizeof(struct udphdr),
-					       saddr, sport, daddr, hnum, dif,
-					       udp_ehashfn);
-		if (sk) {
-			result = sk;
-			goto done;
-		}
-	}
-
-	/* Got non-wildcard socket or error on first lookup */
-	if (result)
-		goto done;
-
-	/* Lookup wildcard sockets */
-	hash2 = ipv4_portaddr_hash(net, htonl(INADDR_ANY), hnum);
-	hslot2 = udp_hashslot2(udptable, hash2);
-
-	result = udp4_lib_lookup2(net, saddr, sport,
-				  htonl(INADDR_ANY), hnum, dif, sdif,
-				  hslot2, skb);
-	if (!IS_ERR_OR_NULL(result))
-		goto done;
-
-	/* Primary hash (destination port) lookup as fallback for this race:
-	 *   1. __ip4_datagram_connect() sets sk_rcv_saddr
-	 *   2. lookup (this function): new sk_rcv_saddr, hashes not updated yet
-	 *   3. rehash operation updating _secondary and four-tuple_ hashes
-	 * The primary hash doesn't need an update after 1., so, thanks to this
-	 * further step, 1. and 3. don't need to be atomic against the lookup.
-	 */
-	result = udp4_lib_lookup1(net, saddr, sport, daddr, hnum, dif, sdif,
-				  udptable);
-
-done:
-	if (IS_ERR(result))
-		return NULL;
-	return result;
-}
-EXPORT_SYMBOL_GPL(__udp4_lib_lookup);
-
-static inline struct sock *__udp4_lib_lookup_skb(struct sk_buff *skb,
-						 __be16 sport, __be16 dport)
-{
-	const struct iphdr *iph = ip_hdr(skb);
-
-	return __udp4_lib_lookup(dev_net(skb->dev), iph->saddr, sport,
-				 iph->daddr, dport, inet_iif(skb),
-				 inet_sdif(skb), skb);
-}
-
-struct sock *udp4_lib_lookup_skb(const struct sk_buff *skb,
-				 __be16 sport, __be16 dport)
-{
-	const u16 offset = NAPI_GRO_CB(skb)->network_offsets[skb->encapsulation];
-	const struct iphdr *iph = (struct iphdr *)(skb->data + offset);
-	int iif, sdif;
-
-	inet_get_iif_sdif(skb, &iif, &sdif);
-
-	return __udp4_lib_lookup(dev_net(skb->dev), iph->saddr, sport,
-				 iph->daddr, dport, iif, sdif, NULL);
-}
-
-/* Must be called under rcu_read_lock().
- * Does increment socket refcount.
- */
-#if IS_ENABLED(CONFIG_NF_TPROXY_IPV4) || IS_ENABLED(CONFIG_NF_SOCKET_IPV4)
-struct sock *udp4_lib_lookup(const struct net *net, __be32 saddr, __be16 sport,
-			     __be32 daddr, __be16 dport, int dif)
-{
-	struct sock *sk;
-
-	sk = __udp4_lib_lookup(net, saddr, sport, daddr, dport, dif, 0, NULL);
-	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
-		sk = NULL;
-	return sk;
-}
-EXPORT_SYMBOL_GPL(udp4_lib_lookup);
-#endif
-
-static inline bool __udp_is_mcast_sock(struct net *net, const struct sock *sk,
-				       __be16 loc_port, __be32 loc_addr,
-				       __be16 rmt_port, __be32 rmt_addr,
-				       int dif, int sdif, unsigned short hnum)
-{
-	const struct inet_sock *inet = inet_sk(sk);
-
-	if (!net_eq(sock_net(sk), net) ||
-	    udp_sk(sk)->udp_port_hash != hnum ||
-	    (inet->inet_daddr && inet->inet_daddr != rmt_addr) ||
-	    (inet->inet_dport != rmt_port && inet->inet_dport) ||
-	    (inet->inet_rcv_saddr && inet->inet_rcv_saddr != loc_addr) ||
-	    ipv6_only_sock(sk) ||
-	    !udp_sk_bound_dev_eq(net, sk->sk_bound_dev_if, dif, sdif))
-		return false;
-	if (!ip_mc_sf_allow(sk, loc_addr, rmt_addr, dif, sdif))
-		return false;
-	return true;
-}
-
 DEFINE_STATIC_KEY_FALSE(udp_encap_needed_key);
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -822,209 +473,6 @@ void udp_encap_disable(void)
 }
 EXPORT_SYMBOL(udp_encap_disable);
 
-/* Handler for tunnels with arbitrary destination ports: no socket lookup, go
- * through error handlers in encapsulations looking for a match.
- */
-static int __udp4_lib_err_encap_no_sk(struct sk_buff *skb, u32 info)
-{
-	int i;
-
-	for (i = 0; i < MAX_IPTUN_ENCAP_OPS; i++) {
-		int (*handler)(struct sk_buff *skb, u32 info);
-		const struct ip_tunnel_encap_ops *encap;
-
-		encap = rcu_dereference(iptun_encaps[i]);
-		if (!encap)
-			continue;
-		handler = encap->err_handler;
-		if (handler && !handler(skb, info))
-			return 0;
-	}
-
-	return -ENOENT;
-}
-
-/* Try to match ICMP errors to UDP tunnels by looking up a socket without
- * reversing source and destination port: this will match tunnels that force the
- * same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that
- * lwtunnels might actually break this assumption by being configured with
- * different destination ports on endpoints, in this case we won't be able to
- * trace ICMP messages back to them.
- *
- * If this doesn't match any socket, probe tunnels with arbitrary destination
- * ports (e.g. FoU, GUE): there, the receiving socket is useless, as the port
- * we've sent packets to won't necessarily match the local destination port.
- *
- * Then ask the tunnel implementation to match the error against a valid
- * association.
- *
- * Return an error if we can't find a match, the socket if we need further
- * processing, zero otherwise.
- */
-static struct sock *__udp4_lib_err_encap(struct net *net,
-					 const struct iphdr *iph,
-					 struct udphdr *uh,
-					 struct sock *sk,
-					 struct sk_buff *skb, u32 info)
-{
-	int (*lookup)(struct sock *sk, struct sk_buff *skb);
-	int network_offset, transport_offset;
-	struct udp_sock *up;
-
-	network_offset = skb_network_offset(skb);
-	transport_offset = skb_transport_offset(skb);
-
-	/* Network header needs to point to the outer IPv4 header inside ICMP */
-	skb_reset_network_header(skb);
-
-	/* Transport header needs to point to the UDP header */
-	skb_set_transport_header(skb, iph->ihl << 2);
-
-	if (sk) {
-		up = udp_sk(sk);
-
-		lookup = READ_ONCE(up->encap_err_lookup);
-		if (lookup && lookup(sk, skb))
-			sk = NULL;
-
-		goto out;
-	}
-
-	sk = __udp4_lib_lookup(net, iph->daddr, uh->source,
-			       iph->saddr, uh->dest, skb->dev->ifindex, 0, NULL);
-	if (sk) {
-		up = udp_sk(sk);
-
-		lookup = READ_ONCE(up->encap_err_lookup);
-		if (!lookup || lookup(sk, skb))
-			sk = NULL;
-	}
-
-out:
-	if (!sk)
-		sk = ERR_PTR(__udp4_lib_err_encap_no_sk(skb, info));
-
-	skb_set_transport_header(skb, transport_offset);
-	skb_set_network_header(skb, network_offset);
-
-	return sk;
-}
-
-static void udp_err_update_exception(struct net *net, struct sk_buff *skb,
-				     int type, int code, u32 info)
-{
-	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
-		ipv4_update_pmtu(skb, net, info, 0, IPPROTO_UDP);
-	else if (type == ICMP_REDIRECT)
-		ipv4_redirect(skb, net, 0, IPPROTO_UDP);
-}
-
-/*
- * This routine is called by the ICMP module when it gets some
- * sort of error condition.  If err < 0 then the socket should
- * be closed and the error returned to the user.  If err > 0
- * it's just the icmp type << 8 | icmp code.
- * Header points to the ip header of the error packet. We move
- * on past this. Then (as it used to claim before adjustment)
- * header points to the first 8 bytes of the udp header.  We need
- * to find the appropriate port.
- */
-int udp_err(struct sk_buff *skb, u32 info)
-{
-	const struct iphdr *iph = (const struct iphdr *)skb->data;
-	const int type = icmp_hdr(skb)->type;
-	const int code = icmp_hdr(skb)->code;
-	struct net *net = dev_net(skb->dev);
-	struct inet_sock *inet;
-	bool tunnel = false;
-	struct udphdr *uh;
-	struct sock *sk;
-	int harderr;
-	int err;
-
-	udp_err_update_exception(net, skb, type, code, info);
-
-	uh = (struct udphdr *)(skb->data + (iph->ihl << 2));
-	sk = __udp4_lib_lookup(net, iph->daddr, uh->dest,
-			       iph->saddr, uh->source, skb->dev->ifindex,
-			       inet_sdif(skb), NULL);
-
-	if (!sk || READ_ONCE(udp_sk(sk)->encap_type)) {
-		/* No socket for error: try tunnels before discarding */
-		if (static_branch_unlikely(&udp_encap_needed_key)) {
-			sk = __udp4_lib_err_encap(net, iph, uh, sk, skb, info);
-			if (!sk)
-				return 0;
-		} else
-			sk = ERR_PTR(-ENOENT);
-
-		if (IS_ERR(sk)) {
-			__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
-			return PTR_ERR(sk);
-		}
-
-		tunnel = true;
-	}
-
-	err = 0;
-	harderr = 0;
-	inet = inet_sk(sk);
-
-	switch (type) {
-	default:
-	case ICMP_TIME_EXCEEDED:
-		err = EHOSTUNREACH;
-		break;
-	case ICMP_SOURCE_QUENCH:
-		goto out;
-	case ICMP_PARAMETERPROB:
-		err = EPROTO;
-		harderr = 1;
-		break;
-	case ICMP_DEST_UNREACH:
-		if (code == ICMP_FRAG_NEEDED) { /* Path MTU discovery */
-			ipv4_sk_update_pmtu(skb, sk, info);
-			if (READ_ONCE(inet->pmtudisc) != IP_PMTUDISC_DONT) {
-				err = EMSGSIZE;
-				harderr = 1;
-				break;
-			}
-			goto out;
-		}
-		err = EHOSTUNREACH;
-		if (code <= NR_ICMP_UNREACH) {
-			harderr = icmp_err_convert[code].fatal;
-			err = icmp_err_convert[code].errno;
-		}
-		break;
-	case ICMP_REDIRECT:
-		ipv4_sk_redirect(skb, sk);
-		goto out;
-	}
-
-	/*
-	 *      RFC1122: OK.  Passes ICMP errors back to application, as per
-	 *	4.1.3.3.
-	 */
-	if (tunnel) {
-		/* ...not for tunnels though: we don't have a sending socket */
-		if (udp_sk(sk)->encap_err_rcv)
-			udp_sk(sk)->encap_err_rcv(sk, skb, err, uh->dest, info,
-						  (u8 *)(uh+1));
-		goto out;
-	}
-	if (!inet_test_bit(RECVERR, sk)) {
-		if (!harderr || sk->sk_state != TCP_ESTABLISHED)
-			goto out;
-	} else
-		ip_icmp_error(sk, skb, err, uh->dest, info, (u8 *)(uh+1));
-
-	sk->sk_err = err;
-	sk_error_report(sk);
-out:
-	return 0;
-}
-
 /*
  * Throw away all pending data and cancel the corking. Socket is locked.
  */
@@ -1039,52 +487,6 @@ void udp_flush_pending_frames(struct sock *sk)
 	}
 }
 
-/**
- * 	udp4_hwcsum  -  handle outgoing HW checksumming
- * 	@skb: 	sk_buff containing the filled-in UDP header
- * 	        (checksum field must be zeroed out)
- *	@src:	source IP address
- *	@dst:	destination IP address
- */
-void udp4_hwcsum(struct sk_buff *skb, __be32 src, __be32 dst)
-{
-	struct udphdr *uh = udp_hdr(skb);
-	int offset = skb_transport_offset(skb);
-	int len = skb->len - offset;
-	int hlen = len;
-	__wsum csum = 0;
-
-	if (!skb_has_frag_list(skb)) {
-		/*
-		 * Only one fragment on the socket.
-		 */
-		skb->csum_start = skb_transport_header(skb) - skb->head;
-		skb->csum_offset = offsetof(struct udphdr, check);
-		uh->check = ~csum_tcpudp_magic(src, dst, len,
-					       IPPROTO_UDP, 0);
-	} else {
-		struct sk_buff *frags;
-
-		/*
-		 * HW-checksum won't work as there are two or more
-		 * fragments on the socket so that all csums of sk_buffs
-		 * should be together
-		 */
-		skb_walk_frags(skb, frags) {
-			csum = csum_add(csum, frags->csum);
-			hlen -= frags->len;
-		}
-
-		csum = skb_checksum(skb, offset, hlen, csum);
-		skb->ip_summed = CHECKSUM_NONE;
-
-		uh->check = csum_tcpudp_magic(src, dst, len, IPPROTO_UDP, csum);
-		if (uh->check == 0)
-			uh->check = CSUM_MANGLED_0;
-	}
-}
-EXPORT_SYMBOL_GPL(udp4_hwcsum);
-
 /* Function to set UDP checksum for an IPv4 UDP packet. This is intended
  * for the simple case like when setting the checksum for a UDP tunnel.
  */
@@ -1111,112 +513,6 @@ void udp_set_csum(bool nocheck, struct sk_buff *skb,
 }
 EXPORT_SYMBOL(udp_set_csum);
 
-static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
-			struct inet_cork *cork)
-{
-	struct sock *sk = skb->sk;
-	int offset, len, datalen;
-	struct udphdr *uh;
-	int err;
-
-	offset = skb_transport_offset(skb);
-	len = skb->len - offset;
-	datalen = len - sizeof(*uh);
-
-	/*
-	 * Create a UDP header
-	 */
-	uh = udp_hdr(skb);
-	uh->source = inet_sk(sk)->inet_sport;
-	uh->dest = fl4->fl4_dport;
-	/* Datagram length checked in udp_sendmsg. */
-	udp_set_len_short(uh, len);
-	uh->check = 0;
-
-	if (cork->gso_size) {
-		const int hlen = skb_network_header_len(skb) +
-				 sizeof(struct udphdr);
-
-		if (hlen + min(datalen, cork->gso_size) > cork->fragsize) {
-			kfree_skb(skb);
-			return -EMSGSIZE;
-		}
-		if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
-			kfree_skb(skb);
-			return -EINVAL;
-		}
-		if (sk->sk_no_check_tx) {
-			kfree_skb(skb);
-			return -EINVAL;
-		}
-		if (dst_xfrm(skb_dst(skb))) {
-			kfree_skb(skb);
-			return -EIO;
-		}
-
-		if (datalen > cork->gso_size) {
-			skb_shinfo(skb)->gso_size = cork->gso_size;
-			skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
-			skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
-								 cork->gso_size);
-
-			/* Don't checksum the payload, skb will get segmented */
-			goto csum_partial;
-		}
-	}
-
-	if (sk->sk_no_check_tx) {			 /* UDP csum off */
-		skb->ip_summed = CHECKSUM_NONE;
-		goto send;
-	} else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
-csum_partial:
-		udp4_hwcsum(skb, fl4->saddr, fl4->daddr);
-		goto send;
-	}
-
-	/* add protocol-dependent pseudo-header */
-	uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len,
-				      IPPROTO_UDP, udp_csum(skb));
-	if (uh->check == 0)
-		uh->check = CSUM_MANGLED_0;
-
-send:
-	err = ip_send_skb(sock_net(sk), skb);
-	if (unlikely(err)) {
-		if (err == -ENOBUFS &&
-		    !inet_test_bit(RECVERR, sk)) {
-			UDP_INC_STATS(sock_net(sk), UDP_MIB_SNDBUFERRORS);
-			err = 0;
-		}
-	} else {
-		UDP_INC_STATS(sock_net(sk), UDP_MIB_OUTDATAGRAMS);
-	}
-	return err;
-}
-
-/*
- * Push out all pending data as one UDP datagram. Socket is locked.
- */
-int udp_push_pending_frames(struct sock *sk)
-{
-	struct udp_sock  *up = udp_sk(sk);
-	struct inet_sock *inet = inet_sk(sk);
-	struct flowi4 *fl4 = &inet->cork.fl.u.ip4;
-	struct sk_buff *skb;
-	int err = 0;
-
-	skb = ip_finish_skb(sk, fl4);
-	if (!skb)
-		goto out;
-
-	err = udp_send_skb(skb, fl4, &inet->cork.base);
-
-out:
-	up->len = 0;
-	WRITE_ONCE(up->pending, 0);
-	return err;
-}
-
 static int __udp_cmsg_send(struct cmsghdr *cmsg, u16 *gso_size)
 {
 	switch (cmsg->cmsg_type) {
@@ -1253,296 +549,6 @@ int udp_cmsg_send(struct sock *sk, struct msghdr *msg, u16 *gso_size)
 	return need_ip;
 }
 
-int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
-{
-	int corkreq = udp_test_bit(CORK, sk) || msg->msg_flags & MSG_MORE;
-	DEFINE_RAW_FLEX(struct ip_options_rcu, opt_copy, opt.__data,
-			IP_OPTIONS_DATA_FIXED_SIZE);
-	DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
-	int ulen = len, free = 0, connected = 0;
-	struct inet_sock *inet = inet_sk(sk);
-	struct udp_sock *up = udp_sk(sk);
-	__be32 daddr, faddr, saddr;
-	struct rtable *rt = NULL;
-	struct flowi4 fl4_stack;
-	struct ipcm_cookie ipc;
-	struct sk_buff *skb;
-	struct flowi4 *fl4;
-	__be16 dport;
-	int uc_index;
-	u8 scope;
-	int err;
-
-	if (len > 0xFFFF)
-		return -EMSGSIZE;
-
-	/*
-	 *	Check the flags.
-	 */
-
-	if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
-		return -EOPNOTSUPP;
-
-	fl4 = &inet->cork.fl.u.ip4;
-	if (READ_ONCE(up->pending)) {
-		/*
-		 * There are pending frames.
-		 * The socket lock must be held while it's corked.
-		 */
-		lock_sock(sk);
-		if (likely(up->pending)) {
-			if (unlikely(up->pending != AF_INET)) {
-				release_sock(sk);
-				return -EINVAL;
-			}
-			goto do_append_data;
-		}
-		release_sock(sk);
-	}
-	ulen += sizeof(struct udphdr);
-
-	/*
-	 *	Get and verify the address.
-	 */
-	if (usin) {
-		if (msg->msg_namelen < sizeof(*usin))
-			return -EINVAL;
-		if (usin->sin_family != AF_INET) {
-			if (usin->sin_family != AF_UNSPEC)
-				return -EAFNOSUPPORT;
-		}
-
-		daddr = usin->sin_addr.s_addr;
-		dport = usin->sin_port;
-		if (dport == 0)
-			return -EINVAL;
-	} else {
-		if (sk->sk_state != TCP_ESTABLISHED)
-			return -EDESTADDRREQ;
-		daddr = inet->inet_daddr;
-		dport = inet->inet_dport;
-		/* Open fast path for connected socket.
-		   Route will not be used, if at least one option is set.
-		 */
-		connected = 1;
-	}
-
-	ipcm_init_sk(&ipc, inet);
-	ipc.gso_size = READ_ONCE(up->gso_size);
-
-	if (msg->msg_controllen) {
-		err = udp_cmsg_send(sk, msg, &ipc.gso_size);
-		if (err > 0) {
-			err = ip_cmsg_send(sk, msg, &ipc,
-					   sk->sk_family == AF_INET6);
-			connected = 0;
-		}
-		if (unlikely(err < 0)) {
-			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();
-	}
-
-	if (cgroup_bpf_enabled(CGROUP_UDP4_SENDMSG) && !connected) {
-		err = BPF_CGROUP_RUN_PROG_UDP4_SENDMSG_LOCK(sk,
-					    (struct sockaddr *)usin,
-					    &msg->msg_namelen,
-					    &ipc.addr);
-		if (err)
-			goto out_free;
-		if (usin) {
-			if (usin->sin_port == 0) {
-				/* BPF program set invalid port. Reject it. */
-				err = -EINVAL;
-				goto out_free;
-			}
-			daddr = usin->sin_addr.s_addr;
-			dport = usin->sin_port;
-		}
-	}
-
-	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;
-		connected = 0;
-	}
-	scope = ip_sendmsg_scope(inet, &ipc, msg);
-	if (scope == RT_SCOPE_LINK)
-		connected = 0;
-
-	uc_index = READ_ONCE(inet->uc_index);
-	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);
-		connected = 0;
-	} else if (!ipc.oif) {
-		ipc.oif = uc_index;
-	} else if (ipv4_is_lbcast(daddr) && uc_index) {
-		/* oif is set, packet is to local broadcast and
-		 * uc_index is set. oif is most likely set
-		 * by sk_bound_dev_if. If uc_index != oif check if the
-		 * oif is an L3 master and uc_index is an L3 slave.
-		 * If so, we want to allow the send using the uc_index.
-		 */
-		if (ipc.oif != uc_index &&
-		    ipc.oif == l3mdev_master_ifindex_by_index(sock_net(sk),
-							      uc_index)) {
-			ipc.oif = uc_index;
-		}
-	}
-
-	if (connected)
-		rt = dst_rtable(sk_dst_check(sk, 0));
-
-	if (!rt) {
-		struct net *net = sock_net(sk);
-		__u8 flow_flags = inet_sk_flowi_flags(sk);
-
-		fl4 = &fl4_stack;
-
-		flowi4_init_output(fl4, ipc.oif, ipc.sockc.mark,
-				   ipc.tos & INET_DSCP_MASK, scope,
-				   IPPROTO_UDP, flow_flags, faddr, saddr,
-				   dport, inet->inet_sport,
-				   sk_uid(sk));
-
-		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 (connected)
-			sk_dst_set(sk, dst_clone(&rt->dst));
-	}
-
-	if (msg->msg_flags&MSG_CONFIRM)
-		goto do_confirm;
-back_from_confirm:
-
-	saddr = fl4->saddr;
-	if (!ipc.addr)
-		daddr = ipc.addr = fl4->daddr;
-
-	/* Lockless fast path for the non-corking case. */
-	if (!corkreq) {
-		struct inet_cork cork;
-
-		skb = ip_make_skb(sk, fl4, ip_generic_getfrag, msg, ulen,
-				  sizeof(struct udphdr), &ipc, &rt,
-				  &cork, msg->msg_flags);
-		err = PTR_ERR(skb);
-		if (!IS_ERR_OR_NULL(skb))
-			err = udp_send_skb(skb, fl4, &cork);
-		goto out;
-	}
-
-	lock_sock(sk);
-	if (unlikely(up->pending)) {
-		/* The socket is already corked while preparing it. */
-		/* ... which is an evident application bug. --ANK */
-		release_sock(sk);
-
-		net_dbg_ratelimited("socket already corked\n");
-		err = -EINVAL;
-		goto out;
-	}
-	/*
-	 *	Now cork the socket to pend data.
-	 */
-	fl4 = &inet->cork.fl.u.ip4;
-	fl4->daddr = daddr;
-	fl4->saddr = saddr;
-	fl4->fl4_dport = dport;
-	fl4->fl4_sport = inet->inet_sport;
-	WRITE_ONCE(up->pending, AF_INET);
-
-do_append_data:
-	up->len += ulen;
-	err = ip_append_data(sk, fl4, ip_generic_getfrag, msg, ulen,
-			     sizeof(struct udphdr), &ipc, &rt,
-			     corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
-	if (err)
-		udp_flush_pending_frames(sk);
-	else if (!corkreq)
-		err = udp_push_pending_frames(sk);
-	else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
-		WRITE_ONCE(up->pending, 0);
-	release_sock(sk);
-
-out:
-	ip_rt_put(rt);
-out_free:
-	if (free)
-		kfree(ipc.opt);
-	if (!err)
-		return len;
-	/*
-	 * ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space.  Reporting
-	 * ENOBUFS might not be good (it's not tunable per se), but otherwise
-	 * we don't have a good statistic (IpOutDiscards but it can be too many
-	 * things).  We could add another new stat but at least for now that
-	 * seems like overkill.
-	 */
-	if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags))
-		UDP_INC_STATS(sock_net(sk), UDP_MIB_SNDBUFERRORS);
-
-	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;
-}
-EXPORT_SYMBOL(udp_sendmsg);
-
-void udp_splice_eof(struct socket *sock)
-{
-	struct sock *sk = sock->sk;
-	struct udp_sock *up = udp_sk(sk);
-
-	if (!READ_ONCE(up->pending) || udp_test_bit(CORK, sk))
-		return;
-
-	lock_sock(sk);
-	if (up->pending && !udp_test_bit(CORK, sk))
-		udp_push_pending_frames(sk);
-	release_sock(sk);
-}
-
 #define UDP_SKB_IS_STATELESS 0x80000000
 
 /* all head states (dst, sk, nf conntrack) except skb extensions are
@@ -1825,21 +831,6 @@ void udp_destruct_common(struct sock *sk)
 	kfree(up->udp_prod_queue);
 }
 
-static void udp_destruct_sock(struct sock *sk)
-{
-	udp_destruct_common(sk);
-	inet_sock_destruct(sk);
-}
-
-static int udp_init_sock(struct sock *sk)
-{
-	int res = udp_lib_init_sock(sk);
-
-	sk->sk_destruct = udp_destruct_sock;
-	set_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags);
-	return res;
-}
-
 void skb_consume_udp(struct sock *sk, struct sk_buff *skb, int len)
 {
 	if (unlikely(READ_ONCE(udp_sk(sk)->peeking_with_offset)))
@@ -1999,158 +990,50 @@ struct sk_buff *__skb_recv_udp(struct sock *sk, unsigned int flags,
 			if (!sk_can_busy_loop(sk))
 				break;
 
-			sk_busy_loop(sk, flags & MSG_DONTWAIT);
-		} while (!skb_queue_empty_lockless(sk_queue));
-
-		/* sk_queue is empty, reader_queue may contain peeked packets */
-	} while (timeo &&
-		 !__skb_wait_for_more_packets(sk, &sk->sk_receive_queue,
-					      &error, &timeo,
-					      (struct sk_buff *)sk_queue));
-
-	*err = error;
-	return NULL;
-}
-EXPORT_SYMBOL(__skb_recv_udp);
-
-int udp_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
-{
-	struct sk_buff *skb;
-	int err;
-
-try_again:
-	skb = skb_recv_udp(sk, MSG_DONTWAIT, &err);
-	if (!skb)
-		return err;
-
-	if (udp_lib_checksum_complete(skb)) {
-		struct net *net = sock_net(sk);
-
-		__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
-		__UDP_INC_STATS(net, UDP_MIB_INERRORS);
-		udp_drops_inc(sk);
-		kfree_skb_reason(skb, SKB_DROP_REASON_UDP_CSUM);
-		goto try_again;
-	}
-
-	WARN_ON_ONCE(!skb_set_owner_sk_safe(skb, sk));
-
-	/*
-	 * skb->dev still aliases the UDP rx dev_scratch (its charge was freed
-	 * on dequeue above); a sockmap verdict program may deref it via
-	 * bpf_sk_lookup_*(), so clear it -> bpf_skc_lookup() uses skb->sk
-	 */
-	skb->dev = NULL;
-
-	return recv_actor(sk, skb);
-}
-
-/*
- * 	This should be easy, if there is something there we
- * 	return it, otherwise we block.
- */
-
-INDIRECT_CALLABLE_SCOPE
-int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
-{
-	DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
-	int off, err, peeking = flags & MSG_PEEK;
-	struct inet_sock *inet = inet_sk(sk);
-	struct net *net = sock_net(sk);
-	bool checksum_valid = false;
-	unsigned int ulen, copied;
-	struct sk_buff *skb;
-
-	if (flags & MSG_ERRQUEUE)
-		return ip_recv_error(sk, msg, len);
-
-try_again:
-	off = sk_peek_offset(sk, flags);
-	skb = __skb_recv_udp(sk, flags, &off, &err);
-	if (!skb)
-		return err;
-
-	ulen = udp_skb_len(skb);
-	copied = len;
-	if (copied > ulen - off)
-		copied = ulen - off;
-	else if (copied < ulen)
-		msg->msg_flags |= MSG_TRUNC;
-
-	/* If checksum is needed at all, try to do it while copying the
-	 * data.  If the data is truncated, do it before the copy.
-	 */
-	if (copied < ulen || peeking) {
-		checksum_valid = udp_skb_csum_unnecessary(skb) ||
-				!__udp_lib_checksum_complete(skb);
-		if (!checksum_valid)
-			goto csum_copy_err;
-	}
-
-	if (checksum_valid || udp_skb_csum_unnecessary(skb)) {
-		if (udp_skb_is_linear(skb))
-			err = copy_linear_skb(skb, copied, off, &msg->msg_iter);
-		else
-			err = skb_copy_datagram_msg(skb, off, msg, copied);
-	} else {
-		err = skb_copy_and_csum_datagram_msg(skb, off, msg);
-
-		if (err == -EINVAL)
-			goto csum_copy_err;
-	}
-
-	if (unlikely(err)) {
-		if (!peeking) {
-			udp_drops_inc(sk);
-			UDP_INC_STATS(net, UDP_MIB_INERRORS);
-		}
-		kfree_skb(skb);
-		return err;
-	}
-
-	if (!peeking)
-		UDP_INC_STATS(net, UDP_MIB_INDATAGRAMS);
+			sk_busy_loop(sk, flags & MSG_DONTWAIT);
+		} while (!skb_queue_empty_lockless(sk_queue));
 
-	sock_recv_cmsgs(msg, sk, skb);
+		/* sk_queue is empty, reader_queue may contain peeked packets */
+	} while (timeo &&
+		 !__skb_wait_for_more_packets(sk, &sk->sk_receive_queue,
+					      &error, &timeo,
+					      (struct sk_buff *)sk_queue));
 
-	/* Copy the address. */
-	if (sin) {
-		sin->sin_family = AF_INET;
-		sin->sin_port = udp_hdr(skb)->source;
-		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
-		memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
-		msg->msg_namelen = sizeof(*sin);
+	*err = error;
+	return NULL;
+}
+EXPORT_SYMBOL(__skb_recv_udp);
 
-		BPF_CGROUP_RUN_PROG_UDP4_RECVMSG_LOCK(sk,
-						      (struct sockaddr *)sin,
-						      &msg->msg_namelen);
-	}
+int udp_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
+{
+	struct sk_buff *skb;
+	int err;
 
-	if (udp_test_bit(GRO_ENABLED, sk))
-		udp_cmsg_recv(msg, sk, skb);
+try_again:
+	skb = skb_recv_udp(sk, MSG_DONTWAIT, &err);
+	if (!skb)
+		return err;
 
-	if (inet_cmsg_flags(inet))
-		ip_cmsg_recv_offset(msg, sk, skb, sizeof(struct udphdr), off);
+	if (udp_lib_checksum_complete(skb)) {
+		struct net *net = sock_net(sk);
 
-	err = copied;
-	if (flags & MSG_TRUNC)
-		err = ulen;
+		__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
+		__UDP_INC_STATS(net, UDP_MIB_INERRORS);
+		udp_drops_inc(sk);
+		kfree_skb_reason(skb, SKB_DROP_REASON_UDP_CSUM);
+		goto try_again;
+	}
 
-	skb_consume_udp(sk, skb, peeking ? -err : err);
-	return err;
+	WARN_ON_ONCE(!skb_set_owner_sk_safe(skb, sk));
 
-csum_copy_err:
-	if (!__sk_queue_drop_skb(sk, &udp_sk(sk)->reader_queue, skb, flags,
-				 udp_skb_destructor)) {
-		UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
-		UDP_INC_STATS(net, UDP_MIB_INERRORS);
-	}
-	kfree_skb_reason(skb, SKB_DROP_REASON_UDP_CSUM);
+	/*
+	 * skb->dev still aliases the UDP rx dev_scratch (its charge was freed
+	 * on dequeue above); a sockmap verdict program may deref it via
+	 * bpf_sk_lookup_*(), so clear it -> bpf_skc_lookup() uses skb->sk
+	 */
+	skb->dev = NULL;
 
-	/* starting over for a new packet, but check if we need to yield */
-	cond_resched();
-	msg->msg_flags &= ~MSG_TRUNC;
-	goto try_again;
+	return recv_actor(sk, skb);
 }
 
 int udp_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
@@ -2166,19 +1049,6 @@ int udp_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 	return BPF_CGROUP_RUN_PROG_INET4_CONNECT_LOCK(sk, uaddr, &addr_len);
 }
 
-static int udp_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
-		       int addr_len)
-{
-	int res;
-
-	lock_sock(sk);
-	res = __ip4_datagram_connect(sk, uaddr, addr_len);
-	if (!res)
-		udp4_hash4(sk);
-	release_sock(sk);
-	return res;
-}
-
 int __udp_disconnect(struct sock *sk, int flags)
 {
 	struct inet_sock *inet = inet_sk(sk);
@@ -2337,155 +1207,6 @@ void udp_lib_rehash(struct sock *sk, u16 newhash, u16 newhash4)
 	}
 }
 
-static void udp_v4_rehash(struct sock *sk)
-{
-	u16 new_hash = ipv4_portaddr_hash(sock_net(sk),
-					  inet_sk(sk)->inet_rcv_saddr,
-					  inet_sk(sk)->inet_num);
-	u16 new_hash4 = udp_ehashfn(sock_net(sk),
-				    sk->sk_rcv_saddr, sk->sk_num,
-				    sk->sk_daddr, sk->sk_dport);
-
-	udp_lib_rehash(sk, new_hash, new_hash4);
-}
-
-static int __udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
-{
-	int rc;
-
-	if (inet_sk(sk)->inet_daddr) {
-		sock_rps_save_rxhash(sk, skb);
-		sk_mark_napi_id(sk, skb);
-		sk_incoming_cpu_update(sk);
-	} else {
-		sk_mark_napi_id_once(sk, skb);
-	}
-
-	rc = __udp_enqueue_schedule_skb(sk, skb);
-	if (rc < 0) {
-		struct net *net = sock_net(sk);
-		int drop_reason;
-
-		/* Note that an ENOMEM error is charged twice */
-		if (rc == -ENOMEM) {
-			UDP_INC_STATS(net, UDP_MIB_RCVBUFERRORS);
-			drop_reason = SKB_DROP_REASON_SOCKET_RCVBUFF;
-		} else {
-			UDP_INC_STATS(net, UDP_MIB_MEMERRORS);
-			drop_reason = SKB_DROP_REASON_PROTO_MEM;
-		}
-		UDP_INC_STATS(net, UDP_MIB_INERRORS);
-		trace_udp_fail_queue_rcv_skb(rc, sk, skb);
-		sk_skb_reason_drop(sk, skb, drop_reason);
-		return -1;
-	}
-
-	return 0;
-}
-
-/* returns:
- *  -1: error
- *   0: success
- *  >0: "udp encap" protocol resubmission
- *
- * Note that in the success and error cases, the skb is assumed to
- * have either been requeued or freed.
- */
-static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
-{
-	enum skb_drop_reason drop_reason = SKB_DROP_REASON_NOT_SPECIFIED;
-	struct udp_sock *up = udp_sk(sk);
-	struct net *net = sock_net(sk);
-
-	/*
-	 *	Charge it to the socket, dropping if the queue is full.
-	 */
-	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
-		drop_reason = SKB_DROP_REASON_XFRM_POLICY;
-		goto drop;
-	}
-	nf_reset_ct(skb);
-
-	if (static_branch_unlikely(&udp_encap_needed_key) &&
-	    READ_ONCE(up->encap_type)) {
-		int (*encap_rcv)(struct sock *sk, struct sk_buff *skb);
-
-		/*
-		 * This is an encapsulation socket so pass the skb to
-		 * the socket's udp_encap_rcv() hook. Otherwise, just
-		 * fall through and pass this up the UDP socket.
-		 * up->encap_rcv() returns the following value:
-		 * =0 if skb was successfully passed to the encap
-		 *    handler or was discarded by it.
-		 * >0 if skb should be passed on to UDP.
-		 * <0 if skb should be resubmitted as proto -N
-		 */
-
-		/* if we're overly short, let UDP handle it */
-		encap_rcv = READ_ONCE(up->encap_rcv);
-		if (encap_rcv) {
-			int ret;
-
-			/* Verify checksum before giving to encap */
-			if (udp_lib_checksum_complete(skb))
-				goto csum_error;
-
-			ret = encap_rcv(sk, skb);
-			if (ret <= 0) {
-				__UDP_INC_STATS(net, UDP_MIB_INDATAGRAMS);
-				return -ret;
-			}
-		}
-
-		/* FALLTHROUGH -- it's a UDP Packet */
-	}
-
-	prefetch(&sk->sk_rmem_alloc);
-	if (rcu_access_pointer(sk->sk_filter) &&
-	    udp_lib_checksum_complete(skb))
-			goto csum_error;
-
-	drop_reason = sk_filter_trim_cap(sk, skb, sizeof(struct udphdr));
-	if (drop_reason)
-		goto drop;
-
-	udp_csum_pull_header(skb);
-
-	ipv4_pktinfo_prepare(sk, skb, true);
-	return __udp_queue_rcv_skb(sk, skb);
-
-csum_error:
-	drop_reason = SKB_DROP_REASON_UDP_CSUM;
-	__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
-drop:
-	__UDP_INC_STATS(net, UDP_MIB_INERRORS);
-	udp_drops_inc(sk);
-	sk_skb_reason_drop(sk, skb, drop_reason);
-	return -1;
-}
-
-static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
-{
-	struct sk_buff *next, *segs;
-	int ret;
-
-	if (likely(!udp_unexpected_gso(sk, skb)))
-		return udp_queue_rcv_one_skb(sk, skb);
-
-	BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
-	__skb_push(skb, -skb_mac_offset(skb));
-	segs = udp_rcv_segment(sk, skb, true);
-	skb_list_walk_safe(segs, skb, next) {
-		__skb_pull(skb, skb_transport_offset(skb));
-
-		udp_post_segment_fix_csum(skb);
-		ret = udp_queue_rcv_one_skb(sk, skb);
-		if (ret > 0)
-			ip_protocol_deliver_rcu(dev_net(skb->dev), skb, ret);
-	}
-	return 0;
-}
-
 /* For TCP sockets, sk_rx_dst is protected by socket lock
  * For UDP, we use xchg() to guard against concurrent changes.
  */
@@ -2501,401 +1222,6 @@ bool udp_sk_rx_dst_set(struct sock *sk, struct dst_entry *dst)
 	return false;
 }
 
-/*
- *	Multicasts and broadcasts go to each listener.
- *
- *	Note: called only from the BH handler context.
- */
-static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
-				    struct udphdr  *uh,
-				    __be32 saddr, __be32 daddr)
-{
-	struct udp_table *udptable = net->ipv4.udp_table;
-	unsigned int hash2, hash2_any, offset;
-	unsigned short hnum = ntohs(uh->dest);
-	struct sock *sk, *first = NULL;
-	int dif = skb->dev->ifindex;
-	int sdif = inet_sdif(skb);
-	struct hlist_node *node;
-	struct udp_hslot *hslot;
-	struct sk_buff *nskb;
-	bool use_hash2;
-	int ret;
-
-	hash2_any = 0;
-	hash2 = 0;
-	hslot = udp_hashslot(udptable, net, hnum);
-	use_hash2 = hslot->count > 10;
-	offset = offsetof(typeof(*sk), sk_node);
-
-	if (use_hash2) {
-		hash2_any = ipv4_portaddr_hash(net, htonl(INADDR_ANY), hnum) &
-			    udptable->mask;
-		hash2 = ipv4_portaddr_hash(net, daddr, hnum) & udptable->mask;
-start_lookup:
-		hslot = &udptable->hash2[hash2].hslot;
-		offset = offsetof(typeof(*sk), __sk_common.skc_portaddr_node);
-	}
-
-	sk_for_each_entry_offset_rcu(sk, node, &hslot->head, offset) {
-		if (!__udp_is_mcast_sock(net, sk, uh->dest, daddr,
-					 uh->source, saddr, dif, sdif, hnum))
-			continue;
-
-		if (!first) {
-			first = sk;
-			continue;
-		}
-		nskb = skb_clone(skb, GFP_ATOMIC);
-
-		if (unlikely(!nskb)) {
-			udp_drops_inc(sk);
-			__UDP_INC_STATS(net, UDP_MIB_RCVBUFERRORS);
-			__UDP_INC_STATS(net, UDP_MIB_INERRORS);
-			continue;
-		}
-		if (udp_queue_rcv_skb(sk, nskb) > 0)
-			consume_skb(nskb);
-	}
-
-	/* Also lookup *:port if we are using hash2 and haven't done so yet. */
-	if (use_hash2 && hash2 != hash2_any) {
-		hash2 = hash2_any;
-		goto start_lookup;
-	}
-
-	if (first) {
-		ret = udp_queue_rcv_skb(first, skb);
-		if (ret > 0)
-			return -ret;
-	} else {
-		kfree_skb(skb);
-		__UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
-	}
-	return 0;
-}
-
-/* Initialize UDP checksum. If exited with zero value (success),
- * CHECKSUM_UNNECESSARY means, that no more checks are required.
- * Otherwise, csum completion requires checksumming packet body,
- * including udp header and folding it to skb->csum.
- */
-static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh)
-{
-	int err;
-
-	/* Note, we are only interested in != 0 or == 0, thus the
-	 * force to int.
-	 */
-	err = (__force int)skb_checksum_init_zero_check(skb, IPPROTO_UDP, uh->check,
-							inet_compute_pseudo);
-	if (err)
-		return err;
-
-	if (skb->ip_summed == CHECKSUM_COMPLETE && !skb->csum_valid) {
-		/* If SW calculated the value, we know it's bad */
-		if (skb->csum_complete_sw)
-			return 1;
-
-		/* HW says the value is bad. Let's validate that.
-		 * skb->csum is no longer the full packet checksum,
-		 * so don't treat it as such.
-		 */
-		skb_checksum_complete_unset(skb);
-	}
-
-	return 0;
-}
-
-/* wrapper for udp_queue_rcv_skb taking care of csum conversion and
- * return code conversion for ip layer consumption
- */
-static int udp_unicast_rcv_skb(struct sock *sk, struct sk_buff *skb,
-			       struct udphdr *uh)
-{
-	int ret;
-
-	if (inet_get_convert_csum(sk) && uh->check)
-		skb_checksum_try_convert(skb, IPPROTO_UDP, inet_compute_pseudo);
-
-	ret = udp_queue_rcv_skb(sk, skb);
-
-	/* a return value > 0 means to resubmit the input, but
-	 * it wants the return to be -protocol, or 0
-	 */
-	if (ret > 0)
-		return -ret;
-	return 0;
-}
-
-/*
- *	All we need to do is get the socket, and then do a checksum.
- */
-
-int udp_rcv(struct sk_buff *skb)
-{
-	struct rtable *rt = skb_rtable(skb);
-	struct net *net = dev_net(skb->dev);
-	struct sock *sk = NULL;
-	__be32 saddr, daddr;
-	unsigned int ulen;
-	struct udphdr *uh;
-	bool refcounted;
-	int drop_reason;
-
-	drop_reason = SKB_DROP_REASON_NOT_SPECIFIED;
-
-	/*
-	 *  Validate the packet.
-	 */
-	if (!pskb_may_pull(skb, sizeof(struct udphdr)))
-		goto drop;		/* No space for header. */
-
-	uh   = udp_hdr(skb);
-	ulen = udp_get_len(skb, uh, 0);
-	saddr = ip_hdr(skb)->saddr;
-	daddr = ip_hdr(skb)->daddr;
-
-	if (ulen > skb->len)
-		goto short_packet;
-
-	if (ulen < sizeof(*uh))
-		goto short_packet;
-
-	if (ulen < skb->len) {
-		if (pskb_trim_rcsum(skb, ulen))
-			goto short_packet;
-
-		uh = udp_hdr(skb);
-	}
-
-	if (udp4_csum_init(skb, uh))
-		goto csum_error;
-
-	sk = inet_steal_sock(net, skb, sizeof(struct udphdr), saddr, uh->source, daddr, uh->dest,
-			     &refcounted, udp_ehashfn);
-	if (IS_ERR(sk))
-		goto no_sk;
-
-	if (sk) {
-		struct dst_entry *dst = skb_dst(skb);
-		int ret;
-
-		if (unlikely(rcu_dereference(sk->sk_rx_dst) != dst))
-			udp_sk_rx_dst_set(sk, dst);
-
-		ret = udp_unicast_rcv_skb(sk, skb, uh);
-		if (refcounted)
-			sock_put(sk);
-		return ret;
-	}
-
-	if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
-		return __udp4_lib_mcast_deliver(net, skb, uh, saddr, daddr);
-
-	sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest);
-	if (sk)
-		return udp_unicast_rcv_skb(sk, skb, uh);
-no_sk:
-	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
-		goto drop;
-	nf_reset_ct(skb);
-
-	/* No socket. Drop packet silently, if checksum is wrong */
-	if (udp_lib_checksum_complete(skb))
-		goto csum_error;
-
-	drop_reason = SKB_DROP_REASON_NO_SOCKET;
-	__UDP_INC_STATS(net, UDP_MIB_NOPORTS);
-	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
-
-	/*
-	 * Hmm.  We got an UDP packet to a port to which we
-	 * don't wanna listen.  Ignore it.
-	 */
-	sk_skb_reason_drop(sk, skb, drop_reason);
-	return 0;
-
-short_packet:
-	drop_reason = SKB_DROP_REASON_PKT_TOO_SMALL;
-	net_dbg_ratelimited("UDP: short packet: From %pI4:%u %d/%d to %pI4:%u\n",
-			    &saddr, ntohs(uh->source),
-			    ulen, skb->len,
-			    &daddr, ntohs(uh->dest));
-	goto drop;
-
-csum_error:
-	/*
-	 * RFC1122: OK.  Discards the bad packet silently (as far as
-	 * the network is concerned, anyway) as per 4.1.3.4 (MUST).
-	 */
-	drop_reason = SKB_DROP_REASON_UDP_CSUM;
-	net_dbg_ratelimited("UDP: bad checksum. From %pI4:%u to %pI4:%u ulen %d\n",
-			    &saddr, ntohs(uh->source), &daddr, ntohs(uh->dest),
-			    ulen);
-	__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
-drop:
-	__UDP_INC_STATS(net, UDP_MIB_INERRORS);
-	sk_skb_reason_drop(sk, skb, drop_reason);
-	return 0;
-}
-
-/* We can only early demux multicast if there is a single matching socket.
- * If more than one socket found returns NULL
- */
-static struct sock *__udp4_lib_mcast_demux_lookup(struct net *net,
-						  __be16 loc_port, __be32 loc_addr,
-						  __be16 rmt_port, __be32 rmt_addr,
-						  int dif, int sdif)
-{
-	struct udp_table *udptable = net->ipv4.udp_table;
-	unsigned short hnum = ntohs(loc_port);
-	struct sock *sk, *result;
-	struct udp_hslot *hslot;
-	unsigned int slot;
-
-	slot = udp_hashfn(net, hnum, udptable->mask);
-	hslot = &udptable->hash[slot];
-
-	/* Do not bother scanning a too big list */
-	if (hslot->count > 10)
-		return NULL;
-
-	result = NULL;
-	sk_for_each_rcu(sk, &hslot->head) {
-		if (__udp_is_mcast_sock(net, sk, loc_port, loc_addr,
-					rmt_port, rmt_addr, dif, sdif, hnum)) {
-			if (result)
-				return NULL;
-			result = sk;
-		}
-	}
-
-	return result;
-}
-
-/* For unicast we should only early demux connected sockets or we can
- * break forwarding setups.  The chains here can be long so only check
- * if the first socket is an exact match and if not move on.
- */
-static struct sock *__udp4_lib_demux_lookup(struct net *net,
-					    __be16 loc_port, __be32 loc_addr,
-					    __be16 rmt_port, __be32 rmt_addr,
-					    int dif, int sdif)
-{
-	struct udp_table *udptable = net->ipv4.udp_table;
-	INET_ADDR_COOKIE(acookie, rmt_addr, loc_addr);
-	unsigned short hnum = ntohs(loc_port);
-	struct udp_hslot *hslot2;
-	unsigned int hash2;
-	__portpair ports;
-	struct sock *sk;
-
-	hash2 = ipv4_portaddr_hash(net, loc_addr, hnum);
-	hslot2 = udp_hashslot2(udptable, hash2);
-	ports = INET_COMBINED_PORTS(rmt_port, hnum);
-
-	udp_portaddr_for_each_entry_rcu(sk, &hslot2->head) {
-		if (inet_match(net, sk, acookie, ports, dif, sdif))
-			return sk;
-		/* Only check first socket in chain */
-		break;
-	}
-	return NULL;
-}
-
-enum skb_drop_reason udp_v4_early_demux(struct sk_buff *skb)
-{
-	struct net *net = dev_net(skb->dev);
-	struct in_device *in_dev = NULL;
-	const struct iphdr *iph;
-	const struct udphdr *uh;
-	struct sock *sk = NULL;
-	struct dst_entry *dst;
-	int dif = skb->dev->ifindex;
-	int sdif = inet_sdif(skb);
-	int ours;
-
-	/* validate the packet */
-	if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct udphdr)))
-		return SKB_NOT_DROPPED_YET;
-
-	iph = ip_hdr(skb);
-	uh = udp_hdr(skb);
-
-	if (skb->pkt_type == PACKET_MULTICAST) {
-		in_dev = __in_dev_get_rcu(skb->dev);
-
-		if (!in_dev)
-			return SKB_NOT_DROPPED_YET;
-
-		ours = ip_check_mc_rcu(in_dev, iph->daddr, iph->saddr,
-				       iph->protocol);
-		if (!ours)
-			return SKB_NOT_DROPPED_YET;
-
-		sk = __udp4_lib_mcast_demux_lookup(net, uh->dest, iph->daddr,
-						   uh->source, iph->saddr,
-						   dif, sdif);
-	} else if (skb->pkt_type == PACKET_HOST) {
-		sk = __udp4_lib_demux_lookup(net, uh->dest, iph->daddr,
-					     uh->source, iph->saddr, dif, sdif);
-	}
-
-	if (!sk)
-		return SKB_NOT_DROPPED_YET;
-
-	skb->sk = sk;
-	DEBUG_NET_WARN_ON_ONCE(sk_is_refcounted(sk));
-	skb->destructor = sock_pfree;
-	dst = rcu_dereference(sk->sk_rx_dst);
-
-	if (dst)
-		dst = dst_check(dst, 0);
-	if (dst) {
-		u32 itag = 0;
-
-		/* set noref for now.
-		 * any place which wants to hold dst has to call
-		 * dst_hold_safe()
-		 */
-		skb_dst_set_noref(skb, dst);
-
-		/* for unconnected multicast sockets we need to validate
-		 * the source on each packet
-		 */
-		if (!inet_sk(sk)->inet_daddr && in_dev)
-			return ip_mc_validate_source(skb, iph->daddr,
-						     iph->saddr,
-						     ip4h_dscp(iph),
-						     skb->dev, in_dev, &itag);
-	}
-	return SKB_NOT_DROPPED_YET;
-}
-
-static void udp_destroy_sock(struct sock *sk)
-{
-	struct udp_sock *up = udp_sk(sk);
-	bool slow = lock_sock_fast(sk);
-
-	/* protects from races with udp_abort() */
-	sock_set_flag(sk, SOCK_DEAD);
-	udp_flush_pending_frames(sk);
-	unlock_sock_fast(sk, slow);
-	if (static_branch_unlikely(&udp_encap_needed_key)) {
-		if (up->encap_type) {
-			void (*encap_destroy)(struct sock *sk);
-			encap_destroy = READ_ONCE(up->encap_destroy);
-			if (encap_destroy)
-				encap_destroy(sk);
-		}
-		if (udp_test_bit(ENCAP_ENABLED, sk)) {
-			static_branch_dec(&udp_encap_needed_key);
-			udp_tunnel_cleanup_gro(sk);
-		}
-	}
-}
-
 typedef struct sk_buff *(*udp_gro_receive_t)(struct sock *sk,
 					     struct list_head *head,
 					     struct sk_buff *skb);
@@ -3031,16 +1357,6 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,
 	return err;
 }
 
-static int udp_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
-			  unsigned int optlen)
-{
-	if (level == SOL_UDP || level == SOL_SOCKET)
-		return udp_lib_setsockopt(sk, level, optname,
-					  optval, optlen,
-					  udp_push_pending_frames);
-	return ip_setsockopt(sk, level, optname, optval, optlen);
-}
-
 int udp_lib_getsockopt(struct sock *sk, int level, int optname,
 		       sockopt_t *opt)
 {
@@ -3089,34 +1405,6 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname,
 	return 0;
 }
 
-static int udp_getsockopt(struct sock *sk, int level, int optname,
-			  char __user *optval, int __user *optlen)
-{
-	sockopt_t opt;
-	int err;
-
-	/*
-	 * keep the old __user pointers, until ip_getsockopt() moves
-	 * to sockopt_t
-	 */
-	if (level != SOL_UDP)
-		return ip_getsockopt(sk, level, optname, optval, optlen);
-
-	err = sockopt_init_user(&opt, optval, optlen);
-	if (err)
-		return err;
-
-	err = udp_lib_getsockopt(sk, level, optname, &opt);
-	if (err)
-		return err;
-
-	/* optval was written by copy_to_iter() in udp_lib_getsockopt() */
-	if (put_user(opt.optlen, optlen))
-		return -EFAULT;
-
-	return 0;
-}
-
 /**
  * 	udp_poll - wait for a UDP event.
  *	@file: - file struct
@@ -3173,41 +1461,6 @@ int udp_abort(struct sock *sk, int err)
 	return 0;
 }
 
-struct proto udp_prot = {
-	.name			= "UDP",
-	.owner			= THIS_MODULE,
-	.close			= udp_lib_close,
-	.pre_connect		= udp_pre_connect,
-	.connect		= udp_connect,
-	.disconnect		= udp_disconnect,
-	.ioctl			= udp_ioctl,
-	.init			= udp_init_sock,
-	.destroy		= udp_destroy_sock,
-	.setsockopt		= udp_setsockopt,
-	.getsockopt		= udp_getsockopt,
-	.sendmsg		= udp_sendmsg,
-	.recvmsg		= udp_recvmsg,
-	.splice_eof		= udp_splice_eof,
-	.release_cb		= ip4_datagram_release_cb,
-	.hash			= udp_lib_hash,
-	.unhash			= udp_lib_unhash,
-	.rehash			= udp_v4_rehash,
-	.get_port		= udp_v4_get_port,
-	.put_port		= udp_lib_unhash,
-#ifdef CONFIG_BPF_SYSCALL
-	.psock_update_sk_prot	= udp_bpf_update_proto,
-#endif
-	.memory_allocated	= &net_aligned_data.udp_memory_allocated,
-	.per_cpu_fw_alloc	= &udp_memory_per_cpu_fw_alloc,
-
-	.sysctl_mem		= sysctl_udp_mem,
-	.sysctl_wmem_offset	= offsetof(struct net, ipv4.sysctl_udp_wmem_min),
-	.sysctl_rmem_offset	= offsetof(struct net, ipv4.sysctl_udp_rmem_min),
-	.obj_size		= sizeof(struct udp_sock),
-	.diag_destroy		= udp_abort,
-};
-EXPORT_SYMBOL(udp_prot);
-
 /* ------------------------------------------------------------------------ */
 #ifdef CONFIG_PROC_FS
 
diff --git a/net/ipv4/udp_bpf.c b/net/ipv4/udp_bpf.c
index ad57c4c9eaab..a5e9e6daad3a 100644
--- a/net/ipv4/udp_bpf.c
+++ b/net/ipv4/udp_bpf.c
@@ -16,6 +16,8 @@ static int sk_udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 	if (sk->sk_family == AF_INET6)
 		return udpv6_prot_saved->recvmsg(sk, msg, len, flags);
 #endif
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EAFNOSUPPORT;
 	return udp_prot.recvmsg(sk, msg, len, flags);
 }
 
@@ -155,7 +157,8 @@ static void udp_bpf_check_v6_needs_rebuild(struct proto *ops)
 
 static int __init udp_bpf_v4_build_proto(void)
 {
-	udp_bpf_rebuild_protos(&udp_bpf_prots[UDP_BPF_IPV4], &udp_prot);
+	if (IS_ENABLED(CONFIG_IPV4))
+		udp_bpf_rebuild_protos(&udp_bpf_prots[UDP_BPF_IPV4], &udp_prot);
 	return 0;
 }
 late_initcall(udp_bpf_v4_build_proto);
diff --git a/net/ipv4/udp_ipv4.c b/net/ipv4/udp_ipv4.c
new file mode 100644
index 000000000000..893e055f23ab
--- /dev/null
+++ b/net/ipv4/udp_ipv4.c
@@ -0,0 +1,1790 @@
+// 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.
+ *
+ *		The User Datagram Protocol (UDP).
+ *
+ *		IPv4 specific functions
+ *
+ *		code split from:
+ *		net/ipv4/udp.c
+ *
+ *		See udp.c for author information
+ */
+
+#define pr_fmt(fmt) "UDP: " fmt
+
+#include <linux/bpf-cgroup.h>
+#include <linux/igmp.h>
+#include <linux/inetdevice.h>
+#include <linux/socket.h>
+#include <linux/types.h>
+#include <net/aligned_data.h>
+#include <net/busy_poll.h>
+#include <net/gro.h>
+#include <net/icmp.h>
+#include <net/inet_common.h>
+#include <net/inet_hashtables.h>
+#include <net/ip_tunnels.h>
+#include <net/sock_reuseport.h>
+#include <net/udp.h>
+#include <net/udp_tunnel.h>
+#include <net/xfrm.h>
+#include <trace/events/udp.h>
+
+static __always_inline int
+compute_score(struct sock *sk, const struct net *net,
+	      __be32 saddr, __be16 sport, __be32 daddr,
+	      unsigned short hnum, int dif, int sdif)
+{
+	int score;
+	struct inet_sock *inet;
+	bool dev_match;
+
+	if (!net_eq(sock_net(sk), net) ||
+	    udp_sk(sk)->udp_port_hash != hnum ||
+	    ipv6_only_sock(sk))
+		return -1;
+
+	if (sk->sk_rcv_saddr != daddr)
+		return -1;
+
+	score = (sk->sk_family == PF_INET) ? 2 : 1;
+
+	inet = inet_sk(sk);
+	if (inet->inet_daddr) {
+		if (inet->inet_daddr != saddr)
+			return -1;
+		score += 4;
+	}
+
+	if (inet->inet_dport) {
+		if (inet->inet_dport != sport)
+			return -1;
+		score += 4;
+	}
+
+	dev_match = udp_sk_bound_dev_eq(net, sk->sk_bound_dev_if,
+					dif, sdif);
+	if (!dev_match)
+		return -1;
+	if (sk->sk_bound_dev_if)
+		score += 4;
+
+	if (READ_ONCE(sk->sk_incoming_cpu) == raw_smp_processor_id())
+		score++;
+	return score;
+}
+
+/**
+ * udp4_lib_lookup1() - Simplified lookup using primary hash (destination port)
+ * @net:	Network namespace
+ * @saddr:	Source address, network order
+ * @sport:	Source port, network order
+ * @daddr:	Destination address, network order
+ * @hnum:	Destination port, host order
+ * @dif:	Destination interface index
+ * @sdif:	Destination bridge port index, if relevant
+ * @udptable:	Set of UDP hash tables
+ *
+ * Simplified lookup to be used as fallback if no sockets are found due to a
+ * potential race between (receive) address change, and lookup happening before
+ * the rehash operation. This function ignores SO_REUSEPORT groups while scoring
+ * result sockets, because if we have one, we don't need the fallback at all.
+ *
+ * Called under rcu_read_lock().
+ *
+ * Return: socket with highest matching score if any, NULL if none
+ */
+static struct sock *udp4_lib_lookup1(const struct net *net,
+				     __be32 saddr, __be16 sport,
+				     __be32 daddr, unsigned int hnum,
+				     int dif, int sdif,
+				     const struct udp_table *udptable)
+{
+	unsigned int slot = udp_hashfn(net, hnum, udptable->mask);
+	struct udp_hslot *hslot = &udptable->hash[slot];
+	struct sock *sk, *result = NULL;
+	int score, badness = 0;
+
+	sk_for_each_rcu(sk, &hslot->head) {
+		score = compute_score(sk, net,
+				      saddr, sport, daddr, hnum, dif, sdif);
+		if (score > badness) {
+			result = sk;
+			badness = score;
+		}
+	}
+
+	return result;
+}
+
+/* called with rcu_read_lock() */
+static struct sock *udp4_lib_lookup2(const struct net *net,
+				     __be32 saddr, __be16 sport,
+				     __be32 daddr, unsigned int hnum,
+				     int dif, int sdif,
+				     struct udp_hslot *hslot2,
+				     struct sk_buff *skb)
+{
+	struct sock *sk, *result;
+	int score, badness;
+	bool need_rescore;
+
+	result = NULL;
+	badness = 0;
+	udp_portaddr_for_each_entry_rcu(sk, &hslot2->head) {
+		need_rescore = false;
+rescore:
+		score = compute_score(need_rescore ? result : sk, net, saddr,
+				      sport, daddr, hnum, dif, sdif);
+		if (score > badness) {
+			badness = score;
+
+			if (need_rescore)
+				continue;
+
+			if (sk->sk_state == TCP_ESTABLISHED) {
+				result = sk;
+				continue;
+			}
+
+			result = inet_lookup_reuseport(net, sk, skb, sizeof(struct udphdr),
+						       saddr, sport, daddr, hnum, udp_ehashfn);
+			if (!result) {
+				result = sk;
+				continue;
+			}
+
+			/* Fall back to scoring if group has connections */
+			if (!reuseport_has_conns(sk))
+				return result;
+
+			/* Reuseport logic returned an error, keep original score. */
+			if (IS_ERR(result))
+				continue;
+
+			/* compute_score is too long of a function to be
+			 * inlined twice here, and calling it uninlined
+			 * here yields measurable overhead for some
+			 * workloads. Work around it by jumping
+			 * backwards to rescore 'result'.
+			 */
+			need_rescore = true;
+			goto rescore;
+		}
+	}
+	return result;
+}
+
+#if IS_ENABLED(CONFIG_BASE_SMALL)
+static struct sock *udp4_lib_lookup4(const struct net *net,
+				     __be32 saddr, __be16 sport,
+				     __be32 daddr, unsigned int hnum,
+				     int dif, int sdif,
+				     struct udp_table *udptable)
+{
+	return NULL;
+}
+#else /* !CONFIG_BASE_SMALL */
+static struct sock *udp4_lib_lookup4(const struct net *net,
+				     __be32 saddr, __be16 sport,
+				     __be32 daddr, unsigned int hnum,
+				     int dif, int sdif,
+				     struct udp_table *udptable)
+{
+	const __portpair ports = INET_COMBINED_PORTS(sport, hnum);
+	const struct hlist_nulls_node *node;
+	struct udp_hslot *hslot4;
+	unsigned int hash4, slot;
+	struct udp_sock *up;
+	struct sock *sk;
+
+	hash4 = udp_ehashfn(net, daddr, hnum, saddr, sport);
+	slot = hash4 & udptable->mask;
+	hslot4 = &udptable->hash4[slot];
+	INET_ADDR_COOKIE(acookie, saddr, daddr);
+
+begin:
+	/* SLAB_TYPESAFE_BY_RCU not used, so we don't need to touch sk_refcnt */
+	udp_lrpa_for_each_entry_rcu(up, node, &hslot4->nulls_head) {
+		sk = (struct sock *)up;
+		if (inet_match(net, sk, acookie, ports, dif, sdif))
+			return sk;
+	}
+
+	/* if the nulls value we got at the end of this lookup is not the
+	 * expected one, we must restart lookup. We probably met an item that
+	 * was moved to another chain due to rehash.
+	 */
+	if (get_nulls_value(node) != slot)
+		goto begin;
+
+	return NULL;
+}
+
+/* call with sock lock */
+void udp4_hash4(struct sock *sk)
+{
+	struct net *net = sock_net(sk);
+	unsigned int hash;
+
+	if (sk_unhashed(sk) || sk->sk_rcv_saddr == htonl(INADDR_ANY))
+		return;
+
+	hash = udp_ehashfn(net, sk->sk_rcv_saddr, sk->sk_num,
+			   sk->sk_daddr, sk->sk_dport);
+
+	udp_lib_hash4(sk, hash);
+}
+#endif /* CONFIG_BASE_SMALL */
+
+/* UDP is nearly always wildcards out the wazoo, it makes no sense to try
+ * harder than this. -DaveM
+ */
+struct sock *__udp4_lib_lookup(const struct net *net, __be32 saddr,
+			       __be16 sport, __be32 daddr, __be16 dport,
+			       int dif, int sdif, struct sk_buff *skb)
+{
+	struct udp_table *udptable = net->ipv4.udp_table;
+	unsigned short hnum = ntohs(dport);
+	struct udp_hslot *hslot2;
+	struct sock *result, *sk;
+	unsigned int hash2;
+
+	hash2 = ipv4_portaddr_hash(net, daddr, hnum);
+	hslot2 = udp_hashslot2(udptable, hash2);
+
+	if (udp_has_hash4(hslot2)) {
+		result = udp4_lib_lookup4(net, saddr, sport, daddr, hnum,
+					  dif, sdif, udptable);
+		if (result) /* udp4_lib_lookup4 return sk or NULL */
+			return result;
+	}
+
+	/* Lookup connected or non-wildcard socket */
+	result = udp4_lib_lookup2(net, saddr, sport,
+				  daddr, hnum, dif, sdif,
+				  hslot2, skb);
+	if (!IS_ERR_OR_NULL(result) && result->sk_state == TCP_ESTABLISHED)
+		goto done;
+
+	/* Lookup redirect from BPF */
+	if (static_branch_unlikely(&bpf_sk_lookup_enabled)) {
+		sk = inet_lookup_run_sk_lookup(net, IPPROTO_UDP, skb, sizeof(struct udphdr),
+					       saddr, sport, daddr, hnum, dif,
+					       udp_ehashfn);
+		if (sk) {
+			result = sk;
+			goto done;
+		}
+	}
+
+	/* Got non-wildcard socket or error on first lookup */
+	if (result)
+		goto done;
+
+	/* Lookup wildcard sockets */
+	hash2 = ipv4_portaddr_hash(net, htonl(INADDR_ANY), hnum);
+	hslot2 = udp_hashslot2(udptable, hash2);
+
+	result = udp4_lib_lookup2(net, saddr, sport,
+				  htonl(INADDR_ANY), hnum, dif, sdif,
+				  hslot2, skb);
+	if (!IS_ERR_OR_NULL(result))
+		goto done;
+
+	/* Primary hash (destination port) lookup as fallback for this race:
+	 *   1. __ip4_datagram_connect() sets sk_rcv_saddr
+	 *   2. lookup (this function): new sk_rcv_saddr, hashes not updated yet
+	 *   3. rehash operation updating _secondary and four-tuple_ hashes
+	 * The primary hash doesn't need an update after 1., so, thanks to this
+	 * further step, 1. and 3. don't need to be atomic against the lookup.
+	 */
+	result = udp4_lib_lookup1(net, saddr, sport, daddr, hnum, dif, sdif,
+				  udptable);
+
+done:
+	if (IS_ERR(result))
+		return NULL;
+	return result;
+}
+EXPORT_SYMBOL_GPL(__udp4_lib_lookup);
+
+static inline struct sock *__udp4_lib_lookup_skb(struct sk_buff *skb,
+						 __be16 sport, __be16 dport)
+{
+	const struct iphdr *iph = ip_hdr(skb);
+
+	return __udp4_lib_lookup(dev_net(skb->dev), iph->saddr, sport,
+				 iph->daddr, dport, inet_iif(skb),
+				 inet_sdif(skb), skb);
+}
+
+struct sock *udp4_lib_lookup_skb(const struct sk_buff *skb,
+				 __be16 sport, __be16 dport)
+{
+	const u16 offset = NAPI_GRO_CB(skb)->network_offsets[skb->encapsulation];
+	const struct iphdr *iph = (struct iphdr *)(skb->data + offset);
+	int iif, sdif;
+
+	inet_get_iif_sdif(skb, &iif, &sdif);
+
+	return __udp4_lib_lookup(dev_net(skb->dev), iph->saddr, sport,
+				 iph->daddr, dport, iif, sdif, NULL);
+}
+
+/* Must be called under rcu_read_lock().
+ * Does increment socket refcount.
+ */
+#if IS_ENABLED(CONFIG_NF_TPROXY_IPV4) || IS_ENABLED(CONFIG_NF_SOCKET_IPV4)
+struct sock *udp4_lib_lookup(const struct net *net, __be32 saddr, __be16 sport,
+			     __be32 daddr, __be16 dport, int dif)
+{
+	struct sock *sk;
+
+	sk = __udp4_lib_lookup(net, saddr, sport, daddr, dport, dif, 0, NULL);
+	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
+		sk = NULL;
+	return sk;
+}
+EXPORT_SYMBOL_GPL(udp4_lib_lookup);
+#endif
+
+static int udp_v4_get_port(struct sock *sk, unsigned short snum)
+{
+	unsigned int hash2_nulladdr =
+		ipv4_portaddr_hash(sock_net(sk), htonl(INADDR_ANY), snum);
+	unsigned int hash2_partial =
+		ipv4_portaddr_hash(sock_net(sk), inet_sk(sk)->inet_rcv_saddr, 0);
+
+	/* precompute partial secondary hash */
+	udp_sk(sk)->udp_portaddr_hash = hash2_partial;
+	return udp_lib_get_port(sk, snum, hash2_nulladdr);
+}
+
+static void udp_destruct_sock(struct sock *sk)
+{
+	udp_destruct_common(sk);
+	inet_sock_destruct(sk);
+}
+
+static int udp_init_sock(struct sock *sk)
+{
+	int res = udp_lib_init_sock(sk);
+
+	sk->sk_destruct = udp_destruct_sock;
+	set_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags);
+	return res;
+}
+
+/**
+ * udp4_hwcsum  -  handle outgoing HW checksumming
+ * @skb: sk_buff containing the filled-in UDP header
+ *       (checksum field must be zeroed out)
+ * @src: source IP address
+ * @dst: destination IP address
+ */
+void udp4_hwcsum(struct sk_buff *skb, __be32 src, __be32 dst)
+{
+	struct udphdr *uh = udp_hdr(skb);
+	int offset = skb_transport_offset(skb);
+	int len = skb->len - offset;
+	int hlen = len;
+	__wsum csum = 0;
+
+	if (!skb_has_frag_list(skb)) {
+		/*
+		 * Only one fragment on the socket.
+		 */
+		skb->csum_start = skb_transport_header(skb) - skb->head;
+		skb->csum_offset = offsetof(struct udphdr, check);
+		uh->check = ~csum_tcpudp_magic(src, dst, len,
+					       IPPROTO_UDP, 0);
+	} else {
+		struct sk_buff *frags;
+
+		/*
+		 * HW-checksum won't work as there are two or more
+		 * fragments on the socket so that all csums of sk_buffs
+		 * should be together
+		 */
+		skb_walk_frags(skb, frags) {
+			csum = csum_add(csum, frags->csum);
+			hlen -= frags->len;
+		}
+
+		csum = skb_checksum(skb, offset, hlen, csum);
+		skb->ip_summed = CHECKSUM_NONE;
+
+		uh->check = csum_tcpudp_magic(src, dst, len, IPPROTO_UDP, csum);
+		if (uh->check == 0)
+			uh->check = CSUM_MANGLED_0;
+	}
+}
+EXPORT_SYMBOL_GPL(udp4_hwcsum);
+
+static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
+			struct inet_cork *cork)
+{
+	struct sock *sk = skb->sk;
+	int offset, len, datalen;
+	struct udphdr *uh;
+	int err;
+
+	offset = skb_transport_offset(skb);
+	len = skb->len - offset;
+	datalen = len - sizeof(*uh);
+
+	/*
+	 * Create a UDP header
+	 */
+	uh = udp_hdr(skb);
+	uh->source = inet_sk(sk)->inet_sport;
+	uh->dest = fl4->fl4_dport;
+	/* Datagram length checked in udp_sendmsg. */
+	udp_set_len_short(uh, len);
+	uh->check = 0;
+
+	if (cork->gso_size) {
+		const int hlen = skb_network_header_len(skb) +
+				 sizeof(struct udphdr);
+
+		if (hlen + min(datalen, cork->gso_size) > cork->fragsize) {
+			kfree_skb(skb);
+			return -EMSGSIZE;
+		}
+		if (datalen > cork->gso_size * UDP_MAX_SEGMENTS) {
+			kfree_skb(skb);
+			return -EINVAL;
+		}
+		if (sk->sk_no_check_tx) {
+			kfree_skb(skb);
+			return -EINVAL;
+		}
+		if (dst_xfrm(skb_dst(skb))) {
+			kfree_skb(skb);
+			return -EIO;
+		}
+
+		if (datalen > cork->gso_size) {
+			skb_shinfo(skb)->gso_size = cork->gso_size;
+			skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
+			skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(datalen,
+								 cork->gso_size);
+
+			/* Don't checksum the payload, skb will get segmented */
+			goto csum_partial;
+		}
+	}
+
+	if (sk->sk_no_check_tx) {			 /* UDP csum off */
+		skb->ip_summed = CHECKSUM_NONE;
+		goto send;
+	} else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
+csum_partial:
+		udp4_hwcsum(skb, fl4->saddr, fl4->daddr);
+		goto send;
+	}
+
+	/* add protocol-dependent pseudo-header */
+	uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len,
+				      IPPROTO_UDP, udp_csum(skb));
+	if (uh->check == 0)
+		uh->check = CSUM_MANGLED_0;
+
+send:
+	err = ip_send_skb(sock_net(sk), skb);
+	if (unlikely(err)) {
+		if (err == -ENOBUFS &&
+		    !inet_test_bit(RECVERR, sk)) {
+			UDP_INC_STATS(sock_net(sk), UDP_MIB_SNDBUFERRORS);
+			err = 0;
+		}
+	} else {
+		UDP_INC_STATS(sock_net(sk), UDP_MIB_OUTDATAGRAMS);
+	}
+	return err;
+}
+
+/*
+ * Push out all pending data as one UDP datagram. Socket is locked.
+ */
+int udp_push_pending_frames(struct sock *sk)
+{
+	struct udp_sock  *up = udp_sk(sk);
+	struct inet_sock *inet = inet_sk(sk);
+	struct flowi4 *fl4 = &inet->cork.fl.u.ip4;
+	struct sk_buff *skb;
+	int err = 0;
+
+	skb = ip_finish_skb(sk, fl4);
+	if (!skb)
+		goto out;
+
+	err = udp_send_skb(skb, fl4, &inet->cork.base);
+
+out:
+	up->len = 0;
+	WRITE_ONCE(up->pending, 0);
+	return err;
+}
+
+void udp_splice_eof(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+	struct udp_sock *up = udp_sk(sk);
+
+	if (!READ_ONCE(up->pending) || udp_test_bit(CORK, sk))
+		return;
+
+	lock_sock(sk);
+	if (up->pending && !udp_test_bit(CORK, sk))
+		udp_push_pending_frames(sk);
+	release_sock(sk);
+}
+
+/* Handler for tunnels with arbitrary destination ports: no socket lookup, go
+ * through error handlers in encapsulations looking for a match.
+ */
+static int __udp4_lib_err_encap_no_sk(struct sk_buff *skb, u32 info)
+{
+	int i;
+
+	for (i = 0; i < MAX_IPTUN_ENCAP_OPS; i++) {
+		int (*handler)(struct sk_buff *skb, u32 info);
+		const struct ip_tunnel_encap_ops *encap;
+
+		encap = rcu_dereference(iptun_encaps[i]);
+		if (!encap)
+			continue;
+		handler = encap->err_handler;
+		if (handler && !handler(skb, info))
+			return 0;
+	}
+
+	return -ENOENT;
+}
+
+/* Try to match ICMP errors to UDP tunnels by looking up a socket without
+ * reversing source and destination port: this will match tunnels that force the
+ * same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that
+ * lwtunnels might actually break this assumption by being configured with
+ * different destination ports on endpoints, in this case we won't be able to
+ * trace ICMP messages back to them.
+ *
+ * If this doesn't match any socket, probe tunnels with arbitrary destination
+ * ports (e.g. FoU, GUE): there, the receiving socket is useless, as the port
+ * we've sent packets to won't necessarily match the local destination port.
+ *
+ * Then ask the tunnel implementation to match the error against a valid
+ * association.
+ *
+ * Return an error if we can't find a match, the socket if we need further
+ * processing, zero otherwise.
+ */
+static struct sock *__udp4_lib_err_encap(struct net *net,
+					 const struct iphdr *iph,
+					 struct udphdr *uh,
+					 struct sock *sk,
+					 struct sk_buff *skb, u32 info)
+{
+	int (*lookup)(struct sock *sk, struct sk_buff *skb);
+	int network_offset, transport_offset;
+	struct udp_sock *up;
+
+	network_offset = skb_network_offset(skb);
+	transport_offset = skb_transport_offset(skb);
+
+	/* Network header needs to point to the outer IPv4 header inside ICMP */
+	skb_reset_network_header(skb);
+
+	/* Transport header needs to point to the UDP header */
+	skb_set_transport_header(skb, iph->ihl << 2);
+
+	if (sk) {
+		up = udp_sk(sk);
+
+		lookup = READ_ONCE(up->encap_err_lookup);
+		if (lookup && lookup(sk, skb))
+			sk = NULL;
+
+		goto out;
+	}
+
+	sk = __udp4_lib_lookup(net, iph->daddr, uh->source,
+			       iph->saddr, uh->dest, skb->dev->ifindex, 0, NULL);
+	if (sk) {
+		up = udp_sk(sk);
+
+		lookup = READ_ONCE(up->encap_err_lookup);
+		if (!lookup || lookup(sk, skb))
+			sk = NULL;
+	}
+
+out:
+	if (!sk)
+		sk = ERR_PTR(__udp4_lib_err_encap_no_sk(skb, info));
+
+	skb_set_transport_header(skb, transport_offset);
+	skb_set_network_header(skb, network_offset);
+
+	return sk;
+}
+
+static void udp_err_update_exception(struct net *net, struct sk_buff *skb,
+				     int type, int code, u32 info)
+{
+	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
+		ipv4_update_pmtu(skb, net, info, 0, IPPROTO_UDP);
+	else if (type == ICMP_REDIRECT)
+		ipv4_redirect(skb, net, 0, IPPROTO_UDP);
+}
+
+/*
+ * This routine is called by the ICMP module when it gets some
+ * sort of error condition.  If err < 0 then the socket should
+ * be closed and the error returned to the user.  If err > 0
+ * it's just the icmp type << 8 | icmp code.
+ * Header points to the ip header of the error packet. We move
+ * on past this. Then (as it used to claim before adjustment)
+ * header points to the first 8 bytes of the udp header.  We need
+ * to find the appropriate port.
+ */
+int udp_err(struct sk_buff *skb, u32 info)
+{
+	const struct iphdr *iph = (const struct iphdr *)skb->data;
+	const int type = icmp_hdr(skb)->type;
+	const int code = icmp_hdr(skb)->code;
+	struct net *net = dev_net(skb->dev);
+	struct inet_sock *inet;
+	bool tunnel = false;
+	struct udphdr *uh;
+	struct sock *sk;
+	int harderr;
+	int err;
+
+	udp_err_update_exception(net, skb, type, code, info);
+
+	uh = (struct udphdr *)(skb->data + (iph->ihl << 2));
+	sk = __udp4_lib_lookup(net, iph->daddr, uh->dest,
+			       iph->saddr, uh->source, skb->dev->ifindex,
+			       inet_sdif(skb), NULL);
+
+	if (!sk || READ_ONCE(udp_sk(sk)->encap_type)) {
+		/* No socket for error: try tunnels before discarding */
+		if (static_branch_unlikely(&udp_encap_needed_key)) {
+			sk = __udp4_lib_err_encap(net, iph, uh, sk, skb, info);
+			if (!sk)
+				return 0;
+		} else {
+			sk = ERR_PTR(-ENOENT);
+		}
+
+		if (IS_ERR(sk)) {
+			__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
+			return PTR_ERR(sk);
+		}
+
+		tunnel = true;
+	}
+
+	err = 0;
+	harderr = 0;
+	inet = inet_sk(sk);
+
+	switch (type) {
+	default:
+	case ICMP_TIME_EXCEEDED:
+		err = EHOSTUNREACH;
+		break;
+	case ICMP_SOURCE_QUENCH:
+		goto out;
+	case ICMP_PARAMETERPROB:
+		err = EPROTO;
+		harderr = 1;
+		break;
+	case ICMP_DEST_UNREACH:
+		if (code == ICMP_FRAG_NEEDED) { /* Path MTU discovery */
+			ipv4_sk_update_pmtu(skb, sk, info);
+			if (READ_ONCE(inet->pmtudisc) != IP_PMTUDISC_DONT) {
+				err = EMSGSIZE;
+				harderr = 1;
+				break;
+			}
+			goto out;
+		}
+		err = EHOSTUNREACH;
+		if (code <= NR_ICMP_UNREACH) {
+			harderr = icmp_err_convert[code].fatal;
+			err = icmp_err_convert[code].errno;
+		}
+		break;
+	case ICMP_REDIRECT:
+		ipv4_sk_redirect(skb, sk);
+		goto out;
+	}
+
+	/*
+	 *      RFC1122: OK.  Passes ICMP errors back to application, as per
+	 *	4.1.3.3.
+	 */
+	if (tunnel) {
+		/* ...not for tunnels though: we don't have a sending socket */
+		if (udp_sk(sk)->encap_err_rcv)
+			udp_sk(sk)->encap_err_rcv(sk, skb, err, uh->dest, info,
+						  (u8 *)(uh + 1));
+		goto out;
+	}
+	if (!inet_test_bit(RECVERR, sk)) {
+		if (!harderr || sk->sk_state != TCP_ESTABLISHED)
+			goto out;
+	} else {
+		ip_icmp_error(sk, skb, err, uh->dest, info, (u8 *)(uh + 1));
+	}
+
+	sk->sk_err = err;
+	sk_error_report(sk);
+out:
+	return 0;
+}
+
+static int __udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
+{
+	int rc;
+
+	if (inet_sk(sk)->inet_daddr) {
+		sock_rps_save_rxhash(sk, skb);
+		sk_mark_napi_id(sk, skb);
+		sk_incoming_cpu_update(sk);
+	} else {
+		sk_mark_napi_id_once(sk, skb);
+	}
+
+	rc = __udp_enqueue_schedule_skb(sk, skb);
+	if (rc < 0) {
+		struct net *net = sock_net(sk);
+		int drop_reason;
+
+		/* Note that an ENOMEM error is charged twice */
+		if (rc == -ENOMEM) {
+			UDP_INC_STATS(net, UDP_MIB_RCVBUFERRORS);
+			drop_reason = SKB_DROP_REASON_SOCKET_RCVBUFF;
+		} else {
+			UDP_INC_STATS(net, UDP_MIB_MEMERRORS);
+			drop_reason = SKB_DROP_REASON_PROTO_MEM;
+		}
+		UDP_INC_STATS(net, UDP_MIB_INERRORS);
+		trace_udp_fail_queue_rcv_skb(rc, sk, skb);
+		sk_skb_reason_drop(sk, skb, drop_reason);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * This should be easy, if there is something there we
+ * return it, otherwise we block.
+ */
+
+INDIRECT_CALLABLE_SCOPE
+int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
+{
+	DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
+	int off, err, peeking = flags & MSG_PEEK;
+	struct inet_sock *inet = inet_sk(sk);
+	struct net *net = sock_net(sk);
+	bool checksum_valid = false;
+	unsigned int ulen, copied;
+	struct sk_buff *skb;
+
+	if (flags & MSG_ERRQUEUE)
+		return ip_recv_error(sk, msg, len);
+
+try_again:
+	off = sk_peek_offset(sk, flags);
+	skb = __skb_recv_udp(sk, flags, &off, &err);
+	if (!skb)
+		return err;
+
+	ulen = udp_skb_len(skb);
+	copied = len;
+	if (copied > ulen - off)
+		copied = ulen - off;
+	else if (copied < ulen)
+		msg->msg_flags |= MSG_TRUNC;
+
+	/* If checksum is needed at all, try to do it while copying the
+	 * data.  If the data is truncated, do it before the copy.
+	 */
+	if (copied < ulen || peeking) {
+		checksum_valid = udp_skb_csum_unnecessary(skb) ||
+				!__udp_lib_checksum_complete(skb);
+		if (!checksum_valid)
+			goto csum_copy_err;
+	}
+
+	if (checksum_valid || udp_skb_csum_unnecessary(skb)) {
+		if (udp_skb_is_linear(skb))
+			err = copy_linear_skb(skb, copied, off, &msg->msg_iter);
+		else
+			err = skb_copy_datagram_msg(skb, off, msg, copied);
+	} else {
+		err = skb_copy_and_csum_datagram_msg(skb, off, msg);
+
+		if (err == -EINVAL)
+			goto csum_copy_err;
+	}
+
+	if (unlikely(err)) {
+		if (!peeking) {
+			udp_drops_inc(sk);
+			UDP_INC_STATS(net, UDP_MIB_INERRORS);
+		}
+		kfree_skb(skb);
+		return err;
+	}
+
+	if (!peeking)
+		UDP_INC_STATS(net, UDP_MIB_INDATAGRAMS);
+
+	sock_recv_cmsgs(msg, sk, skb);
+
+	/* Copy the address. */
+	if (sin) {
+		sin->sin_family = AF_INET;
+		sin->sin_port = udp_hdr(skb)->source;
+		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
+		memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
+		msg->msg_namelen = sizeof(*sin);
+
+		BPF_CGROUP_RUN_PROG_UDP4_RECVMSG_LOCK(sk,
+						      (struct sockaddr *)sin,
+						      &msg->msg_namelen);
+	}
+
+	if (udp_test_bit(GRO_ENABLED, sk))
+		udp_cmsg_recv(msg, sk, skb);
+
+	if (inet_cmsg_flags(inet))
+		ip_cmsg_recv_offset(msg, sk, skb, sizeof(struct udphdr), off);
+
+	err = copied;
+	if (flags & MSG_TRUNC)
+		err = ulen;
+
+	skb_consume_udp(sk, skb, peeking ? -err : err);
+	return err;
+
+csum_copy_err:
+	if (!__sk_queue_drop_skb(sk, &udp_sk(sk)->reader_queue, skb, flags,
+				 udp_skb_destructor)) {
+		UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
+		UDP_INC_STATS(net, UDP_MIB_INERRORS);
+	}
+	kfree_skb_reason(skb, SKB_DROP_REASON_UDP_CSUM);
+
+	/* starting over for a new packet, but check if we need to yield */
+	cond_resched();
+	msg->msg_flags &= ~MSG_TRUNC;
+	goto try_again;
+}
+
+/* Initialize UDP checksum. If exited with zero value (success),
+ * CHECKSUM_UNNECESSARY means, that no more checks are required.
+ * Otherwise, csum completion requires checksumming packet body,
+ * including udp header and folding it to skb->csum.
+ */
+static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh)
+{
+	int err;
+
+	/* Note, we are only interested in != 0 or == 0, thus the
+	 * force to int.
+	 */
+	err = (__force int)skb_checksum_init_zero_check(skb, IPPROTO_UDP, uh->check,
+							inet_compute_pseudo);
+	if (err)
+		return err;
+
+	if (skb->ip_summed == CHECKSUM_COMPLETE && !skb->csum_valid) {
+		/* If SW calculated the value, we know it's bad */
+		if (skb->csum_complete_sw)
+			return 1;
+
+		/* HW says the value is bad. Let's validate that.
+		 * skb->csum is no longer the full packet checksum,
+		 * so don't treat it as such.
+		 */
+		skb_checksum_complete_unset(skb);
+	}
+
+	return 0;
+}
+
+/* returns:
+ *  -1: error
+ *   0: success
+ *  >0: "udp encap" protocol resubmission
+ *
+ * Note that in the success and error cases, the skb is assumed to
+ * have either been requeued or freed.
+ */
+static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
+{
+	enum skb_drop_reason drop_reason = SKB_DROP_REASON_NOT_SPECIFIED;
+	struct udp_sock *up = udp_sk(sk);
+	struct net *net = sock_net(sk);
+
+	/*
+	 *	Charge it to the socket, dropping if the queue is full.
+	 */
+	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {
+		drop_reason = SKB_DROP_REASON_XFRM_POLICY;
+		goto drop;
+	}
+	nf_reset_ct(skb);
+
+	if (static_branch_unlikely(&udp_encap_needed_key) &&
+	    READ_ONCE(up->encap_type)) {
+		int (*encap_rcv)(struct sock *sk, struct sk_buff *skb);
+
+		/*
+		 * This is an encapsulation socket so pass the skb to
+		 * the socket's udp_encap_rcv() hook. Otherwise, just
+		 * fall through and pass this up the UDP socket.
+		 * up->encap_rcv() returns the following value:
+		 * =0 if skb was successfully passed to the encap
+		 *    handler or was discarded by it.
+		 * >0 if skb should be passed on to UDP.
+		 * <0 if skb should be resubmitted as proto -N
+		 */
+
+		/* if we're overly short, let UDP handle it */
+		encap_rcv = READ_ONCE(up->encap_rcv);
+		if (encap_rcv) {
+			int ret;
+
+			/* Verify checksum before giving to encap */
+			if (udp_lib_checksum_complete(skb))
+				goto csum_error;
+
+			ret = encap_rcv(sk, skb);
+			if (ret <= 0) {
+				__UDP_INC_STATS(net, UDP_MIB_INDATAGRAMS);
+				return -ret;
+			}
+		}
+
+		/* FALLTHROUGH -- it's a UDP Packet */
+	}
+
+	prefetch(&sk->sk_rmem_alloc);
+	if (rcu_access_pointer(sk->sk_filter) &&
+	    udp_lib_checksum_complete(skb))
+		goto csum_error;
+
+	drop_reason = sk_filter_trim_cap(sk, skb, sizeof(struct udphdr));
+	if (drop_reason)
+		goto drop;
+
+	udp_csum_pull_header(skb);
+
+	ipv4_pktinfo_prepare(sk, skb, true);
+	return __udp_queue_rcv_skb(sk, skb);
+
+csum_error:
+	drop_reason = SKB_DROP_REASON_UDP_CSUM;
+	__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
+drop:
+	__UDP_INC_STATS(net, UDP_MIB_INERRORS);
+	udp_drops_inc(sk);
+	sk_skb_reason_drop(sk, skb, drop_reason);
+	return -1;
+}
+
+static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
+{
+	struct sk_buff *next, *segs;
+	int ret;
+
+	if (likely(!udp_unexpected_gso(sk, skb)))
+		return udp_queue_rcv_one_skb(sk, skb);
+
+	BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
+	__skb_push(skb, -skb_mac_offset(skb));
+	segs = udp_rcv_segment(sk, skb, true);
+	skb_list_walk_safe(segs, skb, next) {
+		__skb_pull(skb, skb_transport_offset(skb));
+
+		udp_post_segment_fix_csum(skb);
+		ret = udp_queue_rcv_one_skb(sk, skb);
+		if (ret > 0)
+			ip_protocol_deliver_rcu(dev_net(skb->dev), skb, ret);
+	}
+	return 0;
+}
+
+static inline bool __udp_is_mcast_sock(struct net *net, const struct sock *sk,
+				       __be16 loc_port, __be32 loc_addr,
+				       __be16 rmt_port, __be32 rmt_addr,
+				       int dif, int sdif, unsigned short hnum)
+{
+	const struct inet_sock *inet = inet_sk(sk);
+
+	if (!net_eq(sock_net(sk), net) ||
+	    udp_sk(sk)->udp_port_hash != hnum ||
+	    (inet->inet_daddr && inet->inet_daddr != rmt_addr) ||
+	    (inet->inet_dport != rmt_port && inet->inet_dport) ||
+	    (inet->inet_rcv_saddr && inet->inet_rcv_saddr != loc_addr) ||
+	    ipv6_only_sock(sk) ||
+	    !udp_sk_bound_dev_eq(net, sk->sk_bound_dev_if, dif, sdif))
+		return false;
+	if (!ip_mc_sf_allow(sk, loc_addr, rmt_addr, dif, sdif))
+		return false;
+	return true;
+}
+
+/*
+ *	Multicasts and broadcasts go to each listener.
+ *
+ *	Note: called only from the BH handler context.
+ */
+static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
+				    struct udphdr  *uh,
+				    __be32 saddr, __be32 daddr)
+{
+	struct udp_table *udptable = net->ipv4.udp_table;
+	unsigned int hash2, hash2_any, offset;
+	unsigned short hnum = ntohs(uh->dest);
+	struct sock *sk, *first = NULL;
+	int dif = skb->dev->ifindex;
+	int sdif = inet_sdif(skb);
+	struct hlist_node *node;
+	struct udp_hslot *hslot;
+	struct sk_buff *nskb;
+	bool use_hash2;
+	int ret;
+
+	hash2_any = 0;
+	hash2 = 0;
+	hslot = udp_hashslot(udptable, net, hnum);
+	use_hash2 = hslot->count > 10;
+	offset = offsetof(typeof(*sk), sk_node);
+
+	if (use_hash2) {
+		hash2_any = ipv4_portaddr_hash(net, htonl(INADDR_ANY), hnum) &
+			    udptable->mask;
+		hash2 = ipv4_portaddr_hash(net, daddr, hnum) & udptable->mask;
+start_lookup:
+		hslot = &udptable->hash2[hash2].hslot;
+		offset = offsetof(typeof(*sk), __sk_common.skc_portaddr_node);
+	}
+
+	sk_for_each_entry_offset_rcu(sk, node, &hslot->head, offset) {
+		if (!__udp_is_mcast_sock(net, sk, uh->dest, daddr,
+					 uh->source, saddr, dif, sdif, hnum))
+			continue;
+
+		if (!first) {
+			first = sk;
+			continue;
+		}
+		nskb = skb_clone(skb, GFP_ATOMIC);
+
+		if (unlikely(!nskb)) {
+			udp_drops_inc(sk);
+			__UDP_INC_STATS(net, UDP_MIB_RCVBUFERRORS);
+			__UDP_INC_STATS(net, UDP_MIB_INERRORS);
+			continue;
+		}
+		if (udp_queue_rcv_skb(sk, nskb) > 0)
+			consume_skb(nskb);
+	}
+
+	/* Also lookup *:port if we are using hash2 and haven't done so yet. */
+	if (use_hash2 && hash2 != hash2_any) {
+		hash2 = hash2_any;
+		goto start_lookup;
+	}
+
+	if (first) {
+		ret = udp_queue_rcv_skb(first, skb);
+		if (ret > 0)
+			return -ret;
+	} else {
+		kfree_skb(skb);
+		__UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
+	}
+	return 0;
+}
+
+/* wrapper for udp_queue_rcv_skb taking care of csum conversion and
+ * return code conversion for ip layer consumption
+ */
+static int udp_unicast_rcv_skb(struct sock *sk, struct sk_buff *skb,
+			       struct udphdr *uh)
+{
+	int ret;
+
+	if (inet_get_convert_csum(sk) && uh->check)
+		skb_checksum_try_convert(skb, IPPROTO_UDP, inet_compute_pseudo);
+
+	ret = udp_queue_rcv_skb(sk, skb);
+
+	/* a return value > 0 means to resubmit the input, but
+	 * it wants the return to be -protocol, or 0
+	 */
+	if (ret > 0)
+		return -ret;
+	return 0;
+}
+
+/*
+ *	All we need to do is get the socket, and then do a checksum.
+ */
+
+int udp_rcv(struct sk_buff *skb)
+{
+	struct rtable *rt = skb_rtable(skb);
+	struct net *net = dev_net(skb->dev);
+	struct sock *sk = NULL;
+	__be32 saddr, daddr;
+	unsigned int ulen;
+	struct udphdr *uh;
+	bool refcounted;
+	int drop_reason;
+
+	drop_reason = SKB_DROP_REASON_NOT_SPECIFIED;
+
+	/*
+	 *  Validate the packet.
+	 */
+	if (!pskb_may_pull(skb, sizeof(struct udphdr)))
+		goto drop;		/* No space for header. */
+
+	uh   = udp_hdr(skb);
+	ulen = udp_get_len(skb, uh, 0);
+	saddr = ip_hdr(skb)->saddr;
+	daddr = ip_hdr(skb)->daddr;
+
+	if (ulen > skb->len)
+		goto short_packet;
+
+	if (ulen < sizeof(*uh))
+		goto short_packet;
+
+	if (ulen < skb->len) {
+		if (pskb_trim_rcsum(skb, ulen))
+			goto short_packet;
+
+		uh = udp_hdr(skb);
+	}
+
+	if (udp4_csum_init(skb, uh))
+		goto csum_error;
+
+	sk = inet_steal_sock(net, skb, sizeof(struct udphdr), saddr, uh->source, daddr, uh->dest,
+			     &refcounted, udp_ehashfn);
+	if (IS_ERR(sk))
+		goto no_sk;
+
+	if (sk) {
+		struct dst_entry *dst = skb_dst(skb);
+		int ret;
+
+		if (unlikely(rcu_dereference(sk->sk_rx_dst) != dst))
+			udp_sk_rx_dst_set(sk, dst);
+
+		ret = udp_unicast_rcv_skb(sk, skb, uh);
+		if (refcounted)
+			sock_put(sk);
+		return ret;
+	}
+
+	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
+		return __udp4_lib_mcast_deliver(net, skb, uh, saddr, daddr);
+
+	sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest);
+	if (sk)
+		return udp_unicast_rcv_skb(sk, skb, uh);
+no_sk:
+	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
+		goto drop;
+	nf_reset_ct(skb);
+
+	/* No socket. Drop packet silently, if checksum is wrong */
+	if (udp_lib_checksum_complete(skb))
+		goto csum_error;
+
+	drop_reason = SKB_DROP_REASON_NO_SOCKET;
+	__UDP_INC_STATS(net, UDP_MIB_NOPORTS);
+	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+
+	/*
+	 * Hmm.  We got an UDP packet to a port to which we
+	 * don't wanna listen.  Ignore it.
+	 */
+	sk_skb_reason_drop(sk, skb, drop_reason);
+	return 0;
+
+short_packet:
+	drop_reason = SKB_DROP_REASON_PKT_TOO_SMALL;
+	net_dbg_ratelimited("UDP: short packet: From %pI4:%u %d/%d to %pI4:%u\n",
+			    &saddr, ntohs(uh->source),
+			    ulen, skb->len,
+			    &daddr, ntohs(uh->dest));
+	goto drop;
+
+csum_error:
+	/*
+	 * RFC1122: OK.  Discards the bad packet silently (as far as
+	 * the network is concerned, anyway) as per 4.1.3.4 (MUST).
+	 */
+	drop_reason = SKB_DROP_REASON_UDP_CSUM;
+	net_dbg_ratelimited("UDP: bad checksum. From %pI4:%u to %pI4:%u ulen %d\n",
+			    &saddr, ntohs(uh->source), &daddr, ntohs(uh->dest),
+			    ulen);
+	__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS);
+drop:
+	__UDP_INC_STATS(net, UDP_MIB_INERRORS);
+	sk_skb_reason_drop(sk, skb, drop_reason);
+	return 0;
+}
+
+int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
+{
+	int corkreq = udp_test_bit(CORK, sk) || msg->msg_flags & MSG_MORE;
+	DEFINE_RAW_FLEX(struct ip_options_rcu, opt_copy, opt.__data,
+			IP_OPTIONS_DATA_FIXED_SIZE);
+	DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
+	int ulen = len, free = 0, connected = 0;
+	struct inet_sock *inet = inet_sk(sk);
+	struct udp_sock *up = udp_sk(sk);
+	__be32 daddr, faddr, saddr;
+	struct rtable *rt = NULL;
+	struct flowi4 fl4_stack;
+	struct ipcm_cookie ipc;
+	struct sk_buff *skb;
+	struct flowi4 *fl4;
+	__be16 dport;
+	int uc_index;
+	u8 scope;
+	int err;
+
+	if (len > 0xFFFF)
+		return -EMSGSIZE;
+
+	/*
+	 *	Check the flags.
+	 */
+
+	if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
+		return -EOPNOTSUPP;
+
+	fl4 = &inet->cork.fl.u.ip4;
+	if (READ_ONCE(up->pending)) {
+		/*
+		 * There are pending frames.
+		 * The socket lock must be held while it's corked.
+		 */
+		lock_sock(sk);
+		if (likely(up->pending)) {
+			if (unlikely(up->pending != AF_INET)) {
+				release_sock(sk);
+				return -EINVAL;
+			}
+			goto do_append_data;
+		}
+		release_sock(sk);
+	}
+	ulen += sizeof(struct udphdr);
+
+	/*
+	 *	Get and verify the address.
+	 */
+	if (usin) {
+		if (msg->msg_namelen < sizeof(*usin))
+			return -EINVAL;
+		if (usin->sin_family != AF_INET) {
+			if (usin->sin_family != AF_UNSPEC)
+				return -EAFNOSUPPORT;
+		}
+
+		daddr = usin->sin_addr.s_addr;
+		dport = usin->sin_port;
+		if (dport == 0)
+			return -EINVAL;
+	} else {
+		if (sk->sk_state != TCP_ESTABLISHED)
+			return -EDESTADDRREQ;
+		daddr = inet->inet_daddr;
+		dport = inet->inet_dport;
+		/* Open fast path for connected socket.
+		 * Route will not be used, if at least one option is set.
+		 */
+		connected = 1;
+	}
+
+	ipcm_init_sk(&ipc, inet);
+	ipc.gso_size = READ_ONCE(up->gso_size);
+
+	if (msg->msg_controllen) {
+		err = udp_cmsg_send(sk, msg, &ipc.gso_size);
+		if (err > 0) {
+			err = ip_cmsg_send(sk, msg, &ipc,
+					   sk->sk_family == AF_INET6);
+			connected = 0;
+		}
+		if (unlikely(err < 0)) {
+			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();
+	}
+
+	if (cgroup_bpf_enabled(CGROUP_UDP4_SENDMSG) && !connected) {
+		err = BPF_CGROUP_RUN_PROG_UDP4_SENDMSG_LOCK(sk,
+							    (struct sockaddr *)usin,
+							    &msg->msg_namelen,
+							    &ipc.addr);
+		if (err)
+			goto out_free;
+		if (usin) {
+			if (usin->sin_port == 0) {
+				/* BPF program set invalid port. Reject it. */
+				err = -EINVAL;
+				goto out_free;
+			}
+			daddr = usin->sin_addr.s_addr;
+			dport = usin->sin_port;
+		}
+	}
+
+	saddr = ipc.addr;
+	ipc.addr = daddr;
+	faddr = daddr;
+
+	if (ipc.opt && ipc.opt->opt.srr) {
+		if (!daddr) {
+			err = -EINVAL;
+			goto out_free;
+		}
+		faddr = ipc.opt->opt.faddr;
+		connected = 0;
+	}
+	scope = ip_sendmsg_scope(inet, &ipc, msg);
+	if (scope == RT_SCOPE_LINK)
+		connected = 0;
+
+	uc_index = READ_ONCE(inet->uc_index);
+	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);
+		connected = 0;
+	} else if (!ipc.oif) {
+		ipc.oif = uc_index;
+	} else if (ipv4_is_lbcast(daddr) && uc_index) {
+		/* oif is set, packet is to local broadcast and
+		 * uc_index is set. oif is most likely set
+		 * by sk_bound_dev_if. If uc_index != oif check if the
+		 * oif is an L3 master and uc_index is an L3 slave.
+		 * If so, we want to allow the send using the uc_index.
+		 */
+		if (ipc.oif != uc_index &&
+		    ipc.oif == l3mdev_master_ifindex_by_index(sock_net(sk),
+							      uc_index)) {
+			ipc.oif = uc_index;
+		}
+	}
+
+	if (connected)
+		rt = dst_rtable(sk_dst_check(sk, 0));
+
+	if (!rt) {
+		struct net *net = sock_net(sk);
+		__u8 flow_flags = inet_sk_flowi_flags(sk);
+
+		fl4 = &fl4_stack;
+
+		flowi4_init_output(fl4, ipc.oif, ipc.sockc.mark,
+				   ipc.tos & INET_DSCP_MASK, scope,
+				   IPPROTO_UDP, flow_flags, faddr, saddr,
+				   dport, inet->inet_sport,
+				   sk_uid(sk));
+
+		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 (connected)
+			sk_dst_set(sk, dst_clone(&rt->dst));
+	}
+
+	if (msg->msg_flags & MSG_CONFIRM)
+		goto do_confirm;
+back_from_confirm:
+
+	saddr = fl4->saddr;
+	if (!ipc.addr) {
+		daddr = fl4->daddr;
+		ipc.addr = fl4->daddr;
+	}
+
+	/* Lockless fast path for the non-corking case. */
+	if (!corkreq) {
+		struct inet_cork cork;
+
+		skb = ip_make_skb(sk, fl4, ip_generic_getfrag, msg, ulen,
+				  sizeof(struct udphdr), &ipc, &rt,
+				  &cork, msg->msg_flags);
+		err = PTR_ERR(skb);
+		if (!IS_ERR_OR_NULL(skb))
+			err = udp_send_skb(skb, fl4, &cork);
+		goto out;
+	}
+
+	lock_sock(sk);
+	if (unlikely(up->pending)) {
+		/* The socket is already corked while preparing it. */
+		/* ... which is an evident application bug. --ANK */
+		release_sock(sk);
+
+		net_dbg_ratelimited("socket already corked\n");
+		err = -EINVAL;
+		goto out;
+	}
+	/*
+	 *	Now cork the socket to pend data.
+	 */
+	fl4 = &inet->cork.fl.u.ip4;
+	fl4->daddr = daddr;
+	fl4->saddr = saddr;
+	fl4->fl4_dport = dport;
+	fl4->fl4_sport = inet->inet_sport;
+	WRITE_ONCE(up->pending, AF_INET);
+
+do_append_data:
+	up->len += ulen;
+	err = ip_append_data(sk, fl4, ip_generic_getfrag, msg, ulen,
+			     sizeof(struct udphdr), &ipc, &rt,
+			     corkreq ? msg->msg_flags | MSG_MORE : msg->msg_flags);
+	if (err)
+		udp_flush_pending_frames(sk);
+	else if (!corkreq)
+		err = udp_push_pending_frames(sk);
+	else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
+		WRITE_ONCE(up->pending, 0);
+	release_sock(sk);
+
+out:
+	ip_rt_put(rt);
+out_free:
+	if (free)
+		kfree(ipc.opt);
+	if (!err)
+		return len;
+	/*
+	 * ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space.  Reporting
+	 * ENOBUFS might not be good (it's not tunable per se), but otherwise
+	 * we don't have a good statistic (IpOutDiscards but it can be too many
+	 * things).  We could add another new stat but at least for now that
+	 * seems like overkill.
+	 */
+	if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags))
+		UDP_INC_STATS(sock_net(sk), UDP_MIB_SNDBUFERRORS);
+
+	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;
+}
+EXPORT_SYMBOL(udp_sendmsg);
+
+static int udp_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
+		       int addr_len)
+{
+	int res;
+
+	lock_sock(sk);
+	res = __ip4_datagram_connect(sk, uaddr, addr_len);
+	if (!res)
+		udp4_hash4(sk);
+	release_sock(sk);
+	return res;
+}
+
+static void udp_v4_rehash(struct sock *sk)
+{
+	u16 new_hash = ipv4_portaddr_hash(sock_net(sk),
+					  inet_sk(sk)->inet_rcv_saddr,
+					  inet_sk(sk)->inet_num);
+	u16 new_hash4 = udp_ehashfn(sock_net(sk),
+				    sk->sk_rcv_saddr, sk->sk_num,
+				    sk->sk_daddr, sk->sk_dport);
+
+	udp_lib_rehash(sk, new_hash, new_hash4);
+}
+
+static void udp_destroy_sock(struct sock *sk)
+{
+	struct udp_sock *up = udp_sk(sk);
+	bool slow = lock_sock_fast(sk);
+
+	/* protects from races with udp_abort() */
+	sock_set_flag(sk, SOCK_DEAD);
+	udp_flush_pending_frames(sk);
+	unlock_sock_fast(sk, slow);
+	if (static_branch_unlikely(&udp_encap_needed_key)) {
+		if (up->encap_type) {
+			void (*encap_destroy)(struct sock *sk);
+
+			encap_destroy = READ_ONCE(up->encap_destroy);
+			if (encap_destroy)
+				encap_destroy(sk);
+		}
+		if (udp_test_bit(ENCAP_ENABLED, sk)) {
+			static_branch_dec(&udp_encap_needed_key);
+			udp_tunnel_cleanup_gro(sk);
+		}
+	}
+}
+
+/* We can only early demux multicast if there is a single matching socket.
+ * If more than one socket found returns NULL
+ */
+static struct sock *__udp4_lib_mcast_demux_lookup(struct net *net,
+						  __be16 loc_port, __be32 loc_addr,
+						  __be16 rmt_port, __be32 rmt_addr,
+						  int dif, int sdif)
+{
+	struct udp_table *udptable = net->ipv4.udp_table;
+	unsigned short hnum = ntohs(loc_port);
+	struct sock *sk, *result;
+	struct udp_hslot *hslot;
+	unsigned int slot;
+
+	slot = udp_hashfn(net, hnum, udptable->mask);
+	hslot = &udptable->hash[slot];
+
+	/* Do not bother scanning a too big list */
+	if (hslot->count > 10)
+		return NULL;
+
+	result = NULL;
+	sk_for_each_rcu(sk, &hslot->head) {
+		if (__udp_is_mcast_sock(net, sk, loc_port, loc_addr,
+					rmt_port, rmt_addr, dif, sdif, hnum)) {
+			if (result)
+				return NULL;
+			result = sk;
+		}
+	}
+
+	return result;
+}
+
+/* For unicast we should only early demux connected sockets or we can
+ * break forwarding setups.  The chains here can be long so only check
+ * if the first socket is an exact match and if not move on.
+ */
+static struct sock *__udp4_lib_demux_lookup(struct net *net,
+					    __be16 loc_port, __be32 loc_addr,
+					    __be16 rmt_port, __be32 rmt_addr,
+					    int dif, int sdif)
+{
+	struct udp_table *udptable = net->ipv4.udp_table;
+	INET_ADDR_COOKIE(acookie, rmt_addr, loc_addr);
+	unsigned short hnum = ntohs(loc_port);
+	struct udp_hslot *hslot2;
+	unsigned int hash2;
+	__portpair ports;
+	struct sock *sk;
+
+	hash2 = ipv4_portaddr_hash(net, loc_addr, hnum);
+	hslot2 = udp_hashslot2(udptable, hash2);
+	ports = INET_COMBINED_PORTS(rmt_port, hnum);
+
+	udp_portaddr_for_each_entry_rcu(sk, &hslot2->head) {
+		if (inet_match(net, sk, acookie, ports, dif, sdif))
+			return sk;
+		/* Only check first socket in chain */
+		break;
+	}
+	return NULL;
+}
+
+enum skb_drop_reason udp_v4_early_demux(struct sk_buff *skb)
+{
+	struct net *net = dev_net(skb->dev);
+	struct in_device *in_dev = NULL;
+	const struct iphdr *iph;
+	const struct udphdr *uh;
+	struct sock *sk = NULL;
+	struct dst_entry *dst;
+	int dif = skb->dev->ifindex;
+	int sdif = inet_sdif(skb);
+	int ours;
+
+	/* validate the packet */
+	if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct udphdr)))
+		return SKB_NOT_DROPPED_YET;
+
+	iph = ip_hdr(skb);
+	uh = udp_hdr(skb);
+
+	if (skb->pkt_type == PACKET_MULTICAST) {
+		in_dev = __in_dev_get_rcu(skb->dev);
+
+		if (!in_dev)
+			return SKB_NOT_DROPPED_YET;
+
+		ours = ip_check_mc_rcu(in_dev, iph->daddr, iph->saddr,
+				       iph->protocol);
+		if (!ours)
+			return SKB_NOT_DROPPED_YET;
+
+		sk = __udp4_lib_mcast_demux_lookup(net, uh->dest, iph->daddr,
+						   uh->source, iph->saddr,
+						   dif, sdif);
+	} else if (skb->pkt_type == PACKET_HOST) {
+		sk = __udp4_lib_demux_lookup(net, uh->dest, iph->daddr,
+					     uh->source, iph->saddr, dif, sdif);
+	}
+
+	if (!sk)
+		return SKB_NOT_DROPPED_YET;
+
+	skb->sk = sk;
+	DEBUG_NET_WARN_ON_ONCE(sk_is_refcounted(sk));
+	skb->destructor = sock_pfree;
+	dst = rcu_dereference(sk->sk_rx_dst);
+
+	if (dst)
+		dst = dst_check(dst, 0);
+	if (dst) {
+		u32 itag = 0;
+
+		/* set noref for now.
+		 * any place which wants to hold dst has to call
+		 * dst_hold_safe()
+		 */
+		skb_dst_set_noref(skb, dst);
+
+		/* for unconnected multicast sockets we need to validate
+		 * the source on each packet
+		 */
+		if (!inet_sk(sk)->inet_daddr && in_dev)
+			return ip_mc_validate_source(skb, iph->daddr,
+						     iph->saddr,
+						     ip4h_dscp(iph),
+						     skb->dev, in_dev, &itag);
+	}
+	return SKB_NOT_DROPPED_YET;
+}
+
+static int udp_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
+			  unsigned int optlen)
+{
+	if (level == SOL_UDP || level == SOL_SOCKET)
+		return udp_lib_setsockopt(sk, level, optname,
+					  optval, optlen,
+					  udp_push_pending_frames);
+	return ip_setsockopt(sk, level, optname, optval, optlen);
+}
+
+static int udp_getsockopt(struct sock *sk, int level, int optname,
+			  char __user *optval, int __user *optlen)
+{
+	sockopt_t opt;
+	int err;
+
+	/*
+	 * keep the old __user pointers, until ip_getsockopt() moves
+	 * to sockopt_t
+	 */
+	if (level != SOL_UDP)
+		return ip_getsockopt(sk, level, optname, optval, optlen);
+
+	err = sockopt_init_user(&opt, optval, optlen);
+	if (err)
+		return err;
+
+	err = udp_lib_getsockopt(sk, level, optname, &opt);
+	if (err)
+		return err;
+
+	/* optval was written by copy_to_iter() in udp_lib_getsockopt() */
+	if (put_user(opt.optlen, optlen))
+		return -EFAULT;
+
+	return 0;
+}
+
+struct proto udp_prot = {
+	.name			= "UDP",
+	.owner			= THIS_MODULE,
+	.close			= udp_lib_close,
+	.pre_connect		= udp_pre_connect,
+	.connect		= udp_connect,
+	.disconnect		= udp_disconnect,
+	.ioctl			= udp_ioctl,
+	.init			= udp_init_sock,
+	.destroy		= udp_destroy_sock,
+	.setsockopt		= udp_setsockopt,
+	.getsockopt		= udp_getsockopt,
+	.sendmsg		= udp_sendmsg,
+	.recvmsg		= udp_recvmsg,
+	.splice_eof		= udp_splice_eof,
+	.release_cb		= ip4_datagram_release_cb,
+	.hash			= udp_lib_hash,
+	.unhash			= udp_lib_unhash,
+	.rehash			= udp_v4_rehash,
+	.get_port		= udp_v4_get_port,
+	.put_port		= udp_lib_unhash,
+#ifdef CONFIG_BPF_SYSCALL
+	.psock_update_sk_prot	= udp_bpf_update_proto,
+#endif
+	.memory_allocated	= &net_aligned_data.udp_memory_allocated,
+	.per_cpu_fw_alloc	= &udp_memory_per_cpu_fw_alloc,
+
+	.sysctl_mem		= sysctl_udp_mem,
+	.sysctl_wmem_offset	= offsetof(struct net, ipv4.sysctl_udp_wmem_min),
+	.sysctl_rmem_offset	= offsetof(struct net, ipv4.sysctl_udp_rmem_min),
+	.obj_size		= sizeof(struct udp_sock),
+	.diag_destroy		= udp_abort,
+};
+EXPORT_SYMBOL(udp_prot);
diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index cf07c3c6611a..bea037d6b4eb 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -958,6 +958,9 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
 	const struct iphdr *iph = (struct iphdr *)(skb->data + offset);
 	struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EAFNOSUPPORT;
+
 	/* do fraglist only if there is no outer UDP encap (or we already processed it) */
 	if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {
 		udp_set_len(uh, skb->len - nhoff);
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 59f9872c3552..8cdc24ed996a 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -326,7 +326,8 @@ static void udp6_hash4(struct sock *sk)
 	struct net *net = sock_net(sk);
 	unsigned int hash;
 
-	if (ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr)) {
+	if (IS_ENABLED(CONFIG_IPV4) &&
+	    ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr)) {
 		udp4_hash4(sk);
 		return;
 	}
@@ -1449,7 +1450,8 @@ static int udp_v6_push_pending_frames(struct sock *sk)
 	int err = 0;
 
 	if (up->pending == AF_INET)
-		return udp_push_pending_frames(sk);
+		return IS_ENABLED(CONFIG_IPV4) ? udp_push_pending_frames(sk) :
+						 -EAFNOSUPPORT;
 
 	skb = ip6_finish_skb(sk);
 	if (!skb)
@@ -1529,7 +1531,7 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
 			msg->msg_name = &sin;
 			msg->msg_namelen = sizeof(sin);
 do_udp_sendmsg:
-			err = ipv6_only_sock(sk) ?
+			err = (!IS_ENABLED(CONFIG_IPV4) || ipv6_only_sock(sk)) ?
 				-ENETUNREACH : udp_sendmsg(sk, msg, len);
 			msg->msg_name = sin6;
 			msg->msg_namelen = addr_len;
@@ -1545,7 +1547,8 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
 
 	if (READ_ONCE(up->pending)) {
 		if (READ_ONCE(up->pending) == AF_INET)
-			return udp_sendmsg(sk, msg, len);
+			return IS_ENABLED(CONFIG_IPV4) ?
+			       udp_sendmsg(sk, msg, len) : -EAFNOSUPPORT;
 		/*
 		 * There are pending frames.
 		 * The socket lock must be held while it's corked.
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 07/16 net-next v3] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (5 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 06/16 net-next v3] ipv4: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 08/16 net-next v3] ipv4: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
                   ` (8 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, David Ahern, Ido Schimmel, Eric Biggers,
	Julian Anastasov, Florian Westphal, Kuniyuki Iwashima,
	linux-kernel

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      | 1418 +----------------------------------------
 net/ipv4/icmp_ipv4.c | 1448 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1459 insertions(+), 1417 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 90c0e22c29be..5e8dcf71d135 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,936 +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 = fl4_dec;
-		unsigned long orefdst;
-
-		swap(fl4_2.daddr, fl4_2.saddr);
-		switch (fl4_2.flowi4_proto) {
-		case IPPROTO_TCP:
-		case IPPROTO_UDP:
-		case IPPROTO_SCTP:
-		case IPPROTO_DCCP:
-			swap(fl4_2.fl4_sport, fl4_2.fl4_dport);
-			break;
-		}
-
-		fl4_2.flowi4_oif = l3mdev_master_ifindex(route_lookup_dev);
-		fl4_2.flowi4_flags |= FLOWI_FLAG_ANYSRC;
-
-		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);
-
-		/*
-		 * fl4_dec.daddr is not expected to be local here, but it can be
-		 * added to an interface concurrently, in which case
-		 * ip_route_input() returns a LOCAL route. It can also fail to
-		 * build a forwarding route towards fl4_dec.daddr, for example,
-		 * when forwarding is disabled, and return an UNREACHABLE route.
-		 * Both cases will result in a 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);
-		if (!err && rt2 &&
-		    (rt2->rt_type == RTN_LOCAL || rt2->rt_type == RTN_UNREACHABLE)) {
-			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.
@@ -1355,7 +226,8 @@ bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
 			goto send_mal_query;
 		switch (ntohs(iio->ident.addr.ctype3_hdr.afi)) {
 		case ICMP_AFI_IP:
-			if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in_addr))
+			if (!IS_ENABLED(CONFIG_IPV4) ||
+			    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;
@@ -1398,169 +270,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;
@@ -1619,106 +328,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. */
@@ -1757,28 +366,11 @@ 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;
+	if (IS_ENABLED(CONFIG_IPV4)) {
+		int err = icmp_init_ipv4();
 
-		err = inet_ctl_sock_create(&sk, PF_INET,
-					   SOCK_RAW, IPPROTO_ICMP, &init_net);
-		if (err < 0)
+		if (err)
 			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 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..24a1fb497cf6
--- /dev/null
+++ b/net/ipv4/icmp_ipv4.c
@@ -0,0 +1,1448 @@
+// 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);
+}
+
+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;
+}
+
+/*
+ *	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 = ip_hdr(skb)->saddr;
+	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 = fl4_dec;
+		unsigned long orefdst;
+
+		swap(fl4_2.daddr, fl4_2.saddr);
+		switch (fl4_2.flowi4_proto) {
+		case IPPROTO_TCP:
+		case IPPROTO_UDP:
+		case IPPROTO_SCTP:
+		case IPPROTO_DCCP:
+			swap(fl4_2.fl4_sport, fl4_2.fl4_dport);
+			break;
+		}
+
+		fl4_2.flowi4_oif = l3mdev_master_ifindex(route_lookup_dev);
+		fl4_2.flowi4_flags |= FLOWI_FLAG_ANYSRC;
+
+		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);
+
+		/*
+		 * fl4_dec.daddr is not expected to be local here, but it can be
+		 * added to an interface concurrently, in which case
+		 * ip_route_input() returns a LOCAL route. It can also fail to
+		 * build a forwarding route towards fl4_dec.daddr, for example,
+		 * when forwarding is disabled, and return an UNREACHABLE route.
+		 * Both cases will result in a 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);
+		if (!err && rt2 &&
+		    (rt2->rt_type == RTN_LOCAL || rt2->rt_type == RTN_UNREACHABLE)) {
+			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 = skb_clone(skb_in, GFP_ATOMIC);
+		cloned_skb = skb_in;
+	}
+
+	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;
+}
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 08/16 net-next v3] ipv4: ping: split IPv4 specific logic into ping_ipv4.c
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (6 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 07/16 net-next v3] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 09/16 net-next v3] ipv4: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, Florian Westphal, Neal Cardwell, Willem de Bruijn,
	Kuniyuki Iwashima, Eric Biggers, Marc Kleine-Budde, yuan.gao,
	Gustavo A. R. Silva, Kees Cook, linux-kernel, bpf

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 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      | 210 +--------------------------------------
 net/ipv4/ping_ipv4.c | 228 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 234 insertions(+), 206 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 223a0108b74c..ada4ff932734 100644
--- a/net/ipv4/ping.c
+++ b/net/ipv4/ping.c
@@ -285,19 +285,6 @@ 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)
@@ -308,6 +295,9 @@ static int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,
 		u32 tb_id = RT_TABLE_LOCAL;
 		int chk_addr_ret;
 
+		if (!IS_ENABLED(CONFIG_IPV4))
+			return -EAFNOSUPPORT;
+
 		if (addr_len < sizeof(*addr))
 			return -EINVAL;
 
@@ -527,7 +517,7 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
 	harderr = 0;
 	inet_sock = inet_sk(sk);
 
-	if (skb->protocol == htons(ETH_P_IP)) {
+	if (IS_ENABLED(CONFIG_IPV4) && skb->protocol == htons(ETH_P_IP)) {
 		switch (type) {
 		default:
 		case ICMP_TIME_EXCEEDED:
@@ -580,7 +570,7 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
 		if (!harderr || sk->sk_state != TCP_ESTABLISHED)
 			goto out;
 	} else {
-		if (family == AF_INET) {
+		if (IS_ENABLED(CONFIG_IPV4) && family == AF_INET) {
 			ip_icmp_error(sk, skb, err, 0 /* no remote port */,
 				      info, (u8 *)icmph);
 #if IS_ENABLED(CONFIG_IPV6)
@@ -625,21 +615,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 +660,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);
@@ -976,27 +797,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..4489aa292a4e
--- /dev/null
+++ b/net/ipv4/ping_ipv4.c
@@ -0,0 +1,228 @@
+// 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 = daddr;
+	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.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 09/16 net-next v3] ipv4: fib: split common nexthop logic to fib_core.c
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (7 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 08/16 net-next v3] ipv4: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 10/16 net-next v3] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	David Ahern, Ido Schimmel, Eric Dumazet, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Kuniyuki Iwashima, Jiayuan Chen, Mahe Tardy,
	Chia-Yu Chang, Wyatt Feng, Yung Chih Su, Joel Granados,
	linux-kernel, bpf

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       |  27 +++-
 include/net/route.h        |   6 +
 net/core/Makefile          |   2 +-
 net/core/fib_core.c        | 307 +++++++++++++++++++++++++++++++++++++
 net/ipv4/fib_frontend.c    |  96 ------------
 net/ipv4/fib_semantics.c   | 205 -------------------------
 net/ipv4/nexthop.c         |   5 +-
 net/ipv4/sysctl_net_ipv4.c |   2 +
 8 files changed, 345 insertions(+), 305 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..6058e53fa3de 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);
 
@@ -442,7 +448,7 @@ static inline bool fib_dscp_masked_match(dscp_t dscp, const struct flowi4 *fl4)
 	return dscp == (fl4->flowi4_dscp & INET_DSCP_LEGACY_TOS_MASK);
 }
 
-/* Exported by fib_frontend.c */
+/* Exported by net/core/fib_core.c */
 extern const struct nla_policy rtm_ipv4_policy[];
 void ip_fib_init(void);
 int fib_gw_from_via(struct fib_config *cfg, struct nlattr *nla,
@@ -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 nh_weight,
+			      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)
diff --git a/include/net/route.h b/include/net/route.h
index 8c21881eb5f4..8788bf096c15 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 c20e520358b8..315e437a49d8 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..6f59ee43ccbb
--- /dev/null
+++ b/net/core/fib_core.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/inetdevice.h>
+#include <linux/types.h>
+#include <net/addrconf.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;
+}
+
+int fib_nexthop_info(struct sk_buff *skb, const struct fib_nh_common *nhc,
+		     u8 rt_family, unsigned char *flags, bool skip_oif)
+{
+	if (nhc->nhc_flags & RTNH_F_DEAD)
+		*flags |= RTNH_F_DEAD;
+
+	if (nhc->nhc_flags & RTNH_F_LINKDOWN) {
+		*flags |= RTNH_F_LINKDOWN;
+
+		rcu_read_lock();
+		switch (nhc->nhc_family) {
+		case AF_INET:
+			if (ip_ignore_linkdown(nhc->nhc_dev))
+				*flags |= RTNH_F_DEAD;
+			break;
+		case AF_INET6:
+			if (ip6_ignore_linkdown(nhc->nhc_dev))
+				*flags |= RTNH_F_DEAD;
+			break;
+		}
+		rcu_read_unlock();
+	}
+
+	switch (nhc->nhc_gw_family) {
+	case AF_INET:
+		if (nla_put_in_addr(skb, RTA_GATEWAY, nhc->nhc_gw.ipv4))
+			goto nla_put_failure;
+		break;
+	case AF_INET6:
+		/* if gateway family does not match nexthop family
+		 * gateway is encoded as RTA_VIA
+		 */
+		if (rt_family != nhc->nhc_gw_family) {
+			int alen = sizeof(struct in6_addr);
+			struct nlattr *nla;
+			struct rtvia *via;
+
+			nla = nla_reserve(skb, RTA_VIA, alen + 2);
+			if (!nla)
+				goto nla_put_failure;
+
+			via = nla_data(nla);
+			via->rtvia_family = AF_INET6;
+			memcpy(via->rtvia_addr, &nhc->nhc_gw.ipv6, alen);
+		} else if (nla_put_in6_addr(skb, RTA_GATEWAY,
+					    &nhc->nhc_gw.ipv6) < 0) {
+			goto nla_put_failure;
+		}
+		break;
+	}
+
+	*flags |= (nhc->nhc_flags &
+		   (RTNH_F_ONLINK | RTNH_F_OFFLOAD | RTNH_F_TRAP));
+
+	if (!skip_oif && nhc->nhc_dev &&
+	    nla_put_u32(skb, RTA_OIF, nhc->nhc_dev->ifindex))
+		goto nla_put_failure;
+
+	if (lwtunnel_fill_encap(skb, nhc->nhc_lwtstate,
+				RTA_ENCAP, RTA_ENCAP_TYPE) < 0)
+		goto nla_put_failure;
+
+	return 0;
+
+nla_put_failure:
+	return -EMSGSIZE;
+}
+
+#if IS_ENABLED(CONFIG_IP_ROUTE_MULTIPATH) || IS_ENABLED(CONFIG_IPV6)
+int fib_add_nexthop(struct sk_buff *skb, const struct fib_nh_common *nhc,
+		    int nh_weight, u8 rt_family, u32 nh_tclassid)
+{
+	const struct net_device *dev = nhc->nhc_dev;
+	struct rtnexthop *rtnh;
+	unsigned char flags = 0;
+
+	rtnh = nla_reserve_nohdr(skb, sizeof(*rtnh));
+	if (!rtnh)
+		goto nla_put_failure;
+
+	rtnh->rtnh_hops = nh_weight - 1;
+	rtnh->rtnh_ifindex = dev ? dev->ifindex : 0;
+
+	if (fib_nexthop_info(skb, nhc, rt_family, &flags, true) < 0)
+		goto nla_put_failure;
+
+	rtnh->rtnh_flags = flags;
+
+	if (nh_tclassid && nla_put_u32(skb, RTA_FLOW, nh_tclassid))
+		goto nla_put_failure;
+
+	/* length of rtnetlink header + attributes */
+	rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *)rtnh;
+
+	return 0;
+
+nla_put_failure:
+	return -EMSGSIZE;
+}
+#endif
+
+const struct nla_policy rtm_ipv4_policy[RTA_MAX + 1] = {
+	[RTA_UNSPEC]		= { .strict_start_type = RTA_DPORT + 1 },
+	[RTA_DST]		= { .type = NLA_U32 },
+	[RTA_SRC]		= { .type = NLA_U32 },
+	[RTA_IIF]		= { .type = NLA_U32 },
+	[RTA_OIF]		= { .type = NLA_U32 },
+	[RTA_GATEWAY]		= { .type = NLA_U32 },
+	[RTA_PRIORITY]		= { .type = NLA_U32 },
+	[RTA_PREFSRC]		= { .type = NLA_U32 },
+	[RTA_METRICS]		= { .type = NLA_NESTED },
+	[RTA_MULTIPATH]		= { .len = sizeof(struct rtnexthop) },
+	[RTA_FLOW]		= { .type = NLA_U32 },
+	[RTA_ENCAP_TYPE]	= { .type = NLA_U16 },
+	[RTA_ENCAP]		= { .type = NLA_NESTED },
+	[RTA_UID]		= { .type = NLA_U32 },
+	[RTA_MARK]		= { .type = NLA_U32 },
+	[RTA_TABLE]		= { .type = NLA_U32 },
+	[RTA_IP_PROTO]		= { .type = NLA_U8 },
+	[RTA_SPORT]		= { .type = NLA_U16 },
+	[RTA_DPORT]		= { .type = NLA_U16 },
+	[RTA_NH_ID]		= { .type = NLA_U32 },
+};
+
+int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh,
+			  struct fib_dump_filter *filter,
+			  struct netlink_callback *cb)
+{
+	struct netlink_ext_ack *extack = cb->extack;
+	struct nlattr *tb[RTA_MAX + 1];
+	struct rtmsg *rtm;
+	int err, i;
+
+	rtm = nlmsg_payload(nlh, sizeof(*rtm));
+	if (!rtm) {
+		NL_SET_ERR_MSG(extack, "Invalid header for FIB dump request");
+		return -EINVAL;
+	}
+
+	if (rtm->rtm_dst_len || rtm->rtm_src_len  || rtm->rtm_tos   ||
+	    rtm->rtm_scope) {
+		NL_SET_ERR_MSG(extack, "Invalid values in header for FIB dump request");
+		return -EINVAL;
+	}
+
+	if (rtm->rtm_flags & ~(RTM_F_CLONED | RTM_F_PREFIX)) {
+		NL_SET_ERR_MSG(extack, "Invalid flags for FIB dump request");
+		return -EINVAL;
+	}
+	if (rtm->rtm_flags & RTM_F_CLONED)
+		filter->dump_routes = false;
+	else
+		filter->dump_exceptions = false;
+
+	filter->flags    = rtm->rtm_flags;
+	filter->protocol = rtm->rtm_protocol;
+	filter->rt_type  = rtm->rtm_type;
+	filter->table_id = rtm->rtm_table;
+
+	err = nlmsg_parse_deprecated_strict(nlh, sizeof(*rtm), tb, RTA_MAX,
+					    rtm_ipv4_policy, extack);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i <= RTA_MAX; ++i) {
+		int ifindex;
+
+		if (!tb[i])
+			continue;
+
+		switch (i) {
+		case RTA_TABLE:
+			filter->table_id = nla_get_u32(tb[i]);
+			break;
+		case RTA_OIF:
+			ifindex = nla_get_u32(tb[i]);
+
+			filter->dev = dev_get_by_index_rcu(net, ifindex);
+			if (!filter->dev)
+				return -ENODEV;
+			break;
+		default:
+			NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
+			return -EINVAL;
+		}
+	}
+
+	if (filter->flags || filter->protocol || filter->rt_type ||
+	    filter->table_id || filter->dev) {
+		filter->filter_set = 1;
+		cb->answer_flags = NLM_F_DUMP_FILTERED;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ip_valid_fib_dump_req);
diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c
index 8a3dc04e8cac..c507d53c6da6 100644
--- a/net/ipv4/fib_frontend.c
+++ b/net/ipv4/fib_frontend.c
@@ -684,29 +684,6 @@ int ip_rt_ioctl(struct net *net, unsigned int cmd, struct rtentry *rt)
 	return -EINVAL;
 }
 
-const struct nla_policy rtm_ipv4_policy[RTA_MAX + 1] = {
-	[RTA_UNSPEC]		= { .strict_start_type = RTA_DPORT + 1 },
-	[RTA_DST]		= { .type = NLA_U32 },
-	[RTA_SRC]		= { .type = NLA_U32 },
-	[RTA_IIF]		= { .type = NLA_U32 },
-	[RTA_OIF]		= { .type = NLA_U32 },
-	[RTA_GATEWAY]		= { .type = NLA_U32 },
-	[RTA_PRIORITY]		= { .type = NLA_U32 },
-	[RTA_PREFSRC]		= { .type = NLA_U32 },
-	[RTA_METRICS]		= { .type = NLA_NESTED },
-	[RTA_MULTIPATH]		= { .len = sizeof(struct rtnexthop) },
-	[RTA_FLOW]		= { .type = NLA_U32 },
-	[RTA_ENCAP_TYPE]	= { .type = NLA_U16 },
-	[RTA_ENCAP]		= { .type = NLA_NESTED },
-	[RTA_UID]		= { .type = NLA_U32 },
-	[RTA_MARK]		= { .type = NLA_U32 },
-	[RTA_TABLE]		= { .type = NLA_U32 },
-	[RTA_IP_PROTO]		= { .type = NLA_U8 },
-	[RTA_SPORT]		= { .type = NLA_U16 },
-	[RTA_DPORT]		= { .type = NLA_U16 },
-	[RTA_NH_ID]		= { .type = NLA_U32 },
-};
-
 int fib_gw_from_via(struct fib_config *cfg, struct nlattr *nla,
 		    struct netlink_ext_ack *extack)
 {
@@ -957,79 +934,6 @@ static int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh,
 	return err;
 }
 
-int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh,
-			  struct fib_dump_filter *filter,
-			  struct netlink_callback *cb)
-{
-	struct netlink_ext_ack *extack = cb->extack;
-	struct nlattr *tb[RTA_MAX + 1];
-	struct rtmsg *rtm;
-	int err, i;
-
-	rtm = nlmsg_payload(nlh, sizeof(*rtm));
-	if (!rtm) {
-		NL_SET_ERR_MSG(extack, "Invalid header for FIB dump request");
-		return -EINVAL;
-	}
-
-	if (rtm->rtm_dst_len || rtm->rtm_src_len  || rtm->rtm_tos   ||
-	    rtm->rtm_scope) {
-		NL_SET_ERR_MSG(extack, "Invalid values in header for FIB dump request");
-		return -EINVAL;
-	}
-
-	if (rtm->rtm_flags & ~(RTM_F_CLONED | RTM_F_PREFIX)) {
-		NL_SET_ERR_MSG(extack, "Invalid flags for FIB dump request");
-		return -EINVAL;
-	}
-	if (rtm->rtm_flags & RTM_F_CLONED)
-		filter->dump_routes = false;
-	else
-		filter->dump_exceptions = false;
-
-	filter->flags    = rtm->rtm_flags;
-	filter->protocol = rtm->rtm_protocol;
-	filter->rt_type  = rtm->rtm_type;
-	filter->table_id = rtm->rtm_table;
-
-	err = nlmsg_parse_deprecated_strict(nlh, sizeof(*rtm), tb, RTA_MAX,
-					    rtm_ipv4_policy, extack);
-	if (err < 0)
-		return err;
-
-	for (i = 0; i <= RTA_MAX; ++i) {
-		int ifindex;
-
-		if (!tb[i])
-			continue;
-
-		switch (i) {
-		case RTA_TABLE:
-			filter->table_id = nla_get_u32(tb[i]);
-			break;
-		case RTA_OIF:
-			ifindex = nla_get_u32(tb[i]);
-
-			filter->dev = dev_get_by_index_rcu(net, ifindex);
-			if (!filter->dev)
-				return -ENODEV;
-			break;
-		default:
-			NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
-			return -EINVAL;
-		}
-	}
-
-	if (filter->flags || filter->protocol || filter->rt_type ||
-	    filter->table_id || filter->dev) {
-		filter->filter_set = 1;
-		cb->answer_flags = NLM_F_DUMP_FILTERED;
-	}
-
-	return 0;
-}
-EXPORT_SYMBOL_GPL(ip_valid_fib_dump_req);
-
 static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
 {
 	const struct nlmsghdr *nlh = cb->nlh;
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
index e3bcc25229b0..3ce0f2611e69 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
@@ -639,38 +567,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)
@@ -1605,107 +1501,6 @@ struct fib_info *fib_create_info(struct fib_config *cfg,
 	return ERR_PTR(err);
 }
 
-int fib_nexthop_info(struct sk_buff *skb, const struct fib_nh_common *nhc,
-		     u8 rt_family, unsigned char *flags, bool skip_oif)
-{
-	if (nhc->nhc_flags & RTNH_F_DEAD)
-		*flags |= RTNH_F_DEAD;
-
-	if (nhc->nhc_flags & RTNH_F_LINKDOWN) {
-		*flags |= RTNH_F_LINKDOWN;
-
-		rcu_read_lock();
-		switch (nhc->nhc_family) {
-		case AF_INET:
-			if (ip_ignore_linkdown(nhc->nhc_dev))
-				*flags |= RTNH_F_DEAD;
-			break;
-		case AF_INET6:
-			if (ip6_ignore_linkdown(nhc->nhc_dev))
-				*flags |= RTNH_F_DEAD;
-			break;
-		}
-		rcu_read_unlock();
-	}
-
-	switch (nhc->nhc_gw_family) {
-	case AF_INET:
-		if (nla_put_in_addr(skb, RTA_GATEWAY, nhc->nhc_gw.ipv4))
-			goto nla_put_failure;
-		break;
-	case AF_INET6:
-		/* if gateway family does not match nexthop family
-		 * gateway is encoded as RTA_VIA
-		 */
-		if (rt_family != nhc->nhc_gw_family) {
-			int alen = sizeof(struct in6_addr);
-			struct nlattr *nla;
-			struct rtvia *via;
-
-			nla = nla_reserve(skb, RTA_VIA, alen + 2);
-			if (!nla)
-				goto nla_put_failure;
-
-			via = nla_data(nla);
-			via->rtvia_family = AF_INET6;
-			memcpy(via->rtvia_addr, &nhc->nhc_gw.ipv6, alen);
-		} else if (nla_put_in6_addr(skb, RTA_GATEWAY,
-					    &nhc->nhc_gw.ipv6) < 0) {
-			goto nla_put_failure;
-		}
-		break;
-	}
-
-	*flags |= (nhc->nhc_flags &
-		   (RTNH_F_ONLINK | RTNH_F_OFFLOAD | RTNH_F_TRAP));
-
-	if (!skip_oif && nhc->nhc_dev &&
-	    nla_put_u32(skb, RTA_OIF, nhc->nhc_dev->ifindex))
-		goto nla_put_failure;
-
-	if (lwtunnel_fill_encap(skb, nhc->nhc_lwtstate,
-				RTA_ENCAP, RTA_ENCAP_TYPE) < 0)
-		goto nla_put_failure;
-
-	return 0;
-
-nla_put_failure:
-	return -EMSGSIZE;
-}
-
-#if IS_ENABLED(CONFIG_IP_ROUTE_MULTIPATH) || IS_ENABLED(CONFIG_IPV6)
-int fib_add_nexthop(struct sk_buff *skb, const struct fib_nh_common *nhc,
-		    int nh_weight, u8 rt_family, u32 nh_tclassid)
-{
-	const struct net_device *dev = nhc->nhc_dev;
-	struct rtnexthop *rtnh;
-	unsigned char flags = 0;
-
-	rtnh = nla_reserve_nohdr(skb, sizeof(*rtnh));
-	if (!rtnh)
-		goto nla_put_failure;
-
-	rtnh->rtnh_hops = nh_weight - 1;
-	rtnh->rtnh_ifindex = dev ? dev->ifindex : 0;
-
-	if (fib_nexthop_info(skb, nhc, rt_family, &flags, true) < 0)
-		goto nla_put_failure;
-
-	rtnh->rtnh_flags = flags;
-
-	if (nh_tclassid && nla_put_u32(skb, RTA_FLOW, nh_tclassid))
-		goto nla_put_failure;
-
-	/* length of rtnetlink header + attributes */
-	rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *)rtnh;
-
-	return 0;
-
-nla_put_failure:
-	return -EMSGSIZE;
-}
-#endif
-
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
 static int fib_add_multipath(struct sk_buff *skb, struct fib_info *fi)
 {
diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c
index 42e55b5a755e..ad79943376c5 100644
--- a/net/ipv4/nexthop.c
+++ b/net/ipv4/nexthop.c
@@ -1421,7 +1421,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);
 	}
@@ -4005,7 +4005,8 @@ static int nh_netdev_event(struct notifier_block *this,
 		break;
 	case NETDEV_CHANGEMTU:
 		info_ext = ptr;
-		nexthop_sync_mtu(dev, info_ext->ext.mtu);
+		if (IS_ENABLED(CONFIG_IPV4))
+			nexthop_sync_mtu(dev, info_ext->ext.mtu);
 		rt_cache_flush(dev_net(dev));
 		break;
 	}
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index e3760daa3470..0e7fef5973db 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -615,6 +615,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,
@@ -624,6 +625,7 @@ static struct ctl_table ipv4_table[] = {
 		.extra1		= &sysctl_fib_sync_mem_min,
 		.extra2		= &sysctl_fib_sync_mem_max,
 	},
+#endif
 };
 
 static const struct ctl_table ipv4_net_table[] = {
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 10/16 net-next v3] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (8 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 09/16 net-next v3] ipv4: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 11/16 net-next v3] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n Fernando Fernandez Mancera
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	David Ahern, Ido Schimmel, Eric Dumazet, linux-kernel

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 bab42b9e277f..996e61243922 100644
--- a/net/ipv4/ip_tunnel_core.c
+++ b/net/ipv4/ip_tunnel_core.c
@@ -58,6 +58,14 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb,
 	struct iphdr *iph;
 	int err;
 
+	if (!IS_ENABLED(CONFIG_IPV4)) {
+		if (dev)
+			DEV_STATS_INC(dev, tx_errors);
+		ip_rt_put(rt);
+		kfree_skb(skb);
+		return;
+	}
+
 	if (unlikely(dev_recursion_level() > IP_TUNNEL_RECURSION_LIMIT)) {
 		if (dev) {
 			net_crit_ratelimited("Dead loop on virtual device %s (net %llu), fix it urgently!\n",
@@ -218,6 +226,9 @@ static int iptunnel_pmtud_build_icmp(struct sk_buff *skb, int mtu)
 	struct ethhdr eh;
 	int len, err;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EOPNOTSUPP;
+
 	if (!pskb_may_pull(skb, ETH_HLEN + sizeof(struct iphdr)))
 		return -EINVAL;
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 11/16 net-next v3] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (9 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 10/16 net-next v3] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 12/16 net-next v3] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, Chia-Yu Chang, Wyatt Feng, Ido Schimmel,
	Joel Granados, Yung Chih Su, linux-kernel

To avoid noise and unexpected problems, let's hide all the sysctls that
are related to IPv4 only when the kernel is compiled without IPv4
support.

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

diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index 0e7fef5973db..6096e9e4d82d 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -31,8 +31,8 @@ static int tcp_min_snd_mss_max = 65535;
 static int tcp_rto_max_max = TCP_RTO_MAX_SEC * MSEC_PER_SEC;
 static int ip_privileged_port_min;
 static int ip_privileged_port_max = 65535;
-static int ip_ttl_min = 1;
-static int ip_ttl_max = 255;
+static int ip_ttl_min __maybe_unused = 1;
+static int ip_ttl_max __maybe_unused = 255;
 static int tcp_syn_retries_min = 1;
 static int tcp_syn_retries_max = MAX_TCP_SYNCNT;
 static int tcp_syn_linear_timeouts_max = MAX_TCP_SYNCNT;
@@ -48,7 +48,7 @@ static int tcp_plb_max_rounds = 31;
 static int tcp_plb_max_cong_thresh = 256;
 static unsigned int tcp_tw_reuse_delay_max = TCP_PAWS_MSL * MSEC_PER_SEC;
 static int tcp_ecn_mode_max = 5;
-static u32 icmp_errors_extension_mask_all =
+static u32 icmp_errors_extension_mask_all __maybe_unused =
 	GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0);
 
 static int tcp_min_rcvbuf = 4096;
@@ -201,8 +201,9 @@ static int ipv4_ping_group_range(const struct ctl_table *table, int write,
 	return ret;
 }
 
-static int ipv4_fwd_update_priority(const struct ctl_table *table, int write,
-				    void *buffer, size_t *lenp, loff_t *ppos)
+static int __maybe_unused
+ipv4_fwd_update_priority(const struct ctl_table *table, int write,
+			 void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net;
 	int ret;
@@ -441,9 +442,9 @@ static int proc_udp_hash_entries(const struct ctl_table *table, int write,
 }
 
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-static int proc_fib_multipath_hash_policy(const struct ctl_table *table, int write,
-					  void *buffer, size_t *lenp,
-					  loff_t *ppos)
+static int __maybe_unused
+proc_fib_multipath_hash_policy(const struct ctl_table *table, int write,
+			       void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net = container_of(table->data, struct net,
 	    ipv4.sysctl_fib_multipath_hash_policy);
@@ -456,9 +457,9 @@ static int proc_fib_multipath_hash_policy(const struct ctl_table *table, int wri
 	return ret;
 }
 
-static int proc_fib_multipath_hash_fields(const struct ctl_table *table, int write,
-					  void *buffer, size_t *lenp,
-					  loff_t *ppos)
+static int __maybe_unused
+proc_fib_multipath_hash_fields(const struct ctl_table *table, int write,
+			       void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct net *net;
 	int ret;
@@ -492,9 +493,9 @@ static void proc_fib_multipath_hash_set_seed(struct net *net, u32 user_seed)
 	WRITE_ONCE(net->ipv4.sysctl_fib_multipath_hash_seed.mp_seed, new.mp_seed);
 }
 
-static int proc_fib_multipath_hash_seed(const struct ctl_table *table, int write,
-					void *buffer, size_t *lenp,
-					loff_t *ppos)
+static int __maybe_unused
+proc_fib_multipath_hash_seed(const struct ctl_table *table, int write,
+			     void *buffer, size_t *lenp, loff_t *ppos)
 {
 	struct sysctl_fib_multipath_hash_seed *mphs;
 	struct net *net = table->data;
@@ -636,15 +637,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_dointvec
 	},
-	{
-		.procname	= "icmp_echo_ignore_all",
-		.data		= &init_net.ipv4.sysctl_icmp_echo_ignore_all,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
 	{
 		.procname	= "icmp_echo_enable_probe",
 		.data		= &init_net.ipv4.sysctl_icmp_echo_enable_probe,
@@ -654,56 +646,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.extra1		= SYSCTL_ZERO,
 		.extra2		= SYSCTL_ONE
 	},
-	{
-		.procname	= "icmp_echo_ignore_broadcasts",
-		.data		= &init_net.ipv4.sysctl_icmp_echo_ignore_broadcasts,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_ignore_bogus_error_responses",
-		.data		= &init_net.ipv4.sysctl_icmp_ignore_bogus_error_responses,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_errors_use_inbound_ifaddr",
-		.data		= &init_net.ipv4.sysctl_icmp_errors_use_inbound_ifaddr,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE
-	},
-	{
-		.procname	= "icmp_errors_extension_mask",
-		.data		= &init_net.ipv4.sysctl_icmp_errors_extension_mask,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= &icmp_errors_extension_mask_all,
-	},
-	{
-		.procname	= "icmp_ratelimit",
-		.data		= &init_net.ipv4.sysctl_icmp_ratelimit,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_ms_jiffies,
-	},
-	{
-		.procname	= "icmp_ratemask",
-		.data		= &init_net.ipv4.sysctl_icmp_ratemask,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
 	{
 		.procname	= "icmp_msgs_per_sec",
 		.data		= &init_net.ipv4.sysctl_icmp_msgs_per_sec,
@@ -774,13 +716,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.extra1		= SYSCTL_ZERO,
 		.extra2		= SYSCTL_ONE,
 	},
-	{
-		.procname	= "ip_dynaddr",
-		.data		= &init_net.ipv4.sysctl_ip_dynaddr,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
 	{
 		.procname	= "ip_early_demux",
 		.data		= &init_net.ipv4.sysctl_ip_early_demux,
@@ -811,15 +746,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.extra1		= SYSCTL_ZERO,
 		.extra2		= SYSCTL_ONE,
 	},
-	{
-		.procname	= "ip_default_ttl",
-		.data		= &init_net.ipv4.sysctl_ip_default_ttl,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= &ip_ttl_min,
-		.extra2		= &ip_ttl_max,
-	},
 	{
 		.procname	= "ip_local_port_range",
 		.maxlen		= 0,
@@ -841,36 +767,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_do_large_bitmap,
 	},
-	{
-		.procname	= "ip_no_pmtu_disc",
-		.data		= &init_net.ipv4.sysctl_ip_no_pmtu_disc,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ip_forward_use_pmtu",
-		.data		= &init_net.ipv4.sysctl_ip_fwd_use_pmtu,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "ip_forward_update_priority",
-		.data		= &init_net.ipv4.sysctl_ip_fwd_update_priority,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler   = ipv4_fwd_update_priority,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "ip_nonlocal_bind",
-		.data		= &init_net.ipv4.sysctl_ip_nonlocal_bind,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
 	{
 		.procname	= "ip_autobind_reuse",
 		.data		= &init_net.ipv4.sysctl_ip_autobind_reuse,
@@ -880,13 +776,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.extra1         = SYSCTL_ZERO,
 		.extra2         = SYSCTL_ONE,
 	},
-	{
-		.procname	= "fwmark_reflect",
-		.data		= &init_net.ipv4.sysctl_fwmark_reflect,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
 	{
 		.procname	= "tcp_fwmark_accept",
 		.data		= &init_net.ipv4.sysctl_tcp_fwmark_accept,
@@ -952,37 +841,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.proc_handler	= proc_douintvec_minmax,
 		.extra2		= &u32_max_div_HZ,
 	},
-	{
-		.procname	= "igmp_link_local_mcast_reports",
-		.data		= &init_net.ipv4.sysctl_igmp_llm_reports,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-	},
-	{
-		.procname	= "igmp_max_memberships",
-		.data		= &init_net.ipv4.sysctl_igmp_max_memberships,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-	{
-		.procname	= "igmp_max_msf",
-		.data		= &init_net.ipv4.sysctl_igmp_max_msf,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec
-	},
-#ifdef CONFIG_IP_MULTICAST
-	{
-		.procname	= "igmp_qrv",
-		.data		= &init_net.ipv4.sysctl_igmp_qrv,
-		.maxlen		= sizeof(int),
-		.mode		= 0644,
-		.proc_handler	= proc_dointvec_minmax,
-		.extra1		= SYSCTL_ONE
-	},
-#endif
 	{
 		.procname	= "tcp_congestion_control",
 		.data		= &init_net.ipv4.tcp_congestion_control,
@@ -1154,42 +1012,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.proc_handler	= proc_tfo_blackhole_detect_timeout,
 		.extra1		= SYSCTL_ZERO,
 	},
-#ifdef CONFIG_IP_ROUTE_MULTIPATH
-	{
-		.procname	= "fib_multipath_use_neigh",
-		.data		= &init_net.ipv4.sysctl_fib_multipath_use_neigh,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_ONE,
-	},
-	{
-		.procname	= "fib_multipath_hash_policy",
-		.data		= &init_net.ipv4.sysctl_fib_multipath_hash_policy,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_fib_multipath_hash_policy,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_THREE,
-	},
-	{
-		.procname	= "fib_multipath_hash_fields",
-		.data		= &init_net.ipv4.sysctl_fib_multipath_hash_fields,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_fib_multipath_hash_fields,
-		.extra1		= SYSCTL_ONE,
-		.extra2		= &fib_multipath_hash_fields_all_mask,
-	},
-	{
-		.procname	= "fib_multipath_hash_seed",
-		.data		= &init_net,
-		.maxlen		= sizeof(u32),
-		.mode		= 0644,
-		.proc_handler	= proc_fib_multipath_hash_seed,
-	},
-#endif
 	{
 		.procname	= "ip_unprivileged_port_start",
 		.maxlen		= sizeof(int),
@@ -1563,15 +1385,6 @@ static const struct ctl_table ipv4_net_table[] = {
 		.proc_handler	= proc_dointvec_minmax,
 		.extra1		= SYSCTL_ONE
 	},
-	{
-		.procname	= "fib_notify_on_flag_change",
-		.data		= &init_net.ipv4.sysctl_fib_notify_on_flag_change,
-		.maxlen		= sizeof(u8),
-		.mode		= 0644,
-		.proc_handler	= proc_dou8vec_minmax,
-		.extra1		= SYSCTL_ZERO,
-		.extra2		= SYSCTL_TWO,
-	},
 	{
 		.procname       = "tcp_plb_enabled",
 		.data           = &init_net.ipv4.sysctl_tcp_plb_enabled,
@@ -1656,6 +1469,196 @@ static const struct ctl_table ipv4_net_table[] = {
 		.extra1		= SYSCTL_ONE_THOUSAND,
 		.extra2		= &tcp_rto_max_max,
 	},
+#if IS_ENABLED(CONFIG_IPV4)
+	{
+		.procname	= "icmp_echo_ignore_all",
+		.data		= &init_net.ipv4.sysctl_icmp_echo_ignore_all,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE
+	},
+	{
+		.procname	= "icmp_echo_ignore_broadcasts",
+		.data		= &init_net.ipv4.sysctl_icmp_echo_ignore_broadcasts,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE
+	},
+	{
+		.procname	= "icmp_ignore_bogus_error_responses",
+		.data		= &init_net.ipv4.sysctl_icmp_ignore_bogus_error_responses,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE
+	},
+	{
+		.procname	= "icmp_errors_use_inbound_ifaddr",
+		.data		= &init_net.ipv4.sysctl_icmp_errors_use_inbound_ifaddr,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE
+	},
+	{
+		.procname	= "icmp_errors_extension_mask",
+		.data		= &init_net.ipv4.sysctl_icmp_errors_extension_mask,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= &icmp_errors_extension_mask_all,
+	},
+	{
+		.procname	= "icmp_ratelimit",
+		.data		= &init_net.ipv4.sysctl_icmp_ratelimit,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_ms_jiffies,
+	},
+	{
+		.procname	= "icmp_ratemask",
+		.data		= &init_net.ipv4.sysctl_icmp_ratemask,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec
+	},
+	{
+		.procname	= "ip_dynaddr",
+		.data		= &init_net.ipv4.sysctl_ip_dynaddr,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+	},
+	{
+		.procname	= "ip_default_ttl",
+		.data		= &init_net.ipv4.sysctl_ip_default_ttl,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= &ip_ttl_min,
+		.extra2		= &ip_ttl_max,
+	},
+	{
+		.procname	= "ip_no_pmtu_disc",
+		.data		= &init_net.ipv4.sysctl_ip_no_pmtu_disc,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+	},
+	{
+		.procname	= "ip_forward_use_pmtu",
+		.data		= &init_net.ipv4.sysctl_ip_fwd_use_pmtu,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+	},
+	{
+		.procname	= "ip_forward_update_priority",
+		.data		= &init_net.ipv4.sysctl_ip_fwd_update_priority,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler   = ipv4_fwd_update_priority,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE,
+	},
+	{
+		.procname	= "ip_nonlocal_bind",
+		.data		= &init_net.ipv4.sysctl_ip_nonlocal_bind,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+	},
+	{
+		.procname	= "fwmark_reflect",
+		.data		= &init_net.ipv4.sysctl_fwmark_reflect,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+	},
+	{
+		.procname	= "igmp_link_local_mcast_reports",
+		.data		= &init_net.ipv4.sysctl_igmp_llm_reports,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+	},
+	{
+		.procname	= "igmp_max_memberships",
+		.data		= &init_net.ipv4.sysctl_igmp_max_memberships,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec
+	},
+	{
+		.procname	= "igmp_max_msf",
+		.data		= &init_net.ipv4.sysctl_igmp_max_msf,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec
+	},
+#ifdef CONFIG_IP_MULTICAST
+	{
+		.procname	= "igmp_qrv",
+		.data		= &init_net.ipv4.sysctl_igmp_qrv,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= SYSCTL_ONE
+	},
+#endif
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+	{
+		.procname	= "fib_multipath_use_neigh",
+		.data		= &init_net.ipv4.sysctl_fib_multipath_use_neigh,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE,
+	},
+	{
+		.procname	= "fib_multipath_hash_policy",
+		.data		= &init_net.ipv4.sysctl_fib_multipath_hash_policy,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_fib_multipath_hash_policy,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_THREE,
+	},
+	{
+		.procname	= "fib_multipath_hash_fields",
+		.data		= &init_net.ipv4.sysctl_fib_multipath_hash_fields,
+		.maxlen		= sizeof(u32),
+		.mode		= 0644,
+		.proc_handler	= proc_fib_multipath_hash_fields,
+		.extra1		= SYSCTL_ONE,
+		.extra2		= &fib_multipath_hash_fields_all_mask,
+	},
+	{
+		.procname	= "fib_multipath_hash_seed",
+		.data		= &init_net,
+		.maxlen		= sizeof(u32),
+		.mode		= 0644,
+		.proc_handler	= proc_fib_multipath_hash_seed,
+	},
+#endif
+	{
+		.procname	= "fib_notify_on_flag_change",
+		.data		= &init_net.ipv4.sysctl_fib_notify_on_flag_change,
+		.maxlen		= sizeof(u8),
+		.mode		= 0644,
+		.proc_handler	= proc_dou8vec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_TWO,
+	},
+#endif
 };
 
 static const struct ctl_table *ipv4_net_table_dup(struct net *net)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 12/16 net-next v3] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (10 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 11/16 net-next v3] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 13/16 net-next v3] net: bridge: guard ARP/RARP proxy and suppression " Fernando Fernandez Mancera
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Pablo Neira Ayuso, Florian Westphal, Phil Sutter, Eric Dumazet,
	netfilter-devel, coreteam, linux-kernel

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 | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/ipv4/netfilter.c b/net/ipv4/netfilter.c
index ce9e1bfa4259..f2d9e0110b77 100644
--- a/net/ipv4/netfilter.c
+++ b/net/ipv4/netfilter.c
@@ -30,6 +30,9 @@ int ip_route_me_harder(struct net *net, struct sock *sk, struct sk_buff *skb, un
 	struct flow_keys flkeys;
 	unsigned int hh_len;
 
+	if (!IS_ENABLED(CONFIG_IPV4))
+		return -EPROTONOSUPPORT;
+
 	sk = sk_to_full_sk(sk);
 	flags = sk ? inet_sk_flowi_flags(sk) : 0;
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 13/16 net-next v3] net: bridge: guard ARP/RARP proxy and suppression with CONFIG_IPV4
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (11 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 12/16 net-next v3] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 14/16 net-next v3] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards Fernando Fernandez Mancera
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Nikolay Aleksandrov, Ido Schimmel, Eric Dumazet, bridge,
	linux-kernel

Historically, the bridge subsystem relied on CONFIG_INET to guard ARP and
RARP proxy and suppression routines. As CONFIG_IPV4 is decoupled from the
core stack and made optional, relying on CONFIG_INET is no longer accurate,
as ARP and RARP are exclusively IPv4 protocols.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 net/bridge/br_arp_nd_proxy.c | 2 ++
 net/bridge/br_device.c       | 2 +-
 net/bridge/br_input.c        | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index da15f4d7c1ae..1809648a883b 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -56,7 +56,9 @@ br_is_neigh_suppress_enabled_vid(const struct net_bridge_port *p, u16 vid)
 
 	return br_is_neigh_suppress_enabled(p, v);
 }
+#endif /* CONFIG_INET */
 
+#if IS_ENABLED(CONFIG_IPV4)
 static void br_arp_send(struct net_bridge *br, struct net_bridge_port *p,
 			struct net_device *dev, __be32 dest_ip, __be32 src_ip,
 			const unsigned char *dest_hw,
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index ce9ea9ac3d0a..a3e5a521178f 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -69,7 +69,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 				&state, &vlan))
 		goto out;
 
-	if (IS_ENABLED(CONFIG_INET) &&
+	if (IS_ENABLED(CONFIG_IPV4) &&
 	    (eth_hdr(skb)->h_proto == htons(ETH_P_ARP) ||
 	     eth_hdr(skb)->h_proto == htons(ETH_P_RARP)) &&
 	    br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) {
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 4357d78524a6..584ac6c2215c 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -166,7 +166,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 	BR_INPUT_SKB_CB(skb)->brdev = br->dev;
 	BR_INPUT_SKB_CB(skb)->src_port_isolated = test_bit(BR_ISOLATED_BIT, &p->flags);
 
-	if (IS_ENABLED(CONFIG_INET) &&
+	if (IS_ENABLED(CONFIG_IPV4) &&
 	    (skb->protocol == htons(ETH_P_ARP) ||
 	     skb->protocol == htons(ETH_P_RARP))) {
 		br_do_proxy_suppress_arp(skb, br, vid, p);
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 14/16 net-next v3] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (12 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 13/16 net-next v3] net: bridge: guard ARP/RARP proxy and suppression " Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 15/16 net-next v3] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency Fernando Fernandez Mancera
  2026-09-30 13:52 ` [PATCH 16/16 net-next v3] ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
  15 siblings, 0 replies; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Johannes Berg, linux-wireless, linux-kernel

Historically, the mac80211 subsystem used CONFIG_INET to guard IPv4-specific
address notifiers, assuming that if IP networking was enabled, IPv4 was
guaranteed to be present. As part of the effort to make CONFIG_IPV4 optional,
this assumption is no longer valid.

Update the conditional compilation guards around the IPv4 address change
notifier and its associated registration routines to explicitly check
for IS_ENABLED(CONFIG_IPV4). This ensures the mac80211 subsystem
compiles successfully without undefined references when building an
IPv6-only kernel.

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

diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 7816395681fa..1a0fa55db861 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -558,7 +558,7 @@ void ieee80211_restart_hw(struct ieee80211_hw *hw)
 }
 EXPORT_SYMBOL(ieee80211_restart_hw);
 
-#ifdef CONFIG_INET
+#if IS_ENABLED(CONFIG_IPV4)
 static int ieee80211_ifa_changed(struct notifier_block *nb,
 				 unsigned long data, void *arg)
 {
@@ -1655,7 +1655,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
 	wiphy_unlock(hw->wiphy);
 	rtnl_unlock();
 
-#ifdef CONFIG_INET
+#if IS_ENABLED(CONFIG_IPV4)
 	local->ifa_notifier.notifier_call = ieee80211_ifa_changed;
 	result = register_inetaddr_notifier(&local->ifa_notifier);
 	if (result)
@@ -1673,11 +1673,11 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
 
 #if IS_ENABLED(CONFIG_IPV6)
  fail_ifa6:
-#ifdef CONFIG_INET
+#if IS_ENABLED(CONFIG_IPV4)
 	unregister_inetaddr_notifier(&local->ifa_notifier);
 #endif
 #endif
-#if defined(CONFIG_INET) || defined(CONFIG_IPV6)
+#if IS_ENABLED(CONFIG_IPV4)
  fail_ifa:
 #endif
 	wiphy_unregister(local->hw.wiphy);
@@ -1705,7 +1705,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)
 	tasklet_kill(&local->tx_pending_tasklet);
 	tasklet_kill(&local->tasklet);
 
-#ifdef CONFIG_INET
+#if IS_ENABLED(CONFIG_IPV4)
 	unregister_inetaddr_notifier(&local->ifa_notifier);
 #endif
 #if IS_ENABLED(CONFIG_IPV6)
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 15/16 net-next v3] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (13 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 14/16 net-next v3] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  2026-09-30 13:52 ` [PATCH 16/16 net-next v3] ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Paul Moore, Eric Dumazet, Casey Schaufler, James Morris,
	Serge E. Hallyn, Willem de Bruijn, Eric Biggers,
	Kuniyuki Iwashima, Florian Westphal, Chia-Yu Chang, Yung Chih Su,
	Wyatt Feng, Joel Granados, Ido Schimmel, linux-security-module,
	linux-kernel

Currently, the Commercial IP Security Option (CIPSO) is unconditionally
tied to CONFIG_NETLABEL. Because CIPSO is inherently an IPv4 protocol
feature, this creates a transitive dependency where subsystems relying on
NetLabel (such as Smack) are forced to depend on CONFIG_IPV4, even if
the user only wants to utilize IPv6/CALIPSO.

This patch introduces a new CONFIG_CIPSO boolean that is automatically
enabled only when both NETLABEL and IPV4 are selected. It abstracts the
CIPSO-specific Makefile targets, sysctls, and kernel APIs behind this
new config.

By safely stubbing out the CIPSO netlabel_kapi functions to return
-ENOSYS when disabled, this allows NetLabel and Smack to be successfully
built and used on IPv6-only kernels.

Acked-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
 include/net/cipso_ipv4.h         | 18 +++++++++++-------
 net/Kconfig                      |  3 ---
 net/ipv4/Makefile                |  2 +-
 net/ipv4/sysctl_net_ipv4.c       |  4 ++--
 net/netlabel/Kconfig             |  4 ++++
 net/netlabel/Makefile            |  2 +-
 net/netlabel/netlabel_cipso_v4.h |  7 +++++++
 net/netlabel/netlabel_kapi.c     |  3 +++
 security/smack/Kconfig           |  1 -
 9 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/include/net/cipso_ipv4.h b/include/net/cipso_ipv4.h
index d6780d7903f4..6f50a0a6951b 100644
--- a/include/net/cipso_ipv4.h
+++ b/include/net/cipso_ipv4.h
@@ -100,7 +100,7 @@ struct cipso_v4_std_map_tbl {
  * Sysctl Variables
  */
 
-#ifdef CONFIG_NETLABEL
+#ifdef CONFIG_CIPSO
 extern int cipso_v4_cache_enabled;
 extern int cipso_v4_cache_bucketsize;
 extern int cipso_v4_rbm_optfmt;
@@ -111,7 +111,7 @@ extern int cipso_v4_rbm_strictvalid;
  * DOI List Functions
  */
 
-#ifdef CONFIG_NETLABEL
+#ifdef CONFIG_CIPSO
 int cipso_v4_doi_add(struct cipso_v4_doi *doi_def,
 		     struct netlbl_audit *audit_info);
 void cipso_v4_doi_free(struct cipso_v4_doi *doi_def);
@@ -144,19 +144,23 @@ static inline struct cipso_v4_doi *cipso_v4_doi_getdef(u32 doi)
 	return NULL;
 }
 
+static inline void cipso_v4_doi_putdef(struct cipso_v4_doi *doi_def)
+{
+}
+
 static inline int cipso_v4_doi_walk(u32 *skip_cnt,
 		     int (*callback) (struct cipso_v4_doi *doi_def, void *arg),
 		     void *cb_arg)
 {
 	return 0;
 }
-#endif /* CONFIG_NETLABEL */
+#endif /* CONFIG_CIPSO */
 
 /*
  * Label Mapping Cache Functions
  */
 
-#ifdef CONFIG_NETLABEL
+#ifdef CONFIG_CIPSO
 void cipso_v4_cache_invalidate(void);
 int cipso_v4_cache_add(const unsigned char *cipso_ptr,
 		       const struct netlbl_lsm_secattr *secattr);
@@ -171,13 +175,13 @@ static inline int cipso_v4_cache_add(const unsigned char *cipso_ptr,
 {
 	return 0;
 }
-#endif /* CONFIG_NETLABEL */
+#endif /* CONFIG_CIPSO */
 
 /*
  * Protocol Handling Functions
  */
 
-#ifdef CONFIG_NETLABEL
+#ifdef CONFIG_CIPSO
 void cipso_v4_error(struct sk_buff *skb, int error, u32 gateway);
 int cipso_v4_getattr(const unsigned char *cipso,
 		     struct netlbl_lsm_secattr *secattr);
@@ -303,6 +307,6 @@ static inline int cipso_v4_validate(const struct sk_buff *skb,
 	return err_offset;
 
 }
-#endif /* CONFIG_NETLABEL */
+#endif /* CONFIG_CIPSO */
 
 #endif /* _CIPSO_IPV4_H */
diff --git a/net/Kconfig b/net/Kconfig
index ca86f20540dd..2ef4ea6ce056 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -136,10 +136,7 @@ if INET
 source "net/ipv4/Kconfig"
 source "net/ipv6/Kconfig"
 source "net/mptcp/Kconfig"
-
-if IPV4
 source "net/netlabel/Kconfig"
-endif # if IPV4
 
 endif # if INET
 
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 83c25f52eb58..871187937add 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -62,7 +62,7 @@ obj-$(CONFIG_TCP_CONG_YEAH) += tcp_yeah.o
 obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o
 obj-$(CONFIG_NET_SOCK_MSG) += tcp_bpf.o
 obj-$(CONFIG_BPF_SYSCALL) += udp_bpf.o
-obj-$(CONFIG_NETLABEL) += cipso_ipv4.o
+obj-$(CONFIG_CIPSO) += cipso_ipv4.o
 
 obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \
 		      xfrm4_output.o xfrm4_protocol.o
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index 6096e9e4d82d..89b0caf5a9f5 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -573,7 +573,7 @@ static struct ctl_table ipv4_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_dointvec
 	},
-#ifdef CONFIG_NETLABEL
+#ifdef CONFIG_CIPSO
 	{
 		.procname	= "cipso_cache_enable",
 		.data		= &cipso_v4_cache_enabled,
@@ -602,7 +602,7 @@ static struct ctl_table ipv4_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_dointvec,
 	},
-#endif /* CONFIG_NETLABEL */
+#endif /* CONFIG_CIPSO */
 	{
 		.procname	= "tcp_available_ulp",
 		.maxlen		= TCP_ULP_BUF_MAX,
diff --git a/net/netlabel/Kconfig b/net/netlabel/Kconfig
index 4383ac29693e..bcc27196d5bd 100644
--- a/net/netlabel/Kconfig
+++ b/net/netlabel/Kconfig
@@ -17,3 +17,7 @@ config NETLABEL
 	   * https://github.com/netlabel/netlabel_tools
 
 	  If you are unsure, say N.
+
+config CIPSO
+	def_bool y
+	depends on NETLABEL && IPV4
diff --git a/net/netlabel/Makefile b/net/netlabel/Makefile
index 5a46381a64e7..8afc1bf00424 100644
--- a/net/netlabel/Makefile
+++ b/net/netlabel/Makefile
@@ -12,5 +12,5 @@ obj-y	+= netlabel_mgmt.o
 
 # protocol modules
 obj-y	+= netlabel_unlabeled.o
-obj-y	+= netlabel_cipso_v4.o
+obj-$(CONFIG_CIPSO) += netlabel_cipso_v4.o
 obj-$(subst m,y,$(CONFIG_IPV6)) += netlabel_calipso.o
diff --git a/net/netlabel/netlabel_cipso_v4.h b/net/netlabel/netlabel_cipso_v4.h
index 9518ab56ec98..fb718f86bcbd 100644
--- a/net/netlabel/netlabel_cipso_v4.h
+++ b/net/netlabel/netlabel_cipso_v4.h
@@ -147,6 +147,13 @@ enum {
 #define NLBL_CIPSOV4_A_MAX (__NLBL_CIPSOV4_A_MAX - 1)
 
 /* NetLabel protocol functions */
+#if IS_ENABLED(CONFIG_CIPSO)
 int netlbl_cipsov4_genl_init(void);
+#else
+static inline int netlbl_cipsov4_genl_init(void)
+{
+	return 0;
+}
+#endif
 
 #endif
diff --git a/net/netlabel/netlabel_kapi.c b/net/netlabel/netlabel_kapi.c
index 3583fa63dd01..c088f599b53d 100644
--- a/net/netlabel/netlabel_kapi.c
+++ b/net/netlabel/netlabel_kapi.c
@@ -332,6 +332,9 @@ int netlbl_cfg_cipsov4_map_add(u32 doi,
 	struct netlbl_domaddr_map *addrmap = NULL;
 	struct netlbl_domaddr4_map *addrinfo = NULL;
 
+	if (!IS_ENABLED(CONFIG_CIPSO))
+		return -ENOSYS;
+
 	doi_def = cipso_v4_doi_getdef(doi);
 	if (doi_def == NULL)
 		return -ENOENT;
diff --git a/security/smack/Kconfig b/security/smack/Kconfig
index b4e6d0168bd1..5a8dfad469c3 100644
--- a/security/smack/Kconfig
+++ b/security/smack/Kconfig
@@ -3,7 +3,6 @@ config SECURITY_SMACK
 	bool "Simplified Mandatory Access Control Kernel Support"
 	depends on NET
 	depends on INET
-	depends on IPV4
 	depends on SECURITY
 	select NETLABEL
 	select SECURITY_NETWORK
-- 
2.55.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 16/16 net-next v3] ipv4: make CONFIG_IPV4 boolean
       [not found] <20260930135334.4739-1-fmancera@suse.de>
                   ` (14 preceding siblings ...)
  2026-09-30 13:52 ` [PATCH 15/16 net-next v3] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency Fernando Fernandez Mancera
@ 2026-09-30 13:52 ` Fernando Fernandez Mancera
  2026-10-04 17:22   ` netdev-bot+sashiko
  15 siblings, 1 reply; 26+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-30 13:52 UTC (permalink / raw)
  To: netdev
  Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
	Eric Dumazet, Eric Biggers, Ard Biesheuvel, Krzysztof Kozlowski,
	linux-kernel

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. The default is kept at 'INET' to prevent breaking existing
configurations.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
Sashiko: the point of this is to provide a minimal build. There plenty
of features that do not work without IPv4 for now. Follow-up patches
will be sent to fix that in the future.
---
 net/ipv4/Kconfig | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

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


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 02/16 net-next v3] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n
  2026-09-30 13:52 ` [PATCH 02/16 net-next v3] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 2 · Low: 3

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] With CONFIG_IPV4=n, the new ip_setsockopt()/ip_getsockopt()
  stubs make every SOL_IP option return -EAFNOSUPPORT.
- [Medium] The patch adds `if (!IS_ENABLED(CONFIG_IPV4)) return
  -EAFNOSUPPORT;` at the top of dev_ifconf() (net/core/dev_ioctl.c).
- [Low] Baseline decision: false (not a real bug by the end of the
  series).
- [Low] The patch changes the INDIRECT_CALL_INET_1() gate in
  include/linux/indirect_call_wrapper.h from CONFIG_INET to CONFIG_IPV4.
- [Low] The CONFIG_IPV4=n stub of ip_route_output_flow() in
  include/net/route.h is a function definition with an unnamed first…

--

net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n

This commit adds static inline stubs to include/net/ip.h and
include/net/route.h for use when CONFIG_IPV4 is disabled. It also adds
IS_ENABLED(CONFIG_IPV4) guards to generic networking code so that code
links without IPv4. Finally, it gates the INDIRECT_CALL_INET() and
INDIRECT_CALL_INET_1() helpers on CONFIG_IPV4 instead of CONFIG_INET.

> diff --git a/include/linux/indirect_call_wrapper.h b/include/linux/indirect_call_wrapper.h
> index 0e4340ecd857a..583efbc034466 100644
> --- a/include/linux/indirect_call_wrapper.h
> +++ b/include/linux/indirect_call_wrapper.h

[ ... ]

> -#if IS_ENABLED(CONFIG_INET)
> +#if IS_ENABLED(CONFIG_IPV4)
>  #define INDIRECT_CALL_INET_1(f, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__)
>  #else
>  #define INDIRECT_CALL_INET_1(f, f1, ...) f(__VA_ARGS__)

[Severity: Low]
Does INDIRECT_CALL_INET_1() need to be gated on CONFIG_IPV4?

Every f1 passed to this macro is a TCP core function that is still built
when IPV4=n: tcp_release_cb, tcp_leave_memory_pressure,
tcp_stream_memory_free and tcp_bpf_bypass_getsockopt. tcpv6_prot uses
all of them as well.

For example, release_sock() in net/core/sock.c has:

	INDIRECT_CALL_INET_1(sk->sk_prot->release_cb,
			     tcp_release_cb, sk);

Consider a CONFIG_IPV4=n, CONFIG_IPV6=y kernel with retpolines. There,
release_sock(), sk_leave_memory_pressure(), sk_stream_memory_free() and
BPF_CGROUP_RUN_PROG_GETSOCKOPT would make an indirect call instead of
the direct tcp_* call.

INDIRECT_CALL_INET() above keeps an IPv6 fast path, but
INDIRECT_CALL_INET_1() loses it. Could this macro stay gated on
CONFIG_INET?

> diff --git a/include/net/ip.h b/include/net/ip.h
> index 6f602df72ee62..194a464c443af 100644
> --- a/include/net/ip.h
> +++ b/include/net/ip.h

[ ... ]

> @@ -225,27 +235,58 @@ int ip_append_data(struct sock *sk, struct flowi4 *fl4,

[ ... ]

> +static inline struct sk_buff *ip_finish_skb(struct sock *sk, struct flowi4 *fl4)
> +{
> +	return ERR_PTR(-EAFNOSUPPORT);
> +}

[Severity: Low]
The real ip_finish_skb() calls __ip_make_skb(), which only returns NULL
or a valid skb. Its callers check only for NULL:

	skb = ip_finish_skb(sk, fl4);
	if (!skb)
		goto out;

Later in the series, both callers, udp_push_pending_frames() and
ip_push_pending_frames(), end up in objects that are built only with
CONFIG_IPV4. udp_v6_push_pending_frames() also guards its call with
IS_ENABLED(CONFIG_IPV4).

So this stub looks unreachable. At this commit CONFIG_IPV4 is still
def_bool y, so the stub isn't compiled at all.

Should the stub return NULL to match the return contract of the real
function?

[ ... ]

> @@ -813,24 +854,60 @@ int ip_options_rcv_srr(struct sk_buff *skb, struct net_device *dev);

[ ... ]

> +static inline int ip_setsockopt(struct sock *sk, int level, int optname,
> +				sockptr_t optval, unsigned int optlen)
> +{
> +	return -EAFNOSUPPORT;
> +}
> +
> +static inline int ip_getsockopt(struct sock *sk, int level, int optname,
> +				char __user *optval, int __user *optlen)
> +{
> +	return -EAFNOSUPPORT;
> +}
> +#endif

[Severity: Medium]
With CONFIG_IPV4=n, do these stubs remove SOL_IP options that IPv6
sockets rely on?

ipv6_setsockopt() and ipv6_getsockopt() forward SOL_IP to these
functions for AF_INET6 sockets:

net/ipv6/ipv6_sockglue.c:ipv6_setsockopt() {
    ...
	if (level == SOL_IP && sk->sk_type != SOCK_RAW)
		return ip_setsockopt(sk, level, optname, optval, optlen);
    ...
}

MPTCP also calls ip_setsockopt() directly, from
mptcp_setsockopt_sol_ip_set() and mptcp_setsockopt_v4_set_tos().

Some of these options work for any address family and have no SOL_IPV6
equivalent, such as IP_BIND_ADDRESS_NO_PORT and IP_LOCAL_PORT_RANGE.
The only code that sets INET_FLAGS_BIND_ADDRESS_NO_PORT and the
per-socket local_port_range is in ip_sockglue.c. By the end of the
series that file is built only with CONFIG_IPV4. However, __inet6_bind()
and the port range selection still read that state for IPv6 sockets.

On an IPv6-only kernel, this call would then fail with EAFNOSUPPORT:

	setsockopt(fd6, SOL_IP, IP_BIND_ADDRESS_NO_PORT, ...)

The commit message says:

    This behaves as IPv6 code does.

However, CONFIG_IPV6=n doesn't remove any options from IPv4 sockets. Is
this loss of IPv6 socket functionality intended?

> diff --git a/include/net/route.h b/include/net/route.h
> index 6b55de2e4df88..8c21881eb5f4f 100644
> --- a/include/net/route.h
> +++ b/include/net/route.h
> @@ -169,8 +169,16 @@ static inline struct rtable *__ip_route_output_key(struct net *net,
>  	return ip_route_output_key_hash(net, flp, NULL);
>  }
>  
> +#if IS_ENABLED(CONFIG_IPV4)
>  struct rtable *ip_route_output_flow(struct net *, struct flowi4 *flp,
>  				    const struct sock *sk);
> +#else
> +static inline struct rtable *ip_route_output_flow(struct net *, struct flowi4 *flp,
> +						  const struct sock *sk)
> +{
> +	return ERR_PTR(-EAFNOSUPPORT);
> +}
> +#endif

[Severity: Low]
Will this stub build with all supported compilers once the later patch
in the series makes CONFIG_IPV4=n selectable?

The stub is a function definition whose first parameter has no name.
gnu11 doesn't allow that; only C23 does. GCC 8 through 10 reject it with
"parameter name omitted", and Documentation/process/changes.rst lists
GCC 8.1 as the minimum version. Clang warns that this is a C23
extension, which breaks CONFIG_WERROR builds.

Naming the parameter (struct net *net) would avoid this.

> diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
> index 164643140a523..a10921aa3e23f 100644
> --- a/net/core/dev_ioctl.c
> +++ b/net/core/dev_ioctl.c
> @@ -44,6 +44,9 @@ int dev_ifconf(struct net *net, struct ifconf __user *uifc)
>  	size_t size;
>  	int len, total = 0, done;
>  
> +	if (!IS_ENABLED(CONFIG_IPV4))
> +		return -EAFNOSUPPORT;
> +

[Severity: Medium]
Does this early return change SIOCGIFCONF behaviour on existing
CONFIG_INET=n kernels?

CONFIG_IPV4 is only defined inside "if INET" in net/Kconfig. That means
IS_ENABLED(CONFIG_IPV4) evaluates to 0 whenever CONFIG_INET=n.

Before this patch, those kernels used the !CONFIG_INET stub in
include/linux/inetdevice.h:

static inline int inet_gifconf(struct net_device *dev, char __user *buf,
			       int len, int size)
{
	return 0;
}

So SIOCGIFCONF succeeded and returned ifc_len = 0.

sock_ioctl() passes SIOCGIFCONF to dev_ifconf() for any socket family,
for example AF_UNIX, AF_PACKET or AF_NETLINK. On CONFIG_INET=n kernels
the ioctl now fails with EAFNOSUPPORT. The inet_gifconf() stub also
becomes unreachable.

Could the inet_gifconf() stub be gated on CONFIG_IPV4 instead, and this
early return dropped? That would keep the existing result of success
with an empty list.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 03/16 net-next v3] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic
  2026-09-30 13:52 ` [PATCH 03/16 net-next v3] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.

Critical: 1 · High: 0 · Medium: 1 · Low: 3

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Critical] This patch makes inet_init() skip inet4_init() when
  CONFIG_IPV4=n.
- [Medium] When CONFIG_IPV4=n, inet_gso_segment() now always returns
  ERR_PTR(-EPROTONOSUPPORT).
- [Low] The commit message does not describe the boot-time init
  restructuring, which changes behaviour on every kernel because…
- [Low] Baseline decision: false (resolved later in the series).
- [Low] Baseline decision: false (resolved later in the series).

--

net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic

This patch moves ip_generic_getfrag() from ip_output.c to af_inet.c and
adds CONFIG_IPV4 guards in af_inet.c for IPv4 ioctls, protocol
registration, bind, release and GSO. It splits inet_init() into
inet4_init() and inet4_late_init() so the IPv4-only parts can be skipped.
It also rejects IPv4-mapped IPv6 addresses in __inet6_bind() and
__ip6_datagram_connect() when IPv4 is disabled.

> diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
> index d9421ac38d78c..a9d46a178f9ab 100644
> --- a/net/ipv4/af_inet.c
> +++ b/net/ipv4/af_inet.c

[ ... ]

> @@ -1371,6 +1412,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,
>  	int ihl;
>  	int id;
>  
> +	if (!IS_ENABLED(CONFIG_IPV4))
> +		return ERR_PTR(-EPROTONOSUPPORT);
> +
>  	skb_reset_network_header(skb);
>  	nhoff = skb_network_header(skb) - skb_mac_header(skb);
>  	if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))

[Severity: Medium]

With CONFIG_IPV4=n, this rejects all IPv4 GSO. However, ipv4_offload_init()
still registers the IPv4 GRO callbacks without any guard:

	net_hotdata.ip_packet_offload = (struct packet_offload) {
		.type = cpu_to_be16(ETH_P_IP),
		.callbacks = {
			.gso_segment = inet_gso_segment,
			.gro_receive = inet_gro_receive,
			.gro_complete = inet_gro_complete,
		},
	};

An IPv6-only kernel can still forward IPv4 frames at L2, for example
through a bridge, OVS, tc redirect, VXLAN/Geneve over an IPv6 underlay, or
ip4ip6 via ip4ip6_gso_segment(). In that case IPv4 TCP/UDP traffic still
gets GRO-merged. TSO skbs from tap, virtio and packet sockets are also
still accepted.

If the egress device needs software GSO, are these packets dropped?

validate_xmit_skb()
  skb_gso_segment()
    skb_mac_gso_segment()
      inet_gso_segment()
        return ERR_PTR(-EPROTONOSUPPORT);

The guard seems to be needed only because ip_send_check() is out of line
in ip_output.c. Would moving ip_send_check() into shared code avoid this?

This is still present at the end of the series. The commit message also
doesn't mention the GSO change.

[ ... ]

> @@ -1959,6 +2001,45 @@ static int __init inet_init(void)
>  
>  	ip_init();
>  
> +	return 0;

[ ... ]

> +static int __init inet_init(void)
> +{
> +	int rc;
> +
> +	sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
> +
> +	if (IS_ENABLED(CONFIG_IPV4)) {
> +		rc = inet4_init();
> +		if (rc)
> +			return rc;
> +	}
> +

[Severity: Critical]

With CONFIG_IPV4=n, is the inet_peer slab cache ever created?

The only way to reach ip_init() is through inet4_init(). ip_init() is also
the only caller of inet_initpeers():

net/ipv4/ip_output.c:ip_init() {
	ip_rt_init();
	inet_initpeers();
	...
}

inet_initpeers() is the only place that sets peer_cachep:

net/ipv4/inetpeer.c:inet_initpeers() {
	...
	peer_cachep = KMEM_CACHE(inet_peer, SLAB_HWCACHE_ALIGN | SLAB_PANIC);
}

inetpeer.o is built as obj-y and IPv6 still uses it. For example, a remote
host that sends a UDP datagram to a closed IPv6 port ends up here:

icmpv6_send()
  icmp6_send()
    icmpv6_xrlim_allow()
      inet_getpeer_v6(net->ipv6.peers, &fl6->daddr)
        inet_getpeer()
          kmem_cache_alloc(peer_cachep, GFP_ATOMIC)

On a lookup miss, peer_cachep is NULL at this point. Would that cause a
NULL pointer dereference in softirq context?

ip6_forward() redirect handling and ndisc_send_redirect() reach the same
allocation through inet_getpeer_v6().

CONFIG_IPV4 is still def_bool y at this commit. The problem remains at the
end of the series, where the final patch makes CONFIG_IPV4 selectable.
Should inet_initpeers() be moved out of the IPv4-only init path?

>  	/* Initialise per-cpu ipv4 mibs */
>  	if (init_ipv4_mibs())
>  		panic("%s: Cannot init ipv4 mibs\n", __func__);
> @@ -1980,35 +2061,20 @@ static int __init inet_init(void)
>  	if (icmp_init() < 0)
>  		panic("Failed to create the ICMP control socket.\n");
>  
> -	/*
> -	 *	Initialise the multicast router
> -	 */
> -#if defined(CONFIG_IP_MROUTE)
> -	if (ip_mr_init())
> -		pr_crit("%s: Cannot init ipv4 mroute\n", __func__);
> -#endif
> -
>  	if (init_inet_pernet_ops())
>  		pr_crit("%s: Cannot init ipv4 inet pernet ops\n", __func__);
>  
> -	ipv4_proc_init();
> -
> -	ipfrag_init();
> +#ifdef CONFIG_PROC_FS
> +	if (ip_misc_proc_init())
> +		pr_crit("%s: Cannot init ipv4 misc proc\n", __func__);
> +#endif

[Severity: Low]

ip_misc_proc_init() now runs even when CONFIG_IPV4=n. In that
configuration, net->ipv4.fqdir (set up through ipfrag_init()) and
net->ipv4.devconf_all (set up by devinet) are never initialized.

Would reading /proc/net/sockstat or /proc/net/snmp make sockstat_seq_show()
or snmp_seq_show_ipstats() dereference NULL?

This looks resolved later in the series. The patch "tcp: move protocol
agnostic TCP functions out of tcp_ipv4.c" wraps two things in
IS_ENABLED(CONFIG_IPV4):

- the FRAG line in sockstat_seq_show()
- the snmp_seq_show_ipstats(), icmp_put() and icmpmsg_put() calls in
  snmp_seq_show()

netstat_seq_show() only reads net->mib counters, which init_ipv4_mibs()
always allocates. Since CONFIG_IPV4 is def_bool y at this commit, the
intermediate state can't be reached.

>  
> -	dev_add_pack(&ip_packet_type);
> +	if (IS_ENABLED(CONFIG_IPV4))
> +		inet4_late_init();
>  

[ ... ]

> @@ -2028,12 +2094,8 @@ static int __init ipv4_proc_init(void)
>  		goto out_udp;
>  	if (ping_proc_init())
>  		goto out_ping;
> -	if (ip_misc_proc_init())
> -		goto out_misc;
>  out:
>  	return rc;
> -out_misc:
> -	ping_proc_exit();
>  out_ping:
>  	udp4_proc_exit();
>  out_udp:

[Severity: Low]

This isn't a bug, but the commit message doesn't describe the init
restructuring. That restructuring changes behaviour on default kernels,
since CONFIG_IPV4 is def_bool y at this commit.

ip_mr_init() calls register_pernet_subsys(&ipmr_net_ops). It now runs after
init_inet_pernet_ops() and ip_misc_proc_init() instead of before them. This
changes the pernet init order and the reverse exit order.

ip_misc_proc_init() has also moved out of ipv4_proc_init():

- it now runs before the raw, tcp4, udp4 and ping proc registrations
- it runs even if those registrations fail
- if it fails, it only prints pr_crit() and no longer unwinds the ping,
  udp4, tcp4 and raw proc entries

The message also doesn't mention the new guards in inet_gso_segment(),
around ip_mc_drop_socket() in inet_release(), and in
inet_sk_reselect_saddr().

There is also a typo in the commit message:

    This patch moves the generic ip_generec_getfrag() from ip_output.c to
    af_inet.c.

ip_generec_getfrag() should be ip_generic_getfrag().

Could the commit message be updated to cover these changes?

[ ... ]

> diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
> index 191c9733ff9fa..079d1951405b9 100644
> --- a/net/ipv6/datagram.c
> +++ b/net/ipv6/datagram.c

[ ... ]

> @@ -186,7 +186,7 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
>  	if (addr_type & IPV6_ADDR_MAPPED) {
>  		struct sockaddr_in sin;
>  
> -		if (ipv6_only_sock(sk)) {
> +		if (!IS_ENABLED(CONFIG_IPV4) || ipv6_only_sock(sk)) {
>  			err = -ENETUNREACH;
>  			goto out;
>  		}

[Severity: Low]

The v4-mapped rejection here and in __inet6_bind() doesn't cover
tcp_v6_connect()->tcp_v4_connect() or udpv6_sendmsg()->udp_sendmsg(). With
CONFIG_IPV4=n, can those paths still be reached at this commit?

This looks handled later in the series. "tcp: move protocol agnostic TCP
functions out of tcp_ipv4.c" adds this to tcp_v6_connect():

	if (!IS_ENABLED(CONFIG_IPV4) || ipv6_only_sock(sk))
		return -ENETUNREACH;

The same patch also guards tcp_v6_conn_request(), tcp_v6_syn_recv_sock()
and tcp_v6_do_rcv().

The UDP split later in the series guards the v4-mapped paths in
udpv6_sendmsg(), udp_v6_push_pending_frames() and udp6_hash4(). CONFIG_IPV4
can't be disabled until the final patch, so no intermediate state is
affected.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 04/16 net-next v3] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c
  2026-09-30 13:52 ` [PATCH 04/16 net-next v3] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The commit message understates the user-visible changes.
- [Low] union bpf_tcp_iter_batch_item and struct bpf_tcp_iter_state were
  file-local in tcp_ipv4.c and are now moved into the widely included…

Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but the
  relocated TCP BPF iterator can call vfree() while holding a plain…

--

tcp: move protocol agnostic TCP functions out of tcp_ipv4.c

This moves the protocol agnostic TCP code (proc and BPF iterators,
tcp_twsk_unique(), tcp_add_backlog(), the pernet ops and more) from
tcp_ipv4.c into tcp.c, so it still builds with CONFIG_IPV4=n. It also
renames tcp_v4_destroy_sock() to tcp_destroy_sock() and guards the
IPv4-mapped paths in tcp_ipv6.c. Finally, it changes the /proc/net/sockstat
and /proc/net/snmp output for IPv4-less builds.

> In addition, guard IPv4 code around IPv6 TCP implementation. This
> ensures that IPv4-mapped IPv6 sockects are rejected with a proper error
> code when IPv4 stack is disabled.
>
> Please note that this commit also addresses how /proc/net/sockstat
> output is handled for all protocols. The implementation now force the
> inuse attrtibute to be zero when CONFIG_IPV4=n but the shared stats are
> represented correctly.

[Severity: Low]
Is this description complete? sockstat_seq_show() zeroes the TCP, UDP and
RAW inuse counters, but it drops the FRAG: line entirely instead of
zeroing it:

    if (IS_ENABLED(CONFIG_IPV4))
        seq_printf(seq,  "FRAG: inuse %u memory %lu\n",
    ...

/proc/net/snmp changes too, and the message doesn't mention it:

- snmp_seq_show() leaves out the Ip:, Icmp: and IcmpMsg: sections.
- snmp_seq_show_tcp_udp() gains a first argument, so the output starts
  with "Tcp:" instead of "\nTcp:".

These output formats only show up in CONFIG_IPV4=n builds. Those become
selectable in the later commit "ipv4: make CONFIG_IPV4 boolean", so
IPv6-only kernels would see procfs format changes that aren't documented.

Some structural changes are not mentioned either:

- register_pernet_subsys(&tcp_sk_ops) and bpf_iter_register() move from
  tcp_v4_init() into tcp_init(), where they now run unconditionally.
- tcp_bpf_v4_build_proto() is now gated on CONFIG_IPV4.
- struct bpf_tcp_iter_state and union bpf_tcp_iter_batch_item move into
  include/net/tcp.h.

About "rejected with a proper error code": only tcp_v6_connect() returns
an error (-ENETUNREACH). The other guards return no error to anyone:

- tcp_v6_conn_request() does goto drop.
- tcp_v6_syn_recv_sock() returns NULL.
- tcp_v6_do_rcv() calls kfree_skb() and returns 0.

These guards seem to be there to avoid references to IPv4 symbols on
paths that can't run once IPv4 is compiled out.

Could the commit message describe these changes more precisely?

> diff --git a/include/net/tcp.h b/include/net/tcp.h
> index 96a1a23e47fac..04d487a19b178 100644
> --- a/include/net/tcp.h
> +++ b/include/net/tcp.h

[ ... ]

> @@ -2855,6 +2855,20 @@ struct sk_psock;
>  #ifdef CONFIG_BPF_SYSCALL
>  int tcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore);
>  void tcp_bpf_clone(const struct sock *sk, struct sock *newsk);
> +
> +union bpf_tcp_iter_batch_item {
> +	struct sock *sk;
> +	__u64 cookie;
> +};
> +
> +struct bpf_tcp_iter_state {
> +	struct tcp_iter_state state;
> +	unsigned int cur_sk;
> +	unsigned int end_sk;
> +	unsigned int max_sk;
> +	union bpf_tcp_iter_batch_item *batch;
> +};
> +

[Severity: Low]
Do these definitions need to go into include/net/tcp.h?

After this patch, and still at the end of the series, net/ipv4/tcp.c is
the only user of union bpf_tcp_iter_batch_item and struct
bpf_tcp_iter_state. All of the BPF TCP iterator code now lives there, so
both types could stay file-local in tcp.c. That would keep iterator
internals out of a widely included header.

>  #ifdef CONFIG_BPF_STREAM_PARSER
>  struct strparser;
>  int tcp_bpf_strp_read_sock(struct strparser *strp, read_descriptor_t *desc,

[ ... ]

> diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
> index 3ac4856852794..1f0fc46136ead 100644
> --- a/net/ipv4/tcp.c
> +++ b/net/ipv4/tcp.c

[ ... ]

> @@ -1458,6 +1464,1115 @@ int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)

[ ... ]

> +static int bpf_iter_tcp_realloc_batch(struct bpf_tcp_iter_state *iter,
> +				      unsigned int new_batch_sz, gfp_t flags)
> +{
> +	union bpf_tcp_iter_batch_item *new_batch;
> +
> +	new_batch = kvmalloc_objs(*new_batch, new_batch_sz,
> +				   flags | __GFP_NOWARN);
> +	if (!new_batch)
> +		return -ENOMEM;
> +
> +	memcpy(new_batch, iter->batch, sizeof(*iter->batch) * iter->end_sk);
> +	kvfree(iter->batch);
> +	iter->batch = new_batch;
> +	iter->max_sk = new_batch_sz;
> +
> +	return 0;
> +}

[ ... ]

> +	/* Batch size was too small. */
> +	bpf_iter_tcp_unlock_bucket(seq);
> +	bpf_iter_tcp_put_batch(iter);
> +	err = bpf_iter_tcp_realloc_batch(iter, expected * 3 / 2,
> +					 GFP_USER);
> +	if (err) {
> +		iter->cur_sk = 0;
> +		iter->end_sk = 0;
> +		return ERR_PTR(err);
> +	}
> +
> +	sk = bpf_iter_tcp_resume(seq);
> +	if (!sk)
> +		return NULL; /* Done */
> +
> +	expected = bpf_iter_fill_batch(seq, &sk);
> +	if (likely(!sk))
> +		goto done;
> +
> +	/* Batch size was still too small. Hold onto the lock while we try
> +	 * again with a larger batch to make sure the current bucket's size
> +	 * does not change in the meantime.
> +	 */
> +	err = bpf_iter_tcp_realloc_batch(iter, expected, GFP_NOWAIT);

[Severity: Medium]
This is a pre-existing issue, not one introduced by this patch; the same
code is already in tcp_ipv4.c. Since it is being moved anyway: can this
call vfree() while a plain spinlock is held?

The first reallocation uses GFP_USER with the bucket unlocked. For more
than about 512 entries the size exceeds PAGE_SIZE. kvmalloc() then tries
kmalloc() with __GFP_NORETRY and can fall back to vmalloc(), so
iter->batch may end up vmalloc-backed.

In the listening state, bpf_iter_tcp_resume() retakes the bucket lock in
listening_get_first() with a plain spin_lock:

    spin_lock(&ilb2->lock);

Concurrent listen() calls may have grown the bucket past the new capacity
in the meantime. If so, this GFP_NOWAIT reallocation runs with the lock
held, and on success:

bpf_iter_tcp_realloc_batch()
    kvfree(iter->batch)
        vfree()

vfree() only defers when in_interrupt() is true:

    if (unlikely(in_interrupt())) {
        vfree_atomic(addr);
        return;
    }
    ...
    might_sleep();

This runs in process context under a plain spin_lock with BH enabled, so
in_interrupt() is false. vfree() would then call might_sleep() and tear
down the area synchronously while ilb2->lock is held. GFP_NOWAIT only
limits the new allocation, not the freeing of the old buffer. The
established case uses spin_lock_bh(), so vfree_atomic() is used there.

With CONFIG_DEBUG_ATOMIC_SLEEP this would give a "sleeping function
called from invalid context" splat.

Would it make sense to free the old batch only after the bucket has been
unlocked?

> +	if (err) {
> +		bpf_iter_tcp_unlock_bucket(seq);
> +		return ERR_PTR(err);
> +	}

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 05/16 net-next v3] ipv4: raw: split IPv4 specific logic into raw_ipv4.c
  2026-09-30 13:52 ` [PATCH 05/16 net-next v3] ipv4: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The IPv4-only `struct raw_frag_vec` is left behind in
  net/ipv4/raw.c (line 72) and a second, identical copy is added in…
- [Low] The new file net/ipv4/raw_ipv4.c uses
  `EXPORT_SYMBOL_GPL(raw_v4_match);` at line 61 but does not include…

--

ipv4: raw: split IPv4 specific logic into raw_ipv4.c

This commit moves the IPv4-specific RAW socket code (packet matching,
input/output processing, ICMP filtering and the AF_INET raw_prot) from
net/ipv4/raw.c into a new net/ipv4/raw_ipv4.c. The new file is built only
when CONFIG_IPV4 is enabled. raw_diag.c now wraps raw_v4_match() in an
IS_ENABLED(CONFIG_IPV4) check.

> diff --git a/net/ipv4/raw_ipv4.c b/net/ipv4/raw_ipv4.c
> new file mode 100644
> index 0000000000000..0419026683b08
> --- /dev/null
> +++ b/net/ipv4/raw_ipv4.c
> @@ -0,0 +1,884 @@

[ ... ]

> +#include <asm/ioctls.h>
> +#include <linux/igmp.h>
> +#include <linux/mroute.h>
> +#include <linux/netfilter.h>
> +#include <linux/sockios.h>
> +#include <linux/stddef.h>
> +#include <linux/types.h>
> +#include <linux/compat.h>
> +#include <net/icmp.h>
> +#include <net/ip.h>
> +#include <net/raw.h>
> +#include <net/sock.h>
> +#include <net/udp.h>
> +#include <net/xfrm.h>
> +
> +struct raw_frag_vec {
> +	struct msghdr *msg;
> +	union {
> +		struct icmphdr icmph;
> +		char c[1];
> +	} hdr;
> +	int hlen;
> +};

[Severity: Low]
This isn't a bug, but should the original copy of struct raw_frag_vec be
removed from net/ipv4/raw.c? After this patch, raw.c still contains:

net/ipv4/raw.c:
struct raw_frag_vec {
	struct msghdr *msg;
	union {
		struct icmphdr icmph;
		char c[1];
	} hdr;
	int hlen;
};

All of its users (raw_probe_proto_opt(), raw_getfrag() and raw_sendmsg())
are now in raw_ipv4.c, so nothing in raw.c uses it.

Can the raw.c copy be dropped as part of the move? That would leave a single
definition of this IPv4-only structure.

> +
> +bool raw_v4_match(struct net *net, const struct sock *sk, unsigned short num,
> +		  __be32 raddr, __be32 laddr, int dif, int sdif)
> +{

[ ... ]

> +	return raw_sk_bound_dev_eq(net, READ_ONCE(sk->sk_bound_dev_if),
> +				   dif, sdif);
> +}
> +EXPORT_SYMBOL_GPL(raw_v4_match);

[Severity: Low]
Should raw_ipv4.c include <linux/export.h>? It uses
EXPORT_SYMBOL_GPL(raw_v4_match), but <linux/export.h> is not in its
include list. raw.c includes that header directly for
EXPORT_SYMBOL_GPL(raw_v4_hashinfo).

With W=2, check_missing_include_linux_export_h() in scripts/misc-check
would warn:

  net/ipv4/raw_ipv4.c: warning: EXPORT_SYMBOL() is used, but #include <linux/export.h> is missing

The file still compiles, because the header is pulled in indirectly.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 06/16 net-next v3] ipv4: udp: split IPv4 specific logic into udp_ipv4.c
  2026-09-30 13:52 ` [PATCH 06/16 net-next v3] ipv4: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] When CONFIG_IPV4=n, udp4_gro_complete()
  (net/ipv4/udp_offload.c) returns -EAFNOSUPPORT for every merged IPv4…

--

ipv4: udp: split IPv4 specific logic into udp_ipv4.c

This commit moves the IPv4-specific UDP socket code out of udp.c and into
a new file, udp_ipv4.c. That covers lookup, input/output, multicast
delivery and the AF_INET udp_prot. The new file is built only when
CONFIG_IPV4 is enabled. Callers in af_inet.c, udp_bpf.c, udp_offload.c and
ipv6/udp.c get IS_ENABLED(CONFIG_IPV4) guards.

> diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
> index cf07c3c6611a2..bea037d6b4eb7 100644
> --- a/net/ipv4/udp_offload.c
> +++ b/net/ipv4/udp_offload.c
> @@ -958,6 +958,9 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
>  	const struct iphdr *iph = (struct iphdr *)(skb->data + offset);
>  	struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
>
> +	if (!IS_ENABLED(CONFIG_IPV4))
> +		return -EAFNOSUPPORT;
> +

[Severity: Medium]
With CONFIG_IPV4=n, can this make every merged IPv4 UDP GRO packet get
dropped silently?

The IPv4 GRO offloads are still registered when IPv4 is disabled.
ipv4_offload_init() in af_inet.c still calls udpv4_offload_init() and
dev_add_offload(&net_hotdata.ip_packet_offload) with no guard. So
inet_gro_receive()->udp4_gro_receive()->udp_gro_receive() still runs for
IPv4 UDP packets.

The socket lookup there ends up in the new __udp4_lib_lookup() stub,
which returns NULL. Even so, udp_gro_receive() still aggregates:

	if (skb->dev->features & NETIF_F_GRO_FRAGLIST)
		NAPI_GRO_CB(skb)->is_flist = sk ? !udp_test_bit(GRO_ENABLED, sk) : 1;

	if ((!sk && (skb->dev->features & NETIF_F_GRO_UDP_FWD)) ||
	    (sk && udp_test_bit(GRO_ENABLED, sk)) || NAPI_GRO_CB(skb)->is_flist)
		return call_gro_receive(udp_gro_receive_segment, head, skb);

At flush time, gro_complete()->inet_gro_complete()->udp4_gro_complete()
now returns -EAFNOSUPPORT, and gro_complete() in net/core/gro.c frees
the whole aggregate:

	if (err) {
		WARN_ON(&ptype->list == head);
		kfree_skb(skb);
		return;
	}

The WARN_ON doesn't fire because a ptype was found, so all segments are
lost with no warning. This would affect an IPv6-only kernel that bridges
or forwards IPv4 UDP with NETIF_F_GRO_UDP_FWD or NETIF_F_GRO_FRAGLIST
enabled on the ingress device. Inner IPv4 carried in IPv6 tunnels would
be hit too.

The fraglist branch and the udp_gro_complete() path don't need IPv4
socket state. The only IPv4-only dependency looks like the
udp4_lib_lookup_skb reference. Would it work to stub or skip only the
lookup, for example with a NULL-returning udp4_lib_lookup_skb when
CONFIG_IPV4=n, and keep the rest of udp4_gro_complete()? Or should IPv4
UDP GRO receive also be disabled in that configuration, so packets
aren't aggregated only to be dropped at completion?

The later patch in the series, "ipv4: make CONFIG_IPV4 boolean", makes
this configuration selectable. udp4_gro_complete() and
ipv4_offload_init() are unchanged at the end of the series.

>  	/* do fraglist only if there is no outer UDP encap (or we already processed it) */
>  	if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) {
>  		udp_set_len(uh, skb->len - nhoff);

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 07/16 net-next v3] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c
  2026-09-30 13:52 ` [PATCH 07/16 net-next v3] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The boot-only per-CPU ICMP control-socket setup loop used to be
  inside `int __init icmp_init(void)`.

--

ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c

This commit moves the IPv4 specific ICMP code from net/ipv4/icmp.c into
a new file, net/ipv4/icmp_ipv4.c. That code covers packet processing,
error handling and the per-CPU control socket setup. The new file is
built only when CONFIG_IPV4 is enabled, while icmp.c is now always
built.

> diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
> index 90c0e22c29bea..5e8dcf71d135f 100644
> --- a/net/ipv4/icmp.c
> +++ b/net/ipv4/icmp.c

[ ... ]

> @@ -1757,28 +366,11 @@ 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;
> +	if (IS_ENABLED(CONFIG_IPV4)) {
> +		int err = icmp_init_ipv4();
>  
> -		err = inet_ctl_sock_create(&sk, PF_INET,
> -					   SOCK_RAW, IPPROTO_ICMP, &init_net);
> -		if (err < 0)
> +		if (err)
>  			return err;

[ ... ]

> diff --git a/net/ipv4/icmp_ipv4.c b/net/ipv4/icmp_ipv4.c
> new file mode 100644
> index 0000000000000..24a1fb497cf6f
> --- /dev/null
> +++ b/net/ipv4/icmp_ipv4.c

[ ... ]

> +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;

[Severity: Low]
This loop used to run inside the __init icmp_init(). The new
icmp_init_ipv4() has no __init annotation, but its only caller is still
icmp_init(). So this code is no longer freed after boot.

Should icmp_init_ipv4() be marked __init, both here and on its prototype
in include/net/icmp.h?

The prototype also sits in the public header next to the normal ICMP
entry points, which makes a boot-only helper look like a general API. If
something called it a second time, it would overwrite the per-CPU
ipv4_icmp_sk pointers and leak the old sockets. No such caller exists
today.

The annotation also appears to be missing at the end of the series.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 09/16 net-next v3] ipv4: fib: split common nexthop logic to fib_core.c
  2026-09-30 13:52 ` [PATCH 09/16 net-next v3] ipv4: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] net/core/Makefile adds fib_core.o to the unconditional obj-y list
  next to fib_notifier.o, so it is built into every CONFIG_NET kernel,…
- [Low] The patch rewrote the section comment in include/net/ip_fib.h
  from "/* Exported by fib_frontend.c */" to "/* Exported by…

--

ipv4: fib: split common nexthop logic to fib_core.c

This moves the shared nexthop init/release helpers, fib_nexthop_info(),
fib_add_nexthop(), rtm_ipv4_policy and ip_valid_fib_dump_req() out of
net/ipv4 into a new net/core/fib_core.c. The goal is to let IPv6 use them
without the IPv4 routing stack. It also adds CONFIG_IPV4=n stubs for
several FIB helpers in ip_fib.h and route.h.

> diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h
> index 0a35355fb0f33..6058e53fa3dec 100644
> --- a/include/net/ip_fib.h
> +++ b/include/net/ip_fib.h

[ ... ]

> @@ -442,7 +448,7 @@ static inline bool fib_dscp_masked_match(dscp_t dscp, const struct flowi4 *fl4)
>  	return dscp == (fl4->flowi4_dscp & INET_DSCP_LEGACY_TOS_MASK);
>  }
>  
> -/* Exported by fib_frontend.c */
> +/* Exported by net/core/fib_core.c */
>  extern const struct nla_policy rtm_ipv4_policy[];
>  void ip_fib_init(void);
>  int fib_gw_from_via(struct fib_config *cfg, struct nlattr *nla,

[Severity: Low]
Does this heading still describe the whole block? Only rtm_ipv4_policy
moved to fib_core.c. The other declarations under it are still defined in
net/ipv4/fib_frontend.c:

  ip_fib_init()
  fib_gw_from_via()
  fib_compute_spec_dst()
  fib_info_nh_uses_dev()
  fib_validate_source()
  fib_unmerge()

The reverse happens further down in the same header.
fib_nh_common_init() and fib_nh_common_release() are now defined in
net/core/fib_core.c, but their declarations are still under the older
heading:

/* Exported by fib_semantics.c */
...
int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc,
...
void fib_nh_common_release(struct fib_nh_common *nhc);

Could rtm_ipv4_policy get its own fib_core.c heading, with the
fib_nh_common_* declarations moved under it? As written, ip_fib_init() or
fib_validate_source() could look available when CONFIG_IPV4=n. The
headings are still like this at the end of the series.

[ ... ]

> diff --git a/net/core/Makefile b/net/core/Makefile
> index c20e520358b8e..315e437a49d8c 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

[Severity: Low]
Should fib_core.o be gated on CONFIG_INET? As written, it is built into
every CONFIG_NET kernel, including CONFIG_INET=n ones.

Everything in fib_core.c is INET specific. That covers the fnhe and
rtable cache teardown, rtm_ipv4_policy, ip_valid_fib_dump_req(),
fib_nexthop_info() and fib_add_nexthop(). The callers are in net/ipv4 and
net/ipv6, and both of those need INET.

The only caller outside INET is net/mpls/af_mpls.c, and it already
skips ip_valid_fib_dump_req() when INET is off:

#if IS_ENABLED(CONFIG_INET)
static int mpls_valid_fib_dump_req(...)
{
	return ip_valid_fib_dump_req(net, nlh, filter, cb);
}
#else

So on !INET kernels this is unused code plus an unused
EXPORT_SYMBOL_GPL(ip_valid_fib_dump_req). The same Makefile already gates
an INET-only object this way:

ifdef CONFIG_INET
obj-$(CONFIG_BPF_SYSCALL) += bpf_ksock.o
endif

This does not break the !INET build today. The rule is still
unconditional at the end of the series.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 11/16 net-next v3] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n
  2026-09-30 13:52 ` [PATCH 11/16 net-next v3] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The patch moves the net.ipv4.fib_multipath_hash_seed entry of
  ipv4_net_table (net/ipv4/sysctl_net_ipv4.c, lines 1644-1650) inside…

--

ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n

This commit moves the IPv4-specific entries of ipv4_net_table into a new
IS_ENABLED(CONFIG_IPV4) block at the end of the table. Those sysctls are
then not registered on kernels built without IPv4. Handlers and min/max
variables used only by those entries are marked __maybe_unused.

> diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
> index 0e7fef5973db6..6096e9e4d82d8 100644
> --- a/net/ipv4/sysctl_net_ipv4.c
> +++ b/net/ipv4/sysctl_net_ipv4.c

[ ... ]

> @@ -1656,6 +1469,196 @@ static const struct ctl_table ipv4_net_table[] = {
>  		.extra1		= SYSCTL_ONE_THOUSAND,
>  		.extra2		= &tcp_rto_max_max,
>  	},
> +#if IS_ENABLED(CONFIG_IPV4)

[ ... ]

> +#ifdef CONFIG_IP_ROUTE_MULTIPATH

[ ... ]

> +	{
> +		.procname	= "fib_multipath_hash_seed",
> +		.data		= &init_net,
> +		.maxlen		= sizeof(u32),
> +		.mode		= 0644,
> +		.proc_handler	= proc_fib_multipath_hash_seed,
> +	},
> +#endif

[Severity: Medium]
Is fib_multipath_hash_seed really IPv4-only? The commit message says it
hides "the sysctls that are related to IPv4 only". However, the IPv6
datapath also uses this seed.

The shared helper in include/net/ip_fib.h is built whenever
CONFIG_IP_ROUTE_MULTIPATH is set:

include/net/ip_fib.h:fib_multipath_hash_from_keys() {
    ...
	mp_seed = READ_ONCE(net->ipv4.sysctl_fib_multipath_hash_seed.mp_seed);
	fib_multipath_hash_construct_key(&hash_key, mp_seed);
    ...
}

IPv6 ECMP reaches it through rt6_multipath_hash() and its custom hash
helpers in net/ipv6/route.c:

rt6_multipath_hash()->fib_multipath_hash_from_keys()

IP_ROUTE_MULTIPATH depends only on IP_ADVANCED_ROUTER, not on IPV4. Also,
ipv4_sysctl_init_net() still calls proc_fib_multipath_hash_set_seed(net, 0)
for every netns.

Take a kernel with CONFIG_IPV4=n, CONFIG_IPV6=y and
CONFIG_IP_ROUTE_MULTIPATH=y. That becomes possible once "ipv4: make
CONFIG_IPV4 boolean" later in this series makes IPV4 selectable. Won't
IPv6 multipath hashing then keep using a random seed that userspace can
no longer set?

There is no net.ipv6 copy of this seed. net/ipv6/sysctl_net_ipv6.c only
has its own fib_multipath_hash_policy. The NETEVENT_IPV4_MPATH_HASH_UPDATE
notification sent from proc_fib_multipath_hash_seed() for offload drivers
would also never be sent.

Documentation/networking/ip-sysctl.rst describes this sysctl as:

  The seed value used when calculating hash for multipath routes. Applies
  to both IPv4 and IPv6 datapath. Only present for kernels built with
  CONFIG_IP_ROUTE_MULTIPATH enabled.

proc_fib_multipath_hash_seed() is marked __maybe_unused in this patch.
That suggests the IPV4=n with IP_ROUTE_MULTIPATH=y case was expected.

Should this entry stay outside the CONFIG_IPV4 block, guarded only by
CONFIG_IP_ROUTE_MULTIPATH?

> +	{
> +		.procname	= "fib_notify_on_flag_change",

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 15/16 net-next v3] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency
  2026-09-30 13:52 ` [PATCH 15/16 net-next v3] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] When CONFIG_CIPSO=n, the NetLabel management PROTOCOLS dump
  still tells userspace that CIPSOv4 is supported.

--

netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency

This patch adds a new CONFIG_CIPSO symbol, which is enabled when both
NETLABEL and IPV4 are set. The CIPSO Makefile targets, sysctls and kernel
APIs are placed behind it, and the CIPSO kAPI is stubbed out when it is
disabled. This lets NetLabel and Smack build on IPv6-only kernels.

[ ... ]

> diff --git a/net/netlabel/Makefile b/net/netlabel/Makefile
> index 5a46381a64e77..8afc1bf004243 100644
> --- a/net/netlabel/Makefile
> +++ b/net/netlabel/Makefile
> @@ -12,5 +12,5 @@ obj-y	+= netlabel_mgmt.o
>
>  # protocol modules
>  obj-y	+= netlabel_unlabeled.o
> -obj-y	+= netlabel_cipso_v4.o
> +obj-$(CONFIG_CIPSO) += netlabel_cipso_v4.o
>  obj-$(subst m,y,$(CONFIG_IPV6)) += netlabel_calipso.o
> diff --git a/net/netlabel/netlabel_cipso_v4.h b/net/netlabel/netlabel_cipso_v4.h
> index 9518ab56ec98f..fb718f86bcbd7 100644
> --- a/net/netlabel/netlabel_cipso_v4.h
> +++ b/net/netlabel/netlabel_cipso_v4.h
> @@ -147,6 +147,13 @@ enum {
>  #define NLBL_CIPSOV4_A_MAX (__NLBL_CIPSOV4_A_MAX - 1)
>
>  /* NetLabel protocol functions */
> +#if IS_ENABLED(CONFIG_CIPSO)
>  int netlbl_cipsov4_genl_init(void);
> +#else
> +static inline int netlbl_cipsov4_genl_init(void)
> +{
> +	return 0;
> +}
> +#endif

[Severity: Medium]
With CONFIG_CIPSO=n, does the NLBL_MGMT_C_PROTOCOLS dump still tell
userspace that CIPSOv4 is supported?

This stub means the "NLBL_CIPSOv4" generic netlink family is never
registered. The rest of the CIPSO kAPI is stubbed out too, for example
cipso_v4_doi_getdef() returns NULL. But netlbl_mgmt_protocols() in
net/netlabel/netlabel_mgmt.c still reports CIPSOv4 unconditionally:

net/netlabel/netlabel_mgmt.c:netlbl_mgmt_protocols() {
    ...
	if (protos_sent == 1) {
		if (netlbl_mgmt_protocols_cb(skb,
					     cb,
					     NETLBL_NLTYPE_CIPSOV4) < 0)
			goto protocols_return;
		protos_sent++;
	}
#if IS_ENABLED(CONFIG_IPV6)
	if (protos_sent == 2) {
		if (netlbl_mgmt_protocols_cb(skb,
					     cb,
					     NETLBL_NLTYPE_CALIPSO) < 0)
			goto protocols_return;
		protos_sent++;
	}
#endif
    ...
}

The CALIPSO entry is guarded by CONFIG_IPV6, but the CIPSOv4 entry has no
guard.

On an IPv6-only kernel with NetLabel enabled (for example selected by
SMACK), userspace such as netlabel_tools would see CIPSOv4 in the list.
It would then fail to resolve the CIPSOv4 genl family. An NLBL_MGMT_C_ADD
with NLBL_MGMT_A_CV4DOI would also fail, because cipso_v4_doi_getdef()
returns NULL.

At this commit IPV4 is still def_bool y, so CIPSO always matches NETLABEL.
The later commit in the series, "ipv4: make CONFIG_IPV4 boolean", makes
IPV6=y, IPV4=n, NETLABEL=y a valid configuration. netlbl_mgmt_protocols()
is unchanged at the end of the series.

Wrapping the CIPSOv4 block in #if IS_ENABLED(CONFIG_CIPSO) would not be
enough by itself. The resume state in cb->args[0] uses fixed indices, and
the CALIPSO step checks protos_sent == 2, so skipping the CIPSOv4 step
would also stop CALIPSO from being reported.

Could netlbl_mgmt_protocols() always increment protos_sent for each step,
and only emit the message when that protocol is enabled?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 16/16 net-next v3] ipv4: make CONFIG_IPV4 boolean
  2026-09-30 13:52 ` [PATCH 16/16 net-next v3] ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
@ 2026-10-04 17:22   ` netdev-bot+sashiko
  0 siblings, 0 replies; 26+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04 17:22 UTC (permalink / raw)
  To: fmancera
  Cc: netdev, horms, kuba, pabeni, edumazet, davem, johannes,
	linux-wireless, linux-kernel

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 1 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] ipv4: exposing CONFIG_IPV4 without BROKEN guard silently drops
  Netfilter
- [Medium] Making CONFIG_IPV4 user-selectable makes an s390 configuration
  reachable that does not link.
- [Low] Now that IPV4 can be disabled, several IPv4-only prompts in
  net/ipv4/Kconfig stay visible and settable with IPV4=n, because they…

--

ipv4: make CONFIG_IPV4 boolean

This commit changes CONFIG_IPV4 from a hidden def_bool y into a bool
prompt that users can see. The prompt appears when IPV6 is enabled and
defaults to INET, which makes an IPv6-only kernel build possible.

> diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
> index 51bf05d9770c5..c7d36cfa4a1d2 100644
> --- a/net/ipv4/Kconfig
> +++ b/net/ipv4/Kconfig
> @@ -3,7 +3,15 @@
>  # IP configuration
>  #
>  config IPV4
> -	def_bool y
> +	bool "The IPv4 protocol" if IPV6

[Severity: High]
With IPV4=n, does this silently turn off all of netfilter, including
the IPv6 firewall?

Earlier patches in this series made both netfilter menus depend on
IPV4:

net/netfilter/Kconfig:
menu "Core Netfilter Configuration"
	depends on INET && IPV4 && NETFILTER

net/ipv6/netfilter/Kconfig:
menu "IPv6: Netfilter Configuration"
	depends on INET && IPV6 && NETFILTER && IPV4

Take INET=y, IPV6=y and NETFILTER=y, then answer N to the new prompt.
Kconfig then drops every symbol under both menus without any warning.
That includes NF_CONNTRACK, NF_TABLES, IP6_NF_IPTABLES, IP6_NF_FILTER
and the rest. The help text only says the result is an IPv6-only
system. Nothing tells the user that the firewall went with it.

The resulting kernel has no packet filtering at all. That is likely
the opposite of what someone building an IPv6-only host wants, and it
seems more serious than a missing feature in a minimal build.

I see from the note after the commit message that this is meant to
provide a minimal build and that follow-ups will fill the gaps. Until
netfilter works without IPv4, should the prompt stay hidden behind
something like "depends on BROKEN" or "depends on EXPERT"? Or should
the help text at least warn that netfilter, including ip6tables and
nf_tables, is unavailable with IPV4=n?

[Severity: Medium]
Does this make an s390 configuration reachable that fails to link?

In drivers/s390/net/Kconfig, QETH needs IP_MULTICAST, but nothing in its
dependencies requires IPV4, and IP_MULTICAST itself does not depend on
IPV4:

config QETH
	def_tristate y
	prompt "Gigabit Ethernet device support"
	depends on CCW && NETDEVICES && IP_MULTICAST && QDIO && ETHERNET

QETH_L3 is also def_tristate y and depends only on QETH.

qeth_l3_main.c calls the inetaddr notifier helpers with no CONFIG_IPV4
guard:

drivers/s390/net/qeth_l3_main.c:qeth_l3_register_notifiers() {
	...
	rc = register_inetaddr_notifier(&qeth_l3_ip_notifier);
	...
		unregister_inetaddr_notifier(&qeth_l3_ip_notifier);
	...
}

qeth_l3_unregister_notifiers() calls unregister_inetaddr_notifier() as
well.

Both helpers are defined only in net/ipv4/devinet.c. That file is built
only under obj-$(CONFIG_IPV4), and include/linux/inetdevice.h has no
stub for the !CONFIG_IPV4 case.

Take INET=y, IPV6=y, IPV4=n, with QETH and QETH_L3 left at their
defaults. Would that give an undefined reference at vmlinux link time
when QETH_L3=y, or at modpost when QETH_L3=m?

Other drivers in this effort got a "depends on IPV4 || !INET" guard, for
example IXGBE_IPSEC, MLX5_CORE, SFC, STMMAC_ETH, PPTP and IWLWIFI.
Should QETH or QETH_L3 get the same guard? Or should the notifier calls
be guarded in the code instead?

> +	default INET
> +	help
> +	  Support for IP version 4 (IPv4).
> +
> +	  Disabling this option compiles the kernel networking stack without
> +	  IPv4 support, resulting in a IPv6-only system.
> +
> +	  If unsure, say Y.
>  
>  config IP_MULTICAST
>  	bool "IP: multicasting"

[Severity: Low]
This isn't a bug, but now that IPV4 can be disabled, should the
IPv4-only prompts in net/ipv4/Kconfig also depend on IPV4?

None of these depend on IPV4, so users can still see and set them with
IPV4=n:

  IP_MULTICAST ("IP: multicasting")
  IP_ADVANCED_ROUTER ("IP: advanced router")
  IP_FIB_TRIE_STATS, IP_ROUTE_MULTIPATH and IP_ROUTE_VERBOSE, which
  depend only on IP_ADVANCED_ROUTER
  NET_IPGRE_DEMUX ("IP: GRE demultiplexer")

Most of the code behind these options is in route.o, igmp.o,
fib_frontend.o, fib_semantics.o and fib_trie.o. net/ipv4/Makefile builds
those files only under CONFIG_IPV4:

obj-$(CONFIG_IPV4) += route.o ip_input.o ip_fragment.o ip_forward.o ip_options.o \

In af_inet.c, the CONFIG_IP_MULTICAST IGMP registration sits inside
inet4_init(), which is only called under IS_ENABLED(CONFIG_IPV4). Every
in-tree user of NET_IPGRE_DEMUX (NET_IPGRE, PPTP, IPV6_GRE) already
depends on IPV4.

IP_MULTIPLE_TABLES, IP_PNP, NET_IPIP and IP_MROUTE already have
"depends on IPV4", so these options may simply have been missed.

With IPV4=n, menuconfig still offers these routing and multicast
options, they have no effect, and they are not nested under the new
IPV4 prompt. Would adding "depends on IPV4", or wrapping the block in
"if IPV4", make this consistent?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930135334.4739-1-fmancera%40suse.de

^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-10-04 17:22 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20260930135334.4739-1-fmancera@suse.de>
2026-09-30 13:52 ` [PATCH 01/16 net-next v3] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
2026-09-30 13:52 ` [PATCH 02/16 net-next v3] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 03/16 net-next v3] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 04/16 net-next v3] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 05/16 net-next v3] ipv4: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 06/16 net-next v3] ipv4: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 07/16 net-next v3] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 08/16 net-next v3] ipv4: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
2026-09-30 13:52 ` [PATCH 09/16 net-next v3] ipv4: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 10/16 net-next v3] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
2026-09-30 13:52 ` [PATCH 11/16 net-next v3] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 12/16 net-next v3] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
2026-09-30 13:52 ` [PATCH 13/16 net-next v3] net: bridge: guard ARP/RARP proxy and suppression " Fernando Fernandez Mancera
2026-09-30 13:52 ` [PATCH 14/16 net-next v3] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards Fernando Fernandez Mancera
2026-09-30 13:52 ` [PATCH 15/16 net-next v3] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko
2026-09-30 13:52 ` [PATCH 16/16 net-next v3] ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
2026-10-04 17:22   ` netdev-bot+sashiko

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

all inboxes | Powered by JetHome®