From 758d8928d2c5d6703da827577cef86d643155236 Mon Sep 17 00:00:00 2001 From: Anil Kaushik Date: Fri, 16 Oct 2026 09:32:22 +0000 Subject: [PATCH 1/4] gtp: set a valid genl command in PDP context get and dump replies gtp_genl_fill_info() stamps the reply with the command its callers pass in. gtp_genl_get_pdp() and gtp_genl_dump_pdp() pass nlmsg_type, which in a genl message is the dynamically assigned family id rather than a GTP_CMD_* value, so replies to a PDP context get or dump carry a meaningless command number. libgtpnl ignores the command and is unaffected, but parsers that check it (for example the ynl tooling) treat the reply as an unknown command and discard it. Pass GTP_CMD_GETPDP, as gtp_tunnel_notify() already passes a real command on the notification path. No uapi change. Fixes: 459aa660eb1d ("gtp: add initial driver for datapath of GPRS Tunneling Protocol (GTP-U)") Signed-off-by: Anil Kaushik Link: https://patch.msgid.link/20261016093225.1036375-2-anilkaushikwireless@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/gtp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 4aff23bcfdd1..99836b773e5c 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -2279,7 +2279,7 @@ static int gtp_genl_get_pdp(struct sk_buff *skb, struct genl_info *info) } err = gtp_genl_fill_info(skb2, NETLINK_CB(skb).portid, info->snd_seq, - 0, info->nlhdr->nlmsg_type, pctx); + 0, GTP_CMD_GETPDP, pctx); if (err < 0) goto err_unlock_free; @@ -2326,7 +2326,7 @@ static int gtp_genl_dump_pdp(struct sk_buff *skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, NLM_F_MULTI, - cb->nlh->nlmsg_type, pctx)) { + GTP_CMD_GETPDP, pctx)) { cb->args[0] = i; cb->args[1] = j; cb->args[2] = (unsigned long)gtp; From fe3e1a42fb7eb18d8c55d714b50a3d51f8cd6ffe Mon Sep 17 00:00:00 2001 From: Anil Kaushik Date: Fri, 16 Oct 2026 09:32:23 +0000 Subject: [PATCH 2/4] gtp: hold RCU for the device lookup when adding a PDP context gtp_genl_new_pdp() calls gtp_find_dev(), which looks the device up with dev_get_by_index_rcu(). Since commit e2d1baca2b33 ("gtp: remove useless rcu_read_lock()"), this only runs under the rtnl lock, and kernels with CONFIG_PROVE_RCU_LIST warn: WARNING: suspicious RCU usage net/core/dev.c:1047 RCU-list traversed in non-reader section!! ... dev_get_by_index_rcu gtp_find_dev gtp_genl_new_pdp Hold the RCU read lock around the lookup only: gtp_pdp_add() allocates with GFP_KERNEL, and the rtnl lock keeps the device alive afterwards. Found with the gtp selftest on a debug kernel. Fixes: e2d1baca2b33 ("gtp: remove useless rcu_read_lock()") Signed-off-by: Anil Kaushik Link: https://patch.msgid.link/20261016093225.1036375-3-anilkaushikwireless@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/gtp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 99836b773e5c..15b5d845fc3b 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -2050,7 +2050,10 @@ static int gtp_genl_new_pdp(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); + /* the rtnl lock keeps the device alive once found */ + rcu_read_lock(); gtp = gtp_find_dev(sock_net(skb->sk), info->attrs); + rcu_read_unlock(); if (!gtp) { err = -ENODEV; goto out_unlock; From 8a2f9964fc28954cef5a39e3636690d83cff3951 Mon Sep 17 00:00:00 2001 From: Anil Kaushik Date: Fri, 16 Oct 2026 09:32:24 +0000 Subject: [PATCH 3/4] netlink: specs: add genetlink-legacy spec for GTP The GTP (GPRS Tunnelling Protocol, user plane) generic netlink family has no YAML specification under Documentation/netlink/specs/, so it cannot be consumed by the ynl tooling used for user-space clients, documentation and selftests. Add a genetlink-legacy spec describing the existing family: the PDP context management commands (NEWPDP, DELPDP, GETPDP) and the GTP-U echo request (ECHOREQ), the GTPA_* attribute set, and the "gtp" multicast group. Command and attribute values are taken from include/uapi/linux/gtp.h and match it one-to-one. The request attributes of each command are the ones its handler in drivers/net/gtp.c reads, and the reply attributes the ones gtp_genl_fill_info() emits. The notifications sent on the "gtp" group are not described: they reuse the command values of the NEWPDP, DELPDP and ECHOREQ requests, which the ynl code generator cannot express with the existing uapi header. The family doc says so. Add the matching CFLAGS_gtp entry to tools/net/ynl/Makefile.deps, so the generated code is built against the tree's gtp.h instead of an older one installed on the host. This only adds the description; there is no kernel code or uapi change. Signed-off-by: Anil Kaushik Link: https://patch.msgid.link/20261016093225.1036375-4-anilkaushikwireless@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/gtp.yaml | 196 +++++++++++++++++++++++++++ MAINTAINERS | 1 + tools/net/ynl/Makefile.deps | 1 + 3 files changed, 198 insertions(+) create mode 100644 Documentation/netlink/specs/gtp.yaml diff --git a/Documentation/netlink/specs/gtp.yaml b/Documentation/netlink/specs/gtp.yaml new file mode 100644 index 000000000000..7871f5def054 --- /dev/null +++ b/Documentation/netlink/specs/gtp.yaml @@ -0,0 +1,196 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +--- +name: gtp + +protocol: genetlink-legacy + +doc: | + GPRS Tunnelling Protocol, user plane (GTP-U). + + The gtp netdevice encapsulates and decapsulates user plane packets in + GTP-U tunnels (GTPv0 and GTPv1-U, see 3GPP TS 29.060 and TS 29.281). + This family manages the PDP contexts that describe the tunnels and + triggers GTP-U echo requests. It is driven by user space control planes + such as those built on libgtpnl. + + The kernel registers the family with version 0, which the + genetlink-legacy schema cannot express. + + The notifications multicast on the "gtp" group are not described: PDP + context additions and deletions, and GTP-U echo responses, reuse the + NEWPDP, DELPDP and ECHOREQ command values of the requests. + +kernel-policy: global + +definitions: + - + type: enum + name: version + name-prefix: gtp- + enum-name: gtp-version + header: linux/gtp.h + entries: [v0, v1] + +attribute-sets: + - + name: gtp + name-prefix: gtpa- + attributes: + - + name: link + type: u32 + doc: ifindex of the gtp netdevice the context is attached to. + - + name: version + type: u32 + enum: version + doc: GTP version of the context, GTPv0 or GTPv1-U. + - + name: tid + type: u64 + doc: Tunnel identifier, GTPv0 only. + - + name: peer-address + type: u32 + byte-order: big-endian + display-hint: ipv4 + doc: | + IPv4 address of the remote GSN peer (GGSN or SGSN). Also known + as GTPA_SGSN_ADDRESS, kept for legacy user space. + - + name: ms-address + type: u32 + byte-order: big-endian + display-hint: ipv4 + doc: IPv4 address of the mobile subscriber served by the context. + - + name: flow + type: u16 + doc: Flow label, GTPv0 only. + - + name: net-ns-fd + type: u32 + doc: File descriptor of the network namespace of the gtp netdevice. + - + name: i-tei + type: u32 + doc: Ingress Tunnel Endpoint Identifier, GTPv1-U only. + - + name: o-tei + type: u32 + doc: Egress Tunnel Endpoint Identifier, GTPv1-U only. + - + name: pad + type: pad + - + name: peer-addr6 + type: binary + checks: + min-len: 16 + byte-order: big-endian + display-hint: ipv6 + doc: IPv6 address of the remote GSN peer (GGSN or SGSN). + - + name: ms-addr6 + type: binary + checks: + min-len: 16 + byte-order: big-endian + display-hint: ipv6 + doc: | + IPv6 prefix of the mobile subscriber served by the context: a /64 + prefix, with the lower 64 bits set to zero. + - + name: family + type: u8 + doc: | + Address family (AF_INET or AF_INET6) of ms-address or ms-addr6, + AF_INET when absent. The peer address uses the family of the + tunnel socket. + +operations: + list: + - + name: newpdp + doc: Create or update a PDP context. + attribute-set: gtp + value: 0 + dont-validate: [strict, dump] + flags: [admin-perm] + do: + request: &pdp-attrs + attributes: + - link + - version + - tid + - peer-address + - peer-addr6 + - ms-address + - ms-addr6 + - flow + - i-tei + - o-tei + - family + - net-ns-fd + - + name: delpdp + doc: Delete a PDP context. + attribute-set: gtp + dont-validate: [strict, dump] + flags: [admin-perm] + do: + request: &pdp-find + attributes: + - link + - version + - tid + - ms-address + - ms-addr6 + - i-tei + - family + - net-ns-fd + - + name: getpdp + doc: Get or dump one or more PDP contexts. + attribute-set: gtp + dont-validate: [strict, dump] + flags: [admin-perm] + do: + request: *pdp-find + reply: &pdp-reply + attributes: + - link + - version + - tid + - peer-address + - peer-addr6 + - ms-address + - ms-addr6 + - flow + - i-tei + - o-tei + - family + dump: + reply: *pdp-reply + - + name: echoreq + doc: | + Send a GTP-U echo request to a peer. ms-address is not a mobile + subscriber address here, but the local IPv4 address used as source + of the request. + attribute-set: gtp + dont-validate: [strict, dump] + flags: [admin-perm] + do: + request: + attributes: + - link + - version + - peer-address + - ms-address + - net-ns-fd + +mcast-groups: + list: + - + name: gtp diff --git a/MAINTAINERS b/MAINTAINERS index 2b07bf0fd980..eca83c7da218 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11422,6 +11422,7 @@ M: Harald Welte L: osmocom-net-gprs@lists.osmocom.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pablo/gtp.git +F: Documentation/netlink/specs/gtp.yaml F: drivers/net/gtp.c GUID PARTITION TABLE (GPT) diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps index 9389f3752c00..f8850444d240 100644 --- a/tools/net/ynl/Makefile.deps +++ b/tools/net/ynl/Makefile.deps @@ -24,6 +24,7 @@ CFLAGS_ethtool:=$(call get_hdr_inc,_LINUX_TYPELIMITS_H,typelimits.h) \ $(call get_hdr_inc,_LINUX_ETHTOOL_H,ethtool.h) \ $(call get_hdr_inc,_LINUX_ETHTOOL_NETLINK_H_,ethtool_netlink.h) \ $(call get_hdr_inc,_LINUX_ETHTOOL_NETLINK_GENERATED_H,ethtool_netlink_generated.h) +CFLAGS_gtp:=$(call get_hdr_inc,_LINUX_GTP_H_,gtp.h) CFLAGS_handshake:=$(call get_hdr_inc,_LINUX_HANDSHAKE_H,handshake.h) CFLAGS_lockd_netlink:=$(call get_hdr_inc,_LINUX_LOCKD_NETLINK_H,lockd_netlink.h) CFLAGS_mptcp_pm:=$(call get_hdr_inc,_LINUX_MPTCP_PM_H,mptcp_pm.h) From ffb51f75013f8d572cc3aa942425e0fa0a8e6d09 Mon Sep 17 00:00:00 2001 From: Anil Kaushik Date: Fri, 16 Oct 2026 09:32:25 +0000 Subject: [PATCH 4/4] selftests: net: add a test for the gtp netlink family Exercise the gtp generic netlink family through its ynl spec: create a gtp device, add a PDP context with newpdp, check getpdp dumps it on that device with the expected TEIDs and addresses, and that delpdp by TEI removes it. Two more cases check that two contexts are dumped and one can be deleted by its MS address, and that deleting an unknown context fails with ENOENT, both by TEI and by MS address. ynl resolves the family ids once per process, with a CTRL_CMD_GETFAMILY dump which does not autoload modules, so the gtp device is created before the family handle: that loads gtp.ko through its rtnl link alias. The test is skipped when the kernel or iproute2 lack GTP support; any other failure to create the device fails it. Add a GtpFamily ynl wrapper and register the test. Signed-off-by: Anil Kaushik Link: https://patch.msgid.link/20261016093225.1036375-5-anilkaushikwireless@gmail.com Signed-off-by: Paolo Abeni --- MAINTAINERS | 1 + tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/config | 1 + tools/testing/selftests/net/gtp.py | 123 ++++++++++++++++++ .../testing/selftests/net/lib/py/__init__.py | 5 +- tools/testing/selftests/net/lib/py/ynl.py | 9 +- 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100755 tools/testing/selftests/net/gtp.py diff --git a/MAINTAINERS b/MAINTAINERS index eca83c7da218..83ff18370ece 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11424,6 +11424,7 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pablo/gtp.git F: Documentation/netlink/specs/gtp.yaml F: drivers/net/gtp.c +F: tools/testing/selftests/net/gtp.py GUID PARTITION TABLE (GPT) M: Davidlohr Bueso diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 54beea2e348c..df5bcc55ea8e 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -46,6 +46,7 @@ TEST_PROGS := \ fq_band_pktlimit.sh \ gre_gso.sh \ gre_ipv6_lladdr.sh \ + gtp.py \ icmp.sh \ icmp_redirect.sh \ io_uring_zerocopy_tx.sh \ diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index d355cf980597..cb8cb4696df7 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -16,6 +16,7 @@ CONFIG_DEBUG_INFO_BTF=y CONFIG_DEBUG_INFO_BTF_MODULES=n CONFIG_DUMMY=y CONFIG_GENEVE=m +CONFIG_GTP=m CONFIG_IFB=y CONFIG_INET_DIAG=y CONFIG_INET_ESP=y diff --git a/tools/testing/selftests/net/gtp.py b/tools/testing/selftests/net/gtp.py new file mode 100755 index 000000000000..d793ca7b70d2 --- /dev/null +++ b/tools/testing/selftests/net/gtp.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +"""Tests for the gtp netlink family.""" + +import errno + +from lib.py import ksft_run, ksft_exit +from lib.py import ksft_eq, ksft_in, ksft_not_in, ksft_raises +from lib.py import KsftSkipEx +from lib.py import NetNS, NetNSEnter +from lib.py import GtpFamily, NlError +from lib.py import CmdExitFailure +from lib.py import ip, defer + +_NO_GTP_ERRORS = ("Unknown device type", "Operation not supported", + "Garbage instead of arguments") + + +def _setup(ns): + """Return a gtp family handle, and the ifindex of a new gtp device. + + Create the device first: that loads gtp.ko through its rtnl link + alias. ynl resolves the family ids once per process, with a + CTRL_CMD_GETFAMILY dump which does not autoload modules, so the + family must be registered before the first family handle is created. + """ + try: + ip("link add gtp0 type gtp role ggsn", ns=str(ns)) + except CmdExitFailure as e: + # gtp.ko not available, or iproute2 without gtp support: any other + # failure to create the device is a real error. + if not any(msg in e.cmd.stderr for msg in _NO_GTP_ERRORS): + raise + raise KsftSkipEx("no gtp support (CONFIG_GTP, iproute2)") from e + defer(ip, "link del gtp0", ns=str(ns)) + ip("link set gtp0 up", ns=str(ns)) + link = ip("-d link show gtp0", ns=str(ns), json=True)[0]["ifindex"] + + with NetNSEnter(str(ns)): + gtp = GtpFamily() + defer(gtp.close) + + return gtp, link + + +def _contexts(gtp, link): + return [c for c in gtp.getpdp({}, dump=True) if c.get("link") == link] + + +def _iteis(contexts): + return [c.get("i-tei") for c in contexts] + + +def add_del_pdp(ns) -> None: + """Add a PDP context, query it, then delete it by TEI. + + The dump must return the context with the link, TEIDs and addresses + it was created with, and it must be gone after the delete. The delete + only identifies the context by its TEI, to use the GTPv1 TEI lookup. + """ + gtp, link = _setup(ns) + + gtp.newpdp({"link": link, "version": 1, + "i-tei": 0x100, "o-tei": 0x200, + "ms-address": "10.0.0.1", "peer-address": "192.0.2.1"}) + + pdp = [c for c in _contexts(gtp, link) if c.get("i-tei") == 0x100] + ksft_eq(len(pdp), 1) + ksft_eq(pdp[0]["o-tei"], 0x200) + ksft_eq(pdp[0]["ms-address"], "10.0.0.1") + ksft_eq(pdp[0]["peer-address"], "192.0.2.1") + + gtp.delpdp({"link": link, "version": 1, "i-tei": 0x100}) + ksft_not_in(0x100, _iteis(_contexts(gtp, link))) + + +def add_two_pdp(ns) -> None: + """Add two PDP contexts, check both are dumped, delete one by address. + + The delete identifies the context by its MS address, and must leave + the other context in place. + """ + gtp, link = _setup(ns) + + for i_tei, o_tei, ms in ((0x111, 0x211, "10.0.1.1"), + (0x112, 0x212, "10.0.1.2")): + gtp.newpdp({"link": link, "version": 1, + "i-tei": i_tei, "o-tei": o_tei, + "ms-address": ms, "peer-address": "192.0.2.5"}) + + iteis = _iteis(_contexts(gtp, link)) + ksft_in(0x111, iteis) + ksft_in(0x112, iteis) + + gtp.delpdp({"link": link, "version": 1, "ms-address": "10.0.1.1"}) + iteis = _iteis(_contexts(gtp, link)) + ksft_not_in(0x111, iteis) + ksft_in(0x112, iteis) + + +def del_unknown_pdp(ns) -> None: + """Deleting a context that was never added must fail with ENOENT. + + Both lookups are checked: by TEI, and by MS address. + """ + gtp, link = _setup(ns) + + for attrs in ({"i-tei": 0x999}, {"ms-address": "10.9.9.9"}): + with ksft_raises(NlError) as cm: + gtp.delpdp({"link": link, "version": 1, **attrs}) + ksft_eq(cm.exception.error, errno.ENOENT) + + +def main() -> None: + """Run the PDP context tests in a temporary netns.""" + with NetNS() as ns: + ksft_run([add_del_pdp, add_two_pdp, del_unknown_pdp], args=(ns,)) + ksft_exit() + + +if __name__ == "__main__": + main() diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py index 71df5880b356..e269a260d58e 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -18,7 +18,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from .ynl import NlError, NlctrlFamily, YnlFamily, \ EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily -from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink +from .ynl import NetshaperFamily, DevlinkFamily, GtpFamily, PSPFamily, Netlink __all__ = ["KSRC", "KsftFailEx", "KsftSkipEx", "KsftXfailEx", "ksft_pr", "ksft_eq", @@ -32,6 +32,7 @@ __all__ = ["KSRC", "wait_port_listen", "ctl_file_write", "wait_file", "tool", "tc", "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "NetdevSim", "NetdevSimDev", - "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", + "NetshaperFamily", "DevlinkFamily", "GtpFamily", "PSPFamily", + "NlError", "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily", "NlctrlFamily", "RtnlAddrFamily", "RtnlRouteFamily", "Netlink"] diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py index 08deff756f29..69a6e9d9b390 100644 --- a/tools/testing/selftests/net/lib/py/ynl.py +++ b/tools/testing/selftests/net/lib/py/ynl.py @@ -31,7 +31,7 @@ __all__ = [ "NlError", "NlPolicy", "Netlink", "YnlFamily", "SPEC_PATH", "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", "RtnlRouteFamily", "NetdevFamily", "NetshaperFamily", "NlctrlFamily", "DevlinkFamily", - "PSPFamily", + "GtpFamily", "PSPFamily", ] # @@ -85,3 +85,10 @@ class PSPFamily(YnlFamily): def __init__(self, recv_size=0): super().__init__((SPEC_PATH / Path('psp.yaml')).as_posix(), schema='', recv_size=recv_size) + + +class GtpFamily(YnlFamily): + """YNL family for gtp, see Documentation/netlink/specs/gtp.yaml.""" + def __init__(self, recv_size=0): + super().__init__((SPEC_PATH / Path('gtp.yaml')).as_posix(), + schema='', recv_size=recv_size)