Merge branch 'gtp-ynl-spec-for-the-gtp-netlink-family-two-fixes-and-a-selftest'

Anil Kaushik says:

====================
gtp: ynl spec for the GTP netlink family, two fixes, and a selftest

This adds a ynl spec for the gtp genetlink family, two small driver
fixes, and a selftest.

Patch 1: the GET and dump replies carried the family id in the command
field, so ynl dropped them. Use GTP_CMD_GETPDP instead.
Patch 2: new in v7. On the netdev CI debug kernel, the selftest hit an
RCU warning: adding a PDP context looks up the device without the RCU
read lock. Hold it around the lookup.
Patch 3: the spec (NEWPDP/DELPDP/GETPDP/ECHOREQ), no uapi change.
Patch 4: a selftest that adds, dumps and deletes PDP contexts.

The multicast notifications are not in the spec: they reuse the request
command values, which the ynl C generator can't express with the
current uapi header.

v6: https://lore.kernel.org/netdev/20261013031000.528393-1-anilkaushikwireless@gmail.com/
v5: https://lore.kernel.org/netdev/20261012024800.400616-1-anilkaushikwireless@gmail.com/
v4: https://lore.kernel.org/netdev/20261010105740.223992-1-anilkaushikwireless@gmail.com/
v3: https://lore.kernel.org/netdev/20261009060615.4177402-1-anilkaushikwireless@gmail.com/
v2: https://lore.kernel.org/netdev/20261008181031.4129029-1-anilkaushikwireless@gmail.com/
====================

Link: https://patch.msgid.link/20261016093225.1036375-1-anilkaushikwireless@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-10-21 11:37:10 +02:00
commit 66123066d8
9 changed files with 340 additions and 5 deletions

View File

@ -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

View File

@ -11422,7 +11422,9 @@ M: Harald Welte <laforge@gnumonks.org>
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
F: tools/testing/selftests/net/gtp.py
GUID PARTITION TABLE (GPT)
M: Davidlohr Bueso <dave@stgolabs.net>

View File

@ -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;
@ -2279,7 +2282,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 +2329,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;

View File

@ -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)

View File

@ -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 \

View File

@ -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

View File

@ -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()

View File

@ -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"]

View File

@ -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)