* [PATCH 01/13 RFC net-next] net: ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
@ 2026-07-12 1:38 ` Fernando Fernandez Mancera
2026-07-12 11:01 ` Arnd Bergmann
2026-07-12 1:39 ` [PATCH 02/13 RFC net-next] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
` (11 subsequent siblings)
12 siblings, 1 reply; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:38 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Jason Gunthorpe, Leon Romanovsky,
Andrew Lunn, Tony Nguyen, Przemek Kitszel, Elad Nachman,
Saeed Mahameed, Tariq Toukan, Mark Bloch, Petr Machata,
Edward Cree, Maxime Coquelin, Alexandre Torgue, Arend van Spriel,
Miri Korenblit, Keith Busch, Jens Axboe, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni, Saurav Kashyap, Javed Hasan,
GR-QLogic-Storage-Upstream, James E.J. Bottomley,
Martin K. Petersen, Nilesh Javali, Manish Rangankar,
Varun Prakash, Alexander Viro, Christian Brauner, Jan Kara,
David Howells, Marc Dionne, Trond Myklebust, Anna Schumaker,
Chuck Lever, 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, Johannes Berg, Matthieu Baerts,
Mat Martineau, Geliang Tang, Julian Anastasov, Aaron Conole,
Eelco Chaudron, Ilya Maximets, Allison Henderson,
Jamal Hadi Salim, Jiri Pirko, Marcelo Ricardo Leitner, Xin Long,
D. Wythe, Dust Li, Sidraya Jayagond, Wenjia Zhang,
Mahanta Jambigi, Tony Lu, Wen Gu, Jon Maloy, Steffen Klassert,
Herbert Xu, Vikas Gupta, Rajashekar Hudumula, Justin Chen,
Bhargava Marreddy, Arnd Bergmann, Nicolai Buchwitz,
Florian Fainelli, Heiner Kallweit, Krzysztof Kozlowski,
Russell King (Oracle), Yao Zi, Yanteng Si, Maxime Chevallier,
Julian Braha, Joey Lu, Shangjuan Wei, Chen-Yu Tsai, Inochi Amaoto,
Lad Prabhakar, Qingfang Deng, Greg Kroah-Hartman, Eric Biggers,
Ethan Nelson-Moore, Ard Biesheuvel, Dmitry Safonov,
Kuniyuki Iwashima, Alyssa Ross, linux-rdma, linux-kernel,
intel-wired-lan, oss-drivers, 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, dev, rds-devel,
linux-sctp, linux-s390, tipc-discussion
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.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
drivers/infiniband/Kconfig | 2 +-
drivers/net/Kconfig | 24 ++++++++++---------
drivers/net/ethernet/broadcom/Kconfig | 2 ++
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/netronome/Kconfig | 1 +
drivers/net/ethernet/qlogic/Kconfig | 2 ++
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/bnx2fc/Kconfig | 1 +
drivers/scsi/bnx2i/Kconfig | 1 +
drivers/scsi/cxgbi/cxgb3i/Kconfig | 2 +-
drivers/scsi/cxgbi/cxgb4i/Kconfig | 2 +-
drivers/target/iscsi/Kconfig | 2 +-
fs/Kconfig | 1 +
fs/afs/Kconfig | 2 +-
fs/nfs/Kconfig | 2 +-
fs/nfsd/Kconfig | 2 +-
net/Kconfig | 6 ++---
net/batman-adv/Kconfig | 1 +
net/bridge/Kconfig | 3 ++-
net/bridge/netfilter/Kconfig | 6 ++---
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/mac80211/Kconfig | 2 +-
net/mpls/Kconfig | 1 +
net/mptcp/Kconfig | 2 +-
net/netfilter/Kconfig | 2 +-
net/netfilter/ipset/Kconfig | 2 +-
net/netfilter/ipvs/Kconfig | 2 +-
net/openvswitch/Kconfig | 2 +-
net/packet/Kconfig | 1 +
net/rds/Kconfig | 2 +-
net/rxrpc/Kconfig | 2 +-
net/sched/Kconfig | 2 +-
net/sctp/Kconfig | 2 +-
net/smc/Kconfig | 2 +-
net/sunrpc/Kconfig | 2 +-
net/tipc/Kconfig | 2 +-
net/xfrm/Kconfig | 12 ++++++++--
53 files changed, 121 insertions(+), 65 deletions(-)
diff --git a/drivers/infiniband/Kconfig b/drivers/infiniband/Kconfig
index 086195758a8a..e9302002b6ea 100644
--- a/drivers/infiniband/Kconfig
+++ b/drivers/infiniband/Kconfig
@@ -3,7 +3,7 @@ menuconfig INFINIBAND
tristate "InfiniBand support"
depends on HAS_IOMEM && HAS_DMA
depends on NET
- depends on INET
+ depends on INET && IPV4
depends on !ALPHA
select DMA_SHARED_BUFFER
select IRQ_POLL
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index ff79c466712d..8e66b3015698 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
@@ -164,6 +164,7 @@ source "drivers/net/team/Kconfig"
config MACVLAN
tristate "MAC-VLAN support"
+ depends on IPV4
help
This allows one to create virtual interfaces that map packets to
or from specific MAC addresses to a particular interface.
@@ -198,7 +199,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 +230,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 +245,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 +260,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 +272,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 +289,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 +302,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 +472,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 +606,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/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig
index f0bac0dd1439..02469b439127 100644
--- a/drivers/net/ethernet/broadcom/Kconfig
+++ b/drivers/net/ethernet/broadcom/Kconfig
@@ -97,6 +97,7 @@ config BNX2
config CNIC
tristate "QLogic CNIC support"
+ depends on IPV4
depends on PCI
select BNX2
select UIO
@@ -208,6 +209,7 @@ config BNXT
tristate "Broadcom NetXtreme-C/E support"
depends on PCI
depends on PTP_1588_CLOCK_OPTIONAL
+ depends on IPV4
select FW_LOADER
select CRC32
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 288fa8ce53af..d975e41a2b68 100644
--- a/drivers/net/ethernet/intel/Kconfig
+++ b/drivers/net/ethernet/intel/Kconfig
@@ -193,6 +193,7 @@ config IXGBE_DCB
config IXGBE_IPSEC
bool "IPSec XFRM cryptography-offload acceleration"
+ depends on IPV4
depends on IXGBE
depends on XFRM_OFFLOAD
default y
@@ -219,6 +220,7 @@ config IXGBEVF
config IXGBEVF_IPSEC
bool "IPSec XFRM cryptography-offload acceleration"
+ depends on IPV4
depends on IXGBEVF
depends on XFRM_OFFLOAD
default y
diff --git a/drivers/net/ethernet/marvell/prestera/Kconfig b/drivers/net/ethernet/marvell/prestera/Kconfig
index f2f7663c3d10..4c7486c7e706 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
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..86b0884968b6 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
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..d12c33044a1d 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
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/netronome/Kconfig b/drivers/net/ethernet/netronome/Kconfig
index d115d16d4649..2ac3f89e1f26 100644
--- a/drivers/net/ethernet/netronome/Kconfig
+++ b/drivers/net/ethernet/netronome/Kconfig
@@ -18,6 +18,7 @@ if NET_VENDOR_NETRONOME
config NFP
tristate "Netronome(R) NFP4000/NFP6000 NIC driver"
+ depends on IPV4
depends on PCI_MSI
depends on VXLAN || VXLAN=n
depends on TLS && TLS_DEVICE || TLS_DEVICE=n
diff --git a/drivers/net/ethernet/qlogic/Kconfig b/drivers/net/ethernet/qlogic/Kconfig
index 1203353238e5..d198f6add1a8 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
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
depends on PCI
select FW_LOADER
help
diff --git a/drivers/net/ethernet/sfc/Kconfig b/drivers/net/ethernet/sfc/Kconfig
index 979f2801e2a8..d2e32e8ffce8 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
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 e3dd5adda5ac..4ac59a43d71c 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
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..a62eb98f2e27 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
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..16826da75d1e 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
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..95d685f165ea 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
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..d2efd96cd513 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
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/bnx2fc/Kconfig b/drivers/scsi/bnx2fc/Kconfig
index d12eeb13384a..d5065b8fdea9 100644
--- a/drivers/scsi/bnx2fc/Kconfig
+++ b/drivers/scsi/bnx2fc/Kconfig
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config SCSI_BNX2X_FCOE
tristate "QLogic FCoE offload support"
+ depends on IPV4
depends on PCI
depends on LIBFC
depends on LIBFCOE
diff --git a/drivers/scsi/bnx2i/Kconfig b/drivers/scsi/bnx2i/Kconfig
index e649a04fab1d..0458e4811731 100644
--- a/drivers/scsi/bnx2i/Kconfig
+++ b/drivers/scsi/bnx2i/Kconfig
@@ -2,6 +2,7 @@
config SCSI_BNX2_ISCSI
tristate "QLogic NetXtreme II iSCSI support"
depends on NET
+ depends on IPV4
depends on PCI
select SCSI_ISCSI_ATTRS
select NETDEVICES
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/fs/Kconfig b/fs/Kconfig
index cf6ae64776e6..20199c1cc93a 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -360,6 +360,7 @@ config GRACE_PERIOD
config LOCKD
tristate
+ depends on IPV4
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 e38477393551..27de9e69b5f7 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -385,7 +385,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
@@ -452,7 +452,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
@@ -474,7 +474,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..60ad4b09be6e 100644
--- a/net/batman-adv/Kconfig
+++ b/net/batman-adv/Kconfig
@@ -9,6 +9,7 @@
config BATMAN_ADV
tristate "B.A.T.M.A.N. Advanced Meshing Protocol"
+ depends on IPV4
select CRC32
help
B.A.T.M.A.N. (better approach to mobile ad-hoc networking) is
diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
index 318715c8fc9b..421892a03129 100644
--- a/net/bridge/Kconfig
+++ b/net/bridge/Kconfig
@@ -7,6 +7,7 @@ config BRIDGE
tristate "802.1d Ethernet Bridging"
select LLC
select STP
+ depends on IPV4
help
If you say Y here, then your Linux box will be able to act as an
Ethernet bridge, which means that the different Ethernet segments it
@@ -36,7 +37,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..003f055f8cfe 100644
--- a/net/bridge/netfilter/Kconfig
+++ b/net/bridge/netfilter/Kconfig
@@ -4,7 +4,7 @@
#
#
menuconfig NF_TABLES_BRIDGE
- depends on BRIDGE && NETFILTER && NF_TABLES
+ depends on BRIDGE && NETFILTER && NF_TABLES && IPV4
select NETFILTER_FAMILY_BRIDGE
tristate "Ethernet Bridge nf_tables support"
@@ -43,7 +43,7 @@ config NF_CONNTRACK_BRIDGE
config BRIDGE_NF_EBTABLES_LEGACY
tristate "Legacy EBTABLES support"
depends on BRIDGE && NETFILTER_XTABLES_LEGACY
- depends on NETFILTER_XTABLES
+ depends on NETFILTER_XTABLES && IPV4
default n
help
Legacy ebtables packet/frame classifier.
@@ -52,7 +52,7 @@ config BRIDGE_NF_EBTABLES_LEGACY
menuconfig BRIDGE_NF_EBTABLES
tristate "Ethernet Bridge tables (ebtables) support"
- depends on BRIDGE && NETFILTER && NETFILTER_XTABLES
+ depends on BRIDGE && NETFILTER && NETFILTER_XTABLES && IPV4
select NETFILTER_FAMILY_BRIDGE
help
ebtables is a general, extensible frame/packet identification
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/mac80211/Kconfig b/net/mac80211/Kconfig
index d6bc295e23a1..ec485da28373 100644
--- a/net/mac80211/Kconfig
+++ b/net/mac80211/Kconfig
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config MAC80211
tristate "Generic IEEE 802.11 Networking Stack (mac80211)"
- depends on CFG80211
+ depends on CFG80211 && IPV4
select CRYPTO
select CRYPTO_LIB_AES_CBC_MACS
select CRYPTO_LIB_ARC4
diff --git a/net/mpls/Kconfig b/net/mpls/Kconfig
index d672ab72ab12..541ba192e2a1 100644
--- a/net/mpls/Kconfig
+++ b/net/mpls/Kconfig
@@ -25,6 +25,7 @@ config NET_MPLS_GSO
config MPLS_ROUTING
tristate "MPLS: routing support"
+ depends on IPV4
depends on NET_IP_TUNNEL || NET_IP_TUNNEL=n
depends on PROC_SYSCTL
help
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 4c04cd8d40a2..3ee9bb726e40 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/openvswitch/Kconfig b/net/openvswitch/Kconfig
index e6aaee92dba4..5931c28ef965 100644
--- a/net/openvswitch/Kconfig
+++ b/net/openvswitch/Kconfig
@@ -5,7 +5,7 @@
config OPENVSWITCH
tristate "Open vSwitch"
- depends on INET
+ depends on INET && IPV4
depends on !NF_CONNTRACK || \
(NF_CONNTRACK && ((!NF_DEFRAG_IPV6 || NF_DEFRAG_IPV6) && \
(!NF_NAT || NF_NAT) && \
diff --git a/net/packet/Kconfig b/net/packet/Kconfig
index 2997382d597c..00fda9e08cb0 100644
--- a/net/packet/Kconfig
+++ b/net/packet/Kconfig
@@ -5,6 +5,7 @@
config PACKET
tristate "Packet socket"
+ depends on IPV4
help
The Packet protocol is used by applications which communicate
directly with network devices without an intermediate network
diff --git a/net/rds/Kconfig b/net/rds/Kconfig
index f007730aa2bb..d33cf3d89632 100644
--- a/net/rds/Kconfig
+++ b/net/rds/Kconfig
@@ -2,7 +2,7 @@
config RDS
tristate "The Reliable Datagram Sockets Protocol"
- depends on INET
+ depends on INET && IPV4
help
The RDS (Reliable Datagram Sockets) protocol provides reliable,
sequenced delivery of datagrams over Infiniband or TCP.
diff --git a/net/rxrpc/Kconfig b/net/rxrpc/Kconfig
index 2cebb666dc07..2029d2d1065f 100644
--- a/net/rxrpc/Kconfig
+++ b/net/rxrpc/Kconfig
@@ -5,7 +5,7 @@
config AF_RXRPC
tristate "RxRPC session sockets"
- depends on INET
+ depends on INET && IPV4
select CRYPTO
select CRYPTO_LIB_DES if RXKAD
select KEYS
diff --git a/net/sched/Kconfig b/net/sched/Kconfig
index 6ddff028b81a..bb782822c3fb 100644
--- a/net/sched/Kconfig
+++ b/net/sched/Kconfig
@@ -807,7 +807,7 @@ config NET_ACT_SKBEDIT
config NET_ACT_CSUM
tristate "Checksum Updating"
- depends on NET_CLS_ACT && INET
+ depends on NET_CLS_ACT && INET && IPV4
select NET_CRC32C
help
Say Y here to update some common checksum after some direct
diff --git a/net/sctp/Kconfig b/net/sctp/Kconfig
index fc989a3791b3..eb3e2bf1ec3a 100644
--- a/net/sctp/Kconfig
+++ b/net/sctp/Kconfig
@@ -5,7 +5,7 @@
menuconfig IP_SCTP
tristate "The SCTP Protocol"
- depends on INET
+ depends on INET && IPV4
select CRYPTO_LIB_SHA1
select CRYPTO_LIB_SHA256
select CRYPTO_LIB_UTILS
diff --git a/net/smc/Kconfig b/net/smc/Kconfig
index 277ef504bc26..52005e4bf77d 100644
--- a/net/smc/Kconfig
+++ b/net/smc/Kconfig
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config SMC
tristate "SMC socket protocol family"
- depends on INET && INFINIBAND && DIBS
+ depends on INET && INFINIBAND && DIBS && IPV4
help
SMC-R provides a "sockets over RDMA" solution making use of
RDMA over Converged Ethernet (RoCE) technology to upgrade
diff --git a/net/sunrpc/Kconfig b/net/sunrpc/Kconfig
index e7808e5714dc..58e6f6db3100 100644
--- a/net/sunrpc/Kconfig
+++ b/net/sunrpc/Kconfig
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config SUNRPC
tristate
- depends on MULTIUSER
+ depends on MULTIUSER && IPV4
config SUNRPC_GSS
tristate
diff --git a/net/tipc/Kconfig b/net/tipc/Kconfig
index 18f62135e47b..869e3fe961f9 100644
--- a/net/tipc/Kconfig
+++ b/net/tipc/Kconfig
@@ -5,7 +5,7 @@
menuconfig TIPC
tristate "The TIPC Protocol"
- depends on INET
+ depends on INET && IPV4
help
The Transparent Inter Process Communication (TIPC) protocol is
specially designed for intra cluster communication. This protocol
diff --git a/net/xfrm/Kconfig b/net/xfrm/Kconfig
index 4a62817a88f8..2e4d466c7bd3 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.
@@ -140,6 +146,7 @@ config NET_KEY_MIGRATE
config XFRM_IPTFS
tristate "IPsec IP-TFS/AGGFRAG (RFC 9347) encapsulation support"
+ depends on IPV4
depends on XFRM
help
Information on the IP-TFS/AGGFRAG encapsulation can be found
@@ -156,5 +163,6 @@ config XFRM_IPTFS
config XFRM_ESPINTCP
bool
+ depends on IPV4
endif # INET
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH 01/13 RFC net-next] net: ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack
2026-07-12 1:38 ` [PATCH 01/13 RFC net-next] net: ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
@ 2026-07-12 11:01 ` Arnd Bergmann
0 siblings, 0 replies; 16+ messages in thread
From: Arnd Bergmann @ 2026-07-12 11:01 UTC (permalink / raw)
To: Fernando Fernandez Mancera, Netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Ido Schimmel, Jason Gunthorpe,
Leon Romanovsky, Andrew Lunn, Anthony L Nguyen, Przemek Kitszel,
Elad Nachman, Saeed Mahameed, Tariq Toukan, Mark Bloch,
Petr Machata, Edward Cree, Maxime Coquelin, Alexandre Torgue,
Arend van Spriel, Miri Korenblit, Keith Busch, Jens Axboe,
Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Saurav Kashyap, Javed Hasan, GR-QLogic-Storage-Upstream,
James E . J . Bottomley, Martin K. Petersen, Nilesh Javali,
Manish Rangankar, Varun Prakash, Alexander Viro,
Christian Brauner, Jan Kara, David Howells, Marc Dionne,
Trond Myklebust, Anna Schumaker, Chuck Lever, 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, Johannes Berg, Matthieu Baerts, Mat Martineau,
Geliang Tang, Julian Anastasov, Aaron Conole, Eelco Chaudron,
Ilya Maximets, Allison Henderson, Jamal Hadi Salim, Jiri Pirko,
Marcelo Ricardo Leitner, Xin Long, D. Wythe, Dust Li,
Sidraya Jayagond, Wenjia Zhang, Mahanta Jambigi, Tony Lu, Wen Gu,
Jon Maloy, Steffen Klassert, Herbert Xu, Vikas Gupta,
Rajashekar Hudumula, Justin Chen, Bhargava Marreddy,
Nicolai Buchwitz, Florian Fainelli, Heiner Kallweit,
Krzysztof Kozlowski, Russell King, Yao Zi, Yanteng Si,
Maxime Chevallier, Julian Braha, Joey Lu, Shangjuan Wei,
Chen-Yu Tsai, Inochi Amaoto, Lad, Prabhakar, Qingfang Deng,
Greg Kroah-Hartman, Eric Biggers, Ethan Nelson-Moore,
Ard Biesheuvel, Dmitry Safonov, Kuniyuki Iwashima, Alyssa Ross,
linux-rdma, linux-kernel, intel-wired-lan@lists.osuosl.org,
open list:NETRONOME ETHERNET DRIVERS, 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,
open list:ETHERNET BRIDGE, netfilter-devel, coreteam, mptcp,
lvs-devel, dev, rds-devel, linux-sctp, linux-s390,
open list:TIPC NETWORK LAYER
On Sun, Jul 12, 2026, at 03:38, Fernando Fernandez Mancera wrote:
> 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.
I expect this will cause additional (trivial) build regression in the
next step when randconfig builds run into obscure corner cases, either
with INET=y IPV4=n IPV6=y or with INET=y IPV4=n IPV6=n.
I can probably give your patch (with IPV4 visible or disabled) an
early go on the randconfig tree to find these more quickly.
If I run into regressions, should I just add more 'depends on IPV4',
or do you have other plans?
Should we have some logic to ensure that at least one of IPV4 or
IPV6 is enabled? I think this would work
config IPV4
bool "The IPv4 protocol" if IPV6
default INET
which only allows turning IPV4 off if IPV6 has enabled.
Arnd
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 02/13 RFC net-next] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
2026-07-12 1:38 ` [PATCH 01/13 RFC net-next] net: ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 03/13 RFC net-next] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
` (10 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Stanislav Fomichev,
Neal Cardwell, Kuniyuki Iwashima, Kory Maincent, Vadim Fedorenko,
Carolina Jubran, 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 | 104 ++++++++++++++++++++++----
include/net/route.h | 8 ++
net/core/dev_ioctl.c | 4 +
net/core/filter.c | 18 ++---
net/core/neighbour.c | 4 +
net/ipv4/inet_hashtables.c | 4 +
7 files changed, 122 insertions(+), 28 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 7f2fe1a8401b..aeac40d11c72 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,
@@ -787,24 +828,55 @@ 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);
void ip_cmsg_recv_offset(struct msghdr *msg, struct sock *sk,
struct sk_buff *skb, int tlen, int offset);
+#if IS_ENABLED(CONFIG_IPV4)
+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 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 f90106f383c5..08adb14224a9 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 a320e264eaaf..f7ca3701b6bd 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -39,6 +39,7 @@ static int dev_ifname(struct net *net, struct ifreq *ifr)
*/
int dev_ifconf(struct net *net, struct ifconf __user *uifc)
{
+#if IS_ENABLED(CONFIG_IPV4)
struct net_device *dev;
void __user *pos;
size_t size;
@@ -82,6 +83,9 @@ int dev_ifconf(struct net *net, struct ifconf __user *uifc)
rtnl_net_unlock(net);
return put_user(total, &uifc->ifc_len);
+#else
+ return -EAFNOSUPPORT;
+#endif
}
static int dev_getifmap(struct net_device *dev, struct ifreq *ifr)
diff --git a/net/core/filter.c b/net/core/filter.c
index b446aa8be5c3..55c2466c32ae 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -2314,7 +2314,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)
{
@@ -2423,7 +2423,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)
@@ -5685,7 +5685,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);
@@ -5722,7 +5722,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);
@@ -6212,7 +6212,7 @@ static int bpf_fib_set_fwd_params(struct bpf_fib_lookup *params, u32 mtu)
}
#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)
{
@@ -6504,7 +6504,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);
@@ -6545,7 +6545,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;
@@ -12076,7 +12076,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
@@ -12099,7 +12099,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
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 1349c0eedb64..1a40a0ab041b 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -3208,9 +3208,13 @@ int neigh_xmit(int index, struct net_device *dev,
goto out_kfree_skb;
}
if (index == NEIGH_ARP_TABLE) {
+#if IS_ENABLED(CONFIG_IPV4)
u32 key = *((u32 *)addr);
neigh = __ipv4_neigh_lookup_noref(dev, key);
+#else
+ goto out_kfree_skb;
+#endif
} else {
neigh = __neigh_lookup_noref(tbl, addr, dev);
}
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index ba0faa9ae2bb..47228ff6fa00 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -581,6 +581,7 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row,
bool rcu_lookup,
u32 hash)
{
+#if IS_ENABLED(CONFIG_IPV4) //TODO: should this be moved out of here?
struct inet_hashinfo *hinfo = death_row->hashinfo;
struct inet_sock *inet = inet_sk(sk);
__be32 daddr = inet->inet_rcv_saddr;
@@ -651,6 +652,9 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row,
not_unique:
spin_unlock(lock);
return -EADDRNOTAVAIL;
+#else
+ return -EAFNOSUPPORT;
+#endif
}
static u64 inet_sk_port_offset(const struct sock *sk)
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 03/13 RFC net-next] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
2026-07-12 1:38 ` [PATCH 01/13 RFC net-next] net: ipv4: introduce CONFIG_IPV4 to decouple the IPv4 stack Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 02/13 RFC net-next] net: core: add IPv4 fallback stubs and guards for CONFIG_IPV4=n Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 04/13 RFC net-next] net: tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
` (9 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Willem de Bruijn, Kuniyuki Iwashima,
Kees Cook, Richard Gobert, Jiayuan Chen, Jeff Layton, Qi Tang,
linux-kernel
To enable compiling the INET subsystem without IPv4, shared generic
utilities must be relocated and IPv4 socket logic must be guarded for
CONFIG_IPV4.
This patch moves the generic ip_generec_getfrag() from ip_output.c to
af_inet.c. It also introduces CONFIG_IPV4 guards around af_inet.c to
reject IPv4-specific ioctls, protocol registrations and bind requests.
The same guard is added to reject IPv4-mapped IPv6.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
net/ipv4/af_inet.c | 96 +++++++++++++++++++++++++++++++++++++-------
net/ipv4/ip_output.c | 18 ---------
net/ipv6/af_inet6.c | 5 +++
net/ipv6/datagram.c | 12 ++++++
4 files changed, 99 insertions(+), 32 deletions(-)
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index 32d006c1a8ee..cd8e129394e4 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.
*/
@@ -425,8 +447,10 @@ int inet_release(struct socket *sock)
if (!sk->sk_kern_sock)
BPF_CGROUP_RUN_PROG_INET_SOCK_RELEASE(sk);
+#if IS_ENABLED(CONFIG_IPV4)
/* Applications forget to leave groups before exiting */
ip_mc_drop_socket(sk);
+#endif
/* If linger is set, we don't return until the close
* is complete. Otherwise we return immediately. The
@@ -478,6 +502,7 @@ EXPORT_SYMBOL(inet_bind);
int __inet_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len,
u32 flags)
{
+#if IS_ENABLED(CONFIG_IPV4)
struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
@@ -570,6 +595,9 @@ int __inet_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len,
release_sock(sk);
out:
return err;
+#else
+ return -EAFNOSUPPORT;
+#endif
}
int inet_dgram_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
@@ -965,18 +993,24 @@ EXPORT_SYMBOL(inet_shutdown);
int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
struct sock *sk = sock->sk;
- int err = 0;
- struct net *net = sock_net(sk);
+#if IS_ENABLED(CONFIG_IPV4)
void __user *p = (void __user *)arg;
- struct ifreq ifr;
+ struct net *net = sock_net(sk);
struct rtentry rt;
+ struct ifreq ifr;
+#endif
+ int err = 0;
switch (cmd) {
case SIOCADDRT:
case SIOCDELRT:
+#if IS_ENABLED(CONFIG_IPV4)
if (copy_from_user(&rt, p, sizeof(struct rtentry)))
return -EFAULT;
err = ip_rt_ioctl(net, cmd, &rt);
+#else
+ err = -EOPNOTSUPP;
+#endif
break;
case SIOCRTMSG:
err = -EINVAL;
@@ -984,18 +1018,26 @@ int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
case SIOCDARP:
case SIOCGARP:
case SIOCSARP:
+#if IS_ENABLED(CONFIG_IPV4)
err = arp_ioctl(net, cmd, (void __user *)arg);
+#else
+ err = -EOPNOTSUPP;
+#endif
break;
case SIOCGIFADDR:
case SIOCGIFBRDADDR:
case SIOCGIFNETMASK:
case SIOCGIFDSTADDR:
case SIOCGIFPFLAGS:
+#if IS_ENABLED(CONFIG_IPV4)
if (get_user_ifreq(&ifr, NULL, p))
return -EFAULT;
err = devinet_ioctl(net, cmd, &ifr);
if (!err && put_user_ifreq(&ifr, p))
err = -EFAULT;
+#else
+ err = -EOPNOTSUPP;
+#endif
break;
case SIOCSIFADDR:
@@ -1004,9 +1046,13 @@ int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
case SIOCSIFDSTADDR:
case SIOCSIFPFLAGS:
case SIOCSIFFLAGS:
+#if IS_ENABLED(CONFIG_IPV4)
if (get_user_ifreq(&ifr, NULL, p))
return -EFAULT;
err = devinet_ioctl(net, cmd, &ifr);
+#else
+ err = -EOPNOTSUPP;
+#endif
break;
default:
if (sk->sk_prot->ioctl)
@@ -1023,6 +1069,7 @@ EXPORT_SYMBOL(inet_ioctl);
static int inet_compat_routing_ioctl(struct sock *sk, unsigned int cmd,
struct compat_rtentry __user *ur)
{
+#if IS_ENABLED(CONFIG_IPV4)
compat_uptr_t rtdev;
struct rtentry rt;
@@ -1038,6 +1085,9 @@ static int inet_compat_routing_ioctl(struct sock *sk, unsigned int cmd,
rt.rt_dev = compat_ptr(rtdev);
return ip_rt_ioctl(sock_net(sk), cmd, &rt);
+#else
+ return -EOPNOTSUPP;
+#endif
}
static int inet_compat_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
@@ -1156,6 +1206,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.
*/
@@ -1169,7 +1220,6 @@ static struct inet_protosw inetsw_array[] =
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
-
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
@@ -1196,6 +1246,7 @@ static struct inet_protosw inetsw_array[] =
};
#define INETSW_ARRAY_LEN ARRAY_SIZE(inetsw_array)
+#endif
void inet_register_protosw(struct inet_protosw *p)
{
@@ -1261,6 +1312,7 @@ EXPORT_SYMBOL(inet_unregister_protosw);
static int inet_sk_reselect_saddr(struct sock *sk)
{
+#if IS_ENABLED(CONFIG_IPV4)
struct inet_sock *inet = inet_sk(sk);
__be32 old_saddr = inet->inet_saddr;
__be32 daddr = inet->inet_daddr;
@@ -1312,6 +1364,9 @@ static int inet_sk_reselect_saddr(struct sock *sk)
* uniqueness. Wait for troubles.
*/
return __sk_prot_rehash(sk);
+#else
+ return -EAFNOSUPPORT;
+#endif
}
int inet_sk_rebuild_header(struct sock *sk)
@@ -1364,6 +1419,7 @@ void inet_sk_state_store(struct sock *sk, int newstate)
struct sk_buff *inet_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
+#if IS_ENABLED(CONFIG_IPV4)
bool udpfrag = false, fixedid = false, gso_partial, encap;
struct sk_buff *segs = ERR_PTR(-EINVAL);
const struct net_offload *ops;
@@ -1454,6 +1510,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,
out:
return segs;
+#else
+ return ERR_PTR(-EPROTONOSUPPORT);
+#endif
}
static struct sk_buff *ipip_gso_segment(struct sk_buff *skb,
@@ -1777,6 +1836,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)
{
/*
@@ -1827,8 +1890,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
*/
@@ -1873,26 +1934,30 @@ 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);
if (rc)
goto out;
-
rc = proto_register(&udp_prot, 1);
if (rc)
goto out_unregister_tcp_proto;
@@ -1929,7 +1994,6 @@ static int __init inet_init(void)
};
if (inet_add_protocol(&net_hotdata.udp_protocol, IPPROTO_UDP) < 0)
pr_crit("%s: Cannot add UDP protocol\n", __func__);
-
net_hotdata.tcp_protocol = (struct net_protocol) {
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
@@ -1961,7 +2025,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__);
@@ -1983,6 +2047,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
*/
@@ -1990,19 +2057,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:
@@ -2012,12 +2077,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)
{
@@ -2054,3 +2121,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 e6dd1e5b8c32..beb2f6a27556 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -928,24 +928,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 282912a11999..5652069d5eaa 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -299,6 +299,7 @@ int __inet6_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len,
/* Check if the address belongs to the host. */
if (addr_type == IPV6_ADDR_MAPPED) {
+#if IS_ENABLED(CONFIG_IPV4)
struct net_device *dev = NULL;
int chk_addr_ret;
@@ -329,6 +330,10 @@ int __inet6_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len,
err = -EADDRNOTAVAIL;
goto out;
}
+#else
+ err = -EADDRNOTAVAIL;
+ goto out;
+#endif
} else {
if (addr_type != IPV6_ADDR_ANY) {
struct net_device *dev = NULL;
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 38d7b4845281..8165277629ad 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -33,10 +33,12 @@
#include <linux/errqueue.h>
#include <linux/uaccess.h>
+#if IS_ENABLED(CONFIG_IPV4)
static bool ipv6_mapped_addr_any(const struct in6_addr *a)
{
return ipv6_addr_v4mapped(a) && (a->s6_addr32[3] == 0);
}
+#endif
static void ip6_datagram_flow_key_init(struct flowi6 *fl6,
const struct sock *sk)
@@ -153,10 +155,14 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
int err;
if (usin->sin6_family == AF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
if (ipv6_only_sock(sk))
return -EAFNOSUPPORT;
err = __ip4_datagram_connect(sk, uaddr, addr_len);
goto ipv4_connected;
+#else
+ return -EAFNOSUPPORT;
+#endif
}
if (addr_len < SIN6_LEN_RFC2133)
@@ -184,6 +190,7 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
daddr = &usin->sin6_addr;
if (addr_type & IPV6_ADDR_MAPPED) {
+#if IS_ENABLED(CONFIG_IPV4)
struct sockaddr_in sin;
if (ipv6_only_sock(sk)) {
@@ -217,6 +224,9 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
}
goto out;
+#else
+ return -ENETUNREACH;
+#endif
}
if (__ipv6_addr_needs_scope_id(addr_type)) {
@@ -522,10 +532,12 @@ int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len)
ipv6_iface_scope_id(&sin->sin6_addr,
IP6CB(skb)->iif);
} else {
+#if IS_ENABLED(CONFIG_IPV4)
ipv6_addr_set_v4mapped(ip_hdr(skb)->saddr,
&sin->sin6_addr);
if (inet_cmsg_flags(inet_sk(sk)))
ip_cmsg_recv(msg, skb);
+#endif
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 04/13 RFC net-next] net: tcp: move protocol agnostic TCP functions out of tcp_ipv4.c
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (2 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 03/13 RFC net-next] net: inet: relocate ip_generic_getfrag and guard IPv4 socket logic Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 05/13 RFC net-next] net: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
` (8 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Neal Cardwell, Kuniyuki Iwashima,
John Fastabend, Jakub Sitnicki, Jiayuan Chen, Willem de Bruijn,
Kees Cook, Richard Gobert, Sabrina Dubroca, 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.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
include/net/tcp.h | 16 +-
net/ipv4/af_inet.c | 8 +
net/ipv4/inet_connection_sock.c | 2 +-
net/ipv4/proc.c | 2 +
net/ipv4/tcp.c | 1291 ++++++++++++++++++++++++++++++
net/ipv4/tcp_bpf.c | 2 +
net/ipv4/tcp_ipv4.c | 1299 +------------------------------
net/ipv6/ipv6_sockglue.c | 5 +
net/ipv6/tcp_ipv6.c | 25 +-
9 files changed, 1348 insertions(+), 1302 deletions(-)
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 6d376ea4d1c0..5d4736aa6152 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -2420,7 +2420,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);
@@ -2838,6 +2838,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 cd8e129394e4..863d00973eb5 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1955,6 +1955,7 @@ static int __init inet_init(void)
#if IS_ENABLED(CONFIG_IPV4)
raw_hashinfo_init(&raw_v4_hashinfo);
+#if IS_ENABLED(CONFIG_IPV4)
rc = proto_register(&tcp_prot, 1);
if (rc)
goto out;
@@ -2002,6 +2003,7 @@ static int __init inet_init(void)
};
if (inet_add_protocol(&net_hotdata.tcp_protocol, IPPROTO_TCP) < 0)
pr_crit("%s: Cannot add TCP protocol\n", __func__);
+#endif
#ifdef CONFIG_IP_MULTICAST
if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
pr_crit("%s: Cannot add IGMP protocol\n", __func__);
@@ -2075,7 +2077,9 @@ static int __init inet_init(void)
out_unregister_udp_proto:
proto_unregister(&udp_prot);
out_unregister_tcp_proto:
+#if IS_ENABLED(CONFIG_IPV4)
proto_unregister(&tcp_prot);
+#endif
goto out;
#endif
}
@@ -2092,8 +2096,10 @@ static int __init ipv4_proc_init(void)
if (raw_proc_init())
goto out_raw;
+#if IS_ENABLED(CONFIG_IPV4)
if (tcp4_proc_init())
goto out_tcp;
+#endif
if (udp4_proc_init())
goto out_udp;
if (ping_proc_init())
@@ -2107,8 +2113,10 @@ static int __init ipv4_proc_init(void)
out_ping:
udp4_proc_exit();
out_udp:
+#if IS_ENABLED(CONFIG_IPV4)
tcp4_proc_exit();
out_tcp:
+#endif
raw_proc_exit();
out_raw:
rc = -ENOMEM;
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 56902bba5483..02a80c1cf1e3 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -1371,7 +1371,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..b7f4e5ecf64d 100644
--- a/net/ipv4/proc.c
+++ b/net/ipv4/proc.c
@@ -50,6 +50,7 @@
*/
static int sockstat_seq_show(struct seq_file *seq, void *v)
{
+#if IS_ENABLED(CONFIG_IPV4)
struct net *net = seq->private;
int orphans, sockets;
@@ -69,6 +70,7 @@ static int sockstat_seq_show(struct seq_file *seq, void *v)
seq_printf(seq, "FRAG: inuse %u memory %lu\n",
atomic_read(&net->ipv4.fqdir->rhashtable.nelems),
frag_mem_limit(net->ipv4.fqdir));
+#endif
return 0;
}
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 455441f1b694..088e5c368c2e 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.
*/
@@ -1455,6 +1461,1107 @@ 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(sizeof(*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;
+ 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_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;
+
+ sk = bpf_iter_tcp_resume(seq);
+ if (!sk)
+ return NULL; /* Done */
+
+ expected = bpf_iter_fill_batch(seq, &sk);
+ if (likely(iter->end_sk == expected))
+ 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)
+ return ERR_PTR(err);
+
+ sk = bpf_iter_tcp_resume(seq);
+ if (!sk)
+ return NULL; /* Done */
+
+ expected = bpf_iter_fill_batch(seq, &sk);
+ if (likely(iter->end_sk == expected))
+ 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);
+ }
+
+ expected = bpf_iter_fill_batch(seq, &sk);
+ WARN_ON_ONCE(iter->end_sk != expected);
+done:
+ bpf_iter_tcp_unlock_bucket(seq);
+ 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;
@@ -5280,6 +6387,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 __net_initdata tcp_sk_ops = {
+ .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;
@@ -5374,7 +6657,15 @@ void __init tcp_init(void)
pr_info("Hash tables configured (established %u bind %u)\n",
tcp_hashinfo.ehash_mask + 1, tcp_hashinfo.bhash_size);
+#if IS_ENABLED(CONFIG_IPV4)
tcp_v4_init();
+#endif
+ 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 cc0bd73f36b6..d7eb2c38f343 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -652,12 +652,14 @@ static void tcp_bpf_check_v6_needs_rebuild(struct proto *ops)
}
}
+#if IS_ENABLED(CONFIG_IPV4)
static int __init tcp_bpf_v4_build_proto(void)
{
tcp_bpf_rebuild_protos(tcp_bpf_prots[TCP_BPF_IPV4], &tcp_prot);
return 0;
}
late_initcall(tcp_bpf_v4_build_proto);
+#endif
static int tcp_bpf_assert_proto_ops(struct proto *ops)
{
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 209ef7522508..27d53a711321 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
@@ -1895,144 +1740,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,
@@ -2404,338 +2111,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)
{
@@ -2872,426 +2250,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(sizeof(*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;
- 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_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;
-
- sk = bpf_iter_tcp_resume(seq);
- if (!sk)
- return NULL; /* Done */
-
- expected = bpf_iter_fill_batch(seq, &sk);
- if (likely(iter->end_sk == expected))
- 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)
- return ERR_PTR(err);
-
- sk = bpf_iter_tcp_resume(seq);
- if (!sk)
- return NULL; /* Done */
-
- expected = bpf_iter_fill_batch(seq, &sk);
- if (likely(iter->end_sk == expected))
- 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);
- }
-
- expected = bpf_iter_fill_batch(seq, &sk);
- WARN_ON_ONCE(iter->end_sk != expected);
-done:
- bpf_iter_tcp_unlock_bucket(seq);
- 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,
@@ -3342,7 +2300,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,
@@ -3385,255 +2343,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;
@@ -3656,10 +2365,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/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index b4c977434c2e..8bb6b50e0094 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -559,6 +559,7 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
if (optlen < sizeof(int))
goto e_inval;
if (val == PF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
if (sk->sk_type == SOCK_RAW)
break;
@@ -624,6 +625,10 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
module_put(THIS_MODULE);
retv = 0;
break;
+#else
+ retv = -EAFNOSUPPORT;
+ break;
+#endif
}
goto e_inval;
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index ebe161d72fbd..7f2ec3dfd2f0 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -217,6 +217,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
*/
if (addr_type & IPV6_ADDR_MAPPED) {
+#if IS_ENABLED(CONFIG_IPV4)
u32 exthdrlen = icsk->icsk_ext_hdr_len;
struct sockaddr_in sin;
@@ -253,6 +254,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
np->saddr = sk->sk_v6_rcv_saddr;
return err;
+#else
+ return -ENETUNREACH;
+#endif
}
if (!ipv6_addr_any(&sk->sk_v6_rcv_saddr))
@@ -1314,7 +1318,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 IS_ENABLED(CONFIG_IPV4)
return tcp_v4_conn_request(sk, skb);
+#else
+ goto drop;
+#endif
if (!ipv6_unicast_destination(skb))
goto drop;
@@ -1342,6 +1350,7 @@ static void tcp_v6_restore_cb(struct sk_buff *skb)
sizeof(struct inet6_skb_parm));
}
+#if IS_ENABLED(CONFIG_IPV4)
/* Called from tcp_v4_syn_recv_sock() for v6_mapped children. */
static void tcp_v6_mapped_child_init(struct sock *newsk, const struct sock *sk)
{
@@ -1376,6 +1385,7 @@ static void tcp_v6_mapped_child_init(struct sock *newsk, const struct sock *sk)
if (inet6_test_bit(REPFLOW, sk))
newnp->flow_label = 0;
}
+#endif
static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
@@ -1400,9 +1410,14 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *
struct flowi6 fl6;
if (skb->protocol == htons(ETH_P_IP))
+#if IS_ENABLED(CONFIG_IPV4)
return tcp_v4_syn_recv_sock(sk, skb, req, dst,
req_unhash, own_req,
tcp_v6_mapped_child_init);
+#else
+ return NULL;
+#endif
+
ireq = inet_rsk(req);
if (sk_acceptq_is_full(sk))
@@ -1577,8 +1592,14 @@ 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))
+ if (skb->protocol == htons(ETH_P_IP)) {
+#if IS_ENABLED(CONFIG_IPV4)
return tcp_v4_do_rcv(sk, skb);
+#else
+ kfree_skb(skb);
+ return 0;
+#endif
+ }
reason = psp_sk_rx_policy_check(sk, skb);
if (reason)
@@ -2279,7 +2300,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.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 05/13 RFC net-next] net: raw: split IPv4 specific logic into raw_ipv4.c
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (3 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 04/13 RFC net-next] net: tcp: move protocol agnostic TCP functions out of tcp_ipv4.c Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 06/13 RFC net-next] net: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
` (7 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Willem de Bruijn, Neal Cardwell,
Eric Biggers, Kuniyuki Iwashima, Florian Westphal, Jeff Layton,
Michael Bommarito, Kees Cook, Gustavo A. R. Silva, linux-kernel
To enable compiling the INET subsystem without IPv4, RAW socket IPv4
specific functions must be isolated from the generic RAW socket
infrastructure, similar to how it is done for TCP.
This patch creates raw_ipv4.c and move all the functions for packet
matching, input/output processing, ICMP filtering and the AF_INET
raw_prot definition from raw.c to the new file. The Makefile is updated
to compile raw_ipv4.c only when CONFIG_IPV4 is enabled.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
net/ipv4/Makefile | 2 +-
net/ipv4/raw.c | 844 ------------------------------------------
net/ipv4/raw_diag.c | 4 +
net/ipv4/raw_ipv4.c | 875 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 880 insertions(+), 845 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 2aebaf8297e0..fbc77393be57 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,815 +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);
-
- if (net_eq(sock_net(sk), net) && inet->inet_num == num &&
- !(inet->inet_daddr && inet->inet_daddr != raddr) &&
- !(inet->inet_rcv_saddr && inet->inet_rcv_saddr != laddr) &&
- raw_sk_bound_dev_eq(net, sk->sk_bound_dev_if, dif, sdif))
- return true;
- return false;
-}
-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_rcv_saddr = inet->inet_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);
@@ -936,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..015806a12a7f 100644
--- a/net/ipv4/raw_diag.c
+++ b/net/ipv4/raw_diag.c
@@ -40,10 +40,14 @@ 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)
+#if IS_ENABLED(CONFIG_IPV4)
return raw_v4_match(net, sk, r->sdiag_raw_protocol,
r->id.idiag_dst[0],
r->id.idiag_src[0],
r->id.idiag_if, 0);
+#else
+ return false;
+#endif
#if IS_ENABLED(CONFIG_IPV6)
else
return raw_v6_match(net, sk, r->sdiag_raw_protocol,
diff --git a/net/ipv4/raw_ipv4.c b/net/ipv4/raw_ipv4.c
new file mode 100644
index 000000000000..bf60729d029b
--- /dev/null
+++ b/net/ipv4/raw_ipv4.c
@@ -0,0 +1,875 @@
+// 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);
+
+ if (net_eq(sock_net(sk), net) && inet->inet_num == num &&
+ !(inet->inet_daddr && inet->inet_daddr != raddr) &&
+ !(inet->inet_rcv_saddr && inet->inet_rcv_saddr != laddr) &&
+ raw_sk_bound_dev_eq(net, sk->sk_bound_dev_if, dif, sdif))
+ return true;
+ return false;
+}
+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_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 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 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_rcv_saddr = inet->inet_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.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 06/13 RFC net-next] net: udp: split IPv4 specific logic into udp_ipv4.c
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (4 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 05/13 RFC net-next] net: raw: split IPv4 specific logic into raw_ipv4.c Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 07/13 RFC net-next] net: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
` (6 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, 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,
Willem de Bruijn, Jakub Sitnicki, Jiayuan Chen, Neal Cardwell,
Eric Biggers, Kuniyuki Iwashima, Florian Westphal, Kees Cook,
Richard Gobert, Gal Pressman, Dragos Tatulea, Alice Mikityanska,
Sabrina Dubroca, Felix Fietkau, Jibin Zhang, 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>
---
net/core/filter.c | 4 +
net/ipv4/Makefile | 2 +-
net/ipv4/af_inet.c | 10 +
net/ipv4/udp.c | 1733 ---------------------------------------
net/ipv4/udp_bpf.c | 6 +
net/ipv4/udp_diag.c | 12 +
net/ipv4/udp_ipv4.c | 1773 ++++++++++++++++++++++++++++++++++++++++
net/ipv4/udp_offload.c | 10 +
net/ipv6/udp.c | 18 +
9 files changed, 1834 insertions(+), 1734 deletions(-)
create mode 100644 net/ipv4/udp_ipv4.c
diff --git a/net/core/filter.c b/net/core/filter.c
index 55c2466c32ae..1ceaae6c7f95 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -7003,9 +7003,13 @@ static struct sock *sk_lookup(struct net *net, struct bpf_sock_tuple *tuple,
dst4, tuple->ipv4.dport,
dif, sdif, &refcounted);
else
+#if IS_ENABLED(CONFIG_IPV4)
sk = __udp4_lib_lookup(net, src4, tuple->ipv4.sport,
dst4, tuple->ipv4.dport,
dif, sdif, NULL);
+#else
+ sk = NULL;
+#endif
#if IS_ENABLED(CONFIG_IPV6)
} else {
struct in6_addr *src6 = (struct in6_addr *)&tuple->ipv6.saddr;
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 863d00973eb5..6978ac0e709c 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -890,8 +890,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);
@@ -919,8 +924,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 59248a59358c..b8638664fcc4 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)
@@ -645,155 +443,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)
@@ -812,198 +463,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;
-}
-
-/*
- * 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;
-
- 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.
*/
@@ -1018,52 +477,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.
*/
@@ -1090,111 +503,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;
- uh->len = htons(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) {
@@ -1231,296 +539,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
@@ -1803,21 +821,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)))
@@ -2023,114 +1026,6 @@ int udp_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
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);
-
- 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;
-}
-
int udp_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
int addr_len)
{
@@ -2144,19 +1039,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);
@@ -2293,155 +1175,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.
*/
@@ -2457,399 +1190,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;
-
- 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) {
- if (udp_queue_rcv_skb(first, skb) > 0)
- consume_skb(skb);
- } 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;
- 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;
-}
-
-/* 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);
@@ -2985,16 +1325,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)
{
@@ -3043,34 +1373,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
@@ -3126,41 +1428,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..a8cbb8f56c73 100644
--- a/net/ipv4/udp_bpf.c
+++ b/net/ipv4/udp_bpf.c
@@ -16,7 +16,11 @@ 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 udp_prot.recvmsg(sk, msg, len, flags);
+#else
+ return -EAFNOSUPPORT;
+#endif
}
static bool udp_sk_has_data(struct sock *sk)
@@ -153,12 +157,14 @@ static void udp_bpf_check_v6_needs_rebuild(struct proto *ops)
}
}
+#if IS_ENABLED(CONFIG_IPV4)
static int __init udp_bpf_v4_build_proto(void)
{
udp_bpf_rebuild_protos(&udp_bpf_prots[UDP_BPF_IPV4], &udp_prot);
return 0;
}
late_initcall(udp_bpf_v4_build_proto);
+#endif
int udp_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)
{
diff --git a/net/ipv4/udp_diag.c b/net/ipv4/udp_diag.c
index f4b24e628cf8..51f9267b7441 100644
--- a/net/ipv4/udp_diag.c
+++ b/net/ipv4/udp_diag.c
@@ -37,11 +37,15 @@ static int udp_diag_dump_one(struct netlink_callback *cb,
rcu_read_lock();
if (req->sdiag_family == AF_INET)
+#if IS_ENABLED(CONFIG_IPV4)
/* src and dst are swapped for historical reasons */
sk = __udp4_lib_lookup(net,
req->id.idiag_src[0], req->id.idiag_sport,
req->id.idiag_dst[0], req->id.idiag_dport,
req->id.idiag_if, 0, NULL);
+#else
+ sk = NULL;
+#endif
#if IS_ENABLED(CONFIG_IPV6)
else if (req->sdiag_family == AF_INET6)
sk = __udp6_lib_lookup(net,
@@ -159,18 +163,26 @@ static int udp_diag_destroy(struct sk_buff *in_skb,
rcu_read_lock();
if (req->sdiag_family == AF_INET)
+#if IS_ENABLED(CONFIG_IPV4)
sk = __udp4_lib_lookup(net,
req->id.idiag_dst[0], req->id.idiag_dport,
req->id.idiag_src[0], req->id.idiag_sport,
req->id.idiag_if, 0, NULL);
+#else
+ sk = NULL;
+#endif
#if IS_ENABLED(CONFIG_IPV6)
else if (req->sdiag_family == AF_INET6) {
if (ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_dst) &&
ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_src))
+#if IS_ENABLED(CONFIG_IPV4)
sk = __udp4_lib_lookup(net,
req->id.idiag_dst[3], req->id.idiag_dport,
req->id.idiag_src[3], req->id.idiag_sport,
req->id.idiag_if, 0, NULL);
+#else
+ sk = NULL;
+#endif
else
sk = __udp6_lib_lookup(net,
(struct in6_addr *)req->id.idiag_dst,
diff --git a/net/ipv4/udp_ipv4.c b/net/ipv4/udp_ipv4.c
new file mode 100644
index 000000000000..04ec8dec547a
--- /dev/null
+++ b/net/ipv4/udp_ipv4.c
@@ -0,0 +1,1773 @@
+// 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;
+ uh->len = htons(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;
+}
+
+/*
+ * 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;
+
+ 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;
+
+ 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) {
+ if (udp_queue_rcv_skb(first, skb) > 0)
+ consume_skb(skb);
+ } 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 = 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);
+
+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 29651b1a0bc7..4da369df0a33 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -848,7 +848,9 @@ struct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb,
static struct sock *udp4_gro_lookup_skb(struct sk_buff *skb, __be16 sport,
__be16 dport)
{
+#if IS_ENABLED(CONFIG_IPV4)
const struct iphdr *iph = skb_gro_network_header(skb);
+#endif
struct net *net = dev_net_rcu(skb->dev);
struct sock *sk;
int iif, sdif;
@@ -859,8 +861,12 @@ static struct sock *udp4_gro_lookup_skb(struct sk_buff *skb, __be16 sport,
inet_get_iif_sdif(skb, &iif, &sdif);
+#if IS_ENABLED(CONFIG_IPV4)
return __udp4_lib_lookup(net, iph->saddr, sport,
iph->daddr, dport, iif, sdif, NULL);
+#else
+ return NULL;
+#endif
}
INDIRECT_CALLABLE_SCOPE
@@ -951,6 +957,7 @@ int udp_gro_complete(struct sk_buff *skb, int nhoff,
INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
{
+#if IS_ENABLED(CONFIG_IPV4)
const u16 offset = NAPI_GRO_CB(skb)->network_offsets[skb->encapsulation];
const struct iphdr *iph = (struct iphdr *)(skb->data + offset);
struct udphdr *uh = (struct udphdr *)(skb->data + nhoff);
@@ -972,6 +979,9 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)
iph->daddr, 0);
return udp_gro_complete(skb, nhoff, udp4_lib_lookup_skb);
+#else
+ return -EAFNOSUPPORT;
+#endif
}
int __init udpv4_offload_init(void)
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 392e18b97045..fefaffd981a5 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -326,10 +326,12 @@ static void udp6_hash4(struct sock *sk)
struct net *net = sock_net(sk);
unsigned int hash;
+#if IS_ENABLED(CONFIG_IPV4)
if (ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr)) {
udp4_hash4(sk);
return;
}
+#endif
if (sk_unhashed(sk) || ipv6_addr_any(&sk->sk_v6_rcv_saddr))
return;
@@ -466,7 +468,9 @@ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
{
int off, is_udp4, err, peeking = flags & MSG_PEEK;
struct ipv6_pinfo *np = inet6_sk(sk);
+#if IS_ENABLED(CONFIG_IPV4)
struct inet_sock *inet = inet_sk(sk);
+#endif
struct udp_mib __percpu *mib;
bool checksum_valid = false;
unsigned int ulen, copied;
@@ -558,9 +562,11 @@ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
ip6_datagram_recv_common_ctl(sk, msg, skb);
if (is_udp4) {
+#if IS_ENABLED(CONFIG_IPV4)
if (inet_cmsg_flags(inet))
ip_cmsg_recv_offset(msg, sk, skb,
sizeof(struct udphdr), off);
+#endif
} else {
if (np->rxopt.all)
ip6_datagram_recv_specific_ctl(sk, msg, skb);
@@ -1439,7 +1445,11 @@ static int udp_v6_push_pending_frames(struct sock *sk)
int err = 0;
if (up->pending == AF_INET)
+#if IS_ENABLED(CONFIG_IPV4)
return udp_push_pending_frames(sk);
+#else
+ return -EAFNOSUPPORT;
+#endif
skb = ip6_finish_skb(sk);
if (!skb)
@@ -1519,8 +1529,12 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
msg->msg_name = &sin;
msg->msg_namelen = sizeof(sin);
do_udp_sendmsg:
+#if IS_ENABLED(CONFIG_IPV4)
err = ipv6_only_sock(sk) ?
-ENETUNREACH : udp_sendmsg(sk, msg, len);
+#else
+ err = -ENETUNREACH;
+#endif
msg->msg_name = sin6;
msg->msg_namelen = addr_len;
return err;
@@ -1535,7 +1549,11 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
if (READ_ONCE(up->pending)) {
if (READ_ONCE(up->pending) == AF_INET)
+#if IS_ENABLED(CONFIG_IPV4)
return udp_sendmsg(sk, msg, len);
+#else
+ return -EAFNOSUPPORT;
+#endif
/*
* There are pending frames.
* The socket lock must be held while it's corked.
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 07/13 RFC net-next] net: icmp: split IPv4 specific logic into icmp_ipv4.c
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (5 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 06/13 RFC net-next] net: udp: split IPv4 specific logic into udp_ipv4.c Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 08/13 RFC net-next] net: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
` (5 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Ard Biesheuvel, Neal Cardwell,
Florian Westphal, Eric Biggers, Kuniyuki Iwashima,
Willem de Bruijn, Christian Brauner, Jeff Layton, Kees Cook,
Qi Tang, linux-kernel
To enable compiling INET subsystem withot IPv4, ICMP protocol IPv4
specific functions must be isolated from the generic ICMP protocol
infrastructure.
This patch creates icmp_ipv4.c and move all the functions for packet
processing, error handling, and other from icmp.c to a new file. The
Makefile is updated to compile icmp_ipv4.c only when CONFIG_IPV4 is
enabled.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
include/net/icmp.h | 1 +
net/ipv4/Makefile | 9 +-
net/ipv4/icmp.c | 1407 +----------------------------------------
net/ipv4/icmp_ipv4.c | 1425 ++++++++++++++++++++++++++++++++++++++++++
net/ipv6/datagram.c | 2 +
5 files changed, 1441 insertions(+), 1403 deletions(-)
create mode 100644 net/ipv4/icmp_ipv4.c
diff --git a/include/net/icmp.h b/include/net/icmp.h
index 935ee13d9ae9..85ad655aea5f 100644
--- a/include/net/icmp.h
+++ b/include/net/icmp.h
@@ -57,6 +57,7 @@ static inline void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __b
int icmp_rcv(struct sk_buff *skb);
int icmp_err(struct sk_buff *skb, u32 info);
+int icmp_init_ipv4(void);
int icmp_init(void);
void icmp_out_count(struct net *net, unsigned char type);
bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr);
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 48ef3fb3b164..651835073a29 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -8,12 +8,13 @@ obj-y := inetpeer.o protocol.o inet_hashtables.o inet_timewait_sock.o \
tcp_minisocks.o tcp_cong.o tcp_metrics.o tcp_fastopen.o \
tcp_recovery.o tcp_ulp.o tcp_offload.o tcp_plb.o raw.o udp.o \
udp_offload.o af_inet.o inet_fragment.o ping.o ip_tunnel_core.o \
- gre_offload.o metrics.o netlink.o nexthop.o udp_tunnel_stub.o
+ gre_offload.o metrics.o netlink.o nexthop.o udp_tunnel_stub.o \
+ icmp.o
obj-$(CONFIG_IPV4) += route.o ip_input.o ip_fragment.o ip_forward.o ip_options.o \
- ip_sockglue.o tcp_ipv4.o datagram.o icmp.o arp.o devinet.o \
- igmp.o fib_notifier.o ip_output.o fib_frontend.o \
- fib_semantics.o fib_trie.o raw_ipv4.o udp_ipv4.o
+ ip_sockglue.o tcp_ipv4.o datagram.o arp.o devinet.o igmp.o \
+ fib_notifier.o ip_output.o fib_frontend.o fib_semantics.o \
+ fib_trie.o raw_ipv4.o udp_ipv4.o icmp_ipv4.o
obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 23e921d313b3..3c777f6af276 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -96,134 +96,6 @@
#include <net/l3mdev.h>
#include <net/addrconf.h>
#include <net/inet_dscp.h>
-#define CREATE_TRACE_POINTS
-#include <trace/events/icmp.h>
-
-/*
- * Build xmit assembly blocks
- */
-
-struct icmp_bxm {
- struct sk_buff *skb;
- int offset;
- int data_len;
-
- struct {
- struct icmphdr icmph;
- __be32 times[3];
- } data;
- int head_len;
-
- /* Must be last as it ends in a flexible-array member. */
- struct ip_options_rcu replyopts;
-};
-
-/* An array of errno for error messages from dest unreach. */
-/* RFC 1122: 3.2.2.1 States that NET_UNREACH, HOST_UNREACH and SR_FAILED MUST be considered 'transient errs'. */
-
-const struct icmp_err icmp_err_convert[] = {
- {
- .errno = ENETUNREACH, /* ICMP_NET_UNREACH */
- .fatal = 0,
- },
- {
- .errno = EHOSTUNREACH, /* ICMP_HOST_UNREACH */
- .fatal = 0,
- },
- {
- .errno = ENOPROTOOPT /* ICMP_PROT_UNREACH */,
- .fatal = 1,
- },
- {
- .errno = ECONNREFUSED, /* ICMP_PORT_UNREACH */
- .fatal = 1,
- },
- {
- .errno = EMSGSIZE, /* ICMP_FRAG_NEEDED */
- .fatal = 0,
- },
- {
- .errno = EOPNOTSUPP, /* ICMP_SR_FAILED */
- .fatal = 0,
- },
- {
- .errno = ENETUNREACH, /* ICMP_NET_UNKNOWN */
- .fatal = 1,
- },
- {
- .errno = EHOSTDOWN, /* ICMP_HOST_UNKNOWN */
- .fatal = 1,
- },
- {
- .errno = ENONET, /* ICMP_HOST_ISOLATED */
- .fatal = 1,
- },
- {
- .errno = ENETUNREACH, /* ICMP_NET_ANO */
- .fatal = 1,
- },
- {
- .errno = EHOSTUNREACH, /* ICMP_HOST_ANO */
- .fatal = 1,
- },
- {
- .errno = ENETUNREACH, /* ICMP_NET_UNR_TOS */
- .fatal = 0,
- },
- {
- .errno = EHOSTUNREACH, /* ICMP_HOST_UNR_TOS */
- .fatal = 0,
- },
- {
- .errno = EHOSTUNREACH, /* ICMP_PKT_FILTERED */
- .fatal = 1,
- },
- {
- .errno = EHOSTUNREACH, /* ICMP_PREC_VIOLATION */
- .fatal = 1,
- },
- {
- .errno = EHOSTUNREACH, /* ICMP_PREC_CUTOFF */
- .fatal = 1,
- },
-};
-EXPORT_SYMBOL(icmp_err_convert);
-
-/*
- * ICMP control array. This specifies what to do with each ICMP.
- */
-
-struct icmp_control {
- enum skb_drop_reason (*handler)(struct sk_buff *skb);
- short error; /* This ICMP is classed as an error message */
-};
-
-static const struct icmp_control icmp_pointers[NR_ICMP_TYPES+1];
-
-static DEFINE_PER_CPU(struct sock *, ipv4_icmp_sk);
-
-/* Called with BH disabled */
-static inline struct sock *icmp_xmit_lock(struct net *net)
-{
- struct sock *sk;
-
- sk = this_cpu_read(ipv4_icmp_sk);
-
- if (unlikely(!spin_trylock(&sk->sk_lock.slock))) {
- /* This can happen if the output path signals a
- * dst_link_failure() for an outgoing ICMP packet.
- */
- return NULL;
- }
- sock_net_set(sk, net);
- return sk;
-}
-
-static inline void icmp_xmit_unlock(struct sock *sk)
-{
- sock_net_set(sk, &init_net);
- spin_unlock(&sk->sk_lock.slock);
-}
/**
* icmp_global_allow - Are we allowed to send one more ICMP message ?
@@ -274,77 +146,6 @@ void icmp_global_consume(struct net *net)
atomic_sub(credits, &net->ipv4.icmp_global_credit);
}
-static bool icmpv4_mask_allow(struct net *net, int type, int code)
-{
- if (type > NR_ICMP_TYPES)
- return true;
-
- /* Don't limit PMTU discovery. */
- if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
- return true;
-
- /* Limit if icmp type is enabled in ratemask. */
- if (!((1 << type) & READ_ONCE(net->ipv4.sysctl_icmp_ratemask)))
- return true;
-
- return false;
-}
-
-static bool icmpv4_global_allow(struct net *net, int type, int code,
- bool *apply_ratelimit)
-{
- if (icmpv4_mask_allow(net, type, code))
- return true;
-
- if (icmp_global_allow(net)) {
- *apply_ratelimit = true;
- return true;
- }
- __ICMP_INC_STATS(net, ICMP_MIB_RATELIMITGLOBAL);
- return false;
-}
-
-/*
- * Send an ICMP frame.
- */
-
-static bool icmpv4_xrlim_allow(struct net *net, struct rtable *rt,
- struct flowi4 *fl4, int type, int code,
- bool apply_ratelimit)
-{
- struct dst_entry *dst = &rt->dst;
- struct inet_peer *peer;
- struct net_device *dev;
- int peer_timeout;
- bool rc = true;
-
- if (!apply_ratelimit)
- return true;
-
- peer_timeout = READ_ONCE(net->ipv4.sysctl_icmp_ratelimit);
- if (!peer_timeout)
- goto out;
-
- /* No rate limit on loopback */
- rcu_read_lock();
- dev = dst_dev_rcu(dst);
- if (dev && (dev->flags & IFF_LOOPBACK))
- goto out_unlock;
-
- peer = inet_getpeer_v4(net->ipv4.peers, fl4->daddr,
- l3mdev_master_ifindex_rcu(dev));
- rc = inet_peer_xrlim_allow(peer, peer_timeout);
-
-out_unlock:
- rcu_read_unlock();
-out:
- if (!rc)
- __ICMP_INC_STATS(net, ICMP_MIB_RATELIMITHOST);
- else
- icmp_global_consume(net);
- return rc;
-}
-
/*
* Maintain the counters used in the SNMP statistics for outgoing ICMP
*/
@@ -354,921 +155,6 @@ void icmp_out_count(struct net *net, unsigned char type)
ICMP_INC_STATS(net, ICMP_MIB_OUTMSGS);
}
-/*
- * Checksum each fragment, and on the first include the headers and final
- * checksum.
- */
-static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd,
- struct sk_buff *skb)
-{
- DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
- IP_OPTIONS_DATA_FIXED_SIZE);
- __wsum csum;
-
- icmp_param = from;
-
- csum = skb_copy_and_csum_bits(icmp_param->skb,
- icmp_param->offset + offset,
- to, len);
-
- skb->csum = csum_block_add(skb->csum, csum, odd);
- if (icmp_param->data.icmph.type <= NR_ICMP_TYPES &&
- icmp_pointers[array_index_nospec(icmp_param->data.icmph.type,
- NR_ICMP_TYPES + 1)].error)
- nf_ct_attach(skb, icmp_param->skb);
- return 0;
-}
-
-static void icmp_push_reply(struct sock *sk,
- struct icmp_bxm *icmp_param,
- struct flowi4 *fl4,
- struct ipcm_cookie *ipc, struct rtable **rt)
-{
- struct sk_buff *skb;
-
- if (ip_append_data(sk, fl4, icmp_glue_bits, icmp_param,
- icmp_param->data_len+icmp_param->head_len,
- icmp_param->head_len,
- ipc, rt, MSG_DONTWAIT) < 0) {
- __ICMP_INC_STATS(sock_net(sk), ICMP_MIB_OUTERRORS);
- ip_flush_pending_frames(sk);
- } else if ((skb = skb_peek(&sk->sk_write_queue)) != NULL) {
- struct icmphdr *icmph = icmp_hdr(skb);
- __wsum csum;
- struct sk_buff *skb1;
-
- csum = csum_partial_copy_nocheck((void *)&icmp_param->data,
- (char *)icmph,
- icmp_param->head_len);
- skb_queue_walk(&sk->sk_write_queue, skb1) {
- csum = csum_add(csum, skb1->csum);
- }
- icmph->checksum = csum_fold(csum);
- skb->ip_summed = CHECKSUM_NONE;
- ip_push_pending_frames(sk, fl4);
- }
-}
-
-/*
- * Driving logic for building and sending ICMP messages.
- */
-
-static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
-{
- struct rtable *rt = skb_rtable(skb);
- struct net *net = dev_net_rcu(rt->dst.dev);
- bool apply_ratelimit = false;
- struct ipcm_cookie ipc;
- struct flowi4 fl4;
- struct sock *sk;
- __be32 daddr, saddr;
- u32 mark = IP4_REPLY_MARK(net, skb->mark);
- int type = icmp_param->data.icmph.type;
- int code = icmp_param->data.icmph.code;
-
- if (ip_options_echo(net, &icmp_param->replyopts.opt, skb))
- return;
-
- /* Needed by both icmpv4_global_allow and icmp_xmit_lock */
- local_bh_disable();
-
- /* is global icmp_msgs_per_sec exhausted ? */
- if (!icmpv4_global_allow(net, type, code, &apply_ratelimit))
- goto out_bh_enable;
-
- sk = icmp_xmit_lock(net);
- if (!sk)
- goto out_bh_enable;
-
- icmp_param->data.icmph.checksum = 0;
-
- ipcm_init(&ipc);
- ipc.tos = ip_hdr(skb)->tos;
- ipc.sockc.mark = mark;
- daddr = ipc.addr = ip_hdr(skb)->saddr;
- saddr = fib_compute_spec_dst(skb);
-
- if (icmp_param->replyopts.opt.optlen) {
- ipc.opt = &icmp_param->replyopts;
- if (ipc.opt->opt.srr)
- daddr = icmp_param->replyopts.opt.faddr;
- }
- memset(&fl4, 0, sizeof(fl4));
- fl4.daddr = daddr;
- fl4.saddr = saddr;
- fl4.flowi4_mark = mark;
- fl4.flowi4_uid = sock_net_uid(net, NULL);
- fl4.flowi4_dscp = ip4h_dscp(ip_hdr(skb));
- fl4.flowi4_proto = IPPROTO_ICMP;
- fl4.flowi4_oif = l3mdev_master_ifindex(skb->dev);
- security_skb_classify_flow(skb, flowi4_to_flowi_common(&fl4));
- rt = ip_route_output_key(net, &fl4);
- if (IS_ERR(rt))
- goto out_unlock;
- if (icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
- icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
- ip_rt_put(rt);
-out_unlock:
- icmp_xmit_unlock(sk);
-out_bh_enable:
- local_bh_enable();
-}
-
-/*
- * The device used for looking up which routing table to use for sending an ICMP
- * error is preferably the source whenever it is set, which should ensure the
- * icmp error can be sent to the source host, else lookup using the routing
- * table of the destination device, else use the main routing table (index 0).
- */
-static struct net_device *icmp_get_route_lookup_dev(struct sk_buff *skb)
-{
- struct net_device *dev = skb->dev;
- const struct dst_entry *dst;
-
- if (dev)
- return dev;
- dst = skb_dst(skb);
- return dst ? dst_dev(dst) : NULL;
-}
-
-static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
- struct sk_buff *skb_in,
- const struct iphdr *iph, __be32 saddr,
- dscp_t dscp, u32 mark, int type,
- int code, struct icmp_bxm *param)
-{
- struct net_device *route_lookup_dev;
- struct dst_entry *dst, *dst2;
- struct rtable *rt, *rt2;
- struct flowi4 fl4_dec;
- int err;
-
- memset(fl4, 0, sizeof(*fl4));
- fl4->daddr = (param->replyopts.opt.srr ?
- param->replyopts.opt.faddr : iph->saddr);
- fl4->saddr = saddr;
- fl4->flowi4_mark = mark;
- fl4->flowi4_uid = sock_net_uid(net, NULL);
- fl4->flowi4_dscp = dscp;
- fl4->flowi4_proto = IPPROTO_ICMP;
- fl4->fl4_icmp_type = type;
- fl4->fl4_icmp_code = code;
- route_lookup_dev = icmp_get_route_lookup_dev(skb_in);
- fl4->flowi4_oif = l3mdev_master_ifindex(route_lookup_dev);
-
- security_skb_classify_flow(skb_in, flowi4_to_flowi_common(fl4));
- rt = ip_route_output_key_hash(net, fl4, skb_in);
- if (IS_ERR(rt))
- return rt;
-
- /* No need to clone since we're just using its address. */
- rt2 = rt;
-
- dst = xfrm_lookup(net, &rt->dst,
- flowi4_to_flowi(fl4), NULL, 0);
- rt = dst_rtable(dst);
- if (!IS_ERR(dst)) {
- if (rt != rt2)
- return rt;
- if (inet_addr_type_dev_table(net, route_lookup_dev,
- fl4->daddr) == RTN_LOCAL)
- return rt;
- } else if (PTR_ERR(dst) == -EPERM) {
- rt = NULL;
- } else {
- return rt;
- }
- err = xfrm_decode_session_reverse(net, skb_in, flowi4_to_flowi(&fl4_dec), AF_INET);
- if (err)
- goto relookup_failed;
-
- if (inet_addr_type_dev_table(net, route_lookup_dev,
- fl4_dec.saddr) == RTN_LOCAL) {
- rt2 = __ip_route_output_key(net, &fl4_dec);
- if (IS_ERR(rt2))
- err = PTR_ERR(rt2);
- } else {
- struct flowi4 fl4_2 = {};
- unsigned long orefdst;
-
- fl4_2.daddr = fl4_dec.saddr;
- rt2 = ip_route_output_key(net, &fl4_2);
- if (IS_ERR(rt2)) {
- err = PTR_ERR(rt2);
- goto relookup_failed;
- }
- /* Ugh! */
- orefdst = skb_dstref_steal(skb_in);
- err = ip_route_input(skb_in, fl4_dec.daddr, fl4_dec.saddr,
- dscp, rt2->dst.dev) ? -EINVAL : 0;
-
- dst_release(&rt2->dst);
- rt2 = skb_rtable(skb_in);
- /* steal dst entry from skb_in, don't drop refcnt */
- skb_dstref_steal(skb_in);
- skb_dstref_restore(skb_in, orefdst);
-
- /*
- * At this point, fl4_dec.daddr should NOT be local (we
- * checked fl4_dec.saddr above). However, a race condition
- * may occur if the address is added to the interface
- * concurrently. In that case, ip_route_input() returns a
- * LOCAL route with dst.output=ip_rt_bug, which must not
- * be used for output.
- */
- if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
- net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
- &fl4_dec.daddr, &fl4_dec.saddr);
- dst_release(&rt2->dst);
- err = -EINVAL;
- }
- }
-
- if (err)
- goto relookup_failed;
-
- dst2 = xfrm_lookup(net, &rt2->dst, flowi4_to_flowi(&fl4_dec), NULL,
- XFRM_LOOKUP_ICMP);
- rt2 = dst_rtable(dst2);
- if (!IS_ERR(dst2)) {
- dst_release(&rt->dst);
- rt = rt2;
- } else if (PTR_ERR(dst2) == -EPERM) {
- if (rt)
- dst_release(&rt->dst);
- return rt2;
- } else {
- err = PTR_ERR(dst2);
- goto relookup_failed;
- }
- return rt;
-
-relookup_failed:
- if (rt)
- return rt;
- return ERR_PTR(err);
-}
-
-struct icmp_ext_iio_addr4_subobj {
- __be16 afi;
- __be16 reserved;
- __be32 addr4;
-};
-
-static unsigned int icmp_ext_iio_len(void)
-{
- return sizeof(struct icmp_extobj_hdr) +
- /* ifIndex */
- sizeof(__be32) +
- /* Interface Address Sub-Object */
- sizeof(struct icmp_ext_iio_addr4_subobj) +
- /* Interface Name Sub-Object. Length must be a multiple of 4
- * bytes.
- */
- ALIGN(sizeof(struct icmp_ext_iio_name_subobj), 4) +
- /* MTU */
- sizeof(__be32);
-}
-
-static unsigned int icmp_ext_max_len(u8 ext_objs)
-{
- unsigned int ext_max_len;
-
- ext_max_len = sizeof(struct icmp_ext_hdr);
-
- if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
- ext_max_len += icmp_ext_iio_len();
-
- return ext_max_len;
-}
-
-static __be32 icmp_ext_iio_addr4_find(const struct net_device *dev)
-{
- struct in_device *in_dev;
- struct in_ifaddr *ifa;
-
- in_dev = __in_dev_get_rcu(dev);
- if (!in_dev)
- return 0;
-
- /* It is unclear from RFC 5837 which IP address should be chosen, but
- * it makes sense to choose a global unicast address.
- */
- in_dev_for_each_ifa_rcu(ifa, in_dev) {
- if (READ_ONCE(ifa->ifa_flags) & IFA_F_SECONDARY)
- continue;
- if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
- ipv4_is_multicast(ifa->ifa_address))
- continue;
- return ifa->ifa_address;
- }
-
- return 0;
-}
-
-static void icmp_ext_iio_iif_append(struct net *net, struct sk_buff *skb,
- int iif)
-{
- struct icmp_ext_iio_name_subobj *name_subobj;
- struct icmp_extobj_hdr *objh;
- struct net_device *dev;
- __be32 data;
-
- if (!iif)
- return;
-
- /* Add the fields in the order specified by RFC 5837. */
- objh = skb_put(skb, sizeof(*objh));
- objh->class_num = ICMP_EXT_OBJ_CLASS_IIO;
- objh->class_type = ICMP_EXT_CTYPE_IIO_ROLE(ICMP_EXT_CTYPE_IIO_ROLE_IIF);
-
- data = htonl(iif);
- skb_put_data(skb, &data, sizeof(__be32));
- objh->class_type |= ICMP_EXT_CTYPE_IIO_IFINDEX;
-
- rcu_read_lock();
-
- dev = dev_get_by_index_rcu(net, iif);
- if (!dev)
- goto out;
-
- data = icmp_ext_iio_addr4_find(dev);
- if (data) {
- struct icmp_ext_iio_addr4_subobj *addr4_subobj;
-
- addr4_subobj = skb_put_zero(skb, sizeof(*addr4_subobj));
- addr4_subobj->afi = htons(ICMP_AFI_IP);
- addr4_subobj->addr4 = data;
- objh->class_type |= ICMP_EXT_CTYPE_IIO_IPADDR;
- }
-
- name_subobj = skb_put_zero(skb, ALIGN(sizeof(*name_subobj), 4));
- name_subobj->len = ALIGN(sizeof(*name_subobj), 4);
- netdev_copy_name(dev, name_subobj->name);
- objh->class_type |= ICMP_EXT_CTYPE_IIO_NAME;
-
- data = htonl(READ_ONCE(dev->mtu));
- skb_put_data(skb, &data, sizeof(__be32));
- objh->class_type |= ICMP_EXT_CTYPE_IIO_MTU;
-
-out:
- rcu_read_unlock();
- objh->length = htons(skb_tail_pointer(skb) - (unsigned char *)objh);
-}
-
-static void icmp_ext_objs_append(struct net *net, struct sk_buff *skb,
- u8 ext_objs, int iif)
-{
- if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
- icmp_ext_iio_iif_append(net, skb, iif);
-}
-
-static struct sk_buff *
-icmp_ext_append(struct net *net, struct sk_buff *skb_in, struct icmphdr *icmph,
- unsigned int room, int iif)
-{
- unsigned int payload_len, ext_max_len, ext_len;
- struct icmp_ext_hdr *ext_hdr;
- struct sk_buff *skb;
- u8 ext_objs;
- int nhoff;
-
- switch (icmph->type) {
- case ICMP_DEST_UNREACH:
- case ICMP_TIME_EXCEEDED:
- case ICMP_PARAMETERPROB:
- break;
- default:
- return NULL;
- }
-
- ext_objs = READ_ONCE(net->ipv4.sysctl_icmp_errors_extension_mask);
- if (!ext_objs)
- return NULL;
-
- ext_max_len = icmp_ext_max_len(ext_objs);
- if (ICMP_EXT_ORIG_DGRAM_MIN_LEN + ext_max_len > room)
- return NULL;
-
- skb = skb_clone(skb_in, GFP_ATOMIC);
- if (!skb)
- return NULL;
-
- nhoff = skb_network_offset(skb);
- payload_len = min(skb->len - nhoff, ICMP_EXT_ORIG_DGRAM_MIN_LEN);
-
- if (!pskb_network_may_pull(skb, payload_len))
- goto free_skb;
-
- if (pskb_trim(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN) ||
- __skb_put_padto(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN, false))
- goto free_skb;
-
- if (pskb_expand_head(skb, 0, ext_max_len, GFP_ATOMIC))
- goto free_skb;
-
- ext_hdr = skb_put_zero(skb, sizeof(*ext_hdr));
- ext_hdr->version = ICMP_EXT_VERSION_2;
-
- icmp_ext_objs_append(net, skb, ext_objs, iif);
-
- /* Do not send an empty extension structure. */
- ext_len = skb_tail_pointer(skb) - (unsigned char *)ext_hdr;
- if (ext_len == sizeof(*ext_hdr))
- goto free_skb;
-
- ext_hdr->checksum = ip_compute_csum(ext_hdr, ext_len);
- /* The length of the original datagram in 32-bit words (RFC 4884). */
- icmph->un.reserved[1] = ICMP_EXT_ORIG_DGRAM_MIN_LEN / sizeof(u32);
-
- return skb;
-
-free_skb:
- consume_skb(skb);
- return NULL;
-}
-
-/*
- * Send an ICMP message in response to a situation
- *
- * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header.
- * MAY send more (we do).
- * MUST NOT change this header information.
- * MUST NOT reply to a multicast/broadcast IP address.
- * MUST NOT reply to a multicast/broadcast MAC address.
- * MUST reply to only the first fragment.
- */
-
-void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
- const struct inet_skb_parm *parm)
-{
- DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
- IP_OPTIONS_DATA_FIXED_SIZE);
- struct iphdr *iph;
- int room;
- struct rtable *rt = skb_rtable(skb_in);
- bool apply_ratelimit = false;
- struct sk_buff *ext_skb;
- struct ipcm_cookie ipc;
- struct flowi4 fl4;
- __be32 saddr;
- u8 tos;
- u32 mark;
- struct net *net;
- struct sock *sk;
-
- if (!rt)
- return;
-
- rcu_read_lock();
-
- if (rt->dst.dev)
- net = dev_net_rcu(rt->dst.dev);
- else if (skb_in->dev)
- net = dev_net_rcu(skb_in->dev);
- else
- goto out;
-
- /*
- * Find the original header. It is expected to be valid, of course.
- * Check this, icmp_send is called from the most obscure devices
- * sometimes.
- */
- iph = ip_hdr(skb_in);
-
- if ((u8 *)iph < skb_in->head ||
- (skb_network_header(skb_in) + sizeof(*iph)) >
- skb_tail_pointer(skb_in))
- goto out;
-
- /*
- * No replies to physical multicast/broadcast
- */
- if (skb_in->pkt_type != PACKET_HOST)
- goto out;
-
- /*
- * Now check at the protocol level
- */
- if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
- goto out;
-
- /*
- * Only reply to fragment 0. We byte re-order the constant
- * mask for efficiency.
- */
- if (iph->frag_off & htons(IP_OFFSET))
- goto out;
-
- /*
- * If we send an ICMP error to an ICMP error a mess would result..
- */
- if (icmp_pointers[type].error) {
- /*
- * We are an error, check if we are replying to an
- * ICMP error
- */
- if (iph->protocol == IPPROTO_ICMP) {
- u8 _inner_type, *itp;
-
- itp = skb_header_pointer(skb_in,
- skb_network_header(skb_in) +
- (iph->ihl << 2) +
- offsetof(struct icmphdr,
- type) -
- skb_in->data,
- sizeof(_inner_type),
- &_inner_type);
- if (!itp)
- goto out;
-
- /*
- * Assume any unknown ICMP type is an error. This
- * isn't specified by the RFC, but think about it..
- */
- if (*itp > NR_ICMP_TYPES ||
- icmp_pointers[*itp].error)
- goto out;
- }
- }
-
- /* Needed by both icmpv4_global_allow and icmp_xmit_lock */
- local_bh_disable();
-
- /* Check global sysctl_icmp_msgs_per_sec ratelimit, unless
- * incoming dev is loopback. If outgoing dev change to not be
- * loopback, then peer ratelimit still work (in icmpv4_xrlim_allow)
- */
- if (!(skb_in->dev && (skb_in->dev->flags&IFF_LOOPBACK)) &&
- !icmpv4_global_allow(net, type, code, &apply_ratelimit))
- goto out_bh_enable;
-
- sk = icmp_xmit_lock(net);
- if (!sk)
- goto out_bh_enable;
-
- /*
- * Construct source address and options.
- */
-
- saddr = iph->daddr;
- if (!(rt->rt_flags & RTCF_LOCAL)) {
- struct net_device *dev = NULL;
-
- rcu_read_lock();
- if (rt_is_input_route(rt) &&
- READ_ONCE(net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr))
- dev = dev_get_by_index_rcu(net, parm->iif ? parm->iif :
- inet_iif(skb_in));
-
- if (dev)
- saddr = inet_select_addr(dev, iph->saddr,
- RT_SCOPE_LINK);
- else
- saddr = 0;
- rcu_read_unlock();
- }
-
- tos = icmp_pointers[type].error ? (RT_TOS(iph->tos) |
- IPTOS_PREC_INTERNETCONTROL) :
- iph->tos;
- mark = IP4_REPLY_MARK(net, skb_in->mark);
-
- if (__ip_options_echo(net, &icmp_param->replyopts.opt, skb_in,
- &parm->opt))
- goto out_unlock;
-
-
- /*
- * Prepare data for ICMP header.
- */
-
- icmp_param->data.icmph.type = type;
- icmp_param->data.icmph.code = code;
- icmp_param->data.icmph.un.gateway = info;
- icmp_param->data.icmph.checksum = 0;
- icmp_param->skb = skb_in;
- icmp_param->offset = skb_network_offset(skb_in);
- ipcm_init(&ipc);
- ipc.tos = tos;
- ipc.addr = iph->saddr;
- ipc.opt = &icmp_param->replyopts;
- ipc.sockc.mark = mark;
-
- rt = icmp_route_lookup(net, &fl4, skb_in, iph, saddr,
- inet_dsfield_to_dscp(tos), mark, type, code,
- icmp_param);
- if (IS_ERR(rt))
- goto out_unlock;
-
- if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
- goto ende;
-
- /* peer icmp_ratelimit */
- if (!icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
- goto ende;
-
- /* RFC says return as much as we can without exceeding 576 bytes. */
-
- room = dst4_mtu(&rt->dst);
- if (room > 576)
- room = 576;
- room -= sizeof(struct iphdr) + icmp_param->replyopts.opt.optlen;
- room -= sizeof(struct icmphdr);
- /* Guard against tiny mtu. We need to include at least one
- * IP network header for this message to make any sense.
- */
- if (room <= (int)sizeof(struct iphdr))
- goto ende;
-
- ext_skb = icmp_ext_append(net, skb_in, &icmp_param->data.icmph, room,
- parm->iif);
- if (ext_skb)
- icmp_param->skb = ext_skb;
-
- icmp_param->data_len = icmp_param->skb->len - icmp_param->offset;
- if (icmp_param->data_len > room)
- icmp_param->data_len = room;
- icmp_param->head_len = sizeof(struct icmphdr);
-
- /* if we don't have a source address at this point, fall back to the
- * dummy address instead of sending out a packet with a source address
- * of 0.0.0.0
- */
- if (!fl4.saddr)
- fl4.saddr = htonl(INADDR_DUMMY);
-
- trace_icmp_send(skb_in, type, code);
-
- icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
-
- if (ext_skb)
- consume_skb(ext_skb);
-ende:
- ip_rt_put(rt);
-out_unlock:
- icmp_xmit_unlock(sk);
-out_bh_enable:
- local_bh_enable();
-out:
- rcu_read_unlock();
-}
-EXPORT_SYMBOL(__icmp_send);
-
-#if IS_ENABLED(CONFIG_NF_NAT)
-#include <net/netfilter/nf_conntrack.h>
-void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
-{
- struct sk_buff *cloned_skb = NULL;
- enum ip_conntrack_info ctinfo;
- enum ip_conntrack_dir dir;
- struct inet_skb_parm parm;
- struct nf_conn *ct;
- __be32 orig_ip;
-
- memset(&parm, 0, sizeof(parm));
- ct = nf_ct_get(skb_in, &ctinfo);
- if (!ct || !(READ_ONCE(ct->status) & IPS_NAT_MASK)) {
- __icmp_send(skb_in, type, code, info, &parm);
- return;
- }
-
- if (skb_shared(skb_in))
- skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
-
- if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
- (skb_network_header(skb_in) + sizeof(struct iphdr)) >
- skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
- skb_network_offset(skb_in) + sizeof(struct iphdr))))
- goto out;
-
- orig_ip = ip_hdr(skb_in)->saddr;
- dir = CTINFO2DIR(ctinfo);
- ip_hdr(skb_in)->saddr = ct->tuplehash[dir].tuple.src.u3.ip;
- __icmp_send(skb_in, type, code, info, &parm);
- ip_hdr(skb_in)->saddr = orig_ip;
-out:
- consume_skb(cloned_skb);
-}
-EXPORT_SYMBOL(icmp_ndo_send);
-#endif
-
-static void icmp_socket_deliver(struct sk_buff *skb, u32 info)
-{
- const struct iphdr *iph = (const struct iphdr *)skb->data;
- const struct net_protocol *ipprot;
- int protocol = iph->protocol;
-
- /* Checkin full IP header plus 8 bytes of protocol to
- * avoid additional coding at protocol handlers.
- */
- if (!pskb_may_pull(skb, iph->ihl * 4 + 8))
- goto out;
-
- /* IPPROTO_RAW sockets are not supposed to receive anything. */
- if (protocol == IPPROTO_RAW)
- goto out;
-
- raw_icmp_error(skb, protocol, info);
-
- ipprot = rcu_dereference(inet_protos[protocol]);
- if (ipprot && ipprot->err_handler)
- ipprot->err_handler(skb, info);
- return;
-
-out:
- __ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
-}
-
-static bool icmp_tag_validation(int proto)
-{
- const struct net_protocol *ipprot;
- bool ok;
-
- rcu_read_lock();
- ipprot = rcu_dereference(inet_protos[proto]);
- ok = ipprot ? ipprot->icmp_strict_tag_validation : false;
- rcu_read_unlock();
- return ok;
-}
-
-/*
- * Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEEDED, ICMP_QUENCH, and
- * ICMP_PARAMETERPROB.
- */
-
-static enum skb_drop_reason icmp_unreach(struct sk_buff *skb)
-{
- enum skb_drop_reason reason = SKB_NOT_DROPPED_YET;
- const struct iphdr *iph;
- struct icmphdr *icmph;
- struct net *net;
- u32 info = 0;
-
- net = skb_dst_dev_net_rcu(skb);
-
- /*
- * Incomplete header ?
- * Only checks for the IP header, there should be an
- * additional check for longer headers in upper levels.
- */
-
- if (!pskb_may_pull(skb, sizeof(struct iphdr)))
- goto out_err;
-
- icmph = icmp_hdr(skb);
- iph = (const struct iphdr *)skb->data;
-
- if (iph->ihl < 5) { /* Mangled header, drop. */
- reason = SKB_DROP_REASON_IP_INHDR;
- goto out_err;
- }
-
- switch (icmph->type) {
- case ICMP_DEST_UNREACH:
- switch (icmph->code & 15) {
- case ICMP_NET_UNREACH:
- case ICMP_HOST_UNREACH:
- case ICMP_PROT_UNREACH:
- case ICMP_PORT_UNREACH:
- break;
- case ICMP_FRAG_NEEDED:
- /* for documentation of the ip_no_pmtu_disc
- * values please see
- * Documentation/networking/ip-sysctl.rst
- */
- switch (READ_ONCE(net->ipv4.sysctl_ip_no_pmtu_disc)) {
- default:
- net_dbg_ratelimited("%pI4: fragmentation needed and DF set\n",
- &iph->daddr);
- break;
- case 2:
- goto out;
- case 3:
- if (!icmp_tag_validation(iph->protocol))
- goto out;
- fallthrough;
- case 0:
- info = ntohs(icmph->un.frag.mtu);
- }
- break;
- case ICMP_SR_FAILED:
- net_dbg_ratelimited("%pI4: Source Route Failed\n",
- &iph->daddr);
- break;
- default:
- break;
- }
- if (icmph->code > NR_ICMP_UNREACH)
- goto out;
- break;
- case ICMP_PARAMETERPROB:
- info = ntohl(icmph->un.gateway) >> 24;
- break;
- case ICMP_TIME_EXCEEDED:
- __ICMP_INC_STATS(net, ICMP_MIB_INTIMEEXCDS);
- if (icmph->code == ICMP_EXC_FRAGTIME)
- goto out;
- break;
- }
-
- /*
- * Throw it at our lower layers
- *
- * RFC 1122: 3.2.2 MUST extract the protocol ID from the passed
- * header.
- * RFC 1122: 3.2.2.1 MUST pass ICMP unreach messages to the
- * transport layer.
- * RFC 1122: 3.2.2.2 MUST pass ICMP time expired messages to
- * transport layer.
- */
-
- /*
- * Check the other end isn't violating RFC 1122. Some routers send
- * bogus responses to broadcast frames. If you see this message
- * first check your netmask matches at both ends, if it does then
- * get the other vendor to fix their kit.
- */
-
- if (!READ_ONCE(net->ipv4.sysctl_icmp_ignore_bogus_error_responses) &&
- inet_addr_type_dev_table(net, skb->dev, iph->daddr) == RTN_BROADCAST) {
- net_warn_ratelimited("%pI4 sent an invalid ICMP type %u, code %u error to a broadcast: %pI4 on %s\n",
- &ip_hdr(skb)->saddr,
- icmph->type, icmph->code,
- &iph->daddr, skb->dev->name);
- goto out;
- }
-
- icmp_socket_deliver(skb, info);
-
-out:
- return reason;
-out_err:
- __ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
- return reason ?: SKB_DROP_REASON_NOT_SPECIFIED;
-}
-
-
-/*
- * Handle ICMP_REDIRECT.
- */
-
-static enum skb_drop_reason icmp_redirect(struct sk_buff *skb)
-{
- if (skb->len < sizeof(struct iphdr)) {
- __ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
- return SKB_DROP_REASON_PKT_TOO_SMALL;
- }
-
- if (!pskb_may_pull(skb, sizeof(struct iphdr))) {
- /* there aught to be a stat */
- return SKB_DROP_REASON_NOMEM;
- }
-
- icmp_socket_deliver(skb, ntohl(icmp_hdr(skb)->un.gateway));
- return SKB_NOT_DROPPED_YET;
-}
-
-/*
- * Handle ICMP_ECHO ("ping") and ICMP_EXT_ECHO ("PROBE") requests.
- *
- * RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo
- * requests.
- * RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be
- * included in the reply.
- * RFC 1812: 4.3.3.6 SHOULD have a config option for silently ignoring
- * echo requests, MUST have default=NOT.
- * RFC 8335: 8 MUST have a config option to enable/disable ICMP
- * Extended Echo Functionality, MUST be disabled by default
- * See also WRT handling of options once they are done and working.
- */
-
-static enum skb_drop_reason icmp_echo(struct sk_buff *skb)
-{
- DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
- IP_OPTIONS_DATA_FIXED_SIZE);
- struct net *net;
-
- net = skb_dst_dev_net_rcu(skb);
- /* should there be an ICMP stat for ignored echos? */
- if (READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_all))
- return SKB_NOT_DROPPED_YET;
-
- icmp_param->data.icmph = *icmp_hdr(skb);
- icmp_param->skb = skb;
- icmp_param->offset = 0;
- icmp_param->data_len = skb->len;
- icmp_param->head_len = sizeof(struct icmphdr);
-
- if (icmp_param->data.icmph.type == ICMP_ECHO)
- icmp_param->data.icmph.type = ICMP_ECHOREPLY;
- else if (!icmp_build_probe(skb, &icmp_param->data.icmph))
- return SKB_NOT_DROPPED_YET;
-
- icmp_reply(icmp_param, skb);
- return SKB_NOT_DROPPED_YET;
-}
-
/* Helper for icmp_echo and icmpv6_echo_reply.
* Searches for net_device that matches PROBE interface identifier
* and builds PROBE reply message in icmphdr.
@@ -1339,11 +225,13 @@ bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
iio->ident.addr.ctype3_hdr.addrlen)
goto send_mal_query;
switch (ntohs(iio->ident.addr.ctype3_hdr.afi)) {
+#if IS_ENABLED(CONFIG_IPV4)
case ICMP_AFI_IP:
if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in_addr))
goto send_mal_query;
dev = ip_dev_find(net, iio->ident.addr.ip_addr.ipv4_addr);
break;
+#endif
#if IS_ENABLED(CONFIG_IPV6)
case ICMP_AFI_IP6:
if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in6_addr))
@@ -1383,169 +271,6 @@ bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
return true;
}
-/*
- * Handle ICMP Timestamp requests.
- * RFC 1122: 3.2.2.8 MAY implement ICMP timestamp requests.
- * SHOULD be in the kernel for minimum random latency.
- * MUST be accurate to a few minutes.
- * MUST be updated at least at 15Hz.
- */
-static enum skb_drop_reason icmp_timestamp(struct sk_buff *skb)
-{
- DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
- IP_OPTIONS_DATA_FIXED_SIZE);
- /*
- * Too short.
- */
- if (skb->len < 4)
- goto out_err;
-
- /*
- * Fill in the current time as ms since midnight UT:
- */
- icmp_param->data.times[1] = inet_current_timestamp();
- icmp_param->data.times[2] = icmp_param->data.times[1];
-
- BUG_ON(skb_copy_bits(skb, 0, &icmp_param->data.times[0], 4));
-
- icmp_param->data.icmph = *icmp_hdr(skb);
- icmp_param->data.icmph.type = ICMP_TIMESTAMPREPLY;
- icmp_param->data.icmph.code = 0;
- icmp_param->skb = skb;
- icmp_param->offset = 0;
- icmp_param->data_len = 0;
- icmp_param->head_len = sizeof(struct icmphdr) + 12;
- icmp_reply(icmp_param, skb);
- return SKB_NOT_DROPPED_YET;
-
-out_err:
- __ICMP_INC_STATS(skb_dst_dev_net_rcu(skb), ICMP_MIB_INERRORS);
- return SKB_DROP_REASON_PKT_TOO_SMALL;
-}
-
-static enum skb_drop_reason icmp_discard(struct sk_buff *skb)
-{
- /* pretend it was a success */
- return SKB_NOT_DROPPED_YET;
-}
-
-/*
- * Deal with incoming ICMP packets.
- */
-int icmp_rcv(struct sk_buff *skb)
-{
- enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
- struct rtable *rt = skb_rtable(skb);
- struct net *net = dev_net_rcu(rt->dst.dev);
- struct icmphdr *icmph;
-
- if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
- struct sec_path *sp = skb_sec_path(skb);
- int nh;
-
- if (!(sp && sp->xvec[sp->len - 1]->props.flags &
- XFRM_STATE_ICMP)) {
- reason = SKB_DROP_REASON_XFRM_POLICY;
- goto drop;
- }
-
- if (!pskb_may_pull(skb, sizeof(*icmph) + sizeof(struct iphdr)))
- goto drop;
-
- nh = skb_network_offset(skb);
- skb_set_network_header(skb, sizeof(*icmph));
-
- if (!xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN,
- skb)) {
- reason = SKB_DROP_REASON_XFRM_POLICY;
- goto drop;
- }
-
- skb_set_network_header(skb, nh);
- }
-
- __ICMP_INC_STATS(net, ICMP_MIB_INMSGS);
-
- if (skb_checksum_simple_validate(skb))
- goto csum_error;
-
- if (!pskb_pull(skb, sizeof(*icmph)))
- goto error;
-
- icmph = icmp_hdr(skb);
-
- ICMPMSGIN_INC_STATS(net, icmph->type);
-
- /* Check for ICMP Extended Echo (PROBE) messages */
- if (icmph->type == ICMP_EXT_ECHO) {
- /* We can't use icmp_pointers[].handler() because it is an array of
- * size NR_ICMP_TYPES + 1 (19 elements) and PROBE has code 42.
- */
- reason = icmp_echo(skb);
- goto reason_check;
- }
-
- /*
- * Parse the ICMP message
- */
-
- if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
- /*
- * RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be
- * silently ignored (we let user decide with a sysctl).
- * RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently
- * discarded if to broadcast/multicast.
- */
- if ((icmph->type == ICMP_ECHO ||
- icmph->type == ICMP_TIMESTAMP) &&
- READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_broadcasts)) {
- reason = SKB_DROP_REASON_INVALID_PROTO;
- goto error;
- }
- if (icmph->type != ICMP_ECHO &&
- icmph->type != ICMP_TIMESTAMP &&
- icmph->type != ICMP_ADDRESS &&
- icmph->type != ICMP_ADDRESSREPLY) {
- reason = SKB_DROP_REASON_INVALID_PROTO;
- goto error;
- }
- }
-
- if (icmph->type == ICMP_EXT_ECHOREPLY ||
- icmph->type == ICMP_ECHOREPLY) {
- reason = ping_rcv(skb);
- return reason ? NET_RX_DROP : NET_RX_SUCCESS;
- }
-
- /*
- * 18 is the highest 'known' ICMP type. Anything else is a mystery
- *
- * RFC 1122: 3.2.2 Unknown ICMP messages types MUST be silently
- * discarded.
- */
- if (icmph->type > NR_ICMP_TYPES) {
- reason = SKB_DROP_REASON_UNHANDLED_PROTO;
- goto error;
- }
-
- reason = icmp_pointers[icmph->type].handler(skb);
-reason_check:
- if (!reason) {
- consume_skb(skb);
- return NET_RX_SUCCESS;
- }
-
-drop:
- kfree_skb_reason(skb, reason);
- return NET_RX_DROP;
-csum_error:
- reason = SKB_DROP_REASON_ICMP_CSUM;
- __ICMP_INC_STATS(net, ICMP_MIB_CSUMERRORS);
-error:
- __ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
- goto drop;
-}
-
static bool ip_icmp_error_rfc4884_validate(const struct sk_buff *skb, int off)
{
struct icmp_extobj_hdr *objh, _objh;
@@ -1604,106 +329,6 @@ void ip_icmp_error_rfc4884(const struct sk_buff *skb,
out->flags |= SO_EE_RFC4884_FLAG_INVALID;
}
-int icmp_err(struct sk_buff *skb, u32 info)
-{
- struct iphdr *iph = (struct iphdr *)skb->data;
- int offset = iph->ihl<<2;
- struct icmphdr *icmph = (struct icmphdr *)(skb->data + offset);
- struct net *net = dev_net_rcu(skb->dev);
- int type = icmp_hdr(skb)->type;
- int code = icmp_hdr(skb)->code;
-
- /*
- * Use ping_err to handle all icmp errors except those
- * triggered by ICMP_ECHOREPLY which sent from kernel.
- */
- if (icmph->type != ICMP_ECHOREPLY) {
- ping_err(skb, offset, info);
- return 0;
- }
-
- if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
- ipv4_update_pmtu(skb, net, info, 0, IPPROTO_ICMP);
- else if (type == ICMP_REDIRECT)
- ipv4_redirect(skb, net, 0, IPPROTO_ICMP);
-
- return 0;
-}
-
-/*
- * This table is the definition of how we handle ICMP.
- */
-static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = {
- [ICMP_ECHOREPLY] = {
- .handler = ping_rcv,
- },
- [1] = {
- .handler = icmp_discard,
- .error = 1,
- },
- [2] = {
- .handler = icmp_discard,
- .error = 1,
- },
- [ICMP_DEST_UNREACH] = {
- .handler = icmp_unreach,
- .error = 1,
- },
- [ICMP_SOURCE_QUENCH] = {
- .handler = icmp_unreach,
- .error = 1,
- },
- [ICMP_REDIRECT] = {
- .handler = icmp_redirect,
- .error = 1,
- },
- [6] = {
- .handler = icmp_discard,
- .error = 1,
- },
- [7] = {
- .handler = icmp_discard,
- .error = 1,
- },
- [ICMP_ECHO] = {
- .handler = icmp_echo,
- },
- [9] = {
- .handler = icmp_discard,
- .error = 1,
- },
- [10] = {
- .handler = icmp_discard,
- .error = 1,
- },
- [ICMP_TIME_EXCEEDED] = {
- .handler = icmp_unreach,
- .error = 1,
- },
- [ICMP_PARAMETERPROB] = {
- .handler = icmp_unreach,
- .error = 1,
- },
- [ICMP_TIMESTAMP] = {
- .handler = icmp_timestamp,
- },
- [ICMP_TIMESTAMPREPLY] = {
- .handler = icmp_discard,
- },
- [ICMP_INFO_REQUEST] = {
- .handler = icmp_discard,
- },
- [ICMP_INFO_REPLY] = {
- .handler = icmp_discard,
- },
- [ICMP_ADDRESS] = {
- .handler = icmp_discard,
- },
- [ICMP_ADDRESSREPLY] = {
- .handler = icmp_discard,
- },
-};
-
static int __net_init icmp_sk_init(struct net *net)
{
/* Control parameters for ECHO replies. */
@@ -1742,28 +367,12 @@ static struct pernet_operations __net_initdata icmp_sk_ops = {
int __init icmp_init(void)
{
- int err, i;
-
- for_each_possible_cpu(i) {
- struct sock *sk;
-
- err = inet_ctl_sock_create(&sk, PF_INET,
- SOCK_RAW, IPPROTO_ICMP, &init_net);
- if (err < 0)
- return err;
-
- per_cpu(ipv4_icmp_sk, i) = sk;
-
- /* Enough space for 2 64K ICMP packets, including
- * sk_buff/skb_shared_info struct overhead.
- */
- sk->sk_sndbuf = 2 * SKB_TRUESIZE(64 * 1024);
+#if IS_ENABLED(CONFIG_IPV4)
+ int err;
- /*
- * Speedup sock_wfree()
- */
- sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
- inet_sk(sk)->pmtudisc = IP_PMTUDISC_DONT;
- }
+ err = icmp_init_ipv4();
+ if (err)
+ return err;
+#endif
return register_pernet_subsys(&icmp_sk_ops);
}
diff --git a/net/ipv4/icmp_ipv4.c b/net/ipv4/icmp_ipv4.c
new file mode 100644
index 000000000000..321b4cc40a17
--- /dev/null
+++ b/net/ipv4/icmp_ipv4.c
@@ -0,0 +1,1425 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * INET: Implementation of the ICMP protocol layer.
+ *
+ * IPv4 specific functions
+ *
+ * code split from:
+ * net/ipv4/icmp.c
+ *
+ * See icmp.c for author information
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/inetdevice.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/types.h>
+#include <net/icmp.h>
+#include <net/inet_common.h>
+#include <net/ping.h>
+#include <net/protocol.h>
+#include <net/raw.h>
+#include <net/xfrm.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/icmp.h>
+
+/*
+ * Build xmit assembly blocks
+ */
+
+struct icmp_bxm {
+ struct sk_buff *skb;
+ int offset;
+ int data_len;
+
+ struct {
+ struct icmphdr icmph;
+ __be32 times[3];
+ } data;
+ int head_len;
+
+ /* Must be last as it ends in a flexible-array member. */
+ struct ip_options_rcu replyopts;
+};
+
+/* An array of errno for error messages from dest unreach. */
+/* RFC 1122: 3.2.2.1 States that NET_UNREACH, HOST_UNREACH and SR_FAILED MUST be considered 'transient errs'. */
+
+const struct icmp_err icmp_err_convert[] = {
+ {
+ .errno = ENETUNREACH, /* ICMP_NET_UNREACH */
+ .fatal = 0,
+ },
+ {
+ .errno = EHOSTUNREACH, /* ICMP_HOST_UNREACH */
+ .fatal = 0,
+ },
+ {
+ .errno = ENOPROTOOPT /* ICMP_PROT_UNREACH */,
+ .fatal = 1,
+ },
+ {
+ .errno = ECONNREFUSED, /* ICMP_PORT_UNREACH */
+ .fatal = 1,
+ },
+ {
+ .errno = EMSGSIZE, /* ICMP_FRAG_NEEDED */
+ .fatal = 0,
+ },
+ {
+ .errno = EOPNOTSUPP, /* ICMP_SR_FAILED */
+ .fatal = 0,
+ },
+ {
+ .errno = ENETUNREACH, /* ICMP_NET_UNKNOWN */
+ .fatal = 1,
+ },
+ {
+ .errno = EHOSTDOWN, /* ICMP_HOST_UNKNOWN */
+ .fatal = 1,
+ },
+ {
+ .errno = ENONET, /* ICMP_HOST_ISOLATED */
+ .fatal = 1,
+ },
+ {
+ .errno = ENETUNREACH, /* ICMP_NET_ANO */
+ .fatal = 1,
+ },
+ {
+ .errno = EHOSTUNREACH, /* ICMP_HOST_ANO */
+ .fatal = 1,
+ },
+ {
+ .errno = ENETUNREACH, /* ICMP_NET_UNR_TOS */
+ .fatal = 0,
+ },
+ {
+ .errno = EHOSTUNREACH, /* ICMP_HOST_UNR_TOS */
+ .fatal = 0,
+ },
+ {
+ .errno = EHOSTUNREACH, /* ICMP_PKT_FILTERED */
+ .fatal = 1,
+ },
+ {
+ .errno = EHOSTUNREACH, /* ICMP_PREC_VIOLATION */
+ .fatal = 1,
+ },
+ {
+ .errno = EHOSTUNREACH, /* ICMP_PREC_CUTOFF */
+ .fatal = 1,
+ },
+};
+EXPORT_SYMBOL(icmp_err_convert);
+
+/*
+ * ICMP control array. This specifies what to do with each ICMP.
+ */
+
+struct icmp_control {
+ enum skb_drop_reason (*handler)(struct sk_buff *skb);
+ short error; /* This ICMP is classed as an error message */
+};
+
+static const struct icmp_control icmp_pointers[NR_ICMP_TYPES+1];
+
+static DEFINE_PER_CPU(struct sock *, ipv4_icmp_sk);
+
+/* Called with BH disabled */
+static inline struct sock *icmp_xmit_lock(struct net *net)
+{
+ struct sock *sk;
+
+ sk = this_cpu_read(ipv4_icmp_sk);
+
+ if (unlikely(!spin_trylock(&sk->sk_lock.slock))) {
+ /* This can happen if the output path signals a
+ * dst_link_failure() for an outgoing ICMP packet.
+ */
+ return NULL;
+ }
+ sock_net_set(sk, net);
+ return sk;
+}
+
+static inline void icmp_xmit_unlock(struct sock *sk)
+{
+ sock_net_set(sk, &init_net);
+ spin_unlock(&sk->sk_lock.slock);
+}
+
+/*
+ * Send an ICMP frame.
+ */
+
+static bool icmpv4_xrlim_allow(struct net *net, struct rtable *rt,
+ struct flowi4 *fl4, int type, int code,
+ bool apply_ratelimit)
+{
+ struct dst_entry *dst = &rt->dst;
+ struct inet_peer *peer;
+ struct net_device *dev;
+ int peer_timeout;
+ bool rc = true;
+
+ if (!apply_ratelimit)
+ return true;
+
+ peer_timeout = READ_ONCE(net->ipv4.sysctl_icmp_ratelimit);
+ if (!peer_timeout)
+ goto out;
+
+ /* No rate limit on loopback */
+ rcu_read_lock();
+ dev = dst_dev_rcu(dst);
+ if (dev && (dev->flags & IFF_LOOPBACK))
+ goto out_unlock;
+
+ peer = inet_getpeer_v4(net->ipv4.peers, fl4->daddr,
+ l3mdev_master_ifindex_rcu(dev));
+ rc = inet_peer_xrlim_allow(peer, peer_timeout);
+
+out_unlock:
+ rcu_read_unlock();
+out:
+ if (!rc)
+ __ICMP_INC_STATS(net, ICMP_MIB_RATELIMITHOST);
+ else
+ icmp_global_consume(net);
+ return rc;
+}
+
+/*
+ * Checksum each fragment, and on the first include the headers and final
+ * checksum.
+ */
+static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd,
+ struct sk_buff *skb)
+{
+ DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+ IP_OPTIONS_DATA_FIXED_SIZE);
+ __wsum csum;
+
+ icmp_param = from;
+
+ csum = skb_copy_and_csum_bits(icmp_param->skb,
+ icmp_param->offset + offset,
+ to, len);
+
+ skb->csum = csum_block_add(skb->csum, csum, odd);
+ if (icmp_param->data.icmph.type <= NR_ICMP_TYPES &&
+ icmp_pointers[array_index_nospec(icmp_param->data.icmph.type,
+ NR_ICMP_TYPES + 1)].error)
+ nf_ct_attach(skb, icmp_param->skb);
+ return 0;
+}
+
+static void icmp_push_reply(struct sock *sk,
+ struct icmp_bxm *icmp_param,
+ struct flowi4 *fl4,
+ struct ipcm_cookie *ipc, struct rtable **rt)
+{
+ struct sk_buff *skb;
+
+ if (ip_append_data(sk, fl4, icmp_glue_bits, icmp_param,
+ icmp_param->data_len+icmp_param->head_len,
+ icmp_param->head_len,
+ ipc, rt, MSG_DONTWAIT) < 0) {
+ __ICMP_INC_STATS(sock_net(sk), ICMP_MIB_OUTERRORS);
+ ip_flush_pending_frames(sk);
+ } else if ((skb = skb_peek(&sk->sk_write_queue)) != NULL) {
+ struct icmphdr *icmph = icmp_hdr(skb);
+ __wsum csum;
+ struct sk_buff *skb1;
+
+ csum = csum_partial_copy_nocheck((void *)&icmp_param->data,
+ (char *)icmph,
+ icmp_param->head_len);
+ skb_queue_walk(&sk->sk_write_queue, skb1) {
+ csum = csum_add(csum, skb1->csum);
+ }
+ icmph->checksum = csum_fold(csum);
+ skb->ip_summed = CHECKSUM_NONE;
+ ip_push_pending_frames(sk, fl4);
+ }
+}
+
+static bool icmpv4_mask_allow(struct net *net, int type, int code)
+{
+ if (type > NR_ICMP_TYPES)
+ return true;
+
+ /* Don't limit PMTU discovery. */
+ if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
+ return true;
+
+ /* Limit if icmp type is enabled in ratemask. */
+ if (!((1 << type) & READ_ONCE(net->ipv4.sysctl_icmp_ratemask)))
+ return true;
+
+ return false;
+}
+
+static bool icmpv4_global_allow(struct net *net, int type, int code,
+ bool *apply_ratelimit)
+{
+ if (icmpv4_mask_allow(net, type, code))
+ return true;
+
+ if (icmp_global_allow(net)) {
+ *apply_ratelimit = true;
+ return true;
+ }
+ __ICMP_INC_STATS(net, ICMP_MIB_RATELIMITGLOBAL);
+ return false;
+}
+
+static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
+{
+ struct rtable *rt = skb_rtable(skb);
+ struct net *net = dev_net_rcu(rt->dst.dev);
+ bool apply_ratelimit = false;
+ struct ipcm_cookie ipc;
+ struct flowi4 fl4;
+ struct sock *sk;
+ __be32 daddr, saddr;
+ u32 mark = IP4_REPLY_MARK(net, skb->mark);
+ int type = icmp_param->data.icmph.type;
+ int code = icmp_param->data.icmph.code;
+
+ if (ip_options_echo(net, &icmp_param->replyopts.opt, skb))
+ return;
+
+ /* Needed by both icmpv4_global_allow and icmp_xmit_lock */
+ local_bh_disable();
+
+ /* is global icmp_msgs_per_sec exhausted ? */
+ if (!icmpv4_global_allow(net, type, code, &apply_ratelimit))
+ goto out_bh_enable;
+
+ sk = icmp_xmit_lock(net);
+ if (!sk)
+ goto out_bh_enable;
+
+ icmp_param->data.icmph.checksum = 0;
+
+ ipcm_init(&ipc);
+ ipc.tos = ip_hdr(skb)->tos;
+ ipc.sockc.mark = mark;
+ daddr = ipc.addr = ip_hdr(skb)->saddr;
+ saddr = fib_compute_spec_dst(skb);
+
+ if (icmp_param->replyopts.opt.optlen) {
+ ipc.opt = &icmp_param->replyopts;
+ if (ipc.opt->opt.srr)
+ daddr = icmp_param->replyopts.opt.faddr;
+ }
+ memset(&fl4, 0, sizeof(fl4));
+ fl4.daddr = daddr;
+ fl4.saddr = saddr;
+ fl4.flowi4_mark = mark;
+ fl4.flowi4_uid = sock_net_uid(net, NULL);
+ fl4.flowi4_dscp = ip4h_dscp(ip_hdr(skb));
+ fl4.flowi4_proto = IPPROTO_ICMP;
+ fl4.flowi4_oif = l3mdev_master_ifindex(skb->dev);
+ security_skb_classify_flow(skb, flowi4_to_flowi_common(&fl4));
+ rt = ip_route_output_key(net, &fl4);
+ if (IS_ERR(rt))
+ goto out_unlock;
+ if (icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
+ icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
+ ip_rt_put(rt);
+out_unlock:
+ icmp_xmit_unlock(sk);
+out_bh_enable:
+ local_bh_enable();
+}
+
+/*
+ * The device used for looking up which routing table to use for sending an ICMP
+ * error is preferably the source whenever it is set, which should ensure the
+ * icmp error can be sent to the source host, else lookup using the routing
+ * table of the destination device, else use the main routing table (index 0).
+ */
+static struct net_device *icmp_get_route_lookup_dev(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ const struct dst_entry *dst;
+
+ if (dev)
+ return dev;
+ dst = skb_dst(skb);
+ return dst ? dst_dev(dst) : NULL;
+}
+
+static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4,
+ struct sk_buff *skb_in,
+ const struct iphdr *iph, __be32 saddr,
+ dscp_t dscp, u32 mark, int type,
+ int code, struct icmp_bxm *param)
+{
+ struct net_device *route_lookup_dev;
+ struct dst_entry *dst, *dst2;
+ struct rtable *rt, *rt2;
+ struct flowi4 fl4_dec;
+ int err;
+
+ memset(fl4, 0, sizeof(*fl4));
+ fl4->daddr = (param->replyopts.opt.srr ?
+ param->replyopts.opt.faddr : iph->saddr);
+ fl4->saddr = saddr;
+ fl4->flowi4_mark = mark;
+ fl4->flowi4_uid = sock_net_uid(net, NULL);
+ fl4->flowi4_dscp = dscp;
+ fl4->flowi4_proto = IPPROTO_ICMP;
+ fl4->fl4_icmp_type = type;
+ fl4->fl4_icmp_code = code;
+ route_lookup_dev = icmp_get_route_lookup_dev(skb_in);
+ fl4->flowi4_oif = l3mdev_master_ifindex(route_lookup_dev);
+
+ security_skb_classify_flow(skb_in, flowi4_to_flowi_common(fl4));
+ rt = ip_route_output_key_hash(net, fl4, skb_in);
+ if (IS_ERR(rt))
+ return rt;
+
+ /* No need to clone since we're just using its address. */
+ rt2 = rt;
+
+ dst = xfrm_lookup(net, &rt->dst,
+ flowi4_to_flowi(fl4), NULL, 0);
+ rt = dst_rtable(dst);
+ if (!IS_ERR(dst)) {
+ if (rt != rt2)
+ return rt;
+ if (inet_addr_type_dev_table(net, route_lookup_dev,
+ fl4->daddr) == RTN_LOCAL)
+ return rt;
+ } else if (PTR_ERR(dst) == -EPERM) {
+ rt = NULL;
+ } else {
+ return rt;
+ }
+ err = xfrm_decode_session_reverse(net, skb_in, flowi4_to_flowi(&fl4_dec), AF_INET);
+ if (err)
+ goto relookup_failed;
+
+ if (inet_addr_type_dev_table(net, route_lookup_dev,
+ fl4_dec.saddr) == RTN_LOCAL) {
+ rt2 = __ip_route_output_key(net, &fl4_dec);
+ if (IS_ERR(rt2))
+ err = PTR_ERR(rt2);
+ } else {
+ struct flowi4 fl4_2 = {};
+ unsigned long orefdst;
+
+ fl4_2.daddr = fl4_dec.saddr;
+ rt2 = ip_route_output_key(net, &fl4_2);
+ if (IS_ERR(rt2)) {
+ err = PTR_ERR(rt2);
+ goto relookup_failed;
+ }
+ /* Ugh! */
+ orefdst = skb_dstref_steal(skb_in);
+ err = ip_route_input(skb_in, fl4_dec.daddr, fl4_dec.saddr,
+ dscp, rt2->dst.dev) ? -EINVAL : 0;
+
+ dst_release(&rt2->dst);
+ rt2 = skb_rtable(skb_in);
+ /* steal dst entry from skb_in, don't drop refcnt */
+ skb_dstref_steal(skb_in);
+ skb_dstref_restore(skb_in, orefdst);
+
+ /*
+ * At this point, fl4_dec.daddr should NOT be local (we
+ * checked fl4_dec.saddr above). However, a race condition
+ * may occur if the address is added to the interface
+ * concurrently. In that case, ip_route_input() returns a
+ * LOCAL route with dst.output=ip_rt_bug, which must not
+ * be used for output.
+ */
+ if (!err && rt2 && rt2->rt_type == RTN_LOCAL) {
+ net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n",
+ &fl4_dec.daddr, &fl4_dec.saddr);
+ dst_release(&rt2->dst);
+ err = -EINVAL;
+ }
+ }
+
+ if (err)
+ goto relookup_failed;
+
+ dst2 = xfrm_lookup(net, &rt2->dst, flowi4_to_flowi(&fl4_dec), NULL,
+ XFRM_LOOKUP_ICMP);
+ rt2 = dst_rtable(dst2);
+ if (!IS_ERR(dst2)) {
+ dst_release(&rt->dst);
+ rt = rt2;
+ } else if (PTR_ERR(dst2) == -EPERM) {
+ if (rt)
+ dst_release(&rt->dst);
+ return rt2;
+ } else {
+ err = PTR_ERR(dst2);
+ goto relookup_failed;
+ }
+ return rt;
+
+relookup_failed:
+ if (rt)
+ return rt;
+ return ERR_PTR(err);
+}
+
+struct icmp_ext_iio_addr4_subobj {
+ __be16 afi;
+ __be16 reserved;
+ __be32 addr4;
+};
+
+static unsigned int icmp_ext_iio_len(void)
+{
+ return sizeof(struct icmp_extobj_hdr) +
+ /* ifIndex */
+ sizeof(__be32) +
+ /* Interface Address Sub-Object */
+ sizeof(struct icmp_ext_iio_addr4_subobj) +
+ /* Interface Name Sub-Object. Length must be a multiple of 4
+ * bytes.
+ */
+ ALIGN(sizeof(struct icmp_ext_iio_name_subobj), 4) +
+ /* MTU */
+ sizeof(__be32);
+}
+
+static unsigned int icmp_ext_max_len(u8 ext_objs)
+{
+ unsigned int ext_max_len;
+
+ ext_max_len = sizeof(struct icmp_ext_hdr);
+
+ if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
+ ext_max_len += icmp_ext_iio_len();
+
+ return ext_max_len;
+}
+
+static __be32 icmp_ext_iio_addr4_find(const struct net_device *dev)
+{
+ struct in_device *in_dev;
+ struct in_ifaddr *ifa;
+
+ in_dev = __in_dev_get_rcu(dev);
+ if (!in_dev)
+ return 0;
+
+ /* It is unclear from RFC 5837 which IP address should be chosen, but
+ * it makes sense to choose a global unicast address.
+ */
+ in_dev_for_each_ifa_rcu(ifa, in_dev) {
+ if (READ_ONCE(ifa->ifa_flags) & IFA_F_SECONDARY)
+ continue;
+ if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
+ ipv4_is_multicast(ifa->ifa_address))
+ continue;
+ return ifa->ifa_address;
+ }
+
+ return 0;
+}
+
+static void icmp_ext_iio_iif_append(struct net *net, struct sk_buff *skb,
+ int iif)
+{
+ struct icmp_ext_iio_name_subobj *name_subobj;
+ struct icmp_extobj_hdr *objh;
+ struct net_device *dev;
+ __be32 data;
+
+ if (!iif)
+ return;
+
+ /* Add the fields in the order specified by RFC 5837. */
+ objh = skb_put(skb, sizeof(*objh));
+ objh->class_num = ICMP_EXT_OBJ_CLASS_IIO;
+ objh->class_type = ICMP_EXT_CTYPE_IIO_ROLE(ICMP_EXT_CTYPE_IIO_ROLE_IIF);
+
+ data = htonl(iif);
+ skb_put_data(skb, &data, sizeof(__be32));
+ objh->class_type |= ICMP_EXT_CTYPE_IIO_IFINDEX;
+
+ rcu_read_lock();
+
+ dev = dev_get_by_index_rcu(net, iif);
+ if (!dev)
+ goto out;
+
+ data = icmp_ext_iio_addr4_find(dev);
+ if (data) {
+ struct icmp_ext_iio_addr4_subobj *addr4_subobj;
+
+ addr4_subobj = skb_put_zero(skb, sizeof(*addr4_subobj));
+ addr4_subobj->afi = htons(ICMP_AFI_IP);
+ addr4_subobj->addr4 = data;
+ objh->class_type |= ICMP_EXT_CTYPE_IIO_IPADDR;
+ }
+
+ name_subobj = skb_put_zero(skb, ALIGN(sizeof(*name_subobj), 4));
+ name_subobj->len = ALIGN(sizeof(*name_subobj), 4);
+ netdev_copy_name(dev, name_subobj->name);
+ objh->class_type |= ICMP_EXT_CTYPE_IIO_NAME;
+
+ data = htonl(READ_ONCE(dev->mtu));
+ skb_put_data(skb, &data, sizeof(__be32));
+ objh->class_type |= ICMP_EXT_CTYPE_IIO_MTU;
+
+out:
+ rcu_read_unlock();
+ objh->length = htons(skb_tail_pointer(skb) - (unsigned char *)objh);
+}
+
+static void icmp_ext_objs_append(struct net *net, struct sk_buff *skb,
+ u8 ext_objs, int iif)
+{
+ if (ext_objs & BIT(ICMP_ERR_EXT_IIO_IIF))
+ icmp_ext_iio_iif_append(net, skb, iif);
+}
+
+static struct sk_buff *
+icmp_ext_append(struct net *net, struct sk_buff *skb_in, struct icmphdr *icmph,
+ unsigned int room, int iif)
+{
+ unsigned int payload_len, ext_max_len, ext_len;
+ struct icmp_ext_hdr *ext_hdr;
+ struct sk_buff *skb;
+ u8 ext_objs;
+ int nhoff;
+
+ switch (icmph->type) {
+ case ICMP_DEST_UNREACH:
+ case ICMP_TIME_EXCEEDED:
+ case ICMP_PARAMETERPROB:
+ break;
+ default:
+ return NULL;
+ }
+
+ ext_objs = READ_ONCE(net->ipv4.sysctl_icmp_errors_extension_mask);
+ if (!ext_objs)
+ return NULL;
+
+ ext_max_len = icmp_ext_max_len(ext_objs);
+ if (ICMP_EXT_ORIG_DGRAM_MIN_LEN + ext_max_len > room)
+ return NULL;
+
+ skb = skb_clone(skb_in, GFP_ATOMIC);
+ if (!skb)
+ return NULL;
+
+ nhoff = skb_network_offset(skb);
+ payload_len = min(skb->len - nhoff, ICMP_EXT_ORIG_DGRAM_MIN_LEN);
+
+ if (!pskb_network_may_pull(skb, payload_len))
+ goto free_skb;
+
+ if (pskb_trim(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN) ||
+ __skb_put_padto(skb, nhoff + ICMP_EXT_ORIG_DGRAM_MIN_LEN, false))
+ goto free_skb;
+
+ if (pskb_expand_head(skb, 0, ext_max_len, GFP_ATOMIC))
+ goto free_skb;
+
+ ext_hdr = skb_put_zero(skb, sizeof(*ext_hdr));
+ ext_hdr->version = ICMP_EXT_VERSION_2;
+
+ icmp_ext_objs_append(net, skb, ext_objs, iif);
+
+ /* Do not send an empty extension structure. */
+ ext_len = skb_tail_pointer(skb) - (unsigned char *)ext_hdr;
+ if (ext_len == sizeof(*ext_hdr))
+ goto free_skb;
+
+ ext_hdr->checksum = ip_compute_csum(ext_hdr, ext_len);
+ /* The length of the original datagram in 32-bit words (RFC 4884). */
+ icmph->un.reserved[1] = ICMP_EXT_ORIG_DGRAM_MIN_LEN / sizeof(u32);
+
+ return skb;
+
+free_skb:
+ consume_skb(skb);
+ return NULL;
+}
+
+/*
+ * Send an ICMP message in response to a situation
+ *
+ * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header.
+ * MAY send more (we do).
+ * MUST NOT change this header information.
+ * MUST NOT reply to a multicast/broadcast IP address.
+ * MUST NOT reply to a multicast/broadcast MAC address.
+ * MUST reply to only the first fragment.
+ */
+
+void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
+ const struct inet_skb_parm *parm)
+{
+ DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+ IP_OPTIONS_DATA_FIXED_SIZE);
+ struct iphdr *iph;
+ int room;
+ struct rtable *rt = skb_rtable(skb_in);
+ bool apply_ratelimit = false;
+ struct sk_buff *ext_skb;
+ struct ipcm_cookie ipc;
+ struct flowi4 fl4;
+ __be32 saddr;
+ u8 tos;
+ u32 mark;
+ struct net *net;
+ struct sock *sk;
+
+ if (!rt)
+ return;
+
+ rcu_read_lock();
+
+ if (rt->dst.dev)
+ net = dev_net_rcu(rt->dst.dev);
+ else if (skb_in->dev)
+ net = dev_net_rcu(skb_in->dev);
+ else
+ goto out;
+
+ /*
+ * Find the original header. It is expected to be valid, of course.
+ * Check this, icmp_send is called from the most obscure devices
+ * sometimes.
+ */
+ iph = ip_hdr(skb_in);
+
+ if ((u8 *)iph < skb_in->head ||
+ (skb_network_header(skb_in) + sizeof(*iph)) >
+ skb_tail_pointer(skb_in))
+ goto out;
+
+ /*
+ * No replies to physical multicast/broadcast
+ */
+ if (skb_in->pkt_type != PACKET_HOST)
+ goto out;
+
+ /*
+ * Now check at the protocol level
+ */
+ if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
+ goto out;
+
+ /*
+ * Only reply to fragment 0. We byte re-order the constant
+ * mask for efficiency.
+ */
+ if (iph->frag_off & htons(IP_OFFSET))
+ goto out;
+
+ /*
+ * If we send an ICMP error to an ICMP error a mess would result..
+ */
+ if (icmp_pointers[type].error) {
+ /*
+ * We are an error, check if we are replying to an
+ * ICMP error
+ */
+ if (iph->protocol == IPPROTO_ICMP) {
+ u8 _inner_type, *itp;
+
+ itp = skb_header_pointer(skb_in,
+ skb_network_header(skb_in) +
+ (iph->ihl << 2) +
+ offsetof(struct icmphdr,
+ type) -
+ skb_in->data,
+ sizeof(_inner_type),
+ &_inner_type);
+ if (!itp)
+ goto out;
+
+ /*
+ * Assume any unknown ICMP type is an error. This
+ * isn't specified by the RFC, but think about it..
+ */
+ if (*itp > NR_ICMP_TYPES ||
+ icmp_pointers[*itp].error)
+ goto out;
+ }
+ }
+
+ /* Needed by both icmpv4_global_allow and icmp_xmit_lock */
+ local_bh_disable();
+
+ /* Check global sysctl_icmp_msgs_per_sec ratelimit, unless
+ * incoming dev is loopback. If outgoing dev change to not be
+ * loopback, then peer ratelimit still work (in icmpv4_xrlim_allow)
+ */
+ if (!(skb_in->dev && (skb_in->dev->flags&IFF_LOOPBACK)) &&
+ !icmpv4_global_allow(net, type, code, &apply_ratelimit))
+ goto out_bh_enable;
+
+ sk = icmp_xmit_lock(net);
+ if (!sk)
+ goto out_bh_enable;
+
+ /*
+ * Construct source address and options.
+ */
+
+ saddr = iph->daddr;
+ if (!(rt->rt_flags & RTCF_LOCAL)) {
+ struct net_device *dev = NULL;
+
+ rcu_read_lock();
+ if (rt_is_input_route(rt) &&
+ READ_ONCE(net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr))
+ dev = dev_get_by_index_rcu(net, parm->iif ? parm->iif :
+ inet_iif(skb_in));
+
+ if (dev)
+ saddr = inet_select_addr(dev, iph->saddr,
+ RT_SCOPE_LINK);
+ else
+ saddr = 0;
+ rcu_read_unlock();
+ }
+
+ tos = icmp_pointers[type].error ? (RT_TOS(iph->tos) |
+ IPTOS_PREC_INTERNETCONTROL) :
+ iph->tos;
+ mark = IP4_REPLY_MARK(net, skb_in->mark);
+
+ if (__ip_options_echo(net, &icmp_param->replyopts.opt, skb_in,
+ &parm->opt))
+ goto out_unlock;
+
+
+ /*
+ * Prepare data for ICMP header.
+ */
+
+ icmp_param->data.icmph.type = type;
+ icmp_param->data.icmph.code = code;
+ icmp_param->data.icmph.un.gateway = info;
+ icmp_param->data.icmph.checksum = 0;
+ icmp_param->skb = skb_in;
+ icmp_param->offset = skb_network_offset(skb_in);
+ ipcm_init(&ipc);
+ ipc.tos = tos;
+ ipc.addr = iph->saddr;
+ ipc.opt = &icmp_param->replyopts;
+ ipc.sockc.mark = mark;
+
+ rt = icmp_route_lookup(net, &fl4, skb_in, iph, saddr,
+ inet_dsfield_to_dscp(tos), mark, type, code,
+ icmp_param);
+ if (IS_ERR(rt))
+ goto out_unlock;
+
+ if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
+ goto ende;
+
+ /* peer icmp_ratelimit */
+ if (!icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
+ goto ende;
+
+ /* RFC says return as much as we can without exceeding 576 bytes. */
+
+ room = dst4_mtu(&rt->dst);
+ if (room > 576)
+ room = 576;
+ room -= sizeof(struct iphdr) + icmp_param->replyopts.opt.optlen;
+ room -= sizeof(struct icmphdr);
+ /* Guard against tiny mtu. We need to include at least one
+ * IP network header for this message to make any sense.
+ */
+ if (room <= (int)sizeof(struct iphdr))
+ goto ende;
+
+ ext_skb = icmp_ext_append(net, skb_in, &icmp_param->data.icmph, room,
+ parm->iif);
+ if (ext_skb)
+ icmp_param->skb = ext_skb;
+
+ icmp_param->data_len = icmp_param->skb->len - icmp_param->offset;
+ if (icmp_param->data_len > room)
+ icmp_param->data_len = room;
+ icmp_param->head_len = sizeof(struct icmphdr);
+
+ /* if we don't have a source address at this point, fall back to the
+ * dummy address instead of sending out a packet with a source address
+ * of 0.0.0.0
+ */
+ if (!fl4.saddr)
+ fl4.saddr = htonl(INADDR_DUMMY);
+
+ trace_icmp_send(skb_in, type, code);
+
+ icmp_push_reply(sk, icmp_param, &fl4, &ipc, &rt);
+
+ if (ext_skb)
+ consume_skb(ext_skb);
+ende:
+ ip_rt_put(rt);
+out_unlock:
+ icmp_xmit_unlock(sk);
+out_bh_enable:
+ local_bh_enable();
+out:
+ rcu_read_unlock();
+}
+EXPORT_SYMBOL(__icmp_send);
+
+#if IS_ENABLED(CONFIG_NF_NAT)
+#include <net/netfilter/nf_conntrack.h>
+void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
+{
+ struct sk_buff *cloned_skb = NULL;
+ enum ip_conntrack_info ctinfo;
+ enum ip_conntrack_dir dir;
+ struct inet_skb_parm parm;
+ struct nf_conn *ct;
+ __be32 orig_ip;
+
+ memset(&parm, 0, sizeof(parm));
+ ct = nf_ct_get(skb_in, &ctinfo);
+ if (!ct || !(READ_ONCE(ct->status) & IPS_NAT_MASK)) {
+ __icmp_send(skb_in, type, code, info, &parm);
+ return;
+ }
+
+ if (skb_shared(skb_in))
+ skb_in = cloned_skb = skb_clone(skb_in, GFP_ATOMIC);
+
+ if (unlikely(!skb_in || skb_network_header(skb_in) < skb_in->head ||
+ (skb_network_header(skb_in) + sizeof(struct iphdr)) >
+ skb_tail_pointer(skb_in) || skb_ensure_writable(skb_in,
+ skb_network_offset(skb_in) + sizeof(struct iphdr))))
+ goto out;
+
+ orig_ip = ip_hdr(skb_in)->saddr;
+ dir = CTINFO2DIR(ctinfo);
+ ip_hdr(skb_in)->saddr = ct->tuplehash[dir].tuple.src.u3.ip;
+ __icmp_send(skb_in, type, code, info, &parm);
+ ip_hdr(skb_in)->saddr = orig_ip;
+out:
+ consume_skb(cloned_skb);
+}
+EXPORT_SYMBOL(icmp_ndo_send);
+#endif
+
+static void icmp_socket_deliver(struct sk_buff *skb, u32 info)
+{
+ const struct iphdr *iph = (const struct iphdr *)skb->data;
+ const struct net_protocol *ipprot;
+ int protocol = iph->protocol;
+
+ /* Checkin full IP header plus 8 bytes of protocol to
+ * avoid additional coding at protocol handlers.
+ */
+ if (!pskb_may_pull(skb, iph->ihl * 4 + 8))
+ goto out;
+
+ /* IPPROTO_RAW sockets are not supposed to receive anything. */
+ if (protocol == IPPROTO_RAW)
+ goto out;
+
+ raw_icmp_error(skb, protocol, info);
+
+ ipprot = rcu_dereference(inet_protos[protocol]);
+ if (ipprot && ipprot->err_handler)
+ ipprot->err_handler(skb, info);
+ return;
+
+out:
+ __ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
+}
+
+static bool icmp_tag_validation(int proto)
+{
+ const struct net_protocol *ipprot;
+ bool ok;
+
+ rcu_read_lock();
+ ipprot = rcu_dereference(inet_protos[proto]);
+ ok = ipprot ? ipprot->icmp_strict_tag_validation : false;
+ rcu_read_unlock();
+ return ok;
+}
+
+/*
+ * Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEEDED, ICMP_QUENCH, and
+ * ICMP_PARAMETERPROB.
+ */
+
+static enum skb_drop_reason icmp_unreach(struct sk_buff *skb)
+{
+ enum skb_drop_reason reason = SKB_NOT_DROPPED_YET;
+ const struct iphdr *iph;
+ struct icmphdr *icmph;
+ struct net *net;
+ u32 info = 0;
+
+ net = skb_dst_dev_net_rcu(skb);
+
+ /*
+ * Incomplete header ?
+ * Only checks for the IP header, there should be an
+ * additional check for longer headers in upper levels.
+ */
+
+ if (!pskb_may_pull(skb, sizeof(struct iphdr)))
+ goto out_err;
+
+ icmph = icmp_hdr(skb);
+ iph = (const struct iphdr *)skb->data;
+
+ if (iph->ihl < 5) { /* Mangled header, drop. */
+ reason = SKB_DROP_REASON_IP_INHDR;
+ goto out_err;
+ }
+
+ switch (icmph->type) {
+ case ICMP_DEST_UNREACH:
+ switch (icmph->code & 15) {
+ case ICMP_NET_UNREACH:
+ case ICMP_HOST_UNREACH:
+ case ICMP_PROT_UNREACH:
+ case ICMP_PORT_UNREACH:
+ break;
+ case ICMP_FRAG_NEEDED:
+ /* for documentation of the ip_no_pmtu_disc
+ * values please see
+ * Documentation/networking/ip-sysctl.rst
+ */
+ switch (READ_ONCE(net->ipv4.sysctl_ip_no_pmtu_disc)) {
+ default:
+ net_dbg_ratelimited("%pI4: fragmentation needed and DF set\n",
+ &iph->daddr);
+ break;
+ case 2:
+ goto out;
+ case 3:
+ if (!icmp_tag_validation(iph->protocol))
+ goto out;
+ fallthrough;
+ case 0:
+ info = ntohs(icmph->un.frag.mtu);
+ }
+ break;
+ case ICMP_SR_FAILED:
+ net_dbg_ratelimited("%pI4: Source Route Failed\n",
+ &iph->daddr);
+ break;
+ default:
+ break;
+ }
+ if (icmph->code > NR_ICMP_UNREACH)
+ goto out;
+ break;
+ case ICMP_PARAMETERPROB:
+ info = ntohl(icmph->un.gateway) >> 24;
+ break;
+ case ICMP_TIME_EXCEEDED:
+ __ICMP_INC_STATS(net, ICMP_MIB_INTIMEEXCDS);
+ if (icmph->code == ICMP_EXC_FRAGTIME)
+ goto out;
+ break;
+ }
+
+ /*
+ * Throw it at our lower layers
+ *
+ * RFC 1122: 3.2.2 MUST extract the protocol ID from the passed
+ * header.
+ * RFC 1122: 3.2.2.1 MUST pass ICMP unreach messages to the
+ * transport layer.
+ * RFC 1122: 3.2.2.2 MUST pass ICMP time expired messages to
+ * transport layer.
+ */
+
+ /*
+ * Check the other end isn't violating RFC 1122. Some routers send
+ * bogus responses to broadcast frames. If you see this message
+ * first check your netmask matches at both ends, if it does then
+ * get the other vendor to fix their kit.
+ */
+
+ if (!READ_ONCE(net->ipv4.sysctl_icmp_ignore_bogus_error_responses) &&
+ inet_addr_type_dev_table(net, skb->dev, iph->daddr) == RTN_BROADCAST) {
+ net_warn_ratelimited("%pI4 sent an invalid ICMP type %u, code %u error to a broadcast: %pI4 on %s\n",
+ &ip_hdr(skb)->saddr,
+ icmph->type, icmph->code,
+ &iph->daddr, skb->dev->name);
+ goto out;
+ }
+
+ icmp_socket_deliver(skb, info);
+
+out:
+ return reason;
+out_err:
+ __ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
+ return reason ?: SKB_DROP_REASON_NOT_SPECIFIED;
+}
+
+/*
+ * Handle ICMP_REDIRECT.
+ */
+
+static enum skb_drop_reason icmp_redirect(struct sk_buff *skb)
+{
+ if (skb->len < sizeof(struct iphdr)) {
+ __ICMP_INC_STATS(dev_net_rcu(skb->dev), ICMP_MIB_INERRORS);
+ return SKB_DROP_REASON_PKT_TOO_SMALL;
+ }
+
+ if (!pskb_may_pull(skb, sizeof(struct iphdr))) {
+ /* there aught to be a stat */
+ return SKB_DROP_REASON_NOMEM;
+ }
+
+ icmp_socket_deliver(skb, ntohl(icmp_hdr(skb)->un.gateway));
+ return SKB_NOT_DROPPED_YET;
+}
+
+/*
+ * Handle ICMP_ECHO ("ping") and ICMP_EXT_ECHO ("PROBE") requests.
+ *
+ * RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo
+ * requests.
+ * RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be
+ * included in the reply.
+ * RFC 1812: 4.3.3.6 SHOULD have a config option for silently ignoring
+ * echo requests, MUST have default=NOT.
+ * RFC 8335: 8 MUST have a config option to enable/disable ICMP
+ * Extended Echo Functionality, MUST be disabled by default
+ * See also WRT handling of options once they are done and working.
+ */
+
+static enum skb_drop_reason icmp_echo(struct sk_buff *skb)
+{
+ DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+ IP_OPTIONS_DATA_FIXED_SIZE);
+ struct net *net;
+
+ net = skb_dst_dev_net_rcu(skb);
+ /* should there be an ICMP stat for ignored echos? */
+ if (READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_all))
+ return SKB_NOT_DROPPED_YET;
+
+ icmp_param->data.icmph = *icmp_hdr(skb);
+ icmp_param->skb = skb;
+ icmp_param->offset = 0;
+ icmp_param->data_len = skb->len;
+ icmp_param->head_len = sizeof(struct icmphdr);
+
+ if (icmp_param->data.icmph.type == ICMP_ECHO)
+ icmp_param->data.icmph.type = ICMP_ECHOREPLY;
+ else if (!icmp_build_probe(skb, &icmp_param->data.icmph))
+ return SKB_NOT_DROPPED_YET;
+
+ icmp_reply(icmp_param, skb);
+ return SKB_NOT_DROPPED_YET;
+}
+
+/*
+ * Handle ICMP Timestamp requests.
+ * RFC 1122: 3.2.2.8 MAY implement ICMP timestamp requests.
+ * SHOULD be in the kernel for minimum random latency.
+ * MUST be accurate to a few minutes.
+ * MUST be updated at least at 15Hz.
+ */
+static enum skb_drop_reason icmp_timestamp(struct sk_buff *skb)
+{
+ DEFINE_RAW_FLEX(struct icmp_bxm, icmp_param, replyopts.opt.__data,
+ IP_OPTIONS_DATA_FIXED_SIZE);
+ /*
+ * Too short.
+ */
+ if (skb->len < 4)
+ goto out_err;
+
+ /*
+ * Fill in the current time as ms since midnight UT:
+ */
+ icmp_param->data.times[1] = inet_current_timestamp();
+ icmp_param->data.times[2] = icmp_param->data.times[1];
+
+ BUG_ON(skb_copy_bits(skb, 0, &icmp_param->data.times[0], 4));
+
+ icmp_param->data.icmph = *icmp_hdr(skb);
+ icmp_param->data.icmph.type = ICMP_TIMESTAMPREPLY;
+ icmp_param->data.icmph.code = 0;
+ icmp_param->skb = skb;
+ icmp_param->offset = 0;
+ icmp_param->data_len = 0;
+ icmp_param->head_len = sizeof(struct icmphdr) + 12;
+ icmp_reply(icmp_param, skb);
+ return SKB_NOT_DROPPED_YET;
+
+out_err:
+ __ICMP_INC_STATS(skb_dst_dev_net_rcu(skb), ICMP_MIB_INERRORS);
+ return SKB_DROP_REASON_PKT_TOO_SMALL;
+}
+
+static enum skb_drop_reason icmp_discard(struct sk_buff *skb)
+{
+ /* pretend it was a success */
+ return SKB_NOT_DROPPED_YET;
+}
+
+int icmp_err(struct sk_buff *skb, u32 info)
+{
+ struct iphdr *iph = (struct iphdr *)skb->data;
+ int offset = iph->ihl<<2;
+ struct icmphdr *icmph = (struct icmphdr *)(skb->data + offset);
+ struct net *net = dev_net_rcu(skb->dev);
+ int type = icmp_hdr(skb)->type;
+ int code = icmp_hdr(skb)->code;
+
+ /*
+ * Use ping_err to handle all icmp errors except those
+ * triggered by ICMP_ECHOREPLY which sent from kernel.
+ */
+ if (icmph->type != ICMP_ECHOREPLY) {
+ ping_err(skb, offset, info);
+ return 0;
+ }
+
+ if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
+ ipv4_update_pmtu(skb, net, info, 0, IPPROTO_ICMP);
+ else if (type == ICMP_REDIRECT)
+ ipv4_redirect(skb, net, 0, IPPROTO_ICMP);
+
+ return 0;
+}
+
+/*
+ * Deal with incoming ICMP packets.
+ */
+int icmp_rcv(struct sk_buff *skb)
+{
+ enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED;
+ struct rtable *rt = skb_rtable(skb);
+ struct net *net = dev_net_rcu(rt->dst.dev);
+ struct icmphdr *icmph;
+
+ if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
+ struct sec_path *sp = skb_sec_path(skb);
+ int nh;
+
+ if (!(sp && sp->xvec[sp->len - 1]->props.flags &
+ XFRM_STATE_ICMP)) {
+ reason = SKB_DROP_REASON_XFRM_POLICY;
+ goto drop;
+ }
+
+ if (!pskb_may_pull(skb, sizeof(*icmph) + sizeof(struct iphdr)))
+ goto drop;
+
+ nh = skb_network_offset(skb);
+ skb_set_network_header(skb, sizeof(*icmph));
+
+ if (!xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN,
+ skb)) {
+ reason = SKB_DROP_REASON_XFRM_POLICY;
+ goto drop;
+ }
+
+ skb_set_network_header(skb, nh);
+ }
+
+ __ICMP_INC_STATS(net, ICMP_MIB_INMSGS);
+
+ if (skb_checksum_simple_validate(skb))
+ goto csum_error;
+
+ if (!pskb_pull(skb, sizeof(*icmph)))
+ goto error;
+
+ icmph = icmp_hdr(skb);
+
+ ICMPMSGIN_INC_STATS(net, icmph->type);
+
+ /* Check for ICMP Extended Echo (PROBE) messages */
+ if (icmph->type == ICMP_EXT_ECHO) {
+ /* We can't use icmp_pointers[].handler() because it is an array of
+ * size NR_ICMP_TYPES + 1 (19 elements) and PROBE has code 42.
+ */
+ reason = icmp_echo(skb);
+ goto reason_check;
+ }
+
+ /*
+ * Parse the ICMP message
+ */
+
+ if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
+ /*
+ * RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be
+ * silently ignored (we let user decide with a sysctl).
+ * RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently
+ * discarded if to broadcast/multicast.
+ */
+ if ((icmph->type == ICMP_ECHO ||
+ icmph->type == ICMP_TIMESTAMP) &&
+ READ_ONCE(net->ipv4.sysctl_icmp_echo_ignore_broadcasts)) {
+ reason = SKB_DROP_REASON_INVALID_PROTO;
+ goto error;
+ }
+ if (icmph->type != ICMP_ECHO &&
+ icmph->type != ICMP_TIMESTAMP &&
+ icmph->type != ICMP_ADDRESS &&
+ icmph->type != ICMP_ADDRESSREPLY) {
+ reason = SKB_DROP_REASON_INVALID_PROTO;
+ goto error;
+ }
+ }
+
+ if (icmph->type == ICMP_EXT_ECHOREPLY ||
+ icmph->type == ICMP_ECHOREPLY) {
+ reason = ping_rcv(skb);
+ return reason ? NET_RX_DROP : NET_RX_SUCCESS;
+ }
+
+ /*
+ * 18 is the highest 'known' ICMP type. Anything else is a mystery
+ *
+ * RFC 1122: 3.2.2 Unknown ICMP messages types MUST be silently
+ * discarded.
+ */
+ if (icmph->type > NR_ICMP_TYPES) {
+ reason = SKB_DROP_REASON_UNHANDLED_PROTO;
+ goto error;
+ }
+
+ reason = icmp_pointers[icmph->type].handler(skb);
+reason_check:
+ if (!reason) {
+ consume_skb(skb);
+ return NET_RX_SUCCESS;
+ }
+
+drop:
+ kfree_skb_reason(skb, reason);
+ return NET_RX_DROP;
+csum_error:
+ reason = SKB_DROP_REASON_ICMP_CSUM;
+ __ICMP_INC_STATS(net, ICMP_MIB_CSUMERRORS);
+error:
+ __ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
+ goto drop;
+}
+
+/*
+ * This table is the definition of how we handle ICMP.
+ */
+static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = {
+ [ICMP_ECHOREPLY] = {
+ .handler = ping_rcv,
+ },
+ [1] = {
+ .handler = icmp_discard,
+ .error = 1,
+ },
+ [2] = {
+ .handler = icmp_discard,
+ .error = 1,
+ },
+ [ICMP_DEST_UNREACH] = {
+ .handler = icmp_unreach,
+ .error = 1,
+ },
+ [ICMP_SOURCE_QUENCH] = {
+ .handler = icmp_unreach,
+ .error = 1,
+ },
+ [ICMP_REDIRECT] = {
+ .handler = icmp_redirect,
+ .error = 1,
+ },
+ [6] = {
+ .handler = icmp_discard,
+ .error = 1,
+ },
+ [7] = {
+ .handler = icmp_discard,
+ .error = 1,
+ },
+ [ICMP_ECHO] = {
+ .handler = icmp_echo,
+ },
+ [9] = {
+ .handler = icmp_discard,
+ .error = 1,
+ },
+ [10] = {
+ .handler = icmp_discard,
+ .error = 1,
+ },
+ [ICMP_TIME_EXCEEDED] = {
+ .handler = icmp_unreach,
+ .error = 1,
+ },
+ [ICMP_PARAMETERPROB] = {
+ .handler = icmp_unreach,
+ .error = 1,
+ },
+ [ICMP_TIMESTAMP] = {
+ .handler = icmp_timestamp,
+ },
+ [ICMP_TIMESTAMPREPLY] = {
+ .handler = icmp_discard,
+ },
+ [ICMP_INFO_REQUEST] = {
+ .handler = icmp_discard,
+ },
+ [ICMP_INFO_REPLY] = {
+ .handler = icmp_discard,
+ },
+ [ICMP_ADDRESS] = {
+ .handler = icmp_discard,
+ },
+ [ICMP_ADDRESSREPLY] = {
+ .handler = icmp_discard,
+ },
+};
+
+int icmp_init_ipv4(void)
+{
+ int err, i;
+
+ for_each_possible_cpu(i) {
+ struct sock *sk;
+
+ err = inet_ctl_sock_create(&sk, PF_INET,
+ SOCK_RAW, IPPROTO_ICMP, &init_net);
+ if (err < 0)
+ return err;
+
+ per_cpu(ipv4_icmp_sk, i) = sk;
+
+ /* Enough space for 2 64K ICMP packets, including
+ * sk_buff/skb_shared_info struct overhead.
+ */
+ sk->sk_sndbuf = 2 * SKB_TRUESIZE(64 * 1024);
+
+ /*
+ * Speedup sock_wfree()
+ */
+ sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
+ inet_sk(sk)->pmtudisc = IP_PMTUDISC_DONT;
+ }
+
+ return 0;
+}
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 8165277629ad..62007dbadf7a 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -205,7 +205,9 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
(struct sockaddr_unsized *)&sin,
sizeof(sin));
+#if IS_ENABLED(CONFIG_IPV4)
ipv4_connected:
+#endif
if (err)
goto out;
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 08/13 RFC net-next] net: ping: split IPv4 specific logic into ping_ipv4.c
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (6 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 07/13 RFC net-next] net: icmp: split IPv4 specific logic into icmp_ipv4.c Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 09/13 RFC net-next] net: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
` (4 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Eric Biggers, Kuniyuki Iwashima,
Ard Biesheuvel, Julian Anastasov, Florian Westphal, Yue Haibing,
yuan.gao, Jeff Layton, Gustavo A. R. Silva, Kees Cook,
linux-kernel, bpf
To enable compiling INET subsystem withot IPv4, ping socket IPv4
specific functions must be isolated from the generic ping
infrastructure.
This patch creates ping_ipv4.c and move all the functions for packet
transmission, error handling and the AF_INET ping_prot definition from
ping.c to the new file. The Makefile is updated to compile ping_ipv4.c
only when CONFIG_IPV4 is enabled.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
net/ipv4/Makefile | 2 +-
net/ipv4/ping.c | 215 +++--------------------------------------
net/ipv4/ping_ipv4.c | 225 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 238 insertions(+), 204 deletions(-)
create mode 100644 net/ipv4/ping_ipv4.c
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 651835073a29..83c25f52eb58 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -14,7 +14,7 @@ obj-y := inetpeer.o protocol.o inet_hashtables.o inet_timewait_sock.o \
obj-$(CONFIG_IPV4) += route.o ip_input.o ip_fragment.o ip_forward.o ip_options.o \
ip_sockglue.o tcp_ipv4.o datagram.o arp.o devinet.o igmp.o \
fib_notifier.o ip_output.o fib_frontend.o fib_semantics.o \
- fib_trie.o raw_ipv4.o udp_ipv4.o icmp_ipv4.o
+ fib_trie.o raw_ipv4.o udp_ipv4.o icmp_ipv4.o ping_ipv4.o
obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
diff --git a/net/ipv4/ping.c b/net/ipv4/ping.c
index d36f1e273fde..31ee56eeac81 100644
--- a/net/ipv4/ping.c
+++ b/net/ipv4/ping.c
@@ -285,25 +285,13 @@ void ping_close(struct sock *sk, long timeout)
sk_common_release(sk);
}
-static int ping_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
- int addr_len)
-{
- /* This check is replicated from __ip4_datagram_connect() and
- * intended to prevent BPF program called below from accessing bytes
- * that are out of the bound specified by user in addr_len.
- */
- if (addr_len < sizeof(struct sockaddr_in))
- return -EINVAL;
-
- return BPF_CGROUP_RUN_PROG_INET4_CONNECT_LOCK(sk, uaddr, &addr_len);
-}
-
/* Checks the bind address and possibly modifies sk->sk_bound_dev_if. */
static int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,
struct sockaddr_unsized *uaddr, int addr_len)
{
struct net *net = sock_net(sk);
if (sk->sk_family == AF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;
u32 tb_id = RT_TABLE_LOCAL;
int chk_addr_ret;
@@ -331,6 +319,9 @@ static int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,
!inet_can_nonlocal_bind(net, isk)))
return -EADDRNOTAVAIL;
+#else
+ return -EAFNOSUPPORT;
+#endif
#if IS_ENABLED(CONFIG_IPV6)
} else if (sk->sk_family == AF_INET6) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *) uaddr;
@@ -528,6 +519,7 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
inet_sock = inet_sk(sk);
if (skb->protocol == htons(ETH_P_IP)) {
+#if IS_ENABLED(CONFIG_IPV4)
switch (type) {
default:
case ICMP_TIME_EXCEEDED:
@@ -565,6 +557,7 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
err = EREMOTEIO;
break;
}
+#endif
#if IS_ENABLED(CONFIG_IPV6)
} else if (skb->protocol == htons(ETH_P_IPV6)) {
harderr = pingv6_ops.icmpv6_err_convert(type, code, &err);
@@ -581,8 +574,10 @@ void ping_err(struct sk_buff *skb, int offset, u32 info)
goto out;
} else {
if (family == AF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
ip_icmp_error(sk, skb, err, 0 /* no remote port */,
info, (u8 *)icmph);
+#endif
#if IS_ENABLED(CONFIG_IPV6)
} else if (family == AF_INET6) {
pingv6_ops.ipv6_icmp_error(sk, skb, err, 0,
@@ -625,21 +620,6 @@ int ping_getfrag(void *from, char *to,
return 0;
}
-static int ping_v4_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,
- struct flowi4 *fl4)
-{
- struct sk_buff *skb = skb_peek(&sk->sk_write_queue);
-
- if (!skb)
- return 0;
- pfh->wcheck = csum_partial((char *)&pfh->icmph,
- sizeof(struct icmphdr), pfh->wcheck);
- pfh->icmph.checksum = csum_fold(pfh->wcheck);
- memcpy(icmp_hdr(skb), &pfh->icmph, sizeof(struct icmphdr));
- skb->ip_summed = CHECKSUM_NONE;
- return ip_push_pending_frames(sk, fl4);
-}
-
int ping_common_sendmsg(int family, struct msghdr *msg, size_t len,
void *user_icmph, size_t icmph_len)
{
@@ -685,160 +665,6 @@ int ping_common_sendmsg(int family, struct msghdr *msg, size_t len,
return 0;
}
-static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
-{
- DEFINE_RAW_FLEX(struct ip_options_rcu, opt_copy, opt.__data,
- IP_OPTIONS_DATA_FIXED_SIZE);
- struct net *net = sock_net(sk);
- struct flowi4 fl4;
- struct inet_sock *inet = inet_sk(sk);
- struct ipcm_cookie ipc;
- struct icmphdr user_icmph;
- struct pingfakehdr pfh;
- struct rtable *rt = NULL;
- int free = 0;
- __be32 saddr, daddr, faddr;
- u8 scope;
- int err;
-
- pr_debug("ping_v4_sendmsg(sk=%p,sk->num=%u)\n", inet, inet->inet_num);
-
- err = ping_common_sendmsg(AF_INET, msg, len, &user_icmph,
- sizeof(user_icmph));
- if (err)
- return err;
-
- /*
- * Get and verify the address.
- */
-
- if (msg->msg_name) {
- DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
- if (msg->msg_namelen < sizeof(*usin))
- return -EINVAL;
- if (usin->sin_family != AF_INET)
- return -EAFNOSUPPORT;
- daddr = usin->sin_addr.s_addr;
- /* no remote port */
- } else {
- if (sk->sk_state != TCP_ESTABLISHED)
- return -EDESTADDRREQ;
- daddr = inet->inet_daddr;
- /* no remote port */
- }
-
- ipcm_init_sk(&ipc, inet);
-
- if (msg->msg_controllen) {
- err = ip_cmsg_send(sk, msg, &ipc, false);
- if (unlikely(err)) {
- kfree(ipc.opt);
- return err;
- }
- if (ipc.opt)
- free = 1;
- }
- if (!ipc.opt) {
- struct ip_options_rcu *inet_opt;
-
- rcu_read_lock();
- inet_opt = rcu_dereference(inet->inet_opt);
- if (inet_opt) {
- memcpy(opt_copy, inet_opt,
- sizeof(*inet_opt) + inet_opt->opt.optlen);
- ipc.opt = opt_copy;
- }
- rcu_read_unlock();
- }
-
- saddr = ipc.addr;
- ipc.addr = faddr = daddr;
-
- if (ipc.opt && ipc.opt->opt.srr) {
- if (!daddr) {
- err = -EINVAL;
- goto out_free;
- }
- faddr = ipc.opt->opt.faddr;
- }
- scope = ip_sendmsg_scope(inet, &ipc, msg);
-
- if (ipv4_is_multicast(daddr)) {
- if (!ipc.oif || netif_index_is_l3_master(sock_net(sk), ipc.oif))
- ipc.oif = READ_ONCE(inet->mc_index);
- if (!saddr)
- saddr = READ_ONCE(inet->mc_addr);
- } else if (!ipc.oif)
- ipc.oif = READ_ONCE(inet->uc_index);
-
- flowi4_init_output(&fl4, ipc.oif, ipc.sockc.mark,
- ipc.tos & INET_DSCP_MASK, scope,
- sk->sk_protocol, inet_sk_flowi_flags(sk), faddr,
- saddr, 0, 0, sk_uid(sk));
-
- fl4.fl4_icmp_type = user_icmph.type;
- fl4.fl4_icmp_code = user_icmph.code;
-
- security_sk_classify_flow(sk, flowi4_to_flowi_common(&fl4));
- rt = ip_route_output_flow(net, &fl4, sk);
- if (IS_ERR(rt)) {
- err = PTR_ERR(rt);
- rt = NULL;
- if (err == -ENETUNREACH)
- IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
- goto out;
- }
-
- err = -EACCES;
- if ((rt->rt_flags & RTCF_BROADCAST) &&
- !sock_flag(sk, SOCK_BROADCAST))
- goto out;
-
- if (msg->msg_flags & MSG_CONFIRM)
- goto do_confirm;
-back_from_confirm:
-
- if (!ipc.addr)
- ipc.addr = fl4.daddr;
-
- lock_sock(sk);
-
- pfh.icmph.type = user_icmph.type; /* already checked */
- pfh.icmph.code = user_icmph.code; /* ditto */
- pfh.icmph.checksum = 0;
- pfh.icmph.un.echo.id = inet->inet_sport;
- pfh.icmph.un.echo.sequence = user_icmph.un.echo.sequence;
- pfh.msg = msg;
- pfh.wcheck = 0;
- pfh.family = AF_INET;
-
- err = ip_append_data(sk, &fl4, ping_getfrag, &pfh, len,
- sizeof(struct icmphdr), &ipc, &rt,
- msg->msg_flags);
- if (err)
- ip_flush_pending_frames(sk);
- else
- err = ping_v4_push_pending_frames(sk, &pfh, &fl4);
- release_sock(sk);
-
-out:
- ip_rt_put(rt);
-out_free:
- if (free)
- kfree(ipc.opt);
- if (!err)
- return len;
- return err;
-
-do_confirm:
- if (msg->msg_flags & MSG_PROBE)
- dst_confirm_neigh(&rt->dst, &fl4.daddr);
- if (!(msg->msg_flags & MSG_PROBE) || len)
- goto back_from_confirm;
- err = 0;
- goto out;
-}
-
int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
{
struct inet_sock *isk = inet_sk(sk);
@@ -875,6 +701,7 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
/* Copy the address and add cmsg data. */
if (family == AF_INET) {
+#if IS_ENABLED(CONFIG_IPV4)
DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
if (sin) {
@@ -888,6 +715,7 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
if (inet_cmsg_flags(isk))
ip_cmsg_recv(msg, skb);
+#endif
#if IS_ENABLED(CONFIG_IPV6)
} else if (family == AF_INET6) {
struct ipv6hdr *ip6 = ipv6_hdr(skb);
@@ -911,9 +739,11 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
if (skb->protocol == htons(ETH_P_IPV6) &&
inet6_sk(sk)->rxopt.all)
pingv6_ops.ip6_datagram_recv_specific_ctl(sk, msg, skb);
+#if IS_ENABLED(CONFIG_IPV4)
else if (skb->protocol == htons(ETH_P_IP) &&
inet_cmsg_flags(isk))
ip_cmsg_recv(msg, skb);
+#endif
#endif
} else {
BUG();
@@ -976,27 +806,6 @@ enum skb_drop_reason ping_rcv(struct sk_buff *skb)
return SKB_DROP_REASON_NO_SOCKET;
}
-struct proto ping_prot = {
- .name = "PING",
- .owner = THIS_MODULE,
- .init = ping_init_sock,
- .close = ping_close,
- .pre_connect = ping_pre_connect,
- .connect = ip4_datagram_connect,
- .disconnect = __udp_disconnect,
- .setsockopt = ip_setsockopt,
- .getsockopt = ip_getsockopt,
- .sendmsg = ping_v4_sendmsg,
- .recvmsg = ping_recvmsg,
- .bind = ping_bind,
- .backlog_rcv = ping_queue_rcv_skb,
- .release_cb = ip4_datagram_release_cb,
- .unhash = ping_unhash,
- .get_port = ping_get_port,
- .put_port = ping_unhash,
- .obj_size = sizeof(struct inet_sock),
-};
-
#ifdef CONFIG_PROC_FS
static struct sock *ping_get_first(struct seq_file *seq, int start)
diff --git a/net/ipv4/ping_ipv4.c b/net/ipv4/ping_ipv4.c
new file mode 100644
index 000000000000..09fc1d85846d
--- /dev/null
+++ b/net/ipv4/ping_ipv4.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * "Ping" sockets
+ *
+ * IPv4 specific functions
+ *
+ * code split from:
+ * net/ipv4/ping.c
+ *
+ * See ping.c for author information
+ */
+
+#include <linux/bpf-cgroup.h>
+#include <linux/in.h>
+#include <linux/socket.h>
+#include <linux/types.h>
+#include <net/ping.h>
+#include <net/udp.h>
+
+static int ping_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
+ int addr_len)
+{
+ /* This check is replicated from __ip4_datagram_connect() and
+ * intended to prevent BPF program called below from accessing bytes
+ * that are out of the bound specified by user in addr_len.
+ */
+ if (addr_len < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ return BPF_CGROUP_RUN_PROG_INET4_CONNECT_LOCK(sk, uaddr, &addr_len);
+}
+
+static int ping_v4_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,
+ struct flowi4 *fl4)
+{
+ struct sk_buff *skb = skb_peek(&sk->sk_write_queue);
+
+ if (!skb)
+ return 0;
+ pfh->wcheck = csum_partial((char *)&pfh->icmph,
+ sizeof(struct icmphdr), pfh->wcheck);
+ pfh->icmph.checksum = csum_fold(pfh->wcheck);
+ memcpy(icmp_hdr(skb), &pfh->icmph, sizeof(struct icmphdr));
+ skb->ip_summed = CHECKSUM_NONE;
+ return ip_push_pending_frames(sk, fl4);
+}
+
+static int ping_v4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
+{
+ DEFINE_RAW_FLEX(struct ip_options_rcu, opt_copy, opt.__data,
+ IP_OPTIONS_DATA_FIXED_SIZE);
+ struct net *net = sock_net(sk);
+ struct flowi4 fl4;
+ struct inet_sock *inet = inet_sk(sk);
+ struct ipcm_cookie ipc;
+ struct icmphdr user_icmph;
+ struct pingfakehdr pfh;
+ struct rtable *rt = NULL;
+ int free = 0;
+ __be32 saddr, daddr, faddr;
+ u8 scope;
+ int err;
+
+ pr_debug("ping_v4_sendmsg(sk=%p,sk->num=%u)\n", inet, inet->inet_num);
+
+ err = ping_common_sendmsg(AF_INET, msg, len, &user_icmph,
+ sizeof(user_icmph));
+ if (err)
+ return err;
+
+ /*
+ * Get and verify the address.
+ */
+
+ if (msg->msg_name) {
+ DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
+ if (msg->msg_namelen < sizeof(*usin))
+ return -EINVAL;
+ if (usin->sin_family != AF_INET)
+ return -EAFNOSUPPORT;
+ daddr = usin->sin_addr.s_addr;
+ /* no remote port */
+ } else {
+ if (sk->sk_state != TCP_ESTABLISHED)
+ return -EDESTADDRREQ;
+ daddr = inet->inet_daddr;
+ /* no remote port */
+ }
+
+ ipcm_init_sk(&ipc, inet);
+
+ if (msg->msg_controllen) {
+ err = ip_cmsg_send(sk, msg, &ipc, false);
+ if (unlikely(err)) {
+ kfree(ipc.opt);
+ return err;
+ }
+ if (ipc.opt)
+ free = 1;
+ }
+ if (!ipc.opt) {
+ struct ip_options_rcu *inet_opt;
+
+ rcu_read_lock();
+ inet_opt = rcu_dereference(inet->inet_opt);
+ if (inet_opt) {
+ memcpy(opt_copy, inet_opt,
+ sizeof(*inet_opt) + inet_opt->opt.optlen);
+ ipc.opt = opt_copy;
+ }
+ rcu_read_unlock();
+ }
+
+ saddr = ipc.addr;
+ ipc.addr = faddr = daddr;
+
+ if (ipc.opt && ipc.opt->opt.srr) {
+ if (!daddr) {
+ err = -EINVAL;
+ goto out_free;
+ }
+ faddr = ipc.opt->opt.faddr;
+ }
+ scope = ip_sendmsg_scope(inet, &ipc, msg);
+
+ if (ipv4_is_multicast(daddr)) {
+ if (!ipc.oif || netif_index_is_l3_master(sock_net(sk), ipc.oif))
+ ipc.oif = READ_ONCE(inet->mc_index);
+ if (!saddr)
+ saddr = READ_ONCE(inet->mc_addr);
+ } else if (!ipc.oif)
+ ipc.oif = READ_ONCE(inet->uc_index);
+
+ flowi4_init_output(&fl4, ipc.oif, ipc.sockc.mark,
+ ipc.tos & INET_DSCP_MASK, scope,
+ sk->sk_protocol, inet_sk_flowi_flags(sk), faddr,
+ saddr, 0, 0, sk_uid(sk));
+
+ fl4.fl4_icmp_type = user_icmph.type;
+ fl4.fl4_icmp_code = user_icmph.code;
+
+ security_sk_classify_flow(sk, flowi4_to_flowi_common(&fl4));
+ rt = ip_route_output_flow(net, &fl4, sk);
+ if (IS_ERR(rt)) {
+ err = PTR_ERR(rt);
+ rt = NULL;
+ if (err == -ENETUNREACH)
+ IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
+ goto out;
+ }
+
+ err = -EACCES;
+ if ((rt->rt_flags & RTCF_BROADCAST) &&
+ !sock_flag(sk, SOCK_BROADCAST))
+ goto out;
+
+ if (msg->msg_flags & MSG_CONFIRM)
+ goto do_confirm;
+back_from_confirm:
+
+ if (!ipc.addr)
+ ipc.addr = fl4.daddr;
+
+ lock_sock(sk);
+
+ pfh.icmph.type = user_icmph.type; /* already checked */
+ pfh.icmph.code = user_icmph.code; /* ditto */
+ pfh.icmph.checksum = 0;
+ pfh.icmph.un.echo.id = inet->inet_sport;
+ pfh.icmph.un.echo.sequence = user_icmph.un.echo.sequence;
+ pfh.msg = msg;
+ pfh.wcheck = 0;
+ pfh.family = AF_INET;
+
+ err = ip_append_data(sk, &fl4, ping_getfrag, &pfh, len,
+ sizeof(struct icmphdr), &ipc, &rt,
+ msg->msg_flags);
+ if (err)
+ ip_flush_pending_frames(sk);
+ else
+ err = ping_v4_push_pending_frames(sk, &pfh, &fl4);
+ release_sock(sk);
+
+out:
+ ip_rt_put(rt);
+out_free:
+ if (free)
+ kfree(ipc.opt);
+ if (!err)
+ return len;
+ return err;
+
+do_confirm:
+ if (msg->msg_flags & MSG_PROBE)
+ dst_confirm_neigh(&rt->dst, &fl4.daddr);
+ if (!(msg->msg_flags & MSG_PROBE) || len)
+ goto back_from_confirm;
+ err = 0;
+ goto out;
+}
+
+struct proto ping_prot = {
+ .name = "PING",
+ .owner = THIS_MODULE,
+ .init = ping_init_sock,
+ .close = ping_close,
+ .pre_connect = ping_pre_connect,
+ .connect = ip4_datagram_connect,
+ .disconnect = __udp_disconnect,
+ .setsockopt = ip_setsockopt,
+ .getsockopt = ip_getsockopt,
+ .sendmsg = ping_v4_sendmsg,
+ .recvmsg = ping_recvmsg,
+ .bind = ping_bind,
+ .backlog_rcv = ping_queue_rcv_skb,
+ .release_cb = ip4_datagram_release_cb,
+ .unhash = ping_unhash,
+ .get_port = ping_get_port,
+ .put_port = ping_unhash,
+ .obj_size = sizeof(struct inet_sock),
+};
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 09/13 RFC net-next] net: fib: split common nexthop logic to fib_core.c
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (7 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 08/13 RFC net-next] net: ping: split IPv4 specific logic into ping_ipv4.c Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 10/13 RFC net-next] net: tunnel: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
` (3 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Alexei Starovoitov, Daniel Borkmann,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Kuniyuki Iwashima, Pavel Begunkov, Dragos Tatulea, Eric Biggers,
Ard Biesheuvel, Florian Westphal, Chia-Yu Chang,
Ilpo Järvinen, Yung Chih Su, Wyatt Feng, linux-kernel, bpf
To enable compiling INET subsystem without IPv4, shared FIB and nexthop
logic must be accessible to IPv6 when IPv4 routing stack is omitted.
This patch creates fib_core.c and move all the functions for common
nexthop initialization and release from fib_semantics.c to fib_core.c.
Note that fib_core.c is under net/core.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
include/net/ip_fib.h | 43 +++++++++++
include/net/route.h | 6 ++
net/core/Makefile | 2 +-
net/core/fib_core.c | 146 +++++++++++++++++++++++++++++++++++++
net/ipv4/Makefile | 2 +
net/ipv4/fib_semantics.c | 142 ------------------------------------
net/ipv4/netlink.c | 2 +
net/ipv4/nexthop.c | 2 +-
net/ipv4/sysctl_net_ipv4.c | 2 +
net/ipv6/ip6_fib.c | 2 +
10 files changed, 205 insertions(+), 144 deletions(-)
create mode 100644 net/core/fib_core.c
diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h
index 0a35355fb0f3..0c56d8690e00 100644
--- a/include/net/ip_fib.h
+++ b/include/net/ip_fib.h
@@ -250,7 +250,13 @@ int call_fib4_notifiers(struct net *net, enum fib_event_type event_type,
int __net_init fib4_notifier_init(struct net *net);
void __net_exit fib4_notifier_exit(struct net *net);
+#if IS_ENABLED(CONFIG_IPV4)
void fib_info_notify_update(struct net *net, struct nl_info *info);
+#else
+static inline void fib_info_notify_update(struct net *net, struct nl_info *info)
+{
+}
+#endif
int fib_notify(struct net *net, struct notifier_block *nb,
struct netlink_ext_ack *extack);
@@ -578,10 +584,23 @@ void fib_select_multipath(struct fib_result *res, int hash,
void fib_select_path(struct net *net, struct fib_result *res,
struct flowi4 *fl4, const struct sk_buff *skb);
+#if IS_ENABLED(CONFIG_IPV4)
int fib_nh_init(struct net *net, struct fib_nh *fib_nh,
struct fib_config *cfg, int nh_weight,
struct netlink_ext_ack *extack);
void fib_nh_release(struct net *net, struct fib_nh *fib_nh);
+#else
+static inline int fib_nh_init(struct net *net, struct fib_nh *fib_nh,
+ struct fib_config *cfg, int gfp,
+ struct netlink_ext_ack *extack)
+{
+ return -EAFNOSUPPORT;
+}
+
+static inline void fib_nh_release(struct net *net, struct fib_nh *fib_nh)
+{
+}
+#endif
int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc,
struct nlattr *fc_encap, u16 fc_encap_type,
void *cfg, gfp_t gfp_flags,
@@ -620,7 +639,13 @@ static inline void fib_combine_itag(u32 *itag, const struct fib_result *res)
#endif
}
+#if IS_ENABLED(CONFIG_IPV4)
void fib_flush(struct net *net);
+#else
+static inline void fib_flush(struct net *net)
+{
+}
+#endif
void free_fib_info(struct fib_info *fi);
static inline void fib_info_hold(struct fib_info *fi)
@@ -658,8 +683,26 @@ int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh,
struct fib_dump_filter *filter,
struct netlink_callback *cb);
+#if IS_ENABLED(CONFIG_IPV4)
int fib_nexthop_info(struct sk_buff *skb, const struct fib_nh_common *nh,
u8 rt_family, unsigned char *flags, bool skip_oif);
int fib_add_nexthop(struct sk_buff *skb, const struct fib_nh_common *nh,
int nh_weight, u8 rt_family, u32 nh_tclassid);
+#else
+static inline int fib_nexthop_info(struct sk_buff *skb,
+ const struct fib_nh_common *nh,
+ u8 rt_family, unsigned char *flags,
+ bool skip_oif)
+{
+ return 0;
+}
+
+static inline int fib_add_nexthop(struct sk_buff *skb,
+ const struct fib_nh_common *nh,
+ int nh_weight, u8 rt_family,
+ u32 nh_tclassid)
+{
+ return 0;
+}
+#endif
#endif /* _NET_FIB_H */
diff --git a/include/net/route.h b/include/net/route.h
index 08adb14224a9..29944be291f8 100644
--- a/include/net/route.h
+++ b/include/net/route.h
@@ -128,7 +128,13 @@ extern struct ip_rt_acct __percpu *ip_rt_acct;
struct in_device;
int ip_rt_init(void);
+#if IS_ENABLED(CONFIG_IPV4)
void rt_cache_flush(struct net *net);
+#else
+static inline void rt_cache_flush(struct net *net)
+{
+}
+#endif
void rt_flush_dev(struct net_device *dev);
static inline void inet_sk_init_flowi4(const struct inet_sock *inet,
diff --git a/net/core/Makefile b/net/core/Makefile
index b3fdcb4e355f..63ebcdc6b9f1 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -12,7 +12,7 @@ obj-$(CONFIG_SYSCTL) += sysctl_net_core.o
obj-y += dev.o dev_api.o dev_addr_lists.o dst.o netevent.o \
neighbour.o rtnetlink.o utils.o link_watch.o filter.o \
sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \
- fib_notifier.o xdp.o flow_offload.o gro.o \
+ fib_notifier.o fib_core.o xdp.o flow_offload.o gro.o \
netdev-genl.o netdev-genl-gen.o netdev_work.o gso.o
obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o
diff --git a/net/core/fib_core.c b/net/core/fib_core.c
new file mode 100644
index 000000000000..93894b5847e9
--- /dev/null
+++ b/net/core/fib_core.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/types.h>
+#include <net/lwtunnel.h>
+#include <net/route.h>
+
+static void rt_fibinfo_free(struct rtable __rcu **rtp)
+{
+ struct rtable *rt = rcu_dereference_protected(*rtp, 1);
+
+ if (!rt)
+ return;
+
+ /* Not even needed : RCU_INIT_POINTER(*rtp, NULL);
+ * because we waited an RCU grace period before calling
+ * free_fib_info_rcu()
+ */
+
+ dst_dev_put(&rt->dst);
+ dst_release_immediate(&rt->dst);
+}
+
+static void free_nh_exceptions(struct fib_nh_common *nhc)
+{
+ struct fnhe_hash_bucket *hash;
+ int i;
+
+ hash = rcu_dereference_protected(nhc->nhc_exceptions, 1);
+ if (!hash)
+ return;
+ for (i = 0; i < FNHE_HASH_SIZE; i++) {
+ struct fib_nh_exception *fnhe;
+
+ fnhe = rcu_dereference_protected(hash[i].chain, 1);
+ while (fnhe) {
+ struct fib_nh_exception *next;
+
+ next = rcu_dereference_protected(fnhe->fnhe_next, 1);
+
+ rt_fibinfo_free(&fnhe->fnhe_rth_input);
+ rt_fibinfo_free(&fnhe->fnhe_rth_output);
+
+ kfree(fnhe);
+
+ fnhe = next;
+ }
+ }
+ kfree(hash);
+}
+
+static void rt_fibinfo_free_cpus(struct rtable __rcu * __percpu *rtp)
+{
+ int cpu;
+
+ if (!rtp)
+ return;
+
+ for_each_possible_cpu(cpu) {
+ struct rtable *rt;
+
+ rt = rcu_dereference_protected(*per_cpu_ptr(rtp, cpu), 1);
+ if (rt) {
+ dst_dev_put(&rt->dst);
+ dst_release_immediate(&rt->dst);
+ }
+ }
+ free_percpu(rtp);
+}
+
+void fib_nh_common_release(struct fib_nh_common *nhc)
+{
+ netdev_put(nhc->nhc_dev, &nhc->nhc_dev_tracker);
+ lwtstate_put(nhc->nhc_lwtstate);
+ rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
+ rt_fibinfo_free(&nhc->nhc_rth_input);
+ free_nh_exceptions(nhc);
+}
+
+int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc,
+ struct nlattr *encap, u16 encap_type,
+ void *cfg, gfp_t gfp_flags,
+ struct netlink_ext_ack *extack)
+{
+ int err;
+
+ nhc->nhc_pcpu_rth_output = alloc_percpu_gfp(struct rtable __rcu *,
+ gfp_flags);
+ if (!nhc->nhc_pcpu_rth_output)
+ return -ENOMEM;
+
+ if (encap) {
+ struct lwtunnel_state *lwtstate;
+
+ err = lwtunnel_build_state(net, encap_type, encap,
+ nhc->nhc_family, cfg, &lwtstate,
+ extack);
+ if (err)
+ goto lwt_failure;
+
+ nhc->nhc_lwtstate = lwtstate_get(lwtstate);
+ }
+
+ return 0;
+
+lwt_failure:
+ rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
+ nhc->nhc_pcpu_rth_output = NULL;
+ return err;
+}
+
+/* Update the PMTU of exceptions when:
+ * - the new MTU of the first hop becomes smaller than the PMTU
+ * - the old MTU was the same as the PMTU, and it limited discovery of
+ * larger MTUs on the path. With that limit raised, we can now
+ * discover larger MTUs
+ * A special case is locked exceptions, for which the PMTU is smaller
+ * than the minimal accepted PMTU:
+ * - if the new MTU is greater than the PMTU, don't make any change
+ * - otherwise, unlock and set PMTU
+ */
+void fib_nhc_update_mtu(struct fib_nh_common *nhc, u32 new, u32 orig)
+{
+ struct fnhe_hash_bucket *bucket;
+ int i;
+
+ bucket = rcu_dereference_protected(nhc->nhc_exceptions, 1);
+ if (!bucket)
+ return;
+
+ for (i = 0; i < FNHE_HASH_SIZE; i++) {
+ struct fib_nh_exception *fnhe;
+
+ for (fnhe = rcu_dereference_protected(bucket[i].chain, 1);
+ fnhe;
+ fnhe = rcu_dereference_protected(fnhe->fnhe_next, 1)) {
+ if (fnhe->fnhe_mtu_locked) {
+ if (new <= fnhe->fnhe_pmtu) {
+ fnhe->fnhe_pmtu = new;
+ fnhe->fnhe_mtu_locked = false;
+ }
+ } else if (new < fnhe->fnhe_pmtu ||
+ orig == fnhe->fnhe_pmtu) {
+ fnhe->fnhe_pmtu = new;
+ }
+ }
+ }
+}
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 83c25f52eb58..b540d549f25a 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -19,7 +19,9 @@ obj-$(CONFIG_IPV4) += route.o ip_input.o ip_fragment.o ip_forward.o ip_options.o
obj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o
obj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o
obj-$(CONFIG_PROC_FS) += proc.o
+ifeq ($(CONFIG_IPV4),y)
obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o
+endif
obj-$(CONFIG_IP_MROUTE) += ipmr.o
obj-$(CONFIG_IP_MROUTE_COMMON) += ipmr_base.o
obj-$(CONFIG_NET_IPIP) += ipip.o
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
index 4f3c0740dde9..6ed252e9559e 100644
--- a/net/ipv4/fib_semantics.c
+++ b/net/ipv4/fib_semantics.c
@@ -136,78 +136,6 @@ const struct fib_prop fib_props[RTN_MAX + 1] = {
},
};
-static void rt_fibinfo_free(struct rtable __rcu **rtp)
-{
- struct rtable *rt = rcu_dereference_protected(*rtp, 1);
-
- if (!rt)
- return;
-
- /* Not even needed : RCU_INIT_POINTER(*rtp, NULL);
- * because we waited an RCU grace period before calling
- * free_fib_info_rcu()
- */
-
- dst_dev_put(&rt->dst);
- dst_release_immediate(&rt->dst);
-}
-
-static void free_nh_exceptions(struct fib_nh_common *nhc)
-{
- struct fnhe_hash_bucket *hash;
- int i;
-
- hash = rcu_dereference_protected(nhc->nhc_exceptions, 1);
- if (!hash)
- return;
- for (i = 0; i < FNHE_HASH_SIZE; i++) {
- struct fib_nh_exception *fnhe;
-
- fnhe = rcu_dereference_protected(hash[i].chain, 1);
- while (fnhe) {
- struct fib_nh_exception *next;
-
- next = rcu_dereference_protected(fnhe->fnhe_next, 1);
-
- rt_fibinfo_free(&fnhe->fnhe_rth_input);
- rt_fibinfo_free(&fnhe->fnhe_rth_output);
-
- kfree(fnhe);
-
- fnhe = next;
- }
- }
- kfree(hash);
-}
-
-static void rt_fibinfo_free_cpus(struct rtable __rcu * __percpu *rtp)
-{
- int cpu;
-
- if (!rtp)
- return;
-
- for_each_possible_cpu(cpu) {
- struct rtable *rt;
-
- rt = rcu_dereference_protected(*per_cpu_ptr(rtp, cpu), 1);
- if (rt) {
- dst_dev_put(&rt->dst);
- dst_release_immediate(&rt->dst);
- }
- }
- free_percpu(rtp);
-}
-
-void fib_nh_common_release(struct fib_nh_common *nhc)
-{
- netdev_put(nhc->nhc_dev, &nhc->nhc_dev_tracker);
- lwtstate_put(nhc->nhc_lwtstate);
- rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
- rt_fibinfo_free(&nhc->nhc_rth_input);
- free_nh_exceptions(nhc);
-}
-
void fib_nh_release(struct net *net, struct fib_nh *fib_nh)
{
#ifdef CONFIG_IP_ROUTE_CLASSID
@@ -607,38 +535,6 @@ static int fib_detect_death(struct fib_info *fi, int order,
return 1;
}
-int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc,
- struct nlattr *encap, u16 encap_type,
- void *cfg, gfp_t gfp_flags,
- struct netlink_ext_ack *extack)
-{
- int err;
-
- nhc->nhc_pcpu_rth_output = alloc_percpu_gfp(struct rtable __rcu *,
- gfp_flags);
- if (!nhc->nhc_pcpu_rth_output)
- return -ENOMEM;
-
- if (encap) {
- struct lwtunnel_state *lwtstate;
-
- err = lwtunnel_build_state(net, encap_type, encap,
- nhc->nhc_family, cfg, &lwtstate,
- extack);
- if (err)
- goto lwt_failure;
-
- nhc->nhc_lwtstate = lwtstate_get(lwtstate);
- }
-
- return 0;
-
-lwt_failure:
- rt_fibinfo_free_cpus(nhc->nhc_pcpu_rth_output);
- nhc->nhc_pcpu_rth_output = NULL;
- return err;
-}
-
int fib_nh_init(struct net *net, struct fib_nh *nh,
struct fib_config *cfg, int nh_weight,
struct netlink_ext_ack *extack)
@@ -1864,44 +1760,6 @@ static int call_fib_nh_notifiers(struct fib_nh *nh,
return NOTIFY_DONE;
}
-/* Update the PMTU of exceptions when:
- * - the new MTU of the first hop becomes smaller than the PMTU
- * - the old MTU was the same as the PMTU, and it limited discovery of
- * larger MTUs on the path. With that limit raised, we can now
- * discover larger MTUs
- * A special case is locked exceptions, for which the PMTU is smaller
- * than the minimal accepted PMTU:
- * - if the new MTU is greater than the PMTU, don't make any change
- * - otherwise, unlock and set PMTU
- */
-void fib_nhc_update_mtu(struct fib_nh_common *nhc, u32 new, u32 orig)
-{
- struct fnhe_hash_bucket *bucket;
- int i;
-
- bucket = rcu_dereference_protected(nhc->nhc_exceptions, 1);
- if (!bucket)
- return;
-
- for (i = 0; i < FNHE_HASH_SIZE; i++) {
- struct fib_nh_exception *fnhe;
-
- for (fnhe = rcu_dereference_protected(bucket[i].chain, 1);
- fnhe;
- fnhe = rcu_dereference_protected(fnhe->fnhe_next, 1)) {
- if (fnhe->fnhe_mtu_locked) {
- if (new <= fnhe->fnhe_pmtu) {
- fnhe->fnhe_pmtu = new;
- fnhe->fnhe_mtu_locked = false;
- }
- } else if (new < fnhe->fnhe_pmtu ||
- orig == fnhe->fnhe_pmtu) {
- fnhe->fnhe_pmtu = new;
- }
- }
- }
-}
-
void fib_sync_mtu(struct net_device *dev, u32 orig_mtu)
{
struct hlist_head *head = fib_nh_head(dev);
diff --git a/net/ipv4/netlink.c b/net/ipv4/netlink.c
index b920e1bdcf58..57d4dca16d3e 100644
--- a/net/ipv4/netlink.c
+++ b/net/ipv4/netlink.c
@@ -16,10 +16,12 @@ int rtm_getroute_parse_ip_proto(struct nlattr *attr, u8 *ip_proto, u8 family,
case IPPROTO_TCP:
case IPPROTO_UDP:
return 0;
+#if IS_ENABLED(CONFIG_IPV4)
case IPPROTO_ICMP:
if (family != AF_INET)
break;
return 0;
+#endif
#if IS_ENABLED(CONFIG_IPV6)
case IPPROTO_ICMPV6:
if (family != AF_INET6)
diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c
index 6205bd57aa85..b113a31f6150 100644
--- a/net/ipv4/nexthop.c
+++ b/net/ipv4/nexthop.c
@@ -1414,7 +1414,7 @@ static bool nexthop_is_good_nh(const struct nexthop *nh)
switch (nhi->family) {
case AF_INET:
- return ipv4_good_nh(&nhi->fib_nh);
+ return IS_ENABLED(CONFIG_IPV4) && ipv4_good_nh(&nhi->fib_nh);
case AF_INET6:
return IS_ENABLED(CONFIG_IPV6) && ipv6_good_nh(&nhi->fib6_nh);
}
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index ca1180dba1de..9c343c9566b7 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -613,6 +613,7 @@ static struct ctl_table ipv4_table[] = {
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
},
+#if IS_ENABLED(CONFIG_IPV4)
{
.procname = "fib_sync_mem",
.data = &sysctl_fib_sync_mem,
@@ -622,6 +623,7 @@ static struct ctl_table ipv4_table[] = {
.extra1 = &sysctl_fib_sync_mem_min,
.extra2 = &sysctl_fib_sync_mem_max,
},
+#endif
};
static struct ctl_table ipv4_net_table[] = {
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index a130cdfaebfb..8b5f855c8dcb 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -645,9 +645,11 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
rcu_read_lock();
if (cb->strict_check) {
+#if IS_ENABLED(CONFIG_IPV4)
err = ip_valid_fib_dump_req(net, nlh, &arg.filter, cb);
if (err < 0)
goto unlock;
+#endif
} else if (nlmsg_len(nlh) >= sizeof(struct rtmsg)) {
struct rtmsg *rtm = nlmsg_data(nlh);
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 10/13 RFC net-next] net: tunnel: guard IPv4 tunnel functions with CONFIG_IPV4
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (8 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 09/13 RFC net-next] net: fib: split common nexthop logic to fib_core.c Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 11/13 RFC net-next] net: cipso: guard IPv4 packet manipulation functions Fernando Fernandez Mancera
` (2 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, linux-kernel
To enable compiling INET subsystem withou IPv4, IPv4 specific IP tunnel
core functions must be bypassed when the protocol is disabled.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
net/ipv4/ip_tunnel_core.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c
index d3c677e9bff2..700a759d3c46 100644
--- a/net/ipv4/ip_tunnel_core.c
+++ b/net/ipv4/ip_tunnel_core.c
@@ -52,6 +52,7 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb,
__u8 tos, __u8 ttl, __be16 df, bool xnet,
u16 ipcb_flags)
{
+#if IS_ENABLED(CONFIG_IPV4)
int pkt_len = skb->len - skb_inner_network_offset(skb);
struct net *net = dev_net(rt->dst.dev);
struct net_device *dev = skb->dev;
@@ -103,6 +104,12 @@ void iptunnel_xmit(struct sock *sk, struct rtable *rt, struct sk_buff *skb,
}
dev_xmit_recursion_dec();
+#else
+ if (skb->dev)
+ DEV_STATS_INC(skb->dev, tx_errors);
+ ip_rt_put(rt);
+ kfree_skb(skb);
+#endif
}
EXPORT_SYMBOL_GPL(iptunnel_xmit);
@@ -212,6 +219,7 @@ EXPORT_SYMBOL_GPL(iptunnel_handle_offloads);
*/
static int iptunnel_pmtud_build_icmp(struct sk_buff *skb, int mtu)
{
+#if IS_ENABLED(CONFIG_IPV4)
const struct iphdr *iph;
struct icmphdr *icmph;
struct iphdr *niph;
@@ -269,6 +277,9 @@ static int iptunnel_pmtud_build_icmp(struct sk_buff *skb, int mtu)
skb_reset_mac_header(skb);
return skb->len;
+#else
+ return -EOPNOTSUPP;
+#endif
}
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 11/13 RFC net-next] net: cipso: guard IPv4 packet manipulation functions
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (9 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 10/13 RFC net-next] net: tunnel: guard IPv4 tunnel functions with CONFIG_IPV4 Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 16:22 ` Paul Moore
2026-07-12 1:39 ` [PATCH 12/13 RFC net-next] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 13/13 RFC net-next] net: ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
12 siblings, 1 reply; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Paul Moore, linux-security-module,
linux-kernel
To enable compiling the network stack without IPv4, the CIPSO functions
that manipulate IPv4 options and generate ICMP errors must be bypassed.
Ideally, CIPSO should not be compiled when IPv4 is disabled but
currently it is too integrated within netlabel, so let's just bypassed
the relevant functions.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
net/ipv4/cipso_ipv4.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/net/ipv4/cipso_ipv4.c b/net/ipv4/cipso_ipv4.c
index a05aa075de1a..17bb723299d7 100644
--- a/net/ipv4/cipso_ipv4.c
+++ b/net/ipv4/cipso_ipv4.c
@@ -1714,6 +1714,7 @@ int cipso_v4_validate(const struct sk_buff *skb, unsigned char **option)
*/
void cipso_v4_error(struct sk_buff *skb, int error, u32 gateway)
{
+#if IS_ENABLED(CONFIG_IPV4)
struct inet_skb_parm parm;
int res;
@@ -1738,6 +1739,7 @@ void cipso_v4_error(struct sk_buff *skb, int error, u32 gateway)
__icmp_send(skb, ICMP_DEST_UNREACH, ICMP_NET_ANO, 0, &parm);
else
__icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_ANO, 0, &parm);
+#endif
}
/**
@@ -2171,6 +2173,7 @@ int cipso_v4_skbuff_setattr(struct sk_buff *skb,
const struct cipso_v4_doi *doi_def,
const struct netlbl_lsm_secattr *secattr)
{
+#if IS_ENABLED(CONFIG_IPV4)
int ret_val;
struct iphdr *iph;
struct ip_options *opt = &IPCB(skb)->opt;
@@ -2235,6 +2238,9 @@ int cipso_v4_skbuff_setattr(struct sk_buff *skb,
ip_send_check(iph);
return 0;
+#else
+ return -EOPNOTSUPP;
+#endif
}
/**
@@ -2248,6 +2254,7 @@ int cipso_v4_skbuff_setattr(struct sk_buff *skb,
*/
int cipso_v4_skbuff_delattr(struct sk_buff *skb)
{
+#if IS_ENABLED(CONFIG_IPV4)
int ret_val, cipso_len, hdr_len_actual, new_hdr_len_actual, new_hdr_len,
hdr_len_delta;
struct iphdr *iph;
@@ -2296,6 +2303,9 @@ int cipso_v4_skbuff_delattr(struct sk_buff *skb)
ip_send_check(iph);
return 0;
+#else
+ return -EOPNOTSUPP;
+#endif
}
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH 11/13 RFC net-next] net: cipso: guard IPv4 packet manipulation functions
2026-07-12 1:39 ` [PATCH 11/13 RFC net-next] net: cipso: guard IPv4 packet manipulation functions Fernando Fernandez Mancera
@ 2026-07-12 16:22 ` Paul Moore
0 siblings, 0 replies; 16+ messages in thread
From: Paul Moore @ 2026-07-12 16:22 UTC (permalink / raw)
To: Fernando Fernandez Mancera
Cc: netdev, davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
linux-security-module, linux-kernel
On Sat, Jul 11, 2026 at 9:41 PM Fernando Fernandez Mancera
<fmancera@suse.de> wrote:
>
> To enable compiling the network stack without IPv4, the CIPSO functions
> that manipulate IPv4 options and generate ICMP errors must be bypassed.
>
> Ideally, CIPSO should not be compiled when IPv4 is disabled but
> currently it is too integrated within netlabel, so let's just bypassed
> the relevant functions.
>
> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
> ---
> net/ipv4/cipso_ipv4.c | 10 ++++++++++
> 1 file changed, 10 insertions(+)
I think I would prefer to make CONFIG_NETLABEL dependent on
CONFIG_IPV4 at this point in time. This will keep the code cleaner
and allow time to do the proper work of wrapping the CIPSO code with
CONFIG_CIPSO (or similar) and making that dependent on CONFIG_IPV4.
--
paul-moore.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 12/13 RFC net-next] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (10 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 11/13 RFC net-next] net: cipso: guard IPv4 packet manipulation functions Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
2026-07-12 1:39 ` [PATCH 13/13 RFC net-next] net: ipv4: make CONFIG_IPV4 boolean Fernando Fernandez Mancera
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, netfilter-devel, coreteam, linux-kernel
To enable compiling the network stack without IPv4, IPv4 specific packet
rerouting logic inside netfilter must be bypassed. Therefore, guard
ip_route_me_harder() with IS_ENABLED(CONFIG_IPV4) so it returns
-EPROTONOSUPPORT when IPv4 is disabled.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
net/ipv4/netfilter.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/net/ipv4/netfilter.c b/net/ipv4/netfilter.c
index ce9e1bfa4259..9576d91bb6a6 100644
--- a/net/ipv4/netfilter.c
+++ b/net/ipv4/netfilter.c
@@ -21,6 +21,7 @@
/* route_me_harder function, used by iptable_nat, iptable_mangle + ip_queue */
int ip_route_me_harder(struct net *net, struct sock *sk, struct sk_buff *skb, unsigned int addr_type)
{
+#if IS_ENABLED(CONFIG_IPV4)
struct net_device *dev = skb_dst_dev(skb);
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt;
@@ -85,6 +86,9 @@ int ip_route_me_harder(struct net *net, struct sock *sk, struct sk_buff *skb, un
return -ENOMEM;
return 0;
+#else
+ return -EPROTONOSUPPORT;
+#endif
}
EXPORT_SYMBOL(ip_route_me_harder);
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH 13/13 RFC net-next] net: ipv4: make CONFIG_IPV4 boolean
2026-07-12 1:38 [PATCH 00/13 RFC net-next] Allow compiling an IPv6-only kernel network stack Fernando Fernandez Mancera
` (11 preceding siblings ...)
2026-07-12 1:39 ` [PATCH 12/13 RFC net-next] netfilter: ipv4: guard ip_route_me_harder() with CONFIG_IPV4 Fernando Fernandez Mancera
@ 2026-07-12 1:39 ` Fernando Fernandez Mancera
12 siblings, 0 replies; 16+ messages in thread
From: Fernando Fernandez Mancera @ 2026-07-12 1:39 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, dsahern, horms, idosch,
Fernando Fernandez Mancera, Eric Biggers, Ard Biesheuvel,
Dmitry Safonov, linux-kernel
Change CONFIG_IPV4 from hidden 'def_bool y' into an exposed boolean.
This allows users to explicitly disable IPv4 via Kconfig to build
IPv6-only or pure L2 kernel network stack. The default is kept at 'y' to
prevent breaking existing configurations.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
net/ipv4/Kconfig | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index 51bf05d9770c..fa30cc407223 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -3,7 +3,15 @@
# IP configuration
#
config IPV4
- def_bool y
+ bool "The IPv4 protocol"
+ default y
+ help
+ Support for IP version 4 (IPv4).
+
+ Disabling this option compiles the kernel networking stack without
+ IPv4 support, resulting in a IPv6-only or pure L2 system.
+
+ If unsure, say Y.
config IP_MULTICAST
bool "IP: multicasting"
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread