diff options
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 @@ -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 @@ -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; @@ -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(>p_sock->sockaddr.fd1.in, + >p_sock->addr.v4, 3386); + setup_sockaddr_in(>p_sock->sockaddr.fd2.in, + >p_sock->addr.v4, 2152); + break; + case AF_INET6: + gtp_sock->len = sizeof(struct sockaddr_in6); + setup_sockaddr_in6(>p_sock->sockaddr.fd1.in6, + >p_sock->addr.v6, 3386); + setup_sockaddr_in6(>p_sock->sockaddr.fd2.in6, + >p_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(>p_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, >p_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(>p_sock, family) < 0) { + perror("socket"); + close_socket(>p_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 *) >p_sock.sockaddr.fd1, gtp_sock.len) < 0) { perror("bind"); + close_socket(>p_sock); exit(EXIT_FAILURE); } - if (bind(fd2, (struct sockaddr *) &sockaddr_fd2, - sizeof(sockaddr_fd2)) < 0) { + if (bind(gtp_sock.fd2, (struct sockaddr *) >p_sock.sockaddr.fd2, gtp_sock.len) < 0) { perror("bind"); + close_socket(>p_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(>p_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, >p_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 { |