aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.gitignore8
-rw-r--r--.gitreview3
-rw-r--r--Make_global.am4
-rw-r--r--Makefile.am8
-rw-r--r--README.md15
-rw-r--r--configure.ac61
-rwxr-xr-xcontrib/jenkins.sh4
-rw-r--r--contrib/libgtpnl.spec.in98
-rw-r--r--debian/changelog53
-rw-r--r--debian/compat2
-rw-r--r--debian/control19
-rw-r--r--debian/libgtpnl-tools.install2
-rw-r--r--include/libgtpnl/gtp.h3
-rw-r--r--include/linux/gtp.h14
-rw-r--r--src/Makefile.am2
-rw-r--r--src/gtp-genl.c149
-rw-r--r--src/gtp.c42
-rw-r--r--src/internal.h26
-rw-r--r--src/libgtpnl.map9
-rw-r--r--tests/Makefile.am15
-rw-r--r--tests/qemu/00_test_functions.sh123
-rw-r--r--tests/qemu/01_ms_ip4_sgsn_ip4.sh15
-rw-r--r--tests/qemu/02_ms_ip4_sgsn_ip6.sh15
-rw-r--r--tests/qemu/03_ms_ip6_sgsn_ip4.sh15
-rw-r--r--tests/qemu/04_ms_ip6_sgsn_ip6.sh15
-rw-r--r--tests/qemu/05_ms_ip46_sgsn_ip4.sh22
-rw-r--r--tests/qemu/README.md36
-rwxr-xr-xtests/qemu/check-depends.sh19
-rwxr-xr-xtests/qemu/initrd-build.sh110
-rwxr-xr-xtests/qemu/initrd-init.sh36
-rwxr-xr-xtests/qemu/run-qemu.sh51
-rw-r--r--tools/Makefile.am2
-rw-r--r--tools/gtp-link.c191
-rw-r--r--tools/gtp-tunnel.c66
35 files changed, 1130 insertions, 124 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..7592deb
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+open_collective: osmocom
diff --git a/.gitignore b/.gitignore
index 7289d72..56eefa6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
.deps
.libs
.dirstamp
+*~
# Generated by autoconf/configure
Makefile
@@ -31,3 +32,10 @@ debian/tmp
debian/libgtpnl-dev
debian/libgtpnl-dbg
debian/libgtpnl0
+
+contrib/libgtpnl.spec
+
+tools/gtp-link
+tools/gtp-tunnel
+
+tests/qemu/_*
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..512f064
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,3 @@
+[gerrit]
+host=gerrit.osmocom.org
+project=libgtpnl
diff --git a/Make_global.am b/Make_global.am
index f2cb36c..9a3d5b2 100644
--- a/Make_global.am
+++ b/Make_global.am
@@ -1,2 +1,2 @@
-AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_srcdir}/include ${LIBMNL_CFLAGS}
-AM_CFLAGS = ${regular_CFLAGS} ${GCC_FVISIBILITY_HIDDEN}
+AM_CPPFLAGS = ${CPPFLAGS} -I${top_srcdir}/include ${LIBMNL_CFLAGS}
+AM_CFLAGS = ${CFLAGS} ${GCC_FVISIBILITY_HIDDEN}
diff --git a/Makefile.am b/Makefile.am
index 1fabe69..c6b9682 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4,8 +4,12 @@ include $(top_srcdir)/Make_global.am
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = src include tools
-DIST_SUBDIRS = src include tools
+SUBDIRS = \
+ include \
+ src \
+ tests \
+ tools \
+ $(NULL)
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libgtpnl.pc
diff --git a/README.md b/README.md
index 23c1d25..8a8efdd 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
# libgtpnl - netlink library for Linux kernel GTP
-In order to control the kernel-side GTP-U plane, a netlink based control
-interface between GTP-C in userspace and GTP-U in kernelspace was
-invented.
+In order to control the kernel-side GTP-U plane (`gtp` driver), a
+netlink based control interface between GTP-C in userspace and GTP-U in
+kernelspace was invented.
The encoding and decoding of these control messages is implemented in
-the libgtpnl (library for GTP netlink).
+this *libgtpnl* (library for GTP netlink).
libgtpnl is part of the [Osmocom](https://osmocom.org/) Open Source
Mobile Communications project.
@@ -13,16 +13,17 @@ Mobile Communications project.
## Homepage
The official homepage of the project is
-<https://osmocom.org/projects/libgtpnl/wiki/>
+<https://osmocom.org/projects/linux-kernel-gtp-u/wiki/Libgtpnl>
GIT Repository
--------------
You can clone from the official libgtpnl.git repository using
- git clone git://git.osmocom.org/libgtpnl.git
+ git clone https://gitea.osmocom.org/cellular-infrastructure/libgtpnl
-There is a cgit interface at <http://git.osmocom.org/libgtpnl/>
+Visiting the URL in a browser shows a
+[web interface](https://gitea.osmocom.org/cellular-infrastructure/libgtpnl).
Mailing List
------------
diff --git a/configure.ac b/configure.ac
index f7580e6..b846c8c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -9,10 +9,17 @@ AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([foreign tar-pax no-dist-gzip dist-bzip2 1.6 subdir-objects])
+CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64 -D_REENTRANT"
+CFLAGS="$CFLAGS -Wall -Waggregate-return -Wmissing-declarations \
+ -Wmissing-prototypes -Wshadow -Wstrict-prototypes \
+ -Wformat=2 -pipe"
+
dnl include release helper
RELMAKE='-include osmo-release.mk'
AC_SUBST([RELMAKE])
+CFLAGS="$CFLAGS -std=gnu11"
+
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
@@ -46,11 +53,49 @@ then
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
fi
-regular_CPPFLAGS="-D_FILE_OFFSET_BITS=64 -D_REENTRANT"
-regular_CFLAGS="-Wall -Waggregate-return -Wmissing-declarations \
- -Wmissing-prototypes -Wshadow -Wstrict-prototypes \
- -Wformat=2 -pipe"
-AC_SUBST([regular_CPPFLAGS])
-AC_SUBST([regular_CFLAGS])
-AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/libgtpnl/Makefile include/linux/Makefile tools/Makefile libgtpnl.pc])
-AC_OUTPUT
+AC_ARG_ENABLE(werror,
+ [AS_HELP_STRING(
+ [--enable-werror],
+ [Turn all compiler warnings into errors, with exceptions:
+ a) deprecation (allow upstream to mark deprecation without breaking builds);
+ b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
+ ]
+ )],
+ [werror=$enableval], [werror="no"])
+if test x"$werror" = x"yes"
+then
+ WERROR_FLAGS="-Werror"
+ WERROR_FLAGS+=" -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition"
+ WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
+ WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
+ CFLAGS="$CFLAGS $WERROR_FLAGS"
+ CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
+fi
+
+AC_ARG_ENABLE(qemu_tests,
+ [AS_HELP_STRING(
+ [--enable-qemu-tests],
+ [Run automated tests in QEMU]
+ )],
+ [qemu_tests=$enableval], [qemu_tests="no"])
+AC_MSG_CHECKING([whether to enable QEMU tests])
+AC_MSG_RESULT([$qemu_tests])
+AM_CONDITIONAL(ENABLE_QEMU_TESTS, test x"$qemu_tests" = x"yes")
+if test x"$qemu_tests" = x"yes" && ! $srcdir/tests/qemu/check-depends.sh; then
+ AC_MSG_ERROR([missing programs for --enable-qemu-tests])
+fi
+
+AC_SUBST([CPPFLAGS])
+AC_SUBST([CFLAGS])
+AC_CONFIG_FILES([
+ Makefile
+ contrib/libgtpnl.spec
+ include/Makefile
+ include/libgtpnl/Makefile
+ include/linux/Makefile
+ libgtpnl.pc
+ src/Makefile
+ tests/Makefile
+ tools/Makefile
+])
+AC_OUTPUT()
diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
index 43e81a1..353cf49 100755
--- a/contrib/jenkins.sh
+++ b/contrib/jenkins.sh
@@ -7,7 +7,7 @@ osmo-clean-workspace.sh
autoreconf --install --force
./configure --enable-sanitize CFLAGS="-Werror" CPPFLAGS="-Werror"
$MAKE $PARALLEL_MAKE
-$MAKE distcheck
-$MAKE maintainer-clean
+$MAKE $PARALLEL_MAKE distcheck
+$MAKE $PARALLEL_MAKE maintainer-clean
osmo-clean-workspace.sh
diff --git a/contrib/libgtpnl.spec.in b/contrib/libgtpnl.spec.in
new file mode 100644
index 0000000..0399d5d
--- /dev/null
+++ b/contrib/libgtpnl.spec.in
@@ -0,0 +1,98 @@
+#
+# spec file for package libgtpnl
+#
+# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+#
+# All modifications and additions to the file contributed by third parties
+# remain the property of their copyright owners, unless otherwise agreed
+# upon. The license for this file, and modifications and additions to the
+# file, is the same license as for the pristine package itself (unless the
+# license for the pristine package is not an Open Source License, in which
+# case the license is the MIT License). An "Open Source License" is a
+# license that conforms to the Open Source Definition (Version 1.9)
+# published by the Open Source Initiative.
+
+Name: libgtpnl
+Version: @VERSION@
+Release: 0
+Summary: GPRS tunnel configuration library
+License: GPL-2.0-or-later AND LGPL-2.1-or-later
+Group: Development/Libraries/C and C++
+URL: https://osmocom.org/projects/linux-kernel-gtp-u/wiki
+Source: %{name}-%{version}.tar.xz
+BuildRequires: libtool >= 2
+BuildRequires: pkgconfig
+BuildRequires: xz
+BuildRequires: pkgconfig(libmnl) >= 1.0.0
+
+%description
+libgtpnl wraps the genetlink-based GPRS tunnel configuration of the
+Linux kernel into a C API.
+
+%package -n libgtpnl0
+Summary: GPRS tunnel configuration library
+License: LGPL-2.1-or-later
+Group: System/Libraries
+
+%description -n libgtpnl0
+libgtpnl wraps the genetlink-based GPRS tunnel configuration of the
+Linux kernel into a C API.
+
+%package devel
+Summary: Development files for the GPRS tunnel config library
+License: GPL-2.0-or-later AND LGPL-2.1-or-later
+Group: Development/Libraries/C and C++
+Requires: libgtpnl0 = %{version}
+
+%description devel
+libgtpnl wraps the genetlink-based GPRS tunnel configuration of the
+Linux kernel into a C API.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libgtpnl.
+
+%package tools
+Summary: Libgtpnl user tools
+License: GPL-2.0-or-later AND LGPL-2.1-or-later
+Group: Applications/System
+Requires: libgtpnl0 = %{version}
+
+%description tools
+libgtpnl wraps the genetlink-based GPRS tunnel configuration of the
+Linux kernel into a C API.
+
+This subpackage contains sample tools to manage gtp interfaces and tunnels.
+
+%prep
+%setup -q
+
+%build
+echo "%{version}" >.tarball-version
+autoreconf -fi
+%configure --includedir="%{_includedir}/%{name}"
+make %{?_smp_mflags}
+
+%install
+%make_install
+find %{buildroot} -type f -name "*.la" -delete -print
+
+%check
+make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
+
+%post -n libgtpnl0 -p /sbin/ldconfig
+%postun -n libgtpnl0 -p /sbin/ldconfig
+
+%files -n libgtpnl0
+%{_libdir}/libgtpnl.so.0*
+
+%files devel
+%license COPYING
+%{_includedir}/libgtpnl/
+%{_libdir}/libgtpnl.so
+%{_libdir}/pkgconfig/*.pc
+
+%files tools
+%{_bindir}/gtp-link
+%{_bindir}/gtp-tunnel
+
+%changelog
diff --git a/debian/changelog b/debian/changelog
index 6499958..ebecf3f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,56 @@
+libgtpnl (1.2.5) unstable; urgency=medium
+
+ [ Neels Janosch Hofmeyr ]
+ * fix memleak on del_tunnel() failure
+
+ [ Oliver Smith ]
+ * debian: set compat level to 10
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 12 Sep 2023 14:18:53 +0200
+
+libgtpnl (1.2.4) unstable; urgency=medium
+
+ [ Oliver Smith ]
+ * README: fix link to homepage
+ * tools/gtp-tunnel: fix del usage
+ * tools/gtp-link: add --sgsn to usage
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 07 Feb 2023 14:07:11 +0100
+
+libgtpnl (1.2.3) unstable; urgency=medium
+
+ [ Neels Hofmeyr ]
+ * fix some cases of rc == 0 on error
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 28 Jun 2022 16:13:45 +0200
+
+libgtpnl (1.2.2) unstable; urgency=medium
+
+ [ Oliver Smith ]
+ * contrib: import RPM spec
+ * contrib: integrate RPM spec
+ * configure.ac: set -std=gnu11
+
+ [ Pau Espin Pedrol ]
+ * Enable parallel make in make distcheck
+ * .gitignore: Ignore new autofoo tmp files
+
+ [ Gabriel Ganne ]
+ * install gtp-tunnel and gtp-link tools
+
+ -- Pau Espin Pedrol <pespin@espeweb.net> Tue, 23 Feb 2021 12:52:08 +0100
+
+libgtpnl (1.2.1) unstable; urgency=medium
+
+ [ Pau Espin Pedrol ]
+ * debian/rules: Don't overwrite .tarball-version
+ * debian: Adapt package name to lib's current major version
+
+ [ Oliver Smith ]
+ * contrib/jenkins.sh: run "make maintainer-clean"
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Wed, 07 Aug 2019 12:29:04 +0200
+
libgtpnl (1.2.0) unstable; urgency=medium
* Fix CTRL_ATTR_FAMILY_ID attribute size
diff --git a/debian/compat b/debian/compat
index ec63514..f599e28 100644
--- a/debian/compat
+++ b/debian/compat
@@ -1 +1 @@
-9
+10
diff --git a/debian/control b/debian/control
index 3128f3e..17a8649 100644
--- a/debian/control
+++ b/debian/control
@@ -1,8 +1,8 @@
Source: libgtpnl
-Maintainer: Harald Welte <laforge@gnumonks.org>
+Maintainer: Osmocom team <openbsc@lists.osmocom.org>
Section: libs
Priority: optional
-Build-Depends: debhelper (>= 9),
+Build-Depends: debhelper (>= 10),
autotools-dev,
autoconf,
automake,
@@ -12,9 +12,9 @@ Build-Depends: debhelper (>= 9),
pkg-config,
libmnl-dev
Standards-Version: 3.9.8
-Vcs-Git: git://git.osmocom.org/libgtpnl.git
-Vcs-Browser: http://git.osmocom.org/gitweb?p=libgtpnl.git;a=summary
-Homepage: https://projects.osmocom.org/projects/openggsn
+Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/libgtpnl
+Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/libgtpnl
+Homepage: https://osmocom.org/projects/linux-kernel-gtp-u/wiki/Libgtpnl
Package: libgtpnl0
Section: libs
@@ -43,3 +43,12 @@ Priority: extra
Depends: libgtpnl0 (= ${binary:Version}),
${misc:Depends}
Description: Debug symbols for Linux kernel GTP-U netlink library
+
+Package: libgtpnl-tools
+Architecture: any
+Multi-Arch: same
+Section: net
+Priority: extra
+Depends: libgtpnl0 (= ${binary:Version}),
+ ${misc:Depends}
+Description: Tools to manage gtp interfaces and tunnels.
diff --git a/debian/libgtpnl-tools.install b/debian/libgtpnl-tools.install
new file mode 100644
index 0000000..49983e4
--- /dev/null
+++ b/debian/libgtpnl-tools.install
@@ -0,0 +1,2 @@
+/usr/bin/gtp-link
+/usr/bin/gtp-tunnel
diff --git a/include/libgtpnl/gtp.h b/include/libgtpnl/gtp.h
index a6cd8e2..796932b 100644
--- a/include/libgtpnl/gtp.h
+++ b/include/libgtpnl/gtp.h
@@ -11,8 +11,11 @@ void gtp_tunnel_free(struct gtp_tunnel *t);
void gtp_tunnel_set_ifns(struct gtp_tunnel *t, int ifns);
void gtp_tunnel_set_ifidx(struct gtp_tunnel *t, uint32_t ifidx);
+void gtp_tunnel_set_family(struct gtp_tunnel *t, uint16_t family);
void gtp_tunnel_set_ms_ip4(struct gtp_tunnel *t, struct in_addr *ms_addr);
void gtp_tunnel_set_sgsn_ip4(struct gtp_tunnel *t, struct in_addr *sgsn_addr);
+void gtp_tunnel_set_ms_ip6(struct gtp_tunnel *t, const struct in6_addr *ms_addr);
+void gtp_tunnel_set_sgsn_ip6(struct gtp_tunnel *t, const struct in6_addr *sgsn_addr);
void gtp_tunnel_set_version(struct gtp_tunnel *t, uint32_t version);
void gtp_tunnel_set_tid(struct gtp_tunnel *t, uint64_t tid);
void gtp_tunnel_set_i_tei(struct gtp_tunnel *t, uint32_t i_tei);
diff --git a/include/linux/gtp.h b/include/linux/gtp.h
index c525cb3..40f5388 100644
--- a/include/linux/gtp.h
+++ b/include/linux/gtp.h
@@ -1,10 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _UAPI_LINUX_GTP_H_
-#define _UAPI_LINUX_GTP_H__
+#define _UAPI_LINUX_GTP_H_
+
+#define GTP_GENL_MCGRP_NAME "gtp"
enum gtp_genl_cmds {
GTP_CMD_NEWPDP,
GTP_CMD_DELPDP,
GTP_CMD_GETPDP,
+ GTP_CMD_ECHOREQ,
GTP_CMD_MAX,
};
@@ -19,15 +23,19 @@ enum gtp_attrs {
GTPA_LINK,
GTPA_VERSION,
GTPA_TID, /* for GTPv0 only */
- GTPA_PEER_ADDRESS,
+ GTPA_PEER_ADDRESS, /* Remote GSN peer, either SGSN or GGSN */
+#define GTPA_SGSN_ADDRESS GTPA_PEER_ADDRESS /* maintain legacy attr name */
GTPA_MS_ADDRESS,
GTPA_FLOW,
GTPA_NET_NS_FD,
GTPA_I_TEI, /* for GTPv1 only */
GTPA_O_TEI, /* for GTPv1 only */
GTPA_PAD,
+ GTPA_PEER_ADDR6,
+ GTPA_MS_ADDR6,
+ GTPA_FAMILY,
__GTPA_MAX,
};
-#define GTPA_MAX (__GTPA_MAX + 1)
+#define GTPA_MAX (__GTPA_MAX - 1)
#endif /* _UAPI_LINUX_GTP_H_ */
diff --git a/src/Makefile.am b/src/Makefile.am
index a87c5cc..4d6910e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,7 +2,7 @@ include $(top_srcdir)/Make_global.am
# This is _NOT_ the library release version, it's an API version.
# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification
-LIBVERSION=1:1:1
+LIBVERSION=1:2:1
lib_LTLIBRARIES = libgtpnl.la
diff --git a/src/gtp-genl.c b/src/gtp-genl.c
index f12f872..33a78d8 100644
--- a/src/gtp-genl.c
+++ b/src/gtp-genl.c
@@ -44,20 +44,55 @@
static void gtp_build_payload(struct nlmsghdr *nlh, struct gtp_tunnel *t)
{
- mnl_attr_put_u32(nlh, GTPA_VERSION, t->gtp_version);
- if (t->ifns >= 0)
+ if (t->flags & GTP_TUN_FAMILY)
+ mnl_attr_put_u8(nlh, GTPA_FAMILY, t->ms_addr.family);
+ if (t->flags & GTP_TUN_VERSION)
+ mnl_attr_put_u32(nlh, GTPA_VERSION, t->gtp_version);
+ if (t->flags & GTP_TUN_IFNS)
mnl_attr_put_u32(nlh, GTPA_NET_NS_FD, t->ifns);
- mnl_attr_put_u32(nlh, GTPA_LINK, t->ifidx);
- if (t->sgsn_addr.s_addr)
- mnl_attr_put_u32(nlh, GTPA_PEER_ADDRESS, t->sgsn_addr.s_addr);
- if (t->ms_addr.s_addr)
- mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, t->ms_addr.s_addr);
- if (t->gtp_version == GTP_V0) {
- mnl_attr_put_u64(nlh, GTPA_TID, t->u.v0.tid);
- mnl_attr_put_u16(nlh, GTPA_FLOW, t->u.v0.flowid);
- } else if (t->gtp_version == GTP_V1) {
- mnl_attr_put_u32(nlh, GTPA_I_TEI, t->u.v1.i_tei);
- mnl_attr_put_u32(nlh, GTPA_O_TEI, t->u.v1.o_tei);
+ if (t->flags & GTP_TUN_IFIDX)
+ mnl_attr_put_u32(nlh, GTPA_LINK, t->ifidx);
+
+ if (t->flags & GTP_TUN_MS_ADDR) {
+ switch (t->ms_addr.family) {
+ case AF_INET:
+ mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, t->ms_addr.ip4.s_addr);
+ break;
+ case AF_INET6:
+ mnl_attr_put(nlh, GTPA_MS_ADDR6, sizeof(t->ms_addr.ip6), &t->ms_addr.ip6);
+ break;
+ default:
+ /* No addr is set when deleting a tunnel */
+ break;
+ }
+ }
+
+ if (t->flags & GTP_TUN_SGSN_ADDR) {
+ switch (t->sgsn_addr.family) {
+ case AF_INET:
+ mnl_attr_put_u32(nlh, GTPA_PEER_ADDRESS, t->sgsn_addr.ip4.s_addr);
+ break;
+ case AF_INET6:
+ mnl_attr_put(nlh, GTPA_PEER_ADDR6, sizeof(t->sgsn_addr.ip6), &t->sgsn_addr.ip6);
+ break;
+ default:
+ /* No addr is set when deleting a tunnel */
+ break;
+ }
+ }
+
+ if (t->flags & GTP_TUN_VERSION) {
+ if (t->gtp_version == GTP_V0) {
+ if (t->flags & GTP_TUN_V0_TID)
+ mnl_attr_put_u64(nlh, GTPA_TID, t->u.v0.tid);
+ if (t->flags & GTP_TUN_V0_FLOWID)
+ mnl_attr_put_u16(nlh, GTPA_FLOW, t->u.v0.flowid);
+ } else if (t->gtp_version == GTP_V1) {
+ if (t->flags & GTP_TUN_V1_I_TEI)
+ mnl_attr_put_u32(nlh, GTPA_I_TEI, t->u.v1.i_tei);
+ if (t->flags & GTP_TUN_V1_O_TEI)
+ mnl_attr_put_u32(nlh, GTPA_O_TEI, t->u.v1.o_tei);
+ }
}
}
@@ -77,8 +112,10 @@ int gtp_add_tunnel(int genl_id, struct mnl_socket *nl, struct gtp_tunnel *t)
GTP_CMD_NEWPDP);
gtp_build_payload(nlh, t);
- if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0)
+ if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0) {
perror("genl_socket_talk");
+ return -1;
+ }
return 0;
}
@@ -94,14 +131,17 @@ int gtp_del_tunnel(int genl_id, struct mnl_socket *nl, struct gtp_tunnel *t)
GTP_CMD_DELPDP);
gtp_build_payload(nlh, t);
- if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0)
+ if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0) {
perror("genl_socket_talk");
+ return -1;
+ }
return 0;
}
EXPORT_SYMBOL(gtp_del_tunnel);
struct gtp_pdp {
+ uint32_t ifidx;
uint32_t version;
union {
struct {
@@ -112,8 +152,8 @@ struct gtp_pdp {
uint32_t o_tei;
} v1;
} u;
- struct in_addr sgsn_addr;
- struct in_addr ms_addr;
+ struct gtp_addr sgsn_addr;
+ struct gtp_addr ms_addr;
};
static int genl_gtp_validate_cb(const struct nlattr *attr, void *data)
@@ -125,6 +165,12 @@ static int genl_gtp_validate_cb(const struct nlattr *attr, void *data)
return MNL_CB_OK;
switch(type) {
+ case GTPA_FAMILY:
+ if (mnl_attr_validate(attr, MNL_TYPE_U8) < 0) {
+ perror("mnl_attr_validate");
+ return MNL_CB_ERROR;
+ }
+ break;
case GTPA_TID:
if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0) {
perror("mnl_attr_validate");
@@ -136,11 +182,20 @@ static int genl_gtp_validate_cb(const struct nlattr *attr, void *data)
case GTPA_PEER_ADDRESS:
case GTPA_MS_ADDRESS:
case GTPA_VERSION:
+ case GTPA_LINK:
if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
perror("mnl_attr_validate");
return MNL_CB_ERROR;
}
break;
+ case GTPA_PEER_ADDR6:
+ case GTPA_MS_ADDR6:
+ if (mnl_attr_validate2(attr, MNL_TYPE_BINARY,
+ sizeof(struct in6_addr)) < 0) {
+ perror("mnl_attr_validate");
+ return MNL_CB_ERROR;
+ }
+ break;
default:
break;
}
@@ -151,39 +206,65 @@ static int genl_gtp_validate_cb(const struct nlattr *attr, void *data)
static int genl_gtp_attr_cb(const struct nlmsghdr *nlh, void *data)
{
struct nlattr *tb[GTPA_MAX + 1] = {};
- char buf[INET_ADDRSTRLEN];
+ char buf[INET6_ADDRSTRLEN];
struct gtp_pdp pdp = {};
struct genlmsghdr *genl;
mnl_attr_parse(nlh, sizeof(*genl), genl_gtp_validate_cb, tb);
+
+ if (tb[GTPA_LINK])
+ pdp.ifidx = mnl_attr_get_u32(tb[GTPA_LINK]);
if (tb[GTPA_TID])
pdp.u.v0.tid = mnl_attr_get_u64(tb[GTPA_TID]);
if (tb[GTPA_I_TEI])
pdp.u.v1.i_tei = mnl_attr_get_u32(tb[GTPA_I_TEI]);
if (tb[GTPA_O_TEI])
pdp.u.v1.o_tei = mnl_attr_get_u32(tb[GTPA_O_TEI]);
+
if (tb[GTPA_PEER_ADDRESS]) {
- pdp.sgsn_addr.s_addr =
- mnl_attr_get_u32(tb[GTPA_PEER_ADDRESS]);
+ pdp.sgsn_addr.family = AF_INET;
+ pdp.sgsn_addr.ip4.s_addr = mnl_attr_get_u32(tb[GTPA_PEER_ADDRESS]);
+ } else if (tb[GTPA_PEER_ADDR6]) {
+ pdp.sgsn_addr.family = AF_INET6;
+ memcpy(&pdp.sgsn_addr.ip6, mnl_attr_get_payload(tb[GTPA_PEER_ADDR6]),
+ sizeof(struct in6_addr));
+ } else {
+ fprintf(stderr, "sgsn_addr: no IPv4 nor IPv6 set\n");
+ return MNL_CB_ERROR;
}
+
if (tb[GTPA_MS_ADDRESS]) {
- pdp.ms_addr.s_addr = mnl_attr_get_u32(tb[GTPA_MS_ADDRESS]);
+ pdp.ms_addr.family = AF_INET;
+ pdp.ms_addr.ip4.s_addr = mnl_attr_get_u32(tb[GTPA_MS_ADDRESS]);
+ } else if (tb[GTPA_MS_ADDR6]) {
+ pdp.ms_addr.family = AF_INET6;
+ memcpy(&pdp.ms_addr.ip6, mnl_attr_get_payload(tb[GTPA_MS_ADDR6]), sizeof(struct in6_addr));
+ } else {
+ fprintf(stderr, "ms_addr: no IPv4 nor IPv6 set\n");
+ return MNL_CB_ERROR;
}
- if (tb[GTPA_VERSION]) {
- pdp.version = mnl_attr_get_u32(tb[GTPA_VERSION]);
+
+ if (tb[GTPA_FAMILY] && mnl_attr_get_u32(tb[GTPA_FAMILY]) != pdp.ms_addr.family) {
+ fprintf(stderr, "ms_addr family does not match GTPA_FAMILY\n");
+ return MNL_CB_ERROR;
}
+ printf("%s ", if_indextoname(pdp.ifidx, buf));
+
+ if (tb[GTPA_VERSION])
+ pdp.version = mnl_attr_get_u32(tb[GTPA_VERSION]);
+
printf("version %u ", pdp.version);
- if (pdp.version == GTP_V0) {
- inet_ntop(AF_INET, &pdp.ms_addr, buf, sizeof(buf));
- printf("tid %"PRIu64" ms_addr %s ",
- pdp.u.v0.tid, buf);
- } else if (pdp.version == GTP_V1) {
- inet_ntop(AF_INET, &pdp.ms_addr, buf, sizeof(buf));
- printf("tei %u/%u ms_addr %s ", pdp.u.v1.i_tei,
- pdp.u.v1.o_tei, buf);
- }
- inet_ntop(AF_INET, &pdp.sgsn_addr, buf, sizeof(buf));
+
+ if (pdp.version == GTP_V0)
+ printf("tid %"PRIu64" ", pdp.u.v0.tid);
+ else if (pdp.version == GTP_V1)
+ printf("tei %u/%u ", pdp.u.v1.i_tei, pdp.u.v1.o_tei);
+
+ inet_ntop(pdp.ms_addr.family, &pdp.ms_addr.ip4, buf, sizeof(buf));
+ printf("ms_addr %s ", buf);
+
+ inet_ntop(pdp.sgsn_addr.family, &pdp.sgsn_addr.ip4, buf, sizeof(buf));
printf("sgsn_addr %s\n", buf);
return MNL_CB_OK;
@@ -200,7 +281,7 @@ int gtp_list_tunnel(int genl_id, struct mnl_socket *nl)
if (genl_socket_talk(nl, nlh, seq, genl_gtp_attr_cb, NULL) < 0) {
perror("genl_socket_talk");
- return 0;
+ return -1;
}
return 0;
diff --git a/src/gtp.c b/src/gtp.c
index e65b0b6..af216f7 100644
--- a/src/gtp.c
+++ b/src/gtp.c
@@ -49,54 +49,88 @@ EXPORT_SYMBOL(gtp_tunnel_free);
void gtp_tunnel_set_ifns(struct gtp_tunnel *t, int ifns)
{
t->ifns = ifns;
+ t->flags |= GTP_TUN_IFNS;
}
EXPORT_SYMBOL(gtp_tunnel_set_ifns);
void gtp_tunnel_set_ifidx(struct gtp_tunnel *t, uint32_t ifidx)
{
t->ifidx = ifidx;
+ t->flags |= GTP_TUN_IFIDX;
}
EXPORT_SYMBOL(gtp_tunnel_set_ifidx);
+void gtp_tunnel_set_family(struct gtp_tunnel *t, uint16_t family)
+{
+ t->ms_addr.family = family;
+ t->flags |= GTP_TUN_FAMILY;
+}
+EXPORT_SYMBOL(gtp_tunnel_set_family);
+
void gtp_tunnel_set_ms_ip4(struct gtp_tunnel *t, struct in_addr *ms_addr)
{
- t->ms_addr = *ms_addr;
+ t->ms_addr.family = AF_INET;
+ t->ms_addr.ip4 = *ms_addr;
+ t->flags |= GTP_TUN_FAMILY | GTP_TUN_MS_ADDR;
}
EXPORT_SYMBOL(gtp_tunnel_set_ms_ip4);
void gtp_tunnel_set_sgsn_ip4(struct gtp_tunnel *t, struct in_addr *sgsn_addr)
{
- t->sgsn_addr = *sgsn_addr;
+ t->sgsn_addr.family = AF_INET;
+ t->sgsn_addr.ip4 = *sgsn_addr;
+ t->flags |= GTP_TUN_SGSN_ADDR;
}
EXPORT_SYMBOL(gtp_tunnel_set_sgsn_ip4);
+void gtp_tunnel_set_ms_ip6(struct gtp_tunnel *t, const struct in6_addr *ms_addr)
+{
+ t->ms_addr.family = AF_INET6;
+ t->ms_addr.ip6 = *ms_addr;
+ t->flags |= GTP_TUN_FAMILY | GTP_TUN_MS_ADDR;
+}
+EXPORT_SYMBOL(gtp_tunnel_set_ms_ip6);
+
+void gtp_tunnel_set_sgsn_ip6(struct gtp_tunnel *t, const struct in6_addr *sgsn_addr)
+{
+ t->sgsn_addr.family = AF_INET6;
+ t->sgsn_addr.ip6 = *sgsn_addr;
+ t->flags |= GTP_TUN_SGSN_ADDR;
+}
+EXPORT_SYMBOL(gtp_tunnel_set_sgsn_ip6);
+
void gtp_tunnel_set_version(struct gtp_tunnel *t, uint32_t version)
{
t->gtp_version = version;
+ t->flags |= GTP_TUN_VERSION;
}
EXPORT_SYMBOL(gtp_tunnel_set_version);
void gtp_tunnel_set_tid(struct gtp_tunnel *t, uint64_t tid)
{
t->u.v0.tid = tid;
+ t->flags |= GTP_TUN_V0_TID;
}
EXPORT_SYMBOL(gtp_tunnel_set_tid);
void gtp_tunnel_set_flowid(struct gtp_tunnel *t, uint16_t flowid)
{
t->u.v0.flowid = flowid;
+ t->flags |= GTP_TUN_V0_FLOWID;
}
EXPORT_SYMBOL(gtp_tunnel_set_flowid);
void gtp_tunnel_set_i_tei(struct gtp_tunnel *t, uint32_t i_tei)
{
t->u.v1.i_tei = i_tei;
+ t->flags |= GTP_TUN_V1_I_TEI;
}
EXPORT_SYMBOL(gtp_tunnel_set_i_tei);
void gtp_tunnel_set_o_tei(struct gtp_tunnel *t, uint32_t o_tei)
{
t->u.v1.o_tei = o_tei;
+ t->flags |= GTP_TUN_V1_O_TEI;
}
EXPORT_SYMBOL(gtp_tunnel_set_o_tei);
@@ -114,13 +148,13 @@ EXPORT_SYMBOL(gtp_tunnel_get_ifidx);
const struct in_addr *gtp_tunnel_get_ms_ip4(struct gtp_tunnel *t)
{
- return &t->ms_addr;
+ return &t->ms_addr.ip4;
}
EXPORT_SYMBOL(gtp_tunnel_get_ms_ip4);
const struct in_addr *gtp_tunnel_get_sgsn_ip4(struct gtp_tunnel *t)
{
- return &t->sgsn_addr;
+ return &t->sgsn_addr.ip4;
}
EXPORT_SYMBOL(gtp_tunnel_get_sgsn_ip4);
diff --git a/src/internal.h b/src/internal.h
index 1754e3b..1433621 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -12,11 +12,32 @@
#include <stdint.h>
#include <netinet/in.h>
+struct gtp_addr {
+ sa_family_t family;
+ union {
+ struct in_addr ip4;
+ struct in6_addr ip6;
+ };
+};
+
+enum {
+ GTP_TUN_IFNS = (1 << 0),
+ GTP_TUN_IFIDX = (1 << 1),
+ GTP_TUN_FAMILY = (1 << 2),
+ GTP_TUN_MS_ADDR = (1 << 3),
+ GTP_TUN_SGSN_ADDR = (1 << 4),
+ GTP_TUN_VERSION = (1 << 5),
+ GTP_TUN_V0_TID = (1 << 6),
+ GTP_TUN_V0_FLOWID = (1 << 7),
+ GTP_TUN_V1_I_TEI = (1 << 8),
+ GTP_TUN_V1_O_TEI = (1 << 9),
+};
+
struct gtp_tunnel {
int ifns;
uint32_t ifidx;
- struct in_addr ms_addr;
- struct in_addr sgsn_addr;
+ struct gtp_addr ms_addr;
+ struct gtp_addr sgsn_addr;
int gtp_version;
union {
struct {
@@ -28,6 +49,7 @@ struct gtp_tunnel {
uint32_t o_tei;
} v1;
} u;
+ uint32_t flags;
};
#endif
diff --git a/src/libgtpnl.map b/src/libgtpnl.map
index 804f8b3..41b7dd7 100644
--- a/src/libgtpnl.map
+++ b/src/libgtpnl.map
@@ -38,3 +38,12 @@ global:
local: *;
};
+
+LIBGTPNL_1.1 {
+ gtp_tunnel_set_ms_ip6;
+ gtp_tunnel_set_sgsn_ip6;
+} LIBGTPNL_1.0;
+
+LIBGTPNL_1.2 {
+ gtp_tunnel_set_family;
+} LIBGTPNL_1.1;
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..54af52d
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,15 @@
+check-local:
+ $(MAKE) qemu-tests
+
+if ENABLE_QEMU_TESTS
+qemu-download-kernel:
+ rm -f qemu/_linux
+ wget -O qemu/_linux \
+ https://jenkins.osmocom.org/jenkins/job/ttcn3-ggsn-test-kernel-latest-net-next/ws/_cache/kernel-test/linux
+qemu-tests:
+ qemu/initrd-build.sh
+ qemu/run-qemu.sh
+else
+qemu-tests:
+ @echo "Not running QEMU tests (determined at configure-time)"
+endif
diff --git a/tests/qemu/00_test_functions.sh b/tests/qemu/00_test_functions.sh
new file mode 100644
index 0000000..7be8dbf
--- /dev/null
+++ b/tests/qemu/00_test_functions.sh
@@ -0,0 +1,123 @@
+#!/bin/sh -ex
+
+# Use ip from iproute2 instead of busybox ip, because iproute2's version has
+# "ip netns" implemented. Calling /bin/ip explicitly is needed here, otherwise
+# busybox sh will use busybox ip, regardless of PATH.
+alias ip="/bin/ip"
+alias ggsn_side="ip netns exec ggsn_side"
+
+# MS - SGSN -gtp- GGSN - WEBSERVER
+tunnel_start() {
+ test -n "$MS_PROTO"
+ test -n "$MS"
+ test -n "$MS_PREFLEN"
+ test -n "$SGSN_GGSN_PROTO"
+ test -n "$SGSN"
+ test -n "$SGSN_PREFLEN"
+ test -n "$GGSN"
+ test -n "$WEBSERVER"
+
+ ip netns add ggsn_side
+
+ # SGSN side: prepare veth_sgsn (SGSN), lo (MS)
+ ip link add veth_sgsn type veth peer name veth_ggsn
+ ip addr add "$SGSN"/"$SGSN_PREFLEN" dev veth_sgsn
+ ip link set veth_sgsn up
+ ip addr add "$MS"/"$MS_PREFLEN" dev lo
+ ip link set lo up
+
+ # SGSN side: prepare gtp-tunnel
+ gtp-link add gtp_sgsn "$SGSN_GGSN_PROTO" --sgsn &
+ sleep 1
+ gtp-tunnel add gtp_sgsn v1 200 100 "$MS" "$GGSN"
+ ip route add "$WEBSERVER"/"$MS_PREFLEN" dev gtp_sgsn
+
+ # GGSN side: prepare veth_ggsn (GGSN), lo (WEBSERVER)
+ ip link set veth_ggsn netns ggsn_side
+ ggsn_side ip addr add "$GGSN"/"$SGSN_PREFLEN" dev veth_ggsn
+ ggsn_side ip link set veth_ggsn up
+ ggsn_side ip addr add "$WEBSERVER"/"$MS_PREFLEN" dev lo
+ ggsn_side ip link set lo up
+
+ # GGSN side: prepare gtp-tunnel
+ ggsn_side gtp-link add gtp_ggsn "$SGSN_GGSN_PROTO" &
+ sleep 1
+ ggsn_side gtp-tunnel add gtp_ggsn v1 100 200 "$MS" "$SGSN"
+ ggsn_side ip route add "$MS"/"$MS_PREFLEN" dev gtp_ggsn
+
+ # List tunnel from both sides
+ gtp-tunnel list
+ ggsn_side gtp-tunnel list
+}
+
+# Add a second tunnel to test MS with IPv4v6
+tunnel_start_2() {
+ test -n "$MS2_PROTO"
+ test -n "$MS2"
+ test -n "$MS2_PREFLEN"
+ test -n "$WEBSERVER2"
+
+ # SGSN side: add second IP to lo (MS)
+ ip addr add "$MS2"/"$MS2_PREFLEN" dev lo
+
+ # SGSN side: prepare second gtp-tunnel
+ gtp-tunnel add gtp_sgsn v1 200 100 "$MS2" "$GGSN"
+ ip route add "$WEBSERVER2"/"$MS2_PREFLEN" dev gtp_sgsn
+
+ # GGSN side: add second IP to lo (WEBSERVER)
+ ggsn_side ip addr add "$WEBSERVER2"/"$MS2_PREFLEN" dev lo
+
+ # GGSN side: prepare second gtp-tunnel
+ ggsn_side gtp-tunnel add gtp_ggsn v1 100 200 "$MS2" "$SGSN"
+ ggsn_side ip route add "$MS2"/"$MS2_PREFLEN" dev gtp_ggsn
+
+ # List tunnels from both sides
+ gtp-tunnel list
+ ggsn_side gtp-tunnel list
+}
+
+tunnel_ping() {
+ ip addr show
+ ping -c 1 "$WEBSERVER"
+ ggsn_side ping -c 1 "$MS"
+
+ if [ -n "$MS2" ]; then
+ ping -c 1 "$WEBSERVER2"
+ ggsn_side ping -c 1 "$MS2"
+ fi
+}
+
+tunnel_stop() {
+ killall gtp-link
+
+ ip addr del "$MS"/"$MS_PREFLEN" dev lo
+
+ if [ -n "$MS2" ]; then
+ ip addr del "$MS2"/"$MS2_PREFLEN" dev lo
+ fi
+
+ ip link set veth_sgsn down
+
+ if [ "$SGSN_GGSN_PROTO" == "ip" ]; then # FIXME: doesn't work with ip6
+ ip addr del "$SGSN"/"$SGSN_PREFLEN" dev veth_sgsn
+ fi
+
+ ip link del veth_sgsn
+ ip route del "$WEBSERVER"/"$MS_PREFLEN" dev gtp_sgsn
+ gtp-tunnel delete gtp_sgsn v1 200 "$MS_PROTO"
+
+ if [ -n "$MS2" ]; then
+ ip route del "$WEBSERVER2"/"$MS2_PREFLEN" dev gtp_sgsn
+ gtp-tunnel delete gtp_sgsn v1 200 "$MS2_PROTO"
+ fi
+
+ gtp-link del gtp_sgsn
+ ggsn_side gtp-tunnel delete gtp_ggsn v1 100 "$MS_PROTO"
+
+ if [ -n "$MS2" ]; then
+ ggsn_side gtp-tunnel delete gtp_ggsn v1 100 "$MS2_PROTO"
+ fi
+
+ ggsn_side gtp-link del gtp_ggsn
+ ip netns del ggsn_side
+}
diff --git a/tests/qemu/01_ms_ip4_sgsn_ip4.sh b/tests/qemu/01_ms_ip4_sgsn_ip4.sh
new file mode 100644
index 0000000..4789483
--- /dev/null
+++ b/tests/qemu/01_ms_ip4_sgsn_ip4.sh
@@ -0,0 +1,15 @@
+#!/bin/sh -ex
+. /tests/00_test_functions.sh
+
+MS_PROTO="ip"
+MS="172.99.0.1"
+MS_PREFLEN="32"
+SGSN_GGSN_PROTO="ip"
+SGSN="172.0.0.1"
+SGSN_PREFLEN="24"
+GGSN="172.0.0.2"
+WEBSERVER="172.99.0.2"
+
+tunnel_start
+tunnel_ping
+tunnel_stop
diff --git a/tests/qemu/02_ms_ip4_sgsn_ip6.sh b/tests/qemu/02_ms_ip4_sgsn_ip6.sh
new file mode 100644
index 0000000..2a489cd
--- /dev/null
+++ b/tests/qemu/02_ms_ip4_sgsn_ip6.sh
@@ -0,0 +1,15 @@
+#!/bin/sh -ex
+. /tests/00_test_functions.sh
+
+MS_PROTO="ip"
+MS="172.99.0.1"
+MS_PREFLEN="32"
+SGSN_GGSN_PROTO="ip6"
+SGSN="fd00::1"
+SGSN_PREFLEN="7"
+GGSN="fd00::2"
+WEBSERVER="172.99.0.2"
+
+tunnel_start
+tunnel_ping
+tunnel_stop
diff --git a/tests/qemu/03_ms_ip6_sgsn_ip4.sh b/tests/qemu/03_ms_ip6_sgsn_ip4.sh
new file mode 100644
index 0000000..f873faf
--- /dev/null
+++ b/tests/qemu/03_ms_ip6_sgsn_ip4.sh
@@ -0,0 +1,15 @@
+#!/bin/sh -ex
+. /tests/00_test_functions.sh
+
+MS_PROTO="ip6"
+MS="fd00::"
+MS_PREFLEN="64"
+SGSN_GGSN_PROTO="ip"
+SGSN="172.0.0.1"
+SGSN_PREFLEN="24"
+GGSN="172.0.0.2"
+WEBSERVER="fe00::2"
+
+tunnel_start
+tunnel_ping
+tunnel_stop
diff --git a/tests/qemu/04_ms_ip6_sgsn_ip6.sh b/tests/qemu/04_ms_ip6_sgsn_ip6.sh
new file mode 100644
index 0000000..483a9d7
--- /dev/null
+++ b/tests/qemu/04_ms_ip6_sgsn_ip6.sh
@@ -0,0 +1,15 @@
+#!/bin/sh -ex
+. /tests/00_test_functions.sh
+
+MS_PROTO="ip6"
+MS="fc00::"
+MS_PREFLEN="64"
+SGSN_GGSN_PROTO="ip6"
+SGSN="fd00::1"
+SGSN_PREFLEN="7"
+GGSN="fd00::2"
+WEBSERVER="fe00::2"
+
+tunnel_start
+tunnel_ping
+tunnel_stop
diff --git a/tests/qemu/05_ms_ip46_sgsn_ip4.sh b/tests/qemu/05_ms_ip46_sgsn_ip4.sh
new file mode 100644
index 0000000..0bd48bb
--- /dev/null
+++ b/tests/qemu/05_ms_ip46_sgsn_ip4.sh
@@ -0,0 +1,22 @@
+#!/bin/sh -ex
+. /tests/00_test_functions.sh
+
+MS_PROTO="ip"
+MS="172.99.0.1"
+MS_PREFLEN="32"
+SGSN_GGSN_PROTO="ip"
+SGSN="172.0.0.1"
+SGSN_PREFLEN="24"
+GGSN="172.0.0.2"
+WEBSERVER="172.99.0.2"
+
+tunnel_start
+
+MS2_PROTO="ip6"
+MS2="fd00::"
+MS2_PREFLEN="64"
+WEBSERVER2="fe00::2"
+
+tunnel_start_2
+tunnel_ping
+tunnel_stop
diff --git a/tests/qemu/README.md b/tests/qemu/README.md
new file mode 100644
index 0000000..e0dd344
--- /dev/null
+++ b/tests/qemu/README.md
@@ -0,0 +1,36 @@
+# QEMU tests for libgtpnl
+
+The tests simulate how a GGSN would use libgtpnl, to set up a GTP tunnel
+between SGSN and GGSN, so a MS on the SGSN side can talk to a webserver on the
+GGSN side.
+
+## Running the tests
+
+```
+$ autoreconf -fi
+$ ./configure --enable-qemu-tests
+$ make
+$ make -C tests qemu-download-kernel # or build your own, see below
+$ make check
+```
+
+## Building your own kernel
+
+Clone a kernel tree, then:
+```
+$ make defconfig
+$ make menuconfig
+```
+
+Set the following options:
+```
+CONFIG_GTP=y
+CONFIG_NET_NS=y
+CONFIG_VETH=y
+```
+
+Build the kernel and copy it to the tests dir:
+```
+$ make -j$(nproc)
+$ cp arch/x86/boot/bzImage /path/to/libgtpnl/tests/qemu/_linux
+```
diff --git a/tests/qemu/check-depends.sh b/tests/qemu/check-depends.sh
new file mode 100755
index 0000000..b63b389
--- /dev/null
+++ b/tests/qemu/check-depends.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+RET=0
+
+require_program() {
+ if [ -z "$(command -v "$1")" ]; then
+ RET=1
+ echo "ERROR: missing program: $1"
+ fi
+}
+
+require_program busybox
+require_program cpio
+require_program find
+require_program gzip
+require_program ip
+require_program lddtree
+require_program qemu-system-x86_64
+
+exit "$RET"
diff --git a/tests/qemu/initrd-build.sh b/tests/qemu/initrd-build.sh
new file mode 100755
index 0000000..34d3bc5
--- /dev/null
+++ b/tests/qemu/initrd-build.sh
@@ -0,0 +1,110 @@
+#!/bin/sh -e
+DIR="$(cd "$(dirname "$0")" && pwd)"
+DIR_INITRD="$DIR/_initrd"
+SRC_LIBS="$(realpath "$DIR/../../src/.libs/")"
+TOOLS_LIBS="$(realpath "$DIR/../../tools/.libs/")"
+
+# Add one or more files to the initramfs, with parent directories.
+# usr-merge: resolve symlinks for /lib -> /usr/lib etc. so "cp --parents" does
+# not fail with "cp: cannot make directory '/tmp/initrd/lib': File exists"
+# $@: path to files
+initrd_add_file() {
+ local i
+
+ for i in "$@"; do
+ case "$i" in
+ /bin/*|/sbin/*|/lib/*|/lib64/*)
+ cp -a --parents "$i" "$DIR_INITRD"/usr
+ ;;
+ *)
+ cp -a --parents "$i" "$DIR_INITRD"
+ ;;
+ esac
+ done
+}
+
+# Add binaries with depending libraries
+# $@: paths to binaries
+initrd_add_bin() {
+ local bin
+ local bin_path
+ local file
+
+ for bin in "$@"; do
+ local bin_path="$(which "$bin")"
+ if [ -z "$bin_path" ]; then
+ echo "ERROR: file not found: $bin"
+ exit 1
+ fi
+
+ lddtree_out="$(lddtree -l "$bin_path")"
+ if [ -z "$lddtree_out" ]; then
+ echo "ERROR: lddtree failed on '$bin_path'"
+ exit 1
+ fi
+
+ for file in $lddtree_out; do
+ initrd_add_file "$file"
+
+ # Copy resolved symlink
+ if [ -L "$file" ]; then
+ initrd_add_file "$(realpath "$file")"
+ fi
+ done
+ done
+}
+
+# Add command to run inside the initramfs
+# $@: commands
+initrd_add_cmd() {
+ local i
+
+ if ! [ -e "$DIR_INITRD"/cmd.sh ]; then
+ echo "#!/bin/sh -ex" > "$DIR_INITRD"/cmd.sh
+ chmod +x "$DIR_INITRD"/cmd.sh
+ fi
+
+ for i in "$@"; do
+ echo "$i" >> "$DIR_INITRD"/cmd.sh
+ done
+}
+
+rm -rf "$DIR_INITRD"
+mkdir -p "$DIR_INITRD"
+cd "$DIR_INITRD"
+
+for dir in bin sbin lib lib64; do
+ ln -s usr/"$dir" "$dir"
+done
+
+mkdir -p \
+ dev/net \
+ proc \
+ run \
+ sys \
+ tmp \
+ usr/bin \
+ usr/sbin
+
+initrd_add_bin \
+ busybox \
+ ip
+
+initrd_add_cmd \
+ "export LD_LIBRARY_PATH=$SRC_LIBS:$LD_LIBRARY_PATH"
+
+export LD_LIBRARY_PATH="$SRC_LIBS:$LD_LIBRARY_PATH"
+
+for i in gtp-link gtp-tunnel; do
+ initrd_add_bin "$TOOLS_LIBS"/"$i"
+ ln -s "$TOOLS_LIBS"/"$i" usr/bin/"$i"
+done
+
+mkdir tests
+cp "$DIR"/*.sh tests
+
+cp "$DIR"/initrd-init.sh init
+
+find . -print0 \
+ | cpio --quiet -o -0 -H newc \
+ | gzip -1 > "$DIR"/_initrd.gz
diff --git a/tests/qemu/initrd-init.sh b/tests/qemu/initrd-init.sh
new file mode 100755
index 0000000..5dacb86
--- /dev/null
+++ b/tests/qemu/initrd-init.sh
@@ -0,0 +1,36 @@
+#!/bin/busybox sh
+echo "Running initrd-init.sh"
+set -x
+
+run_test() {
+ echo
+ echo "QEMU test: $1"
+ echo
+ if ! sh -ex "/tests/$1"; then
+ poweroff -f
+ fi
+}
+
+export HOME=/root
+export LD_LIBRARY_PATH=/usr/local/lib
+export PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/local/sbin:/usr/sbin
+export TERM=screen
+
+/bin/busybox --install -s
+hostname qemu
+mount -t proc proc /proc
+mount -t sysfs sys /sys
+mknod /dev/null c 1 3
+. /cmd.sh
+set +x
+
+# Run all tests
+run_test 01_ms_ip4_sgsn_ip4.sh
+run_test 02_ms_ip4_sgsn_ip6.sh
+run_test 03_ms_ip6_sgsn_ip4.sh
+run_test 04_ms_ip6_sgsn_ip6.sh
+run_test 05_ms_ip46_sgsn_ip4.sh
+
+# Success (run-qemu.sh checks for this line)
+echo "QEMU_TEST_SUCCESSFUL"
+poweroff -f
diff --git a/tests/qemu/run-qemu.sh b/tests/qemu/run-qemu.sh
new file mode 100755
index 0000000..35bf756
--- /dev/null
+++ b/tests/qemu/run-qemu.sh
@@ -0,0 +1,51 @@
+#!/bin/sh -e
+DIR="$(cd "$(dirname "$0")" && pwd)"
+
+if [ -e /dev/kvm ]; then
+ MACHINE_ARG="-machine pc,accel=kvm"
+else
+ echo "WARNING: /dev/kvm not found, emulation will be slower"
+ MACHINE_ARG="-machine pc"
+fi
+
+if ! [ -e "$DIR"/_linux ]; then
+ echo "ERROR: linux kernel not found: $DIR/_linux"
+ echo "Put a kernel there, either download it from the Osmocom jenkins:"
+ echo "$ make -C tests qemu-download-kernel"
+ echo
+ echo "Or build your own kernel. Make sure to set:"
+ echo " CONFIG_GTP=y"
+ echo " CONFIG_NET_NS=y"
+ echo " CONFIG_VETH=y"
+ exit 1
+fi
+
+KERNEL_CMDLINE="root=/dev/ram0 console=ttyS0 panic=-1 init=/init"
+
+set -x
+qemu-system-x86_64 \
+ $MACHINE_ARG \
+ -smp 1 \
+ -m 512M \
+ -no-user-config -nodefaults -display none \
+ -gdb unix:"$DIR"/_gdb.pipe,server=on,wait=off \
+ -no-reboot \
+ -kernel "$DIR"/_linux \
+ -initrd "$DIR"/_initrd.gz \
+ -append "${KERNEL_CMDLINE}" \
+ -serial stdio \
+ -chardev socket,id=charserial1,path="$DIR"/_gdb-serial.pipe,server=on,wait=off \
+ -device isa-serial,chardev=charserial1,id=serial1 \
+ 2>&1 | tee "$DIR/_output"
+
+set +x
+if grep -q "QEMU_TEST_SUCCESSFUL" "$DIR/_output"; then
+ echo
+ echo "QEMU tests: successful"
+ echo
+else
+ echo
+ echo "QEMU tests: failed"
+ echo
+ exit 1
+fi
diff --git a/tools/Makefile.am b/tools/Makefile.am
index ec2eb66..7beae57 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,6 +1,6 @@
include $(top_srcdir)/Make_global.am
-noinst_PROGRAMS = gtp-link \
+bin_PROGRAMS = gtp-link \
gtp-tunnel
gtp_link_SOURCES = gtp-link.c
diff --git a/tools/gtp-link.c b/tools/gtp-link.c
index 8367c6e..0b14bc5 100644
--- a/tools/gtp-link.c
+++ b/tools/gtp-link.c
@@ -39,14 +39,106 @@
#include <linux/if_link.h>
#include <libgtpnl/gtpnl.h>
+#include <errno.h>
+
+struct gtp_server_sock {
+ int family;
+ int fd1;
+ int fd2;
+ socklen_t len;
+ struct {
+ union {
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ } fd1;
+ union {
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ } fd2;
+ } sockaddr;
+ union {
+ struct in_addr v4;
+ struct in6_addr v6;
+ } addr;
+};
+
+static void setup_sockaddr_in(struct sockaddr_in *sockaddr, struct in_addr *in,
+ uint16_t port)
+{
+ sockaddr->sin_family = AF_INET;
+ sockaddr->sin_port = htons(port);
+ sockaddr->sin_addr = *in;
+}
+
+static void setup_sockaddr_in6(struct sockaddr_in6 *sockaddr, struct in6_addr *in6,
+ uint16_t port)
+{
+ sockaddr->sin6_family = AF_INET6;
+ sockaddr->sin6_port = htons(port);
+ sockaddr->sin6_addr = *in6;
+}
+
+static int setup_socket(struct gtp_server_sock *gtp_sock, int family)
+{
+ int one = 1;
+
+ gtp_sock->fd1 = socket(family, SOCK_DGRAM, 0);
+ gtp_sock->fd2 = socket(family, SOCK_DGRAM, 0);
+
+ if (gtp_sock->fd1 < 0 || gtp_sock->fd2 < 0)
+ return -1;
+
+ gtp_sock->family = family;
+
+ switch (family) {
+ case AF_INET:
+ gtp_sock->len = sizeof(struct sockaddr_in);
+ setup_sockaddr_in(&gtp_sock->sockaddr.fd1.in,
+ &gtp_sock->addr.v4, 3386);
+ setup_sockaddr_in(&gtp_sock->sockaddr.fd2.in,
+ &gtp_sock->addr.v4, 2152);
+ break;
+ case AF_INET6:
+ gtp_sock->len = sizeof(struct sockaddr_in6);
+ setup_sockaddr_in6(&gtp_sock->sockaddr.fd1.in6,
+ &gtp_sock->addr.v6, 3386);
+ setup_sockaddr_in6(&gtp_sock->sockaddr.fd2.in6,
+ &gtp_sock->addr.v6, 2152);
+ if (setsockopt(gtp_sock->fd1, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0)
+ perror("setsockopt IPV6_V6ONLY: ");
+ if (setsockopt(gtp_sock->fd2, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0)
+ perror("setsockopt IPV6_V6ONLY: ");
+ break;
+ }
+
+ return 0;
+}
+
+static void close_socket(struct gtp_server_sock *gtp_sock)
+{
+ if (gtp_sock->fd1 != -1) {
+ close(gtp_sock->fd1);
+ gtp_sock->fd1 = -1;
+ }
+
+ if (gtp_sock->fd2 != -1) {
+ close(gtp_sock->fd2);
+ gtp_sock->fd2 = -1;
+ }
+}
int main(int argc, char *argv[])
{
char buf[MNL_SOCKET_BUFFER_SIZE];
- int ret, sgsn_mode = 0;
+ struct gtp_server_sock gtp_sock;
+ int ret, sgsn_mode = 0, family;
+ const char *addr = NULL;
+
+ memset(&gtp_sock, 0, sizeof(gtp_sock));
if (argc < 3) {
- printf("Usage: %s <add|del> <device>\n", argv[0]);
+ printf("Usage: %s add <device> <family> [--sgsn] [<address>]\n", argv[0]);
+ printf(" %s del <device>\n", argv[0]);
exit(EXIT_FAILURE);
}
@@ -56,45 +148,78 @@ int main(int argc, char *argv[])
perror("gtp_dev_destroy");
return 0;
+ } else if (!strcmp(argv[1], "add")) {
+ if (argc < 4) {
+ printf("Usage: %s add <device> <family> [--sgsn] [<address>]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if (argc == 5) {
+ if (!strcmp(argv[4], "--sgsn"))
+ sgsn_mode = 1;
+ else
+ addr = argv[4];
+ }
+
+ if (argc == 6) {
+ if (!strcmp(argv[4], "--sgsn"))
+ sgsn_mode = 1;
+
+ addr = argv[5];
+ }
+ }
+
+ if (!strcmp(argv[3], "ip"))
+ family = AF_INET;
+ else if (!strcmp(argv[3], "ip6"))
+ family = AF_INET6;
+ else {
+ fprintf(stderr, "unsupport family `%s', expecting `ip' or `ip6'\n",
+ argv[3]);
+ exit(EXIT_FAILURE);
+ }
+
+ if (addr) {
+ if (!inet_pton(family, addr, &gtp_sock.addr)) {
+ fprintf(stderr, "invalid listener address: %s\n",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ switch (family) {
+ case AF_INET:
+ gtp_sock.addr.v4.s_addr = INADDR_ANY;
+ break;
+ case AF_INET6:
+ gtp_sock.addr.v6 = in6addr_any;
+ break;
+ }
+ }
+
+ if (setup_socket(&gtp_sock, family) < 0) {
+ perror("socket");
+ close_socket(&gtp_sock);
+ exit(EXIT_FAILURE);
}
- if (argc > 3 && !strcmp(argv[3], "--sgsn"))
- sgsn_mode = 1;
-
- int fd1 = socket(AF_INET, SOCK_DGRAM, 0);
- int fd2 = socket(AF_INET, SOCK_DGRAM, 0);
- struct sockaddr_in sockaddr_fd1 = {
- .sin_family = AF_INET,
- .sin_port = htons(3386),
- .sin_addr = {
- .s_addr = INADDR_ANY,
- },
- };
- struct sockaddr_in sockaddr_fd2 = {
- .sin_family = AF_INET,
- .sin_port = htons(2152),
- .sin_addr = {
- .s_addr = INADDR_ANY,
- },
- };
-
- if (bind(fd1, (struct sockaddr *) &sockaddr_fd1,
- sizeof(sockaddr_fd1)) < 0) {
+ if (bind(gtp_sock.fd1, (struct sockaddr *) &gtp_sock.sockaddr.fd1, gtp_sock.len) < 0) {
perror("bind");
+ close_socket(&gtp_sock);
exit(EXIT_FAILURE);
}
- if (bind(fd2, (struct sockaddr *) &sockaddr_fd2,
- sizeof(sockaddr_fd2)) < 0) {
+ if (bind(gtp_sock.fd2, (struct sockaddr *) &gtp_sock.sockaddr.fd2, gtp_sock.len) < 0) {
perror("bind");
+ close_socket(&gtp_sock);
exit(EXIT_FAILURE);
}
if (sgsn_mode)
- ret = gtp_dev_create_sgsn(-1, argv[2], fd1, fd2);
+ ret = gtp_dev_create_sgsn(-1, argv[2], gtp_sock.fd1, gtp_sock.fd2);
else
- ret = gtp_dev_create(-1, argv[2], fd1, fd2);
+ ret = gtp_dev_create(-1, argv[2], gtp_sock.fd1, gtp_sock.fd2);
if (ret < 0) {
perror("cannot create GTP device\n");
+ close_socket(&gtp_sock);
exit(EXIT_FAILURE);
}
@@ -102,11 +227,13 @@ int main(int argc, char *argv[])
"this process running for testing purposes.\n");
while (1) {
- struct sockaddr_in addr;
- socklen_t len = sizeof(addr);
+ union {
+ struct sockaddr_in addr;
+ struct sockaddr_in6 addr6;
+ } sock;
- ret = recvfrom(fd1, buf, sizeof(buf), 0,
- (struct sockaddr *)&addr, &len);
+ ret = recvfrom(gtp_sock.fd1, buf, sizeof(buf), 0,
+ (struct sockaddr *)&sock, &gtp_sock.len);
printf("received %d bytes via UDP socket\n", ret);
}
diff --git a/tools/gtp-tunnel.c b/tools/gtp-tunnel.c
index 513ff4a..2a5540a 100644
--- a/tools/gtp-tunnel.c
+++ b/tools/gtp-tunnel.c
@@ -49,14 +49,35 @@ static void add_usage(const char *name)
name);
}
+static void set_addr(const char *addr, bool is_ms, struct gtp_tunnel *t)
+{
+ struct in_addr ip4;
+ struct in6_addr ip6;
+
+ if (inet_pton(AF_INET, addr, &ip4) == 1) {
+ if (is_ms)
+ return gtp_tunnel_set_ms_ip4(t, &ip4);
+ return gtp_tunnel_set_sgsn_ip4(t, &ip4);
+ }
+
+ if (inet_pton(AF_INET6, addr, &ip6) == 1) {
+ if (is_ms)
+ return gtp_tunnel_set_ms_ip6(t, &ip6);
+ return gtp_tunnel_set_sgsn_ip6(t, &ip6);
+ }
+
+ fprintf(stderr, "bad address: %s\n", addr);
+ exit(EXIT_FAILURE);
+}
+
static int
add_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
{
struct gtp_tunnel *t;
uint32_t gtp_ifidx;
- struct in_addr ms, sgsn;
uint32_t gtp_version;
int optidx;
+ int ret;
if (argc < 7 || argc > 8) {
add_usage(argv[0]);
@@ -88,29 +109,21 @@ add_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
optidx++;
- if (gtp_version == GTP_V0)
+ if (gtp_version == GTP_V0) {
gtp_tunnel_set_tid(t, atoi(argv[optidx++]));
- else if (gtp_version == GTP_V1) {
+ gtp_tunnel_set_flowid(t, 0);
+ } else if (gtp_version == GTP_V1) {
gtp_tunnel_set_i_tei(t, atoi(argv[optidx++]));
gtp_tunnel_set_o_tei(t, atoi(argv[optidx++]));
}
- if (inet_aton(argv[optidx++], &ms) < 0) {
- perror("bad address for ms");
- exit(EXIT_FAILURE);
- }
- gtp_tunnel_set_ms_ip4(t, &ms);
+ set_addr(argv[optidx++], true, t);
+ set_addr(argv[optidx++], false, t);
- if (inet_aton(argv[optidx++], &sgsn) < 0) {
- perror("bad address for sgsn");
- exit(EXIT_FAILURE);
- }
- gtp_tunnel_set_sgsn_ip4(t, &sgsn);
-
- gtp_add_tunnel(genl_id, nl, t);
+ ret = gtp_add_tunnel(genl_id, nl, t);
gtp_tunnel_free(t);
- return 0;
+ return ret;
}
static int
@@ -118,9 +131,10 @@ del_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
{
struct gtp_tunnel *t;
uint32_t gtp_ifidx;
+ int ret;
- if (argc != 5) {
- printf("%s add <gtp device> <version> <tid>\n",
+ if (argc != 6) {
+ printf("%s del <gtp device> <version> <tid|i_tei> <family>\n",
argv[0]);
return EXIT_FAILURE;
}
@@ -130,6 +144,7 @@ del_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
gtp_ifidx = if_nametoindex(argv[2]);
if (gtp_ifidx == 0) {
fprintf(stderr, "wrong GTP interface %s\n", argv[2]);
+ gtp_tunnel_free(t);
return EXIT_FAILURE;
}
gtp_tunnel_set_ifidx(t, gtp_ifidx);
@@ -143,13 +158,24 @@ del_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
} else {
fprintf(stderr, "wrong GTP version %s, use v0 or v1\n",
argv[3]);
+ gtp_tunnel_free(t);
return EXIT_FAILURE;
}
- gtp_del_tunnel(genl_id, nl, t);
+ if (strcmp(argv[5], "ip") == 0) {
+ gtp_tunnel_set_family(t, AF_INET);
+ } else if (strcmp(argv[5], "ip6") == 0) {
+ gtp_tunnel_set_family(t, AF_INET6);
+ } else {
+ fprintf(stderr, "wrong family %s, use ip or ip6\n", argv[5]);
+ gtp_tunnel_free(t);
+ return EXIT_FAILURE;
+ }
+
+ ret = gtp_del_tunnel(genl_id, nl, t);
gtp_tunnel_free(t);
- return 0;
+ return ret;
}
struct gtp_pdp {