* [PATCH 01/16 net-next v2] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack
[not found] <20260928193046.6698-1-fmancera@suse.de>
@ 2026-09-28 19:29 ` Fernando Fernandez Mancera
2026-09-28 19:29 ` [PATCH 02/16 net-next v2] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
` (14 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:29 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, 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,
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, 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>
---
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/sunrpc/Kconfig | 2 +-
net/tipc/Kconfig | 1 +
net/xfrm/Kconfig | 10 ++++++--
security/smack/Kconfig | 1 +
48 files changed, 117 insertions(+), 64 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/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] 16+ messages in thread
* [PATCH 02/16 net-next v2] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n
[not found] <20260928193046.6698-1-fmancera@suse.de>
2026-09-28 19:29 ` [PATCH 01/16 net-next v2] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
@ 2026-09-28 19:29 ` Fernando Fernandez Mancera
2026-09-28 19:29 ` [PATCH 03/16 net-next v2] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
` (13 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:29 UTC (permalink / raw)
To: netdev
Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
Eric Dumazet, David Ahern, Ido Schimmel, Daniel Borkmann,
John Fastabend, Stanislav Fomichev, Martin KaFai Lau,
Alexei Starovoitov, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Neal Cardwell, Kuniyuki Iwashima,
Kory Maincent, Vadim Fedorenko, Jacob Keller, Nicolai Buchwitz,
Kees Cook, 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] 16+ messages in thread
* [PATCH 03/16 net-next v2] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic
[not found] <20260928193046.6698-1-fmancera@suse.de>
2026-09-28 19:29 ` [PATCH 01/16 net-next v2] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
2026-09-28 19:29 ` [PATCH 02/16 net-next v2] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
@ 2026-09-28 19:29 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 04/16 net-next v2] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
` (12 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:29 UTC (permalink / raw)
To: netdev
Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
Eric Dumazet, David Ahern, Ido Schimmel, Kuniyuki Iwashima,
Kees Cook, Willem de Bruijn, Jiayuan Chen, Christian Brauner,
Qi Tang, Jeff Layton, 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 | 69 ++++++++++++++++++++++++++++++++++++++------
net/ipv4/ip_output.c | 18 ------------
net/ipv6/af_inet6.c | 5 ++++
net/ipv6/datagram.c | 4 +--
4 files changed, 67 insertions(+), 29 deletions(-)
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index d9421ac38d78..cf00386933d6 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) ||
@@ -1153,6 +1185,7 @@ static const struct net_proto_family inet_family_ops = {
.owner = THIS_MODULE,
};
+#if IS_ENABLED(CONFIG_IPV4)
/* Upon startup we insert all the elements in inetsw_array[] into
* the linked list inetsw.
*/
@@ -1193,6 +1226,7 @@ static struct inet_protosw inetsw_array[] =
};
#define INETSW_ARRAY_LEN ARRAY_SIZE(inetsw_array)
+#endif
void inet_register_protosw(struct inet_protosw *p)
{
@@ -1267,6 +1301,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 +1408,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))))
@@ -1774,6 +1814,10 @@ static int __init init_ipv4_mibs(void)
return register_pernet_subsys(&ipv4_mib_ops);
}
+#if IS_ENABLED(CONFIG_IPV4)
+static int ipv4_proc_init(void);
+#endif
+
static __net_init int inet_init_net(struct net *net)
{
/*
@@ -1824,8 +1868,6 @@ static int __init init_inet_pernet_ops(void)
return register_pernet_subsys(&af_inet_ops);
}
-static int ipv4_proc_init(void);
-
/*
* IP protocol layer initialiser
*/
@@ -1870,20 +1912,25 @@ static int __init ipv4_offload_init(void)
fs_initcall(ipv4_offload_init);
+#if IS_ENABLED(CONFIG_IPV4)
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
.list_func = ip_list_rcv,
};
+#endif
static int __init inet_init(void)
{
+#if IS_ENABLED(CONFIG_IPV4)
struct inet_protosw *q;
struct list_head *r;
int rc;
+#endif
sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
+#if IS_ENABLED(CONFIG_IPV4)
raw_hashinfo_init(&raw_v4_hashinfo);
rc = proto_register(&tcp_prot, 1);
@@ -1958,7 +2005,7 @@ static int __init inet_init(void)
*/
ip_init();
-
+#endif /* CONFIG_IPV4 */
/* Initialise per-cpu ipv4 mibs */
if (init_ipv4_mibs())
panic("%s: Cannot init ipv4 mibs\n", __func__);
@@ -1980,6 +2027,9 @@ static int __init inet_init(void)
if (icmp_init() < 0)
panic("Failed to create the ICMP control socket.\n");
+ if (init_inet_pernet_ops())
+ pr_crit("%s: Cannot init ipv4 inet pernet ops\n", __func__);
+#if IS_ENABLED(CONFIG_IPV4)
/*
* Initialise the multicast router
*/
@@ -1987,19 +2037,17 @@ static int __init inet_init(void)
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();
dev_add_pack(&ip_packet_type);
+#endif /* CONFIG_IPV4 */
ip_tunnel_core_init();
- rc = 0;
+ return 0;
+#if IS_ENABLED(CONFIG_IPV4)
out:
return rc;
out_unregister_raw_proto:
@@ -2009,12 +2057,14 @@ static int __init inet_init(void)
out_unregister_tcp_proto:
proto_unregister(&tcp_prot);
goto out;
+#endif
}
fs_initcall(inet_init);
/* ------------------------------------------------------------------------ */
+#if IS_ENABLED(CONFIG_IPV4)
#ifdef CONFIG_PROC_FS
static int __init ipv4_proc_init(void)
{
@@ -2051,3 +2101,4 @@ static int __init ipv4_proc_init(void)
return 0;
}
#endif /* CONFIG_PROC_FS */
+#endif
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] 16+ messages in thread
* [PATCH 04/16 net-next v2] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (2 preceding siblings ...)
2026-09-28 19:29 ` [PATCH 03/16 net-next v2] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 05/16 net-next v2] ipv4: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
` (11 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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, Kees Cook, 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/af_inet.c | 93 +--
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 +-
8 files changed, 1412 insertions(+), 1377 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/af_inet.c b/net/ipv4/af_inet.c
index cf00386933d6..6c7b0816f950 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1185,7 +1185,6 @@ static const struct net_proto_family inet_family_ops = {
.owner = THIS_MODULE,
};
-#if IS_ENABLED(CONFIG_IPV4)
/* Upon startup we insert all the elements in inetsw_array[] into
* the linked list inetsw.
*/
@@ -1226,7 +1225,6 @@ static struct inet_protosw inetsw_array[] =
};
#define INETSW_ARRAY_LEN ARRAY_SIZE(inetsw_array)
-#endif
void inet_register_protosw(struct inet_protosw *p)
{
@@ -1814,10 +1812,6 @@ static int __init init_ipv4_mibs(void)
return register_pernet_subsys(&ipv4_mib_ops);
}
-#if IS_ENABLED(CONFIG_IPV4)
-static int ipv4_proc_init(void);
-#endif
-
static __net_init int inet_init_net(struct net *net)
{
/*
@@ -1868,6 +1862,8 @@ static int __init init_inet_pernet_ops(void)
return register_pernet_subsys(&af_inet_ops);
}
+static int ipv4_proc_init(void);
+
/*
* IP protocol layer initialiser
*/
@@ -1912,25 +1908,18 @@ static int __init ipv4_offload_init(void)
fs_initcall(ipv4_offload_init);
-#if IS_ENABLED(CONFIG_IPV4)
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
.list_func = ip_list_rcv,
};
-#endif
-static int __init inet_init(void)
+static int __init inet4_init(void)
{
-#if IS_ENABLED(CONFIG_IPV4)
struct inet_protosw *q;
struct list_head *r;
int rc;
-#endif
- sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
-
-#if IS_ENABLED(CONFIG_IPV4)
raw_hashinfo_init(&raw_v4_hashinfo);
rc = proto_register(&tcp_prot, 1);
@@ -2005,7 +1994,46 @@ static int __init inet_init(void)
*/
ip_init();
-#endif /* CONFIG_IPV4 */
+
+ 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__);
@@ -2029,42 +2057,24 @@ static int __init inet_init(void)
if (init_inet_pernet_ops())
pr_crit("%s: Cannot init ipv4 inet pernet ops\n", __func__);
-#if IS_ENABLED(CONFIG_IPV4)
- /*
- * 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();
+#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);
-#endif /* CONFIG_IPV4 */
+ if (IS_ENABLED(CONFIG_IPV4))
+ inet4_late_init();
ip_tunnel_core_init();
return 0;
-#if IS_ENABLED(CONFIG_IPV4)
-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;
-#endif
}
fs_initcall(inet_init);
/* ------------------------------------------------------------------------ */
-#if IS_ENABLED(CONFIG_IPV4)
#ifdef CONFIG_PROC_FS
static int __init ipv4_proc_init(void)
{
@@ -2078,12 +2088,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:
@@ -2101,4 +2107,3 @@ static int __init ipv4_proc_init(void)
return 0;
}
#endif /* CONFIG_PROC_FS */
-#endif
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] 16+ messages in thread
* [PATCH 05/16 net-next v2] ipv4: raw: split IPv4 specific logic into raw_ipv4.c
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (3 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 04/16 net-next v2] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 06/16 net-next v2] ipv4: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
` (10 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 UTC (permalink / raw)
To: netdev
Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
Eric Dumazet, Kuniyuki Iwashima, Ard Biesheuvel, Eric Biggers,
Willem de Bruijn, Florian Westphal, Gustavo A. R. Silva,
Breno Leitao, Kees Cook, Jeff Layton, Runyu Xiao, 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 | 883 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 886 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..8d3d6f30b0e8
--- /dev/null
+++ b/net/ipv4/raw_ipv4.c
@@ -0,0 +1,883 @@
+// 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 <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] 16+ messages in thread
* [PATCH 06/16 net-next v2] ipv4: udp: split IPv4 specific logic into udp_ipv4.c
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (4 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 05/16 net-next v2] ipv4: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 07/16 net-next v2] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
` (9 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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, Neal Cardwell, Florian Westphal, Julian Anastasov,
Kuniyuki Iwashima, Eric Biggers, 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 6c7b0816f950..5a191d0a3bfc 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 b534b85bc623..0f2ff5a9d4ae 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -347,62 +347,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)
{
@@ -412,117 +356,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)
{
@@ -532,42 +366,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)
@@ -654,155 +452,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)
@@ -821,209 +472,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.
*/
@@ -1038,52 +486,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.
*/
@@ -1110,112 +512,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) {
@@ -1252,296 +548,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
@@ -1824,21 +830,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)))
@@ -1998,158 +989,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,
@@ -2165,19 +1048,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);
@@ -2336,155 +1206,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.
*/
@@ -2500,401 +1221,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);
@@ -3030,16 +1356,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)
{
@@ -3088,34 +1404,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
@@ -3172,41 +1460,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..466008c218df
--- /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;
+ unsigned short ulen;
+ __be32 saddr, daddr;
+ 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 = ntohs(uh->len);
+ 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] 16+ messages in thread
* [PATCH 07/16 net-next v2] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (5 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 06/16 net-next v2] ipv4: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 08/16 net-next v2] ipv4: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
` (8 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 UTC (permalink / raw)
To: netdev
Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
Eric Dumazet, David Ahern, Ido Schimmel, Ard Biesheuvel,
Neal Cardwell, Florian Westphal, Julian Anastasov,
Kuniyuki Iwashima, Eric Biggers, 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] 16+ messages in thread
* [PATCH 08/16 net-next v2] ipv4: ping: split IPv4 specific logic into ping_ipv4.c
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (6 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 07/16 net-next v2] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 09/16 net-next v2] ipv4: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
` (7 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 UTC (permalink / raw)
To: netdev
Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
Eric Dumazet, Willem de Bruijn, Florian Westphal, Neal Cardwell,
Eric Biggers, Kuniyuki Iwashima, Christian Brauner, yuan.gao,
Gustavo A. R. Silva, Jeff Layton, 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] 16+ messages in thread
* [PATCH 09/16 net-next v2] ipv4: fib: split common nexthop logic to fib_core.c
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (7 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 08/16 net-next v2] ipv4: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 10/16 net-next v2] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
` (6 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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, Song Liu, Mahe Tardy,
Chia-Yu Chang, Joel Granados, Wyatt Feng, Yung Chih Su,
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] 16+ messages in thread
* [PATCH 10/16 net-next v2] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (8 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 09/16 net-next v2] ipv4: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 11/16 net-next v2] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n Fernando Fernandez Mancera
` (5 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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] 16+ messages in thread
* [PATCH 11/16 net-next v2] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (9 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 10/16 net-next v2] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 12/16 net-next v2] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
` (4 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 UTC (permalink / raw)
To: netdev
Cc: horms, kuba, pabeni, edumazet, davem, Fernando Fernandez Mancera,
Eric Dumazet, Chia-Yu Chang, Wyatt Feng, Yung Chih Su,
Joel Granados, Ido Schimmel, 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] 16+ messages in thread
* [PATCH 12/16 net-next v2] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (10 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 11/16 net-next v2] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 13/16 net-next v2] net: bridge: guard ARP/RARP proxy and suppression " Fernando Fernandez Mancera
` (3 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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] 16+ messages in thread
* [PATCH 13/16 net-next v2] net: bridge: guard ARP/RARP proxy and suppression with CONFIG_IPV4
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (11 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 12/16 net-next v2] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 14/16 net-next v2] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards Fernando Fernandez Mancera
` (2 subsequent siblings)
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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, 3 insertions(+), 3 deletions(-)
diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index da15f4d7c1ae..d739b8697429 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -39,7 +39,6 @@ void br_recalculate_neigh_suppress_enabled(struct net_bridge *br)
br_opt_toggle(br, BROPT_NEIGH_SUPPRESS_ENABLED, neigh_suppress);
}
-#if IS_ENABLED(CONFIG_INET)
static bool
br_is_neigh_suppress_enabled_vid(const struct net_bridge_port *p, u16 vid)
{
@@ -57,6 +56,7 @@ br_is_neigh_suppress_enabled_vid(const struct net_bridge_port *p, u16 vid)
return br_is_neigh_suppress_enabled(p, v);
}
+#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] 16+ messages in thread
* [PATCH 14/16 net-next v2] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (12 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 13/16 net-next v2] net: bridge: guard ARP/RARP proxy and suppression " Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 15/16 net-next v2] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 16/16 net-next v2] ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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] 16+ messages in thread
* [PATCH 15/16 net-next v2] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (13 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 14/16 net-next v2] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 16/16 net-next v2] ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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, Eric Biggers, Neal Cardwell, Willem de Bruijn,
Kuniyuki Iwashima, Florian Westphal, Chia-Yu Chang, Wyatt Feng,
Joel Granados, Ido Schimmel, Yung Chih Su, 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.
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] 16+ messages in thread
* [PATCH 16/16 net-next v2] ipv4: make CONFIG_IPV4 boolean
[not found] <20260928193046.6698-1-fmancera@suse.de>
` (14 preceding siblings ...)
2026-09-28 19:30 ` [PATCH 15/16 net-next v2] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency Fernando Fernandez Mancera
@ 2026-09-28 19:30 ` Fernando Fernandez Mancera
15 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-09-28 19:30 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>
---
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] 16+ messages in thread
end of thread, other threads:[~2026-09-28 19:32 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <20260928193046.6698-1-fmancera@suse.de>
2026-09-28 19:29 ` [PATCH 01/16 net-next v2] ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
2026-09-28 19:29 ` [PATCH 02/16 net-next v2] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
2026-09-28 19:29 ` [PATCH 03/16 net-next v2] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 04/16 net-next v2] tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 05/16 net-next v2] ipv4: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 06/16 net-next v2] ipv4: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 07/16 net-next v2] ipv4: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 08/16 net-next v2] ipv4: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 09/16 net-next v2] ipv4: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 10/16 net-next v2] tunnels: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 11/16 net-next v2] ipv4: disable IPv4-only sysctls when CONFIG_IPV4=n Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 12/16 net-next v2] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 13/16 net-next v2] net: bridge: guard ARP/RARP proxy and suppression " Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 14/16 net-next v2] wifi: mac80211: replace CONFIG_INET with CONFIG_IPV4 guards Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 15/16 net-next v2] netlabel: cipso: introduce CONFIG_CIPSO to decouple IPv4 dependency Fernando Fernandez Mancera
2026-09-28 19:30 ` [PATCH 16/16 net-next v2] ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
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®