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..83ff18370ece 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11422,7 +11422,9 @@ 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 +F: tools/testing/selftests/net/gtp.py GUID PARTITION TABLE (GPT) M: Davidlohr Bueso diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 4aff23bcfdd1..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; @@ -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; 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) 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)