Compare commits

...

43 Commits

Author SHA1 Message Date
Keith Whyte
cea9044fe4 Implement reserved IPv4 addresses for IMSIs
Allows maintaining, via the VTY, a list of IMSI -> IPv4
address mappings to ensure the GGSN always assigns
the same IP address to a UE identified by the IMSI.

This is NOT an implenentation of static IP address support,
it simply reserves IPs from the dynamic IP pool. No checks
are done to enure that the address indeed forms part of the
defined pool, this is left up to the operator to configure
correctly.

It may be desired to always have the same IP address assigned
after a PDP context is deleted and recreated by the device,
by toggling mobile data, or airplane mode.
This could be useful, for example, in order to avoid having to
adjust firewall or packet capture filters. One might also have
a small subset of IMSIs on a network that should have a special
set of firewall rules, for access to internal infrastructure or
traffic shaping exemptions, as some examples.

This is intented as a developer or small operator feature, but
it is not the intention here that one would go about assigning
'static' IP addresses to an entire HLR of subscribers via the
GGSN vty.
2023-07-14 04:59:24 +01:00
Keith
306e7fa853 Implement ICMP response for inactive IP address.
Send ICMP Host Unreachable packets back on the tun device
in reponse to a packet received for an IP address that is
not active in our pool (No active pdp context)

Only IPv4 implemented.

Change-Id: Ia2c708feab14bb4cada00b0a90e0cb56d680d1aa
2023-07-14 04:59:24 +01:00
Oliver Smith
59f1539ece systemd: depend on networking-online.target
Related: SYS#6400
Change-Id: I29e547242b2ed1cfc4750c7d7e5f8636c2e8f3dc
2023-05-26 14:10:45 +02:00
Oliver Smith
eff88c08e7 debian: set compat level to 10
Related: OS#5958
Change-Id: I4b2988fffba12cc84ff0834bb9ef0f3d9de2bcda
2023-04-25 16:48:22 +02:00
Oliver Smith
92ac7249f9 doc/manuals/chapters/configuration: fix typo
Change-Id: I0e9d2c77200c7c8b49aec669bc39ca91d5d4cf1f
2023-04-11 17:18:34 +02:00
Vadim Yanitskiy
5cf6b75dc9 tests: use -no-install libtool flag to avoid ./lt-* scripts
This option should be used for any executables which are used only
for testing, or for generating other files and are consequently never
installed.  By specifying this option, we are telling Libtool that
the executable it links will only ever be executed from where it is
built in the build tree.  Libtool is usually able to considerably
speed up the link process for such executables.

Change-Id: I2ca675e93dc5b34bb08d3b841adc115e93558137
2023-03-11 04:36:59 +07:00
Vadim Yanitskiy
4aa2e417c9 Do not hard-code -g and -O2 in CFLAGS
Let the user decide on the optimization level and debugging info.

Change-Id: I4b0b523b7dac4d67413bda37b546964262e5ea0d
2023-03-09 17:17:03 +07:00
Vadim Yanitskiy
f14c056310 Bump version: 1.10.0.4-bf69 → 1.10.1
Change-Id: Ibde9f259bccce29638d35efbd597669f5584e295
2023-02-27 22:35:47 +07:00
Vadim Yanitskiy
bf69ddbfef gtp: use OSMO_ASSERT() in gtp_new()
When using built-in static_assert() [1], gcc v12.2.1 fails:

In file included from gsn.c:27:
gsn.c: In function 'gtp_new':
gsn.c:444:54: error: expression in static assertion is not constant
  444 |         osmo_static_assert(gtp_T_defs[0].default_val != 0, first_default_val_not_zero);
      |                                                      ^

The reason is likely that gtp_T_defs[] is not const, so it cannot
be assert()ed statically.  With the current osmo_static_assert()
implementation, this assert does nothing.  One can change the
gtp_T_defs[0].default_val to 0 and the code will still compile.

Change-Id: Ia8af1736b63d501661046fe70befe5bbabc1045a
Related: [1] libosmocore.git I5ca34bc14c05e8c38c721d7df33feb1c6c41c76e
2023-02-27 17:07:26 +07:00
Vadim Yanitskiy
70a4e2e6f8 gtp/gsn.c: fix 'No newline at end of file'
git complains if it's missing, vim adds it automatically.

Change-Id: I3b4808a76da89e65b934d818e7ca280bc0651483
2023-02-27 17:07:26 +07:00
Vadim Yanitskiy
99afe979ef lib/icmpv6.h: fix struct icmpv6_{radv_hdr,opt_prefix}
Fix wrong field order in the big-endian variants.

Change-Id: Ifaa63bb5496e056805bd13b964c8b430fb11c24c
2023-02-27 17:07:05 +07:00
Oliver Smith
35066fb0b0 debian/libgtp6.shlibs: new file
List the most recent library version where new symbols where added, so
debian properly upgrades libgtp6 when upgrading osmo-sgsn from the
version that is currently in Debian to a version from the Osmocom
repositories.

Closes: OS#5318
Change-Id: Ida5dae4655c0acaeb377bc9d556a2ac333bca10a
2023-02-08 18:11:54 +01:00
Pau Espin Pedrol
55fe62f634 Bump version: 1.9.0.10-4fac-dirty → 1.10.0
Change-Id: I553fb72c577181c32005093eaf4fa986ae0e6ca8
2023-02-07 14:29:49 +01:00
Pau Espin Pedrol
4fac842826 Fix typos in comments and VTY descriptions
Change-Id: I359425152dc18d29c57047f1b10942480b7a61e5
2023-01-17 14:17:18 +01:00
arehbein
97f60e3dca osmo-ggsn: Transition to use of 'telnet_init_default'
Related: OS#5809
Change-Id: I51b7c175192759e26d1791723540841e72879b02
2022-12-23 11:13:31 +00:00
Max
a727e6ed38 ctrl: take both address and port from vty config
Change-Id: Ib31d67591657e308eebd1e6b7e23f79e6a3656e9
2022-12-17 21:14:57 +03:00
Pau Espin Pedrol
3a55b89777 gtp: Introduce VTY configurable GTP timer X3
This timer controls the amount of time a resp message transmitted by the
local gsn is to be stored in the resp queue. This is used in order to
detect duplicate requests received, since GTP states the exact same
response should be answered if a duplicate request is received.

Prior to this patch, this timer was hardcoded to 60 seconds.
This patch actually should be set, in general, to a value
equal than (T3-RESPONSE * N3-REQUESTS) values configured at
the peer, since that is the maximum period during which the local gsn
expects to receive req retransmissions from the peer.
Hence, this value must be user configurable to adapt it to the peers
connected to the GSN.

The 60 seconds hardcoded value is therefore changed to default to our
local (T3-RESPONSE * N3-REQUESTS), since the most common scenario for
osmo-ggsn/osmo-sgsn is to run it against a peer osmo-sgsn/osmo-ggsn,
which will have the same values by default.
This way we avoid by default caching response messages for way too long,
potentially filling the queue.

Related: OS#5485
Change-Id: Ia15c1cfd201d7c43e9a1d6ceb6725ddf392d2c65
2022-11-04 11:21:25 +01:00
Pau Espin Pedrol
9f1f747d8e ggsn: Introduce tdef and make it configurable over VTY
Related: OS#5485
Change-Id: I10bc8e2e197c0e8753b23b684b5ae41025672bf7
2022-11-02 20:33:39 +01:00
Pau Espin Pedrol
b9036af7ca Use rate_ctr for gsn_t available_counters
This way they can be inspected with regular osmocom means.

Change-Id: I529305b4f824600c6e733a3c0d2c2c6673f99faf
2022-11-02 18:41:38 +01:00
Pau Espin Pedrol
724ecc6680 Split gsn_t related APIs out of gtp.{c,h}
This way we split the gsn_t object API/logic from the protocol (message
handling) code.

Change-Id: I47cebb51bf08b9fcf7f115fc8dbea5f3493d4388
2022-11-02 18:41:34 +01:00
Pau Espin Pedrol
0d3bd3435f cosmetic: gtp: Fix typo in comment
Change-Id: I54b80bba3126cb3ae534938e253721961d4e08c4
2022-11-02 13:22:17 +01:00
Max
3ed252b58e Ignore .deb build byproducts
Change-Id: Iec63ef5ea0acfc5e6621054926be15ae4754d65d
2022-08-30 19:24:48 +07:00
Max
ac802e63d7 Set working directory in systemd service file
By default systemd will execute service with root directory (or home directory for user instance) which might result in
attempts to create files in unexpected place. Let's set it to 'osmocom' subdir of state directory (/var/lib for system
instance) instead.

Related: OS#4821
Change-Id: Idffc115c21cac77f6f43356333de538ba549fc6a
2022-08-30 19:24:48 +07:00
Pau Espin Pedrol
bc583d9763 Bump version: 1.8.0.13-ade4-dirty → 1.9.0
Change-Id: Id61cbe354437233fc6baf187ea90284da6a6944b
2022-06-28 17:48:22 +02:00
Harald Welte
ade4dc191b update git URLs (git -> https; gitea)
Change-Id: I9d59b62493bcdcb1bdbfbfd0525bae2988359f27
2022-06-18 12:04:24 +02:00
Vadim Yanitskiy
cd05da79e7 tests: use 'check_PROGRAMS' instead of 'noinst_PROGRAMS'
When using 'check_PROGRAMS', autoconf/automake generates smarter
Makefiles, so that the test programs are not being compiled during
the normal 'make all', but only during 'make check'.

Change-Id: Ia8b8dade0056c51d2dd1d814a89d1de064597344
2022-04-13 19:55:33 +03:00
Pau Espin Pedrol
5545bcea5d pco.h: Fix typo in reference to spec
Change-Id: Ic428892161123b62d25a7619128ef7325bf85500
2022-03-24 12:51:17 +01:00
Pau Espin Pedrol
c97286f839 gtp: Fix typo in comment
Change-Id: I0e38e0966081d8b37c3f816f5330b4f52f81b7fa
2022-03-07 16:22:53 +01:00
Pau Espin Pedrol
f471800168 gtp: Log retrans queue register&free entries
Change-Id: I4e12376652fc7a6a96fbdcb579dbe916c1473012
2022-03-07 16:22:53 +01:00
Pau Espin Pedrol
bdf0697a5a gtp: Specify retrans queue name & seqnum in log lines
Change-Id: I4f193d7a482ace33afd8526b5f50d2d03467d5fa
2022-03-07 12:55:07 +01:00
Pau Espin Pedrol
674a912fb5 gtp: Small log improvements in gtp_create_pdp_ind()
Drop unneeded log line, rewrite line to better fit code path.

Change-Id: Id254e04d539cc055fee8c16fb66cd897b041557e
2022-03-07 12:44:15 +01:00
Pau Espin Pedrol
1bf3b3d0f9 gtp: Log detection of rx duplicate
Change-Id: I8bc9143db6743ad4fae2fe6d6fe0417648e9eec9
2022-03-07 12:20:13 +01:00
Pau Espin Pedrol
fb9303c610 gtp: Use switch statement in gtp_create_pdp_ind()
Double if had to be changed to if-else anyway, so let's simply use a
switch statement.

Change-Id: I91e8722947e58776742521d89abef8ae7584cb25
2022-03-07 11:34:30 +01:00
Pau Espin Pedrol
0585769741 libgtp: Define retransmit QUEUE_SIZE relative to PDP_MAX (increase)
QUEUE_SIZE holds the number of pending transmitted messages
which can be handled concurrently.
Current value was 1024, same as PDP contexts (PDP_MAX). However, that
seems to be a quite low amount, which can be filled under certain
conditions, for instance if recovery procedure is triggered on the GSN
which is running full (around PDP_MAX pdp contexts created).
In this scenario, the GSN would need to send around PDP_MAX concurrent
messages (DeletePDPContextReq), which means the queue would very likely
end up being full.
Hence, let's define QUEUE_SIZE based on PDP_MAX, and set it to twice the
size to make sure it won't be filled in extreme conditions.

Change-Id: I6034b0fab2b2e5962314c2fca2f893246ce5cf4f
2022-03-01 12:39:08 +01:00
Pau Espin Pedrol
9b288b788e libgtp: Fix ggsn crash if pdp alloc array is full (PDP_MAX)
osmo-ggsn crashes when concurrent pdp context num 1024 is created, due to
the gsn->pdpa array (of size PDP_MAX, 1024) being full.
The crash happens because return code of gtp_pdp_newpdp was not checked,
and hence a pointer "pdp" pointing to a temporary not-fully-allocated
object was being passed to gsn->cb_create_context_ind() callback.

Let's avoid crashing and instead reject the PDP context.

Related: OS#5469
Change-Id: I0d94ffad97eb4fef477d981bf285bf99740592a3
2022-03-01 12:38:58 +01:00
Pau Espin Pedrol
134ac7e7c8 vty: Fix cmd 'no echo-interval' doing nothing
It was incorrectly implemented and in practice was a NOOP.

Change-Id: I5e03c4965d05871d3f2e56675da6e75af0ec18c2
2022-02-25 17:25:00 +01:00
Pau Espin Pedrol
46f04343a5 tests: in46a_test: Make coverity happy when calling in46a_from_eua
Coverity warns around that test code:
"Overrunning struct type in46_addr of 20 bytes by passing it to a
function which accesses it at byte offset 39."

That's basically because in64a_from_eua expects to be passed a 2 element
array to be filled. The second element, though, is only accessed in the
case where an IPv4v6 EUA is passed, so in the cases where the test
explicitly passes an IPv4 or IPv6 EUA it's not really an issue, hence
coverity throwing a false positive here.
Let's anyway rewrite the code to pass a 2 element array for completeness,
since it doesn't hurt and makes coverity happy.

Related: Coverity CID#249006
Change-Id: Idfc9104f48eeee6e7f11ebc5c17d4b0e4b2fe9e2
2022-02-09 09:41:46 +01:00
Pau Espin Pedrol
a3ca2d185b Bump version: 1.7.1.20-8cbd-dirty → 1.8.0
Change-Id: I21502c6e0b804237fe9bd8f5579dbabd519d6d51
2021-11-16 13:49:16 +01:00
Pau Espin Pedrol
8cbdd21867 gtp_echo_responder: report invalid chars present in node-feautres cmdline arg as error
from "man strtoul":
"""
If endptr is not NULL, strtoul() stores the address of the first invalid character in *endptr.
In particular, if *nptr is not '\0' but **endptr is '\0' on return, the entire string is valid.
"""

Fixes: ae81195418
Change-Id: I89d26a575ef81ee17483db035924354588d9d094
2021-10-08 17:28:16 +02:00
Pau Espin Pedrol
ae81195418 Introduce program gtp-echo-responder
This is a small standalone program (under MIT license, hence cannot make
use of libosmocore) whose only purpose is to answer GTPC (v1 and v2)
Echo Request messages with Echo Reply ones, with information provided
from the command line.

A small python script companion is provided to easily test the program.

Related: SYS#5598
Change-Id: Ibdd6d8f6920571db0c60cf8b3b25d541b15ad3f1
2021-10-04 14:06:51 +02:00
Pau Espin Pedrol
6ee5fa939a cosmetic: configure.ac: Fix tabulation in line
Change-Id: I5cfc90ace5f9cc9c3fe4dde7aeccbdf1909da007
2021-09-23 13:35:13 +02:00
Pau Espin Pedrol
b6a0e3fd2e ggsn: Fix heap-use-after-free during Recovery without associated PDP
Related: OS#4641
Change-Id: Ib4dca2e30e723a196084b0fa0040fbceca835359
2021-06-10 19:41:00 +02:00
Pau Espin Pedrol
bd2b55679e ggsn: Log tun fd write errors
Change-Id: I5f681b5edcc4cf525629d2078ae0c0ffd7ebb72d
2021-06-01 12:00:21 +02:00
39 changed files with 9161 additions and 1340 deletions

4
.gitignore vendored
View File

@@ -82,3 +82,7 @@ doc/manuals/build
doc/manuals/vty/ggsn_vty_reference.xml
contrib/osmo-ggsn.spec
/debian/gtp-echo-responder-dbg/
/debian/gtp-echo-responder/
/debian/osmo-ggsn-doc/
/utils/gtp-echo-responder

View File

@@ -1,5 +1,5 @@
## Process this file with automake to produce Makefile.in
SUBDIRS = lib gtp ggsn sgsnemu doc contrib tests
SUBDIRS = lib gtp ggsn sgsnemu doc contrib utils tests
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libgtp.pc

View File

@@ -20,9 +20,9 @@ GIT Repository
You can clone from the official osmo-ggsn.git repository using
git clone git://git.osmocom.org/osmo-ggsn.git
git clone https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn
There is a cgit interface at http://git.osmocom.org/osmo-ggsn/
There is a web interface at <https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn>
Documentation
-------------

View File

@@ -7,4 +7,3 @@
# If any interfaces have been added since the last public release: c:r:a + 1.
# If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line
libgtp ADD gtp_ran_info_relay_req, gtp_set_cb_ran_info_relay_ind

View File

@@ -154,9 +154,9 @@ adl_FUNC_GETOPT_LONG
AM_INIT_AUTOMAKE([foreign])
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.5.0)
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.8.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.8.0)
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
@@ -257,8 +257,9 @@ AC_CONFIG_FILES([Makefile
lib/Makefile
intl/Makefile
po/Makefile
utils/Makefile
sgsnemu/Makefile
doc/manuals/Makefile
doc/manuals/Makefile
contrib/Makefile
contrib/systemd/Makefile
contrib/osmo-ggsn.spec

View File

@@ -30,9 +30,9 @@ BuildRequires: pkgconfig >= 0.20
BuildRequires: systemd-rpm-macros
%endif
BuildRequires: pkgconfig(libgtpnl) >= 1.2.0
BuildRequires: pkgconfig(libosmocore) >= 1.5.0
BuildRequires: pkgconfig(libosmoctrl) >= 1.5.0
BuildRequires: pkgconfig(libosmovty) >= 1.5.0
BuildRequires: pkgconfig(libosmocore) >= 1.8.0
BuildRequires: pkgconfig(libosmoctrl) >= 1.8.0
BuildRequires: pkgconfig(libosmovty) >= 1.8.0
Obsoletes: openggsn
%{?systemd_requires}
@@ -61,6 +61,15 @@ libgtp implements the GPRS Tunneling Protocol between SGSN and GGSN.
This subpackage contains libraries and header files for developing
applications that want to make use of libgtp.
%package -n gtp-echo-responder
Summary: Small program answering GTP ECHO Request with GTP ECHO Response
License: MIT
Group: System/Libraries
%description -n gtp-echo-responder
Small program answering GTP ECHO Request with GTP ECHO Response for both GTPCv1
and GTPCv2.
%prep
%setup -q
@@ -122,4 +131,7 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%{_libdir}/libgtp.so
%{_libdir}/pkgconfig/libgtp.pc
%files -n gtp-echo-responder
%{_bindir}/gtp-echo-responder
%changelog

View File

@@ -1,10 +1,13 @@
[Unit]
Description=OsmoGGSN
After=networking.service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Restart=always
StateDirectory=osmocom
WorkingDirectory=%S/osmocom
ExecStart=/usr/bin/osmo-ggsn -c /etc/osmocom/osmo-ggsn.cfg
RestartSec=2
RestartPreventExitStatus=1

85
debian/changelog vendored
View File

@@ -1,3 +1,88 @@
osmo-ggsn (1.10.1) unstable; urgency=medium
[ Oliver Smith ]
* debian/libgtp6.shlibs: new file
[ Vadim Yanitskiy ]
* lib/icmpv6.h: fix struct icmpv6_{radv_hdr,opt_prefix}
* gtp/gsn.c: fix 'No newline at end of file'
* gtp: use OSMO_ASSERT() in gtp_new()
-- Vadim Yanitskiy <vyanitskiy@sysmocom.de> Mon, 27 Feb 2023 22:35:47 +0700
osmo-ggsn (1.10.0) unstable; urgency=medium
[ Max ]
* Set working directory in systemd service file
* Ignore .deb build byproducts
* ctrl: take both address and port from vty config
[ Pau Espin Pedrol ]
* cosmetic: gtp: Fix typo in comment
* Split gsn_t related APIs out of gtp.{c,h}
* Use rate_ctr for gsn_t available_counters
* ggsn: Introduce tdef and make it configurable over VTY
* gtp: Introduce VTY configurable GTP timer X3
* Fix typos in comments and VTY descriptions
[ arehbein ]
* osmo-ggsn: Transition to use of 'telnet_init_default'
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 07 Feb 2023 14:29:48 +0100
osmo-ggsn (1.9.0) unstable; urgency=medium
[ Pau Espin Pedrol ]
* tests: in46a_test: Make coverity happy when calling in46a_from_eua
* vty: Fix cmd 'no echo-interval' doing nothing
* libgtp: Fix ggsn crash if pdp alloc array is full (PDP_MAX)
* libgtp: Define retransmit QUEUE_SIZE relative to PDP_MAX (increase)
* gtp: Use switch statement in gtp_create_pdp_ind()
* gtp: Log detection of rx duplicate
* gtp: Small log improvements in gtp_create_pdp_ind()
* gtp: Specify retrans queue name & seqnum in log lines
* gtp: Log retrans queue register&free entries
* gtp: Fix typo in comment
* pco.h: Fix typo in reference to spec
[ Vadim Yanitskiy ]
* tests: use 'check_PROGRAMS' instead of 'noinst_PROGRAMS'
[ Harald Welte ]
* update git URLs (git -> https; gitea)
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 28 Jun 2022 17:48:22 +0200
osmo-ggsn (1.8.0) unstable; urgency=medium
[ Oliver Smith ]
* doc/examples/Makefile.am: add sgsnemu.conf
* doc/examples/osmo-ggsn-kernel-gtp.cfg: new file
* doc/manuals: describe GTP-U kernel module
* gitignore: add ggsn_vty_reference.xml
[ Harald Welte ]
* Don't install osmo-ggsn-kernel-gtp.cfg to /etc/osmocom/
* Don't install sgsnemu.conf to /etc/osmocom/
* ggsn: Reject PDP CTX ACT for static IP addresses
* vty: Inform user that static IP addresses are not supported
[ Pau Espin Pedrol ]
* gtp: Update teic_confirmed only on resp success
* gtp: Rework parsing logic of UpdatePdpCtxResponse
* ggsn: Improve logging on incoming DL data packets
* gtp: Improve logging of failing pdp ctx resolution from TEI/TID
* cosmetic: gtpie.c: Fix trailing whitespace
* gtp: constify pointer arg
* gtp: Support tx/rx RAN Information Relay message
* ggsn: Log tun fd write errors
* ggsn: Fix heap-use-after-free during Recovery without associated PDP
* cosmetic: configure.ac: Fix tabulation in line
* Introduce program gtp-echo-responder
* gtp_echo_responder: report invalid chars present in node-feautres cmdline arg as error
-- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 16 Nov 2021 13:49:16 +0100
osmo-ggsn (1.7.1) unstable; urgency=medium
[ Harald Welte ]

2
debian/compat vendored
View File

@@ -1 +1 @@
9
10

23
debian/control vendored
View File

@@ -2,17 +2,17 @@ Source: osmo-ggsn
Maintainer: Osmocom team <openbsc@lists.osmocom.org>
Section: net
Priority: optional
Build-Depends: debhelper (>= 9),
Build-Depends: debhelper (>= 10),
autotools-dev,
pkg-config,
libdpkg-perl, git,
dh-autoreconf,
libosmocore-dev (>= 1.5.0),
libosmocore-dev (>= 1.8.0),
osmo-gsm-manuals-dev,
libgtpnl-dev (>= 1.2.0)
Standards-Version: 3.9.6
Vcs-Browser: http://git.osmocom.org/osmo-ggsn/
Vcs-Git: git://git.osmocom.org/osmo-ggsn
Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn
Homepage: https://projects.osmocom.org/projects/openggsn
Package: osmo-ggsn
@@ -38,6 +38,12 @@ Description: library implementing the GTP protocol between SGSN and GGSN
This library is part of OsmoGGSN and implements the GTP protocol between
SGSN (Serving GPRS support node) and GGSN.
Package: gtp-echo-responder
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends}
Description: Small program answering GTP ECHO Request with GTP ECHO Response
Package: libgtp-dev
Architecture: any
Multi-Arch: same
@@ -63,6 +69,15 @@ Description: Debug symbols for OsmoGGSN
operators as the interface between the Internet and the rest of the
mobile network infrastructure.
Package: gtp-echo-responder-dbg
Section: debug
Architecture: any
Priority: extra
Depends: ${shlibs:Depends}, ${misc:Depends}, gtp-echo-responder (= ${binary:Version})
Multi-Arch: same
Description: Debug symbols for gtp-echo-responder
Small program answering GTP ECHO Request with GTP ECHO Response.
Package: libgtp-dbg
Section: debug
Architecture: any

5
debian/copyright vendored
View File

@@ -16,6 +16,11 @@ Files: lib/getopt.c
Copyright: 1987-2001 Free Software Foundation, Inc.
License: LGPL-2.1+
Files: utils/gtp_echo_responder.c
utils/gtp_echo_responder_test.py
Copyright: 2021 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
License: MIT
Files: debian/*
Copyright: 2010-2017 Harald Welte <laforge@gnumonks.org>
2016 Ruben Undheim <ruben.undheim@gmail.com>

1
debian/gtp-echo-responder.install vendored Normal file
View File

@@ -0,0 +1 @@
/usr/bin/gtp-echo-responder

2
debian/libgtp6.shlibs vendored Normal file
View File

@@ -0,0 +1,2 @@
# Most recent version of the package that added new symbols (OS#5318)
libgtp 6 libgtp6 (>= 1.8.0)

View File

@@ -302,7 +302,7 @@ Name=apn0 <1>
Address=192.168.7.1/24 <2>
IPMasquerade=yes <3>
----
<1> The netowrk device name, which must match the one in the apn0.netdev unit file above
<1> The network device name, which must match the one in the apn0.netdev unit file above
<2> The local IP address configured on the device
<3> Requesting systemd to configure IP masquerading for this interface. Depending on your needs,
You may not want this if you have proper end-to-end routing set up, and want to have transparent

View File

@@ -2,7 +2,7 @@ bin_PROGRAMS = osmo-ggsn
AM_LDFLAGS = @EXEC_LDFLAGS@
AM_CFLAGS = -O2 -D_GNU_SOURCE -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
AM_CFLAGS = -D_GNU_SOURCE -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
osmo_ggsn_LDADD = @EXEC_LDADD@ -lgtp -L../gtp ../lib/libmisc.a $(LIBOSMOCORE_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBOSMOVTY_LIBS)

View File

@@ -40,6 +40,7 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip6.h>
#include <osmocom/core/timer.h>
@@ -327,6 +328,74 @@ int apn_start(struct apn_ctx *apn)
return 0;
}
static struct imsi_map_entry *apn_imsi_map_lookup_by_imsi(const char *imsi, const struct apn_ctx_ip *ctx)
{
struct imsi_map_entry *map;
llist_for_each_entry(map, &ctx->imsi_ip_map, list) {
if (!strcmp(imsi, map->imsi))
return map;
}
return NULL;
}
static struct imsi_map_entry *apn_imsi_map_lookup_by_ip(const struct in46_addr *addr, const struct apn_ctx_ip *ctx)
{
struct imsi_map_entry *map;
if (!addr)
return NULL;
llist_for_each_entry(map, &ctx->imsi_ip_map, list) {
if (in46a_equal(addr, &map->addr))
return map;
}
return NULL;
}
int apn_imsi_ip_map_add(const char *imsi, const char *ip, struct apn_ctx_ip *ctx)
{
struct imsi_map_entry *map;
struct in46_addr addr = { 0 };
size_t t;
if (ippool_aton(&addr, &t, ip, 0))
return -EINVAL;
if (apn_imsi_map_lookup_by_imsi(imsi, ctx) || apn_imsi_map_lookup_by_ip(&addr, ctx))
return -EEXIST;
map = talloc_zero(NULL, struct imsi_map_entry);
if (!map)
return -ENOMEM;
if (ippool_aton(&map->addr, &t, ip, 0)) {
talloc_free(map);
return -EINVAL;
}
osmo_strlcpy(map->imsi, imsi, sizeof(map->imsi));
llist_add(&map->list, &ctx->imsi_ip_map);
return 0;
}
int apn_imsi_ip_map_del(const char *imsi, const char *ip, struct apn_ctx_ip *ctx)
{
struct imsi_map_entry *map;
map = apn_imsi_map_lookup_by_imsi(imsi, ctx);
if (!map)
return -ENODEV;
llist_del(&map->list);
talloc_free(map);
return 0;
}
static struct imsi_map_entry *imsi_has_reserved_ip(const char *imsi, struct apn_ctx_ip *ctx)
{
if (llist_empty(&ctx->imsi_ip_map))
return NULL;
return apn_imsi_map_lookup_by_imsi(imsi, ctx);
}
static bool send_trap(const struct gsn_t *gsn, const struct pdp_t *pdp, const struct ippoolm_t *member, const char *var)
{
char addrbuf[256];
@@ -384,7 +453,7 @@ static int delete_context(struct pdp_t *pdp)
return 0;
}
static bool apn_supports_ipv4(const struct apn_ctx *apn)
bool apn_supports_ipv4(const struct apn_ctx *apn)
{
if (apn->v4.cfg.static_prefix.addr.len || apn->v4.cfg.dynamic_prefix.addr.len)
return true;
@@ -441,6 +510,7 @@ int create_context_ind(struct pdp_t *pdp)
char *apn_name;
struct sgsn_peer *sgsn;
struct pdp_priv_t *pdp_priv;
struct imsi_map_entry *imsi_map;
apn_name = osmo_apn_to_str(name_buf, pdp->apn_req.v, pdp->apn_req.l);
LOGPPDP(LOGL_DEBUG, pdp, "Processing create PDP context request for APN '%s'\n",
@@ -494,6 +564,17 @@ int create_context_ind(struct pdp_t *pdp)
LOGPPDP(LOGL_ERROR, pdp, "Failed to store APN '%s'\n", apn->cfg.name);
pdp->apn_use.l = rc;
/* Check if we have a entry in the reserved IP map for this IMSI */
imsi_map = imsi_has_reserved_ip(imsi_gtp2str(&pdp->imsi), &apn->v4);
if (imsi_map) {
/* Override (prefill) any requested (dynamic|static) IP
* in the EUA with the one from the configuration map. */
addr[0].len = 4;
memcpy(&addr[0].v4.s_addr, &imsi_map->addr.v4, 4);
LOGPPDP(LOGL_INFO, pdp, "IMSI[%s] has an entry[%s] in reserved IP map\n",
imsi_gtp2str(&pdp->imsi), in46a_ntoa(&imsi_map->addr));
}
/* Allocate dynamic addresses from the pool */
for (i = 0; i < num_addr; i++) {
if (in46a_is_v4(&addr[i])) {
@@ -502,6 +583,18 @@ int create_context_ind(struct pdp_t *pdp)
goto err_wrong_af;
rc = ippool_newip(apn->v4.pool, &member, &addr[i], 0);
if (!imsi_map) {
/* This IMSI does not have a reserved IP, check that we did not assign one */
while (apn_imsi_map_lookup_by_ip(&member->addr, &apn->v4)) {
LOGPPDP(LOGL_INFO, pdp, "Returned IP[%s] is reserved, trying again.\n",
in46a_ntoa(&member->addr));
ippool_freeip(member->pool, member);
rc = ippool_newip(apn->v4.pool, &member, &addr[i], 0);
}
}
LOGPPDP(LOGL_INFO, pdp, "Got IP[%s] from the pool.\n", in46a_ntoa(&member->addr));
if (rc < 0)
goto err_pool_full;
/* copy back */
@@ -584,6 +677,64 @@ err_wrong_af:
return 0;
}
static uint16_t inet_checksum(void *data, int len) {
int nleft = len;
int sum = 0;
unsigned short *w = data;
unsigned short checksum = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1){
*(unsigned char *)(&checksum) = *(unsigned char *)w;
sum += checksum;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
checksum = ~sum;
return (checksum);
}
/* Generate and send an ICMP HOST UNREACHABLE Packet */
static void ipv4_host_unreach(struct tun_t *tun, void *pack, unsigned len)
{
char send_buf[sizeof(struct ip) + sizeof(struct icmp) + len];
len = len - 20;
struct iphdr *iph = (struct iphdr *)pack;
memset(send_buf, 0, sizeof(send_buf));
struct ip *ip = (struct ip *)send_buf;
struct icmp *icmp = (struct icmp *)(ip + 1);
ip->ip_v = 4;
ip->ip_hl = 5;
ip->ip_tos = 0;
ip->ip_len = htons(sizeof(send_buf));
ip->ip_id = rand();
ip->ip_off = 0;
ip->ip_ttl = 64;
ip->ip_sum = 0;
ip->ip_p = IPPROTO_ICMP;
ip->ip_src.s_addr = iph->daddr;
ip->ip_dst.s_addr = iph->saddr;
ip->ip_sum = inet_checksum(ip, sizeof(send_buf));
icmp->icmp_type = ICMP_DEST_UNREACH;
icmp->icmp_code = ICMP_HOST_UNREACH;
icmp->icmp_id = 0;
icmp->icmp_seq = 0;
icmp->icmp_cksum = 0;
memcpy(send_buf + sizeof(ip) + sizeof(icmp) + 12, pack, len);
icmp->icmp_cksum = inet_checksum(icmp, sizeof(icmp) + 12 + len);
tun_encaps(tun, send_buf, sizeof(send_buf));
}
/* Internet-originated IP packet, needs to be sent via GTP towards MS */
static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
{
@@ -660,6 +811,10 @@ static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
iph->version == 4 ?
inet_ntop(AF_INET, &iph->saddr, straddr[1], sizeof(straddr[1])) :
inet_ntop(AF_INET6, &ip6h->ip6_src, straddr[1], sizeof(straddr[1])));
/* TODO: Implement ipv6 */
if (iph->version != 4)
return 0;
ipv4_host_unreach(tun, pack, len);
}
return 0;
}

View File

@@ -6,7 +6,9 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/tdef.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include "../lib/tun.h"
#include "../lib/ippool.h"
@@ -34,6 +36,9 @@ struct apn_ctx_ip {
/* v4 address pool */
struct ippool_t *pool;
/* Static IMSI to IPv4 reserved address mappings. */
struct llist_head imsi_ip_map;
};
struct apn_name {
@@ -46,6 +51,15 @@ enum apn_gtpu_mode {
APN_GTPU_MODE_KERNEL_GTP,
};
/*
* IMSI to static IP map handling
*/
struct imsi_map_entry {
struct llist_head list;
char imsi[OSMO_IMSI_BUF_SIZE];
struct in46_addr addr;
};
struct apn_ctx {
/* list of APNs inside GGSN */
struct llist_head list;
@@ -65,9 +79,9 @@ struct apn_ctx {
uint32_t apn_type_mask;
/* GTP-U via TUN device or in Linux kernel */
enum apn_gtpu_mode gtpu_mode;
/* administratively shut-down (true) or not (false) */
/* administratively shut down (true) or not (false) */
bool shutdown;
/* transmit G-PDU sequeence numbers (true) or not (false) */
/* transmit G-PDU sequence numbers (true) or not (false) */
bool tx_gpdu_seq;
} cfg;
@@ -127,7 +141,7 @@ struct ggsn_ctx {
char *state_dir;
/* Time between Echo requests on each SGSN */
unsigned int echo_interval;
/* administratively shut-down (true) or not (false) */
/* administratively shut down (true) or not (false) */
bool shutdown;
} cfg;
@@ -152,6 +166,7 @@ struct apn_ctx *ggsn_find_or_create_apn(struct ggsn_ctx *ggsn, const char *name)
/* ggsn_main.c */
extern struct ctrl_handle *g_ctrlh;
extern void *tall_ggsn_ctx;
extern struct osmo_tdef_group ggsn_tdef_group[];
/* ggsn.c */
extern int ggsn_start(struct ggsn_ctx *ggsn);
@@ -159,6 +174,9 @@ extern int ggsn_stop(struct ggsn_ctx *ggsn);
extern int apn_start(struct apn_ctx *apn);
extern int apn_stop(struct apn_ctx *apn);
void ggsn_close_one_pdp(struct pdp_t *pdp);
bool apn_supports_ipv4(const struct apn_ctx *apn);
int apn_imsi_ip_map_add(const char *imsi, const char *ip, struct apn_ctx_ip *ctx);
int apn_imsi_ip_map_del(const char *imsi, const char *ip, struct apn_ctx_ip *ctx);
#define LOGPAPN(level, apn, fmt, args...) \
LOGP(DGGSN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args)
@@ -167,6 +185,3 @@ void ggsn_close_one_pdp(struct pdp_t *pdp);
LOGP(DGGSN, level, "GGSN(%s): " fmt, (ggsn)->cfg.name, ## args)
#define LOGPPDP(level, pdp, fmt, args...) LOGPDPX(DGGSN, level, pdp, fmt, ## args)
#define LOGTUN(level, tun, fmt, args...) \
LOGP(DTUN, level, "TUN(%s): " fmt, (tun)->devname, ## args)

View File

@@ -34,6 +34,7 @@
#include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/utils.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ctrl/control_cmd.h>
@@ -60,6 +61,11 @@ struct ul255_t apn;
static char *config_file = "osmo-ggsn.cfg";
struct osmo_tdef_group ggsn_tdef_group[] = {
{.name = "gtp", .tdefs = gtp_T_defs, .desc = "GTP (libgtp) timers" },
{ }
};
/* To exit gracefully. Used with GCC compilation flag -pg and gprof */
static void signal_handler(int s)
{
@@ -217,12 +223,11 @@ int main(int argc, char **argv)
exit(2);
}
rc = telnet_init_dynif(tall_ggsn_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_GGSN);
rc = telnet_init_default(tall_ggsn_ctx, NULL, OSMO_VTY_PORT_GGSN);
if (rc < 0)
exit(1);
g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(),
OSMO_CTRL_PORT_GGSN, NULL);
g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_GGSN, NULL);
if (!g_ctrlh) {
LOGP(DGGSN, LOGL_ERROR, "Failed to create CTRL interface.\n");
exit(1);

View File

@@ -33,6 +33,7 @@
#include <osmocom/vty/command.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/tdef_vty.h>
#include "../gtp/gtp.h"
#include "../gtp/pdp.h"
@@ -111,6 +112,7 @@ struct apn_ctx *ggsn_find_or_create_apn(struct ggsn_ctx *ggsn, const char *name)
apn->cfg.shutdown = true;
apn->cfg.tx_gpdu_seq = true;
INIT_LLIST_HEAD(&apn->cfg.name_list);
INIT_LLIST_HEAD(&apn->v4.imsi_ip_map);
llist_add_tail(&apn->list, &ggsn->apn_list);
return apn;
@@ -298,7 +300,7 @@ DEFUN(cfg_ggsn_no_default_apn, cfg_ggsn_no_default_apn_cmd,
DEFUN(cfg_ggsn_shutdown, cfg_ggsn_shutdown_cmd,
"shutdown ggsn",
"Put the GGSN in administrative shut-down\n" GGSN_STR)
"Put the GGSN in administrative shutdown\n" GGSN_STR)
{
struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
@@ -315,7 +317,7 @@ DEFUN(cfg_ggsn_shutdown, cfg_ggsn_shutdown_cmd,
DEFUN(cfg_ggsn_no_shutdown, cfg_ggsn_no_shutdown_cmd,
"no shutdown ggsn",
NO_STR GGSN_STR "Remove the GGSN from administrative shut-down\n")
NO_STR GGSN_STR "Remove the GGSN from administrative shutdown\n")
{
struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
@@ -343,7 +345,7 @@ static void show_one_sgsn(struct vty *vty, const struct sgsn_peer *sgsn, const c
DEFUN(cfg_ggsn_show_sgsn, cfg_ggsn_show_sgsn_cmd,
"show sgsn",
NO_STR GGSN_STR "Remove the GGSN from administrative shut-down\n")
NO_STR GGSN_STR "Remove the GGSN from administrative shutdown\n")
{
struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
struct sgsn_peer *sgsn;
@@ -389,10 +391,9 @@ DEFUN(cfg_ggsn_no_echo_interval, cfg_ggsn_no_echo_interval_cmd,
NO_STR "Send an echo request to this static GGSN every interval.\n")
{
struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
int prev_interval = ggsn->cfg.echo_interval;
struct sgsn_peer *sgsn;
if (prev_interval == ggsn->cfg.echo_interval)
if (ggsn->cfg.echo_interval == 0)
return CMD_SUCCESS;
ggsn->cfg.echo_interval = 0;
@@ -562,6 +563,58 @@ DEFUN(cfg_apn_no_ip_ifconfig, cfg_apn_no_ip_ifconfig_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_apn_imsi_ip_map, cfg_apn_imsi_ip_map_cmd,
"imsi-ip-map (add|del) IMSI [A.B.C.D]",
"List of IMSI to RESERVED ip4 mappings\n"
"Add IMSI/IP pair to mappings\n"
"Remove IMSI/IP pair from mappings\n"
"IMSI of the subscriber\n"
"IP for the subscriber\n")
{
char imsi_sanitized[GSM23003_IMSI_MAX_DIGITS + 1];
const char *op = argv[0];
const char *imsi = imsi_sanitized;
size_t len = strnlen(argv[1], GSM23003_IMSI_MAX_DIGITS + 1);
const char *ip = argv[2];
int rc;
struct apn_ctx *apn = (struct apn_ctx *) vty->index;
if (!apn_supports_ipv4(apn)) {
vty_out(vty, "%% APN does not support ipv4 addresses%s", VTY_NEWLINE);
return CMD_WARNING;
}
memset(imsi_sanitized, '0', GSM23003_IMSI_MAX_DIGITS);
imsi_sanitized[GSM23003_IMSI_MAX_DIGITS] = '\0';
/* Sanitize IMSI */
if (len > GSM23003_IMSI_MAX_DIGITS) {
vty_out(vty, "%% IMSI (%s) too long (max %u digits) -- ignored!%s",
argv[1], GSM23003_IMSI_MAX_DIGITS, VTY_NEWLINE);
return CMD_WARNING;
}
osmo_strlcpy(imsi_sanitized + GSM23003_IMSI_MAX_DIGITS - len, argv[1],
sizeof(imsi_sanitized) - (GSM23003_IMSI_MAX_DIGITS - len));
if (!strcmp(op, "add")) {
if (!ip) {
vty_out(vty, "%% unable to %s IMSI without IP address%s", op, VTY_NEWLINE);
return CMD_WARNING;
}
rc = apn_imsi_ip_map_add(imsi, ip, &apn->v4);
} else {
rc = apn_imsi_ip_map_del(imsi, ip, &apn->v4);
}
if (rc < 0) {
vty_out(vty, "%% unable to %s IMSI%s", op, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(cfg_apn_ipv6_prefix, cfg_apn_ipv6_prefix_cmd,
"ipv6 prefix (static|dynamic) X:X::X:X/M",
IP6_STR PREFIX_STR "IPv6 Address/Prefix-Length\n")
@@ -681,7 +734,7 @@ DEFUN(cfg_apn_no_gpdu_seq, cfg_apn_no_gpdu_seq_cmd,
DEFUN(cfg_apn_shutdown, cfg_apn_shutdown_cmd,
"shutdown",
"Put the APN in administrative shut-down\n")
"Put the APN in administrative shutdown\n")
{
struct apn_ctx *apn = (struct apn_ctx *) vty->index;
@@ -698,7 +751,7 @@ DEFUN(cfg_apn_shutdown, cfg_apn_shutdown_cmd,
DEFUN(cfg_apn_no_shutdown, cfg_apn_no_shutdown_cmd,
"no shutdown",
NO_STR "Remove the APN from administrative shut-down\n")
NO_STR "Remove the APN from administrative shutdown\n")
{
struct apn_ctx *apn = (struct apn_ctx *) vty->index;
@@ -726,6 +779,7 @@ static void vty_dump_prefix(struct vty *vty, const char *pre, const struct in46_
static void config_write_apn(struct vty *vty, struct apn_ctx *apn)
{
unsigned int i;
struct imsi_map_entry *map;
vty_out(vty, " apn %s%s", apn->cfg.name, VTY_NEWLINE);
if (apn->cfg.description)
@@ -762,6 +816,9 @@ static void config_write_apn(struct vty *vty, struct apn_ctx *apn)
if (apn->v4.cfg.ifconfig_prefix.addr.len)
vty_dump_prefix(vty, " ip ifconfig", &apn->v4.cfg.ifconfig_prefix);
llist_for_each_entry(map, &apn->v4.imsi_ip_map, list)
vty_out(vty, " imsi-ip-map add %s %s%s", map->imsi, in46a_ntoa(&map->addr), VTY_NEWLINE);
/* IPv6 prefixes + DNS */
if (apn->v6.cfg.static_prefix.addr.len)
vty_dump_prefix(vty, " ipv6 prefix static", &apn->v6.cfg.static_prefix);
@@ -796,6 +853,7 @@ static int config_write_ggsn(struct vty *vty)
vty_out(vty, " gtp control-ip %s%s", in46a_ntoa(&ggsn->cfg.gtpc_addr), VTY_NEWLINE);
if (ggsn->cfg.gtpu_addr.v4.s_addr)
vty_out(vty, " gtp user-ip %s%s", in46a_ntoa(&ggsn->cfg.gtpu_addr), VTY_NEWLINE);
osmo_tdef_vty_groups_write(vty, " ");
llist_for_each_entry(apn, &ggsn->apn_list, list)
config_write_apn(vty, apn);
if (ggsn->cfg.default_apn)
@@ -1114,6 +1172,8 @@ int ggsn_vty_init(void)
install_element(GGSN_NODE, &cfg_ggsn_echo_interval_cmd);
install_element(GGSN_NODE, &cfg_ggsn_no_echo_interval_cmd);
osmo_tdef_vty_groups_init(GGSN_NODE, ggsn_tdef_group);
install_node(&apn_node, NULL);
install_element(APN_NODE, &cfg_description_cmd);
install_element(APN_NODE, &cfg_no_description_cmd);
@@ -1128,6 +1188,7 @@ int ggsn_vty_init(void)
install_element(APN_NODE, &cfg_apn_ipdown_script_cmd);
install_element(APN_NODE, &cfg_apn_no_ipdown_script_cmd);
install_element(APN_NODE, &cfg_apn_ip_prefix_cmd);
install_element(APN_NODE, &cfg_apn_imsi_ip_map_cmd);
install_element(APN_NODE, &cfg_apn_ipv6_prefix_cmd);
install_element(APN_NODE, &cfg_apn_ip_dns_cmd);
install_element(APN_NODE, &cfg_apn_ipv6_dns_cmd);

View File

@@ -4,7 +4,7 @@
#include "../gtp/pdp.h"
/* 3GPP TS 24.008 10.6.5.3 */
/* 3GPP TS 24.008 10.5.6.3 */
enum pco_protocols {
PCO_P_LCP = 0xC021,
PCO_P_PAP = 0xC023,

View File

@@ -116,6 +116,7 @@ static unsigned int sgsn_peer_drop_all_pdp_except(struct sgsn_peer *sgsn, struct
{
unsigned int num = 0;
char buf[INET_ADDRSTRLEN];
unsigned int count = llist_count(&sgsn->pdp_list);
inet_ntop(AF_INET, &sgsn->addr, buf, sizeof(buf));
@@ -125,10 +126,17 @@ static unsigned int sgsn_peer_drop_all_pdp_except(struct sgsn_peer *sgsn, struct
continue;
ggsn_close_one_pdp(pdp->lib);
num++;
if (num == count) {
/* Note: if except is NULL, all pdp contexts are freed and sgsn
* is most probably already freed at this point.
* As a result, last access to sgsn->pdp_list before exiting
* loop would access already freed memory. Avoid it by exiting
* the loop without the last check, and make sure sgsn is not
* accessed after this loop. */
break;
}
}
/* Note: if except is NULL, all pdp contexts are freed and sgsn is
already freed at this point */
LOGP(DGGSN, LOGL_INFO, "SGSN(%s) Dropped %u PDP contexts\n", buf, num);
return num;

View File

@@ -2,14 +2,14 @@
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
# If major=current-age is increased, remember to update the dh_strip line in debian/rules!
LIBVERSION=7:0:1
LIBVERSION=9:0:3
lib_LTLIBRARIES = libgtp.la
include_HEADERS = gtp.h pdp.h gtpie.h
include_HEADERS = gtp.h gsn.h pdp.h gtpie.h
AM_CFLAGS = -O2 -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb $(LIBOSMOCORE_CFLAGS)
AM_CFLAGS = -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' $(LIBOSMOCORE_CFLAGS)
libgtp_la_SOURCES = gtp.c gtp.h gtpie.c gtpie.h pdp.c pdp.h lookupa.c lookupa.h queue.c queue.h
libgtp_la_SOURCES = gtp.c gtp.h gsn.c gsn.h gtpie.c gtpie.h pdp.c pdp.h lookupa.c lookupa.h queue.c queue.h
libgtp_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libgtp_la_LIBADD = $(LIBOSMOCORE_LIBS)

596
gtp/gsn.c Normal file
View File

@@ -0,0 +1,596 @@
/*
* OsmoGGSN - Gateway GPRS Support Node
* Copyright (C) 2002, 2003, 2004 Mondru AB.
* Copyright (C) 2010-2011, 2016-2017 Harald Welte <laforge@gnumonks.org>
* Copyright (C) 2015-2017 sysmocom - s.f.m.c. GmbH
*
* The contents of this file may be used under the terms of the GNU
* General Public License Version 2, provided that the above copyright
* notice and this permission notice is included in all copies or
* substantial portions of the software.
*
*/
/*
* gtp.c: Contains all GTP functionality. Should be able to handle multiple
* tunnels in the same program.
*
* TODO:
* - Do we need to handle fragmentation?
*/
#ifdef __linux__
#define _GNU_SOURCE 1
#endif
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h>
#if defined(__FreeBSD__)
#include <sys/endian.h>
#endif
#include "../config.h"
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <arpa/inet.h>
/* #include <stdint.h> ISO C99 types */
#include "pdp.h"
#include "gtp.h"
#include "gtpie.h"
#include "queue.h"
/* Error reporting functions */
#define LOGP_WITH_ADDR(ss, level, addr, fmt, args...) \
LOGP(ss, level, "addr(%s:%d) " fmt, \
inet_ntoa((addr).sin_addr), htons((addr).sin_port), \
##args);
static const struct rate_ctr_desc gsn_ctr_description[] = {
[GSN_CTR_ERR_SOCKET] = { "err:socket", "Socket error" },
[GSN_CTR_ERR_READFROM] = { "err:readfrom", "readfrom() errors" },
[GSN_CTR_ERR_SENDTO] = { "err:sendto", "sendto() errors" },
[GSN_CTR_ERR_QUEUEFULL] = { "err:queuefull", "Failed to queue message because queue is full" },
[GSN_CTR_ERR_SEQ] = { "err:seq", "Sequence number out of range" },
[GSN_CTR_ERR_ADDRESS] = { "err:address", "GSN address conversion failed" },
[GSN_CTR_ERR_UNKNOWN_PDP] = { "err:unknown_pdp", "Failed looking up PDP context" },
[GSN_CTR_ERR_UNEXPECTED_CAUSE] = { "err:unexpected_cause", "Unexpected cause value received" },
[GSN_CTR_ERR_OUT_OF_PDP] = { "err:out_of_pdp", "Out of storage for PDP contexts" },
[GSN_CTR_PKT_EMPTY] = { "pkt:empty", "Empty packet received" },
[GSN_CTR_PKT_UNSUP] = { "pkt:unsupported", "Unsupported GTP version received" },
[GSN_CTR_PKT_TOOSHORT] = { "pkt:too_short", "Packet too short received" },
[GSN_CTR_PKT_UNKNOWN] = { "pkt:unknown", "Unknown packet type received" },
[GSN_CTR_PKT_UNEXPECT] = { "pkt:unexpected", "Unexpected packet type received" },
[GSN_CTR_PKT_DUPLICATE] = { "pkt:duplicate", "Duplicate or unsolicited packet received" },
[GSN_CTR_PKT_MISSING] = { "pkt:missing", "Missing IE in packet received" },
[GSN_CTR_PKT_INCORRECT] = { "pkt:incorrect", "Incorrect IE in packet received" },
[GSN_CTR_PKT_INVALID] = { "pkt:invalid", "Invalid format in packet received" },
};
static const struct rate_ctr_group_desc gsn_ctrg_desc = {
"gsn",
"GSN Statistics",
OSMO_STATS_CLASS_PEER,
ARRAY_SIZE(gsn_ctr_description),
gsn_ctr_description,
};
static unsigned int gsn_ctr_next_idx = 0;
/* Global timer definitions for GTP operation, provided for convenience. To make these user configurable, it is convenient to add
* gtp_gsn_tdefs as one of your program's osmo_tdef_group entries and call osmo_tdef_vty_init(). */
struct osmo_tdef gtp_T_defs[] = {
{ .T = GTP_GSN_TIMER_T3_RESPONSE, .default_val = 5, .unit = OSMO_TDEF_S,
.desc = "Timer T3-RESPONSE holds the maximum wait time for a response of a request message"
},
{ .T = GTP_GSN_TIMER_N3_REQUESTS, .default_val = 3, .unit = OSMO_TDEF_CUSTOM,
.desc = "Counter N3-REQUESTS holds the maximum number of attempts made by GTP to send a request message"
},
{ .T = GTP_GSN_TIMER_T3_HOLD_RESPONSE, .default_val = 5 * 3 /* (GTP_GSN_TIMER_T3_RESPONSE * GTP_GSN_TIMER_N3_REQUESTS) */, .unit = OSMO_TDEF_S,
.desc = "Time a GTP respoonse message is kept cached to re-transmit it when a duplicate request is received. Value is generally equal to (T3-RESPONSE * N3-REQUESTS) set at the peer"
},
{}
};
/* API Functions */
/* Deprecated, use gtp_pdp_newpdp() instead */
int gtp_newpdp(struct gsn_t *gsn, struct pdp_t **pdp,
uint64_t imsi, uint8_t nsapi)
{
int rc;
rc = gtp_pdp_newpdp(gsn, pdp, imsi, nsapi, NULL);
return rc;
}
int gtp_freepdp(struct gsn_t *gsn, struct pdp_t *pdp)
{
if (gsn->cb_delete_context)
gsn->cb_delete_context(pdp);
return pdp_freepdp(pdp);
}
/* Free pdp and all its secondary PDP contexts. Must be called on the primary PDP context. */
int gtp_freepdp_teardown(struct gsn_t *gsn, struct pdp_t *pdp)
{
int n;
struct pdp_t *secondary_pdp;
OSMO_ASSERT(!pdp->secondary);
for (n = 0; n < PDP_MAXNSAPI; n++) {
if (pdp->secondary_tei[n]) {
if (gtp_pdp_getgtp1(gsn, &secondary_pdp,
pdp->secondary_tei[n])) {
LOGP(DLGTP, LOGL_ERROR,
"Unknown secondary PDP context\n");
continue;
}
if (pdp != secondary_pdp) {
gtp_freepdp(gsn, secondary_pdp);
}
}
}
return gtp_freepdp(gsn, pdp);
}
/* gtp_gpdu */
extern int gtp_fd(struct gsn_t *gsn)
{
return gsn->fd0;
}
int gtp_set_cb_unsup_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer))
{
gsn->cb_unsup_ind = cb;
return 0;
}
int gtp_set_cb_extheader_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer))
{
gsn->cb_extheader_ind = cb;
return 0;
}
int gtp_set_cb_ran_info_relay_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer, union gtpie_member **ie))
{
gsn->cb_ran_info_relay_ind = cb;
return 0;
}
/* API: Initialise delete context callback */
/* Called whenever a pdp context is deleted for any reason */
int gtp_set_cb_delete_context(struct gsn_t *gsn, int (*cb) (struct pdp_t * pdp))
{
gsn->cb_delete_context = cb;
return 0;
}
int gtp_set_cb_conf(struct gsn_t *gsn,
int (*cb) (int type, int cause,
struct pdp_t * pdp, void *cbp))
{
gsn->cb_conf = cb;
return 0;
}
int gtp_set_cb_recovery(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer, uint8_t recovery))
{
gsn->cb_recovery = cb;
return 0;
}
/* cb_recovery()
* pdp may be NULL if Recovery IE was received from a message independent
* of any PDP ctx (such as Echo Response), or because pdp ctx is unknown to the
* local setup. In case pdp is known, caller may want to keep that pdp alive to
* handle subsequent msg cb as this specific pdp ctx is still valid according to
* specs.
*/
int gtp_set_cb_recovery2(struct gsn_t *gsn,
int (*cb_recovery2) (struct sockaddr_in * peer, struct pdp_t * pdp, uint8_t recovery))
{
gsn->cb_recovery2 = cb_recovery2;
return 0;
}
/* cb_recovery()
* pdp may be NULL if Recovery IE was received from a message independent
* of any PDP ctx (such as Echo Response), or because pdp ctx is unknown to the
* local setup. In case pdp is known, caller may want to keep that pdp alive to
* handle subsequent msg cb as this specific pdp ctx is still valid according to
* specs.
*/
int gtp_set_cb_recovery3(struct gsn_t *gsn,
int (*cb_recovery3) (struct gsn_t *gsn, struct sockaddr_in *peer,
struct pdp_t *pdp, uint8_t recovery))
{
gsn->cb_recovery3 = cb_recovery3;
return 0;
}
int gtp_set_cb_data_ind(struct gsn_t *gsn,
int (*cb_data_ind) (struct pdp_t * pdp,
void *pack, unsigned len))
{
gsn->cb_data_ind = cb_data_ind;
return 0;
}
static int queue_timer_retrans(struct gsn_t *gsn)
{
/* Retransmit any outstanding packets */
/* Remove from queue if maxretrans exceeded */
time_t now;
struct qmsg_t *qmsg;
unsigned int t3_response, n3_requests;
now = time(NULL);
t3_response = osmo_tdef_get(gsn->tdef, GTP_GSN_TIMER_T3_RESPONSE, OSMO_TDEF_S, -1);
n3_requests = osmo_tdef_get(gsn->tdef, GTP_GSN_TIMER_N3_REQUESTS, OSMO_TDEF_CUSTOM, -1);
/* get first element in queue, as long as the timeout of that
* element has expired */
while ((!queue_getfirst(gsn->queue_req, &qmsg)) &&
(qmsg->timeout <= now)) {
if (qmsg->retrans > n3_requests) { /* Too many retrans */
LOGP(DLGTP, LOGL_NOTICE, "Retransmit req queue timeout of seq %" PRIu16 "\n",
qmsg->seq);
if (gsn->cb_conf)
gsn->cb_conf(qmsg->type, EOF, NULL, qmsg->cbp);
queue_freemsg(gsn->queue_req, qmsg);
} else {
LOGP(DLGTP, LOGL_INFO, "Retransmit (%d) of seq %" PRIu16 "\n",
qmsg->retrans, qmsg->seq);
if (sendto(qmsg->fd, &qmsg->p, qmsg->l, 0,
(struct sockaddr *)&qmsg->peer,
sizeof(struct sockaddr_in)) < 0) {
rate_ctr_inc2(gsn->ctrg, GSN_CTR_ERR_SENDTO);
LOGP(DLGTP, LOGL_ERROR,
"Sendto(fd0=%d, msg=%lx, len=%d) failed: Error = %s\n",
gsn->fd0, (unsigned long)&qmsg->p,
qmsg->l, strerror(errno));
}
queue_back(gsn->queue_req, qmsg);
qmsg->timeout = now + t3_response;
qmsg->retrans++;
}
}
/* Also clean up reply timeouts */
while ((!queue_getfirst(gsn->queue_resp, &qmsg)) &&
(qmsg->timeout < now)) {
LOGP(DLGTP, LOGL_DEBUG, "Retransmit resp queue seq %"
PRIu16 " expired, removing from queue\n", qmsg->seq);
queue_freemsg(gsn->queue_resp, qmsg);
}
return 0;
}
static int queue_timer_retranstimeout(struct gsn_t *gsn, struct timeval *timeout)
{
time_t now, later, diff;
struct qmsg_t *qmsg;
timeout->tv_usec = 0;
if (queue_getfirst(gsn->queue_req, &qmsg)) {
timeout->tv_sec = 10;
} else {
now = time(NULL);
later = qmsg->timeout;
timeout->tv_sec = later - now;
if (timeout->tv_sec < 0)
timeout->tv_sec = 0; /* No negative allowed */
if (timeout->tv_sec > 10)
timeout->tv_sec = 10; /* Max sleep for 10 sec */
}
if (queue_getfirst(gsn->queue_resp, &qmsg)) {
/* already set by queue_req, do nothing */
} else { /* trigger faster if earlier timeout exists in queue_resp */
now = time(NULL);
later = qmsg->timeout;
diff = later - now;
if (diff < 0)
diff = 0;
if (diff < timeout->tv_sec)
timeout->tv_sec = diff;
}
return 0;
}
void gtp_queue_timer_start(struct gsn_t *gsn)
{
struct timeval next;
/* Retrieve next retransmission as timeval */
queue_timer_retranstimeout(gsn, &next);
/* re-schedule the timer */
osmo_timer_schedule(&gsn->queue_timer, next.tv_sec, next.tv_usec/1000);
}
/* timer callback for libgtp retransmission and ping */
static void queue_timer_cb(void *data)
{
struct gsn_t *gsn = data;
/* do all the retransmissions as needed */
queue_timer_retrans(gsn);
gtp_queue_timer_start(gsn);
}
/**
* @brief clear the request and response queue. Useful for debugging to reset "some" state.
* @param gsn The GGSN instance
*/
void gtp_clear_queues(struct gsn_t *gsn)
{
struct qmsg_t *qmsg;
LOGP(DLGTP, LOGL_INFO, "Clearing req & resp retransmit queues\n");
while (!queue_getfirst(gsn->queue_req, &qmsg)) {
queue_freemsg(gsn->queue_req, qmsg);
}
while (!queue_getfirst(gsn->queue_resp, &qmsg)) {
queue_freemsg(gsn->queue_resp, qmsg);
}
}
/* Perform restoration and recovery error handling as described in 29.060 */
static void log_restart(struct gsn_t *gsn)
{
FILE *f;
int i, rc;
int counter = 0;
char *filename;
filename = talloc_asprintf(NULL, "%s/%s", gsn->statedir, RESTART_FILE);
OSMO_ASSERT(filename);
/* We try to open file. On failure we will later try to create file */
if (!(f = fopen(filename, "r"))) {
LOGP(DLGTP, LOGL_NOTICE,
"State information file (%s) not found. Creating new file.\n",
filename);
} else {
rc = fscanf(f, "%d", &counter);
if (rc != 1) {
LOGP(DLGTP, LOGL_ERROR,
"fscanf failed to read counter value\n");
goto close_file;
}
if (fclose(f)) {
LOGP(DLGTP, LOGL_ERROR,
"fclose failed: Error = %s\n", strerror(errno));
}
}
gsn->restart_counter = (unsigned char)counter;
gsn->restart_counter++;
/* Keep the umask closely wrapped around our fopen() call in case the
* log outputs cause file creation. */
i = umask(022);
f = fopen(filename, "w");
umask(i);
if (!f) {
LOGP(DLGTP, LOGL_ERROR,
"fopen(path=%s, mode=%s) failed: Error = %s\n", filename,
"w", strerror(errno));
goto free_filename;
}
fprintf(f, "%d\n", gsn->restart_counter);
close_file:
if (fclose(f))
LOGP(DLGTP, LOGL_ERROR,
"fclose failed: Error = %s\n", strerror(errno));
free_filename:
talloc_free(filename);
}
int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen,
int mode)
{
struct sockaddr_in addr;
LOGP(DLGTP, LOGL_NOTICE, "GTP: gtp_newgsn() started at %s\n", inet_ntoa(*listen));
*gsn = calloc(sizeof(struct gsn_t), 1); /* TODO */
(*gsn)->statedir = statedir;
log_restart(*gsn);
/* Initialise sequence number */
(*gsn)->seq_next = (*gsn)->restart_counter * 1024;
/* Initialize timers: */
(*gsn)->tdef = gtp_T_defs;
/* Small hack to properly reset tdef for old clients not using the tdef_group: */
OSMO_ASSERT(gtp_T_defs[0].default_val != 0);
if (gtp_T_defs[0].val == 0)
osmo_tdefs_reset((*gsn)->tdef);
/* Initialise request retransmit queue */
queue_new(&(*gsn)->queue_req);
queue_new(&(*gsn)->queue_resp);
/* Initialise pdp table */
pdp_init(*gsn);
/* Initialize internal queue timer */
osmo_timer_setup(&(*gsn)->queue_timer, queue_timer_cb, *gsn);
/* Initialize counter group: */
(*gsn)->ctrg = rate_ctr_group_alloc(NULL, &gsn_ctrg_desc, gsn_ctr_next_idx++);
/* Initialise call back functions */
(*gsn)->cb_create_context_ind = 0;
(*gsn)->cb_delete_context = 0;
(*gsn)->cb_unsup_ind = 0;
(*gsn)->cb_conf = 0;
(*gsn)->cb_data_ind = 0;
/* Store function parameters */
(*gsn)->gsnc = *listen;
(*gsn)->gsnu = *listen;
(*gsn)->mode = mode;
/* Create GTP version 0 socket */
if (((*gsn)->fd0 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
rate_ctr_inc2((*gsn)->ctrg, GSN_CTR_ERR_SOCKET);
LOGP(DLGTP, LOGL_ERROR,
"GTPv0 socket(domain=%d, type=%d, protocol=%d) failed: Error = %s\n",
AF_INET, SOCK_DGRAM, 0, strerror(errno));
return -errno;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr = *listen; /* Same IP for user traffic and signalling */
addr.sin_port = htons(GTP0_PORT);
#if defined(__FreeBSD__) || defined(__APPLE__)
addr.sin_len = sizeof(addr);
#endif
if (bind((*gsn)->fd0, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
rate_ctr_inc2((*gsn)->ctrg, GSN_CTR_ERR_SOCKET);
LOGP_WITH_ADDR(DLGTP, LOGL_ERROR, addr,
"bind(fd0=%d) failed: Error = %s\n",
(*gsn)->fd0, strerror(errno));
return -errno;
}
/* Create GTP version 1 control plane socket */
if (((*gsn)->fd1c = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
rate_ctr_inc2((*gsn)->ctrg, GSN_CTR_ERR_SOCKET);
LOGP(DLGTP, LOGL_ERROR,
"GTPv1 control plane socket(domain=%d, type=%d, protocol=%d) failed: Error = %s\n",
AF_INET, SOCK_DGRAM, 0, strerror(errno));
return -errno;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr = *listen; /* Same IP for user traffic and signalling */
addr.sin_port = htons(GTP1C_PORT);
#if defined(__FreeBSD__) || defined(__APPLE__)
addr.sin_len = sizeof(addr);
#endif
if (bind((*gsn)->fd1c, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
rate_ctr_inc2((*gsn)->ctrg, GSN_CTR_ERR_SOCKET);
LOGP_WITH_ADDR(DLGTP, LOGL_ERROR, addr,
"bind(fd1c=%d) failed: Error = %s\n",
(*gsn)->fd1c, strerror(errno));
return -errno;
}
/* Create GTP version 1 user plane socket */
if (((*gsn)->fd1u = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
rate_ctr_inc2((*gsn)->ctrg, GSN_CTR_ERR_SOCKET);
LOGP(DLGTP, LOGL_ERROR,
"GTPv1 user plane socket(domain=%d, type=%d, protocol=%d) failed: Error = %s\n",
AF_INET, SOCK_DGRAM, 0, strerror(errno));
return -errno;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr = *listen; /* Same IP for user traffic and signalling */
addr.sin_port = htons(GTP1U_PORT);
#if defined(__FreeBSD__) || defined(__APPLE__)
addr.sin_len = sizeof(addr);
#endif
if (bind((*gsn)->fd1u, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
rate_ctr_inc2((*gsn)->ctrg, GSN_CTR_ERR_SOCKET);
LOGP_WITH_ADDR(DLGTP, LOGL_ERROR, addr,
"bind(fd1u=%d) failed: Error = %s\n",
(*gsn)->fd1u, strerror(errno));
return -errno;
}
/* Start internal queue timer */
gtp_queue_timer_start(*gsn);
return 0;
}
int gtp_free(struct gsn_t *gsn)
{
/* Cleanup internal queue timer */
osmo_timer_del(&gsn->queue_timer);
/* Clean up retransmit queues */
queue_free(gsn->queue_req);
queue_free(gsn->queue_resp);
close(gsn->fd0);
close(gsn->fd1c);
close(gsn->fd1u);
rate_ctr_group_free(gsn->ctrg);
free(gsn);
return 0;
}
/* API: Register create context indication callback */
int gtp_set_cb_create_context_ind(struct gsn_t *gsn,
int (*cb_create_context_ind) (struct pdp_t *
pdp))
{
gsn->cb_create_context_ind = cb_create_context_ind;
return 0;
}
int gtp_retrans(struct gsn_t *gsn)
{
/* dummy API, deprecated. */
return 0;
}
int gtp_retranstimeout(struct gsn_t *gsn, struct timeval *timeout)
{
timeout->tv_sec = 24*60*60;
timeout->tv_usec = 0;
/* dummy API, deprecated. Return a huge timer to do nothing */
return 0;
}

181
gtp/gsn.h Normal file
View File

@@ -0,0 +1,181 @@
/*
* OsmoGGSN - Gateway GPRS Support Node
* Copyright (C) 2002, 2003, 2004 Mondru AB.
*
* The contents of this file may be used under the terms of the GNU
* General Public License Version 2, provided that the above copyright
* notice and this permission notice is included in all copies or
* substantial portions of the software.
*
*/
#ifndef _GSN_H
#define _GSN_H
#include <osmocom/core/utils.h>
#include <osmocom/core/defs.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/rate_ctr.h>
#include "pdp.h"
#define GTP_MODE_GGSN 1
#define GTP_MODE_SGSN 2
#define RESTART_FILE "gsn_restart"
extern struct osmo_tdef gtp_T_defs[];
/* ***********************************************************
* Information storage for each gsn instance
*
* Normally each instance of the application corresponds to
* one instance of a gsn.
*
* In order to avoid global variables in the application, and
* also in order to allow several instances of a gsn in the same
* application this struct is provided in order to store all
* relevant information related to the gsn.
*
* Note that this does not include information storage for '
* each pdp context. This is stored in another struct.
*************************************************************/
enum gsn_rate_ctr_keys {
GSN_CTR_ERR_SOCKET,
GSN_CTR_ERR_READFROM, /* Number of readfrom errors */
GSN_CTR_ERR_SENDTO, /* Number of sendto errors */
GSN_CTR_ERR_QUEUEFULL, /* Number of times queue was full */
GSN_CTR_ERR_SEQ, /* Number of seq out of range */
GSN_CTR_ERR_ADDRESS, /* GSN address conversion failed */
GSN_CTR_ERR_UNKNOWN_PDP, /* GSN address conversion failed */
GSN_CTR_ERR_UNEXPECTED_CAUSE, /* Unexpected cause value received */
GSN_CTR_ERR_OUT_OF_PDP, /* Out of storage for PDP contexts */
GSN_CTR_PKT_EMPTY, /* Number of empty packets */
GSN_CTR_PKT_UNSUP, /* Number of unsupported version 29.60 11.1.1 */
GSN_CTR_PKT_TOOSHORT, /* Number of too short headers 29.60 11.1.2 */
GSN_CTR_PKT_UNKNOWN, /* Number of unknown messages 29.60 11.1.3 */
GSN_CTR_PKT_UNEXPECT, /* Number of unexpected messages 29.60 11.1.4 */
GSN_CTR_PKT_DUPLICATE, /* Number of duplicate or unsolicited replies */
GSN_CTR_PKT_MISSING, /* Number of missing information field messages */
GSN_CTR_PKT_INCORRECT, /* Number of incorrect information field messages */
GSN_CTR_PKT_INVALID, /* Number of invalid message format messages */
};
/* 3GPP TS 29.006 14.1, 14,2 */
enum gtp_gsn_timers {
GTP_GSN_TIMER_T3_RESPONSE = 3,
GTP_GSN_TIMER_N3_REQUESTS = 1003,
GTP_GSN_TIMER_T3_HOLD_RESPONSE = -3,
};
struct gsn_t {
/* Parameters related to the network interface */
int fd0; /* GTP0 file descriptor */
int fd1c; /* GTP1 control plane file descriptor */
int fd1u; /* GTP0 user plane file descriptor */
int mode; /* Mode of operation: GGSN or SGSN */
struct in_addr gsnc; /* IP address of this gsn for signalling */
struct in_addr gsnu; /* IP address of this gsn for user traffic */
/* Parameters related to signalling messages */
uint16_t seq_next; /* Next sequence number to use */
int seq_first; /* First packet in queue (oldest timeout) */
int seq_last; /* Last packet in queue (youngest timeout) */
unsigned char restart_counter; /* Increment on restart. Stored on disk */
char *statedir; /* Disk location for permanent storage */
void *priv; /* used by libgtp users to attach their own state) */
struct queue_t *queue_req; /* Request queue */
struct queue_t *queue_resp; /* Response queue */
struct pdp_t pdpa[PDP_MAX]; /* PDP storage */
struct pdp_t *hashtid[PDP_MAX]; /* Hash table for IMSI + NSAPI */
struct osmo_timer_list queue_timer; /* internal queue_{req,resp} timer */
/* Call back functions */
int (*cb_delete_context) (struct pdp_t *);
int (*cb_create_context_ind) (struct pdp_t *);
int (*cb_unsup_ind) (struct sockaddr_in * peer);
int (*cb_extheader_ind) (struct sockaddr_in * peer);
int (*cb_ran_info_relay_ind) (struct sockaddr_in *peer, union gtpie_member **ie);
int (*cb_conf) (int type, int cause, struct pdp_t * pdp, void *cbp);
int (*cb_data_ind) (struct pdp_t * pdp, void *pack, unsigned len);
int (*cb_recovery) (struct sockaddr_in * peer, uint8_t recovery);
int (*cb_recovery2) (struct sockaddr_in * peer, struct pdp_t * pdp, uint8_t recovery);
int (*cb_recovery3) (struct gsn_t *gsn, struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery);
/* Counters */
struct rate_ctr_group *ctrg;
/* Timers: */
struct osmo_tdef *tdef;
};
/* External API functions */
extern int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen,
int mode);
extern int gtp_free(struct gsn_t *gsn);
extern int gtp_newpdp(struct gsn_t *gsn, struct pdp_t **pdp,
uint64_t imsi, uint8_t nsapi) OSMO_DEPRECATED("Use gtp_pdp_newpdp() instead");
extern int gtp_freepdp(struct gsn_t *gsn, struct pdp_t *pdp);
extern int gtp_freepdp_teardown(struct gsn_t *gsn, struct pdp_t *pdp);
extern int gtp_create_context_req(struct gsn_t *gsn, struct pdp_t *pdp,
void *cbp);
extern int gtp_set_cb_create_context_ind(struct gsn_t *gsn,
int (*cb_create_context_ind) (struct
pdp_t *
pdp));
extern int gtp_set_cb_data_ind(struct gsn_t *gsn,
int (*cb_data_ind) (struct pdp_t * pdp,
void *pack, unsigned len));
extern int gtp_set_cb_delete_context(struct gsn_t *gsn,
int (*cb_delete_context) (struct pdp_t *
pdp));
/*extern int gtp_set_cb_create_context(struct gsn_t *gsn,
int (*cb_create_context) (struct pdp_t* pdp)); */
extern int gtp_set_cb_unsup_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer));
extern int gtp_set_cb_extheader_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer));
extern int gtp_set_cb_ran_info_relay_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer, union gtpie_member **ie));
extern int gtp_set_cb_conf(struct gsn_t *gsn,
int (*cb) (int type, int cause, struct pdp_t * pdp,
void *cbp));
int gtp_set_cb_recovery(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer,
uint8_t recovery))
OSMO_DEPRECATED("Use gtp_set_cb_recovery2() instead, to obtain pdp ctx originating the recovery");
int gtp_set_cb_recovery2(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer,
struct pdp_t * pdp,
uint8_t recovery))
OSMO_DEPRECATED("Use gtp_set_cb_recovery3() instead, to obtain gsn handling the recovery");
int gtp_set_cb_recovery3(struct gsn_t *gsn,
int (*cb) (struct gsn_t * gsn, struct sockaddr_in * peer,
struct pdp_t * pdp,
uint8_t recovery));
void gtp_clear_queues(struct gsn_t *gsn);
extern int gtp_fd(struct gsn_t *gsn);
extern int gtp_retrans(struct gsn_t *gsn) OSMO_DEPRECATED("This API is a no-op, libgtp already does the job internally");
extern int gtp_retranstimeout(struct gsn_t *gsn, struct timeval *timeout) OSMO_DEPRECATED("This API is a no-op and will return a 1 day timeout");
/* Internal APIs: */
void gtp_queue_timer_start(struct gsn_t *gsn);
#endif /* !_GSN_H */

740
gtp/gtp.c

File diff suppressed because it is too large Load Diff

147
gtp/gtp.h
View File

@@ -13,14 +13,10 @@
#define _GTP_H
#include <osmocom/core/utils.h>
#include <osmocom/core/defs.h>
#include <osmocom/core/timer.h>
#include "gtpie.h"
#include "pdp.h"
#define GTP_MODE_GGSN 1
#define GTP_MODE_SGSN 2
#include "gsn.h"
#define GTP0_PORT 3386
#define GTP1C_PORT 2123
@@ -32,12 +28,10 @@
#define GTP1_HEADER_SIZE_SHORT 8
#define GTP1_HEADER_SIZE_LONG 12
#define NAMESIZE 1024
#define SYSLOG_PRINTSIZE 255
#define ERRMSG_SIZE 255
#define RESTART_FILE "gsn_restart"
#define NAMESIZE 1024
/* GTP version 1 extension header type definitions. */
#define GTP_EXT_PDCP_PDU 0xC0 /* PDCP PDU Number */
@@ -232,105 +226,13 @@ union gtp_packet {
struct gtp1_packet_long gtp1l;
} __attribute__ ((packed));
/* ***********************************************************
* Information storage for each gsn instance
*
* Normally each instance of the application corresponds to
* one instance of a gsn.
*
* In order to avoid global variables in the application, and
* also in order to allow several instances of a gsn in the same
* application this struct is provided in order to store all
* relevant information related to the gsn.
*
* Note that this does not include information storage for '
* each pdp context. This is stored in another struct.
*************************************************************/
struct gsn_t {
/* Parameters related to the network interface */
int fd0; /* GTP0 file descriptor */
int fd1c; /* GTP1 control plane file descriptor */
int fd1u; /* GTP0 user plane file descriptor */
int mode; /* Mode of operation: GGSN or SGSN */
struct in_addr gsnc; /* IP address of this gsn for signalling */
struct in_addr gsnu; /* IP address of this gsn for user traffic */
/* Parameters related to signalling messages */
uint16_t seq_next; /* Next sequence number to use */
int seq_first; /* First packet in queue (oldest timeout) */
int seq_last; /* Last packet in queue (youngest timeout) */
unsigned char restart_counter; /* Increment on restart. Stored on disk */
char *statedir; /* Disk location for permanent storage */
void *priv; /* used by libgtp users to attach their own state) */
struct queue_t *queue_req; /* Request queue */
struct queue_t *queue_resp; /* Response queue */
struct pdp_t pdpa[PDP_MAX]; /* PDP storage */
struct pdp_t *hashtid[PDP_MAX]; /* Hash table for IMSI + NSAPI */
struct osmo_timer_list queue_timer; /* internal queue_{req,resp} timer */
/* Call back functions */
int (*cb_delete_context) (struct pdp_t *);
int (*cb_create_context_ind) (struct pdp_t *);
int (*cb_unsup_ind) (struct sockaddr_in * peer);
int (*cb_extheader_ind) (struct sockaddr_in * peer);
int (*cb_ran_info_relay_ind) (struct sockaddr_in *peer, union gtpie_member **ie);
int (*cb_conf) (int type, int cause, struct pdp_t * pdp, void *cbp);
int (*cb_data_ind) (struct pdp_t * pdp, void *pack, unsigned len);
int (*cb_recovery) (struct sockaddr_in * peer, uint8_t recovery);
int (*cb_recovery2) (struct sockaddr_in * peer, struct pdp_t * pdp, uint8_t recovery);
int (*cb_recovery3) (struct gsn_t *gsn, struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery);
/* Counters */
uint64_t err_socket; /* Number of socket errors */
uint64_t err_readfrom; /* Number of readfrom errors */
uint64_t err_sendto; /* Number of sendto errors */
uint64_t err_memcpy; /* Number of memcpy */
uint64_t err_queuefull; /* Number of times queue was full */
uint64_t err_seq; /* Number of seq out of range */
uint64_t err_address; /* GSN address conversion failed */
uint64_t err_unknownpdp; /* GSN address conversion failed */
uint64_t err_unknowntid; /* Application supplied unknown imsi+nsapi */
uint64_t err_cause; /* Unexpected cause value received */
uint64_t err_outofpdp; /* Out of storage for PDP contexts */
uint64_t empty; /* Number of empty packets */
uint64_t unsup; /* Number of unsupported version 29.60 11.1.1 */
uint64_t tooshort; /* Number of too short headers 29.60 11.1.2 */
uint64_t unknown; /* Number of unknown messages 29.60 11.1.3 */
uint64_t unexpect; /* Number of unexpected messages 29.60 11.1.4 */
uint64_t duplicate; /* Number of duplicate or unsolicited replies */
uint64_t missing; /* Number of missing information field messages */
uint64_t incorrect; /* Number of incorrect information field messages */
uint64_t invalid; /* Number of invalid message format messages */
};
/* External API functions */
extern const char *gtp_version();
extern int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen,
int mode);
extern int gtp_free(struct gsn_t *gsn);
extern int gtp_newpdp(struct gsn_t *gsn, struct pdp_t **pdp,
uint64_t imsi, uint8_t nsapi) OSMO_DEPRECATED("Use gtp_pdp_newpdp() instead");
extern int gtp_freepdp(struct gsn_t *gsn, struct pdp_t *pdp);
extern int gtp_freepdp_teardown(struct gsn_t *gsn, struct pdp_t *pdp);
extern int gtp_create_context_req(struct gsn_t *gsn, struct pdp_t *pdp,
void *cbp);
extern int gtp_set_cb_create_context_ind(struct gsn_t *gsn,
int (*cb_create_context_ind) (struct
pdp_t *
pdp));
extern int gtp_create_context_resp(struct gsn_t *gsn, struct pdp_t *pdp,
int cause);
@@ -351,53 +253,10 @@ extern int gtp_ran_info_relay_req(struct gsn_t *gsn, const struct sockaddr_in *p
const uint8_t *rim_route_addr, size_t rim_route_addr_len,
uint8_t rim_route_addr_discr);
extern int gtp_set_cb_data_ind(struct gsn_t *gsn,
int (*cb_data_ind) (struct pdp_t * pdp,
void *pack, unsigned len));
extern int gtp_fd(struct gsn_t *gsn);
extern int gtp_decaps0(struct gsn_t *gsn);
extern int gtp_decaps1c(struct gsn_t *gsn);
extern int gtp_decaps1u(struct gsn_t *gsn);
extern int gtp_retrans(struct gsn_t *gsn) OSMO_DEPRECATED("This API is a no-op, libgtp already does the job internally");
extern int gtp_retranstimeout(struct gsn_t *gsn, struct timeval *timeout) OSMO_DEPRECATED("This API is a no-op and will return a 1 day timeout");
extern int gtp_set_cb_delete_context(struct gsn_t *gsn,
int (*cb_delete_context) (struct pdp_t *
pdp));
/*extern int gtp_set_cb_create_context(struct gsn_t *gsn,
int (*cb_create_context) (struct pdp_t* pdp)); */
extern int gtp_set_cb_unsup_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer));
extern int gtp_set_cb_extheader_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer));
extern int gtp_set_cb_ran_info_relay_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer, union gtpie_member **ie));
extern int gtp_set_cb_conf(struct gsn_t *gsn,
int (*cb) (int type, int cause, struct pdp_t * pdp,
void *cbp));
int gtp_set_cb_recovery(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer,
uint8_t recovery))
OSMO_DEPRECATED("Use gtp_set_cb_recovery2() instead, to obtain pdp ctx originating the recovery");
int gtp_set_cb_recovery2(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer,
struct pdp_t * pdp,
uint8_t recovery))
OSMO_DEPRECATED("Use gtp_set_cb_recovery3() instead, to obtain gsn handling the recovery");;
int gtp_set_cb_recovery3(struct gsn_t *gsn,
int (*cb) (struct gsn_t * gsn, struct sockaddr_in * peer,
struct pdp_t * pdp,
uint8_t recovery));
void gtp_clear_queues(struct gsn_t *gsn);
/* Internal functions (not part of the API */
/* Internal functions (not part of the API) */
extern int gtp_echo_req(struct gsn_t *gsn, int version, void *cbp,
struct in_addr *inetaddrs);

View File

@@ -23,7 +23,7 @@
#define QUEUE_DEBUG 0 /* Print debug information */
#define QUEUE_SIZE 1024 /* Size of retransmission queue */
#define QUEUE_SIZE (PDP_MAX*2) /* Size of retransmission queue */
#define QUEUE_HASH_SIZE 65536 /* Size of hash table (2^16) */
struct qmsg_t { /* Holder for queued packets */

View File

@@ -2,7 +2,7 @@ noinst_LIBRARIES = libmisc.a
noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.h netns.h util.h icmpv6.h checksum.h
AM_CFLAGS = -O2 -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb $(LIBOSMOCORE_CFLAGS)
AM_CFLAGS = -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' $(LIBOSMOCORE_CFLAGS)
libmisc_a_SOURCES = getopt1.c getopt.c ippool.c lookup.c tun.c debug.c in46_addr.c netdev.c netns.c util.c icmpv6.c checksum.c

View File

@@ -44,10 +44,9 @@ struct icmpv6_radv_hdr {
uint8_t res:6,
m:1,
o:1;
#else
uint8_t m:1,
o:1,
res:6;
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t o:1, m:1, res:6;
#endif
uint16_t router_lifetime;
uint32_t reachable_time;
@@ -72,10 +71,9 @@ struct icmpv6_opt_prefix {
uint8_t res:6,
a:1,
l:1;
#else
uint8_t l:1,
a:1,
res:6;
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t l:1, a:1, res:6;
#endif
uint32_t valid_lifetime;
uint32_t preferred_lifetime;

View File

@@ -318,7 +318,14 @@ int tun_decaps(struct tun_t *this)
int tun_encaps(struct tun_t *tun, void *pack, unsigned len)
{
return write(tun->fd, pack, len);
int rc;
rc = write(tun->fd, pack, len);
if (rc < 0) {
SYS_ERR(DTUN, LOGL_ERROR, errno, "TUN(%s): write() failed", tun->devname);
} else if (rc < len) {
LOGTUN(LOGL_ERROR, tun, "short write() %d < %u\n", rc, len);
}
return rc;
}
int tun_runscript(struct tun_t *tun, char *script)

View File

@@ -59,4 +59,7 @@ extern int tun_runscript(struct tun_t *tun, char *script);
int tun_ip_local_get(const struct tun_t *tun, struct in46_prefix *prefix_list,
size_t prefix_size, int flags);
#define LOGTUN(level, tun, fmt, args...) \
LOGP(DTUN, level, "TUN(%s): " fmt, (tun)->devname, ## args)
#endif /* !_TUN_H */

View File

@@ -2,7 +2,7 @@ bin_PROGRAMS = sgsnemu
AM_LDFLAGS = @EXEC_LDFLAGS@
AM_CFLAGS = -O2 -D_GNU_SOURCE -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb $(LIBOSMOCORE_CFLAGS)
AM_CFLAGS = -D_GNU_SOURCE -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' $(LIBOSMOCORE_CFLAGS)
sgsnemu_LDADD = @EXEC_LDADD@ -lgtp -L../gtp ../lib/libmisc.a $(LIBOSMOCORE_LIBS)

View File

@@ -1,11 +1,12 @@
AM_CFLAGS = -Wall -I$(top_srcdir)/include $(LIBOSMOCORE_CFLAGS) -g
AM_CFLAGS = -Wall -I$(top_srcdir)/include $(LIBOSMOCORE_CFLAGS)
AM_LDFLAGS = -no-install
EXTRA_DIST = \
gtpie_test.ok \
queue_test.ok \
$(NULL)
noinst_PROGRAMS = \
check_PROGRAMS = \
gtpie_test \
queue_test \
$(NULL)

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
AM_CFLAGS = -Wall -I$(top_srcdir)/include $(LIBOSMOCORE_CFLAGS) -g
AM_CFLAGS = -Wall -I$(top_srcdir)/include $(LIBOSMOCORE_CFLAGS)
AM_LDFLAGS = -no-install
EXTRA_DIST = ippool_test.ok \
ippool_test.err \
@@ -7,7 +8,7 @@ EXTRA_DIST = ippool_test.ok \
in46a_test.ok \
in46a_v6_test.ok
noinst_PROGRAMS = ippool_test in46a_test
check_PROGRAMS = ippool_test in46a_test
ippool_test_SOURCES = \
ippool_test.c \

View File

@@ -146,7 +146,7 @@ static void test_in46a_to_eua(void)
static void test_in46a_from_eua(void)
{
struct in46_addr ia;
struct in46_addr ia[2];
struct ul66_t eua;
const uint8_t v4_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4 };
const uint8_t v4_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4, 1,2,3,4 };
@@ -155,35 +155,35 @@ static void test_in46a_from_eua(void)
printf("Testing in46a_from_eua() with IPv4 addresses\n");
/* default: v4 unspec */
OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1);
OSMO_ASSERT(ia.len == 4);
OSMO_ASSERT(ia.v4.s_addr == 0);
OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1);
OSMO_ASSERT(ia[0].len == 4);
OSMO_ASSERT(ia[0].v4.s_addr == 0);
/* invalid */
eua.v[0] = 0x23;
eua.v[1] = PDP_EUA_TYPE_v4;
eua.l = 6;
OSMO_ASSERT(in46a_from_eua(&eua, &ia) < 0);
OSMO_ASSERT(in46a_from_eua(&eua, ia) < 0);
/* invalid */
eua.v[0] = PDP_EUA_ORG_IETF;
eua.v[1] = 0x23;
eua.l = 6;
OSMO_ASSERT(in46a_from_eua(&eua, &ia) < 0);
OSMO_ASSERT(in46a_from_eua(&eua, ia) < 0);
/* unspecified V4 */
memcpy(eua.v, v4_unspec, sizeof(v4_unspec));
eua.l = sizeof(v4_unspec);
OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1);
OSMO_ASSERT(ia.len == 4);
OSMO_ASSERT(ia.v4.s_addr == 0);
OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1);
OSMO_ASSERT(ia[0].len == 4);
OSMO_ASSERT(ia[0].v4.s_addr == 0);
/* specified V4 */
memcpy(eua.v, v4_spec, sizeof(v4_spec));
eua.l = sizeof(v4_spec);
OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1);
OSMO_ASSERT(ia.len == 4);
OSMO_ASSERT(ia.v4.s_addr == htonl(0x01020304));
OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1);
OSMO_ASSERT(ia[0].len == 4);
OSMO_ASSERT(ia[0].v4.s_addr == htonl(0x01020304));
}
static void test_in46a_netmasklen(void)
@@ -318,7 +318,7 @@ static void test_in46a_to_eua_v4v6() {
static void test_in46a_from_eua_v6(void)
{
struct in46_addr ia;
struct in46_addr ia[2];
struct ul66_t eua;
const uint8_t v6_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v6 };
const uint8_t v6_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v6,
@@ -331,16 +331,16 @@ static void test_in46a_from_eua_v6(void)
/* unspecified V6 */
memcpy(eua.v, v6_unspec, sizeof(v6_unspec));
eua.l = sizeof(v6_unspec);
OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1);
OSMO_ASSERT(ia.len == 16);
OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia.v6));
OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1);
OSMO_ASSERT(ia[0].len == 16);
OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia[0].v6));
/* specified V6 */
memcpy(eua.v, v6_spec, sizeof(v6_spec));
eua.l = sizeof(v6_spec);
OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1);
OSMO_ASSERT(ia.len == 16);
OSMO_ASSERT(!memcmp(&ia.v6, v6_spec+2, ia.len));
OSMO_ASSERT(in46a_from_eua(&eua, ia) == 1);
OSMO_ASSERT(ia[0].len == 16);
OSMO_ASSERT(!memcmp(&ia[0].v6, v6_spec+2, ia[0].len));
}
static void test_in46a_from_eua_v4v6(void) {

3
utils/Makefile.am Normal file
View File

@@ -0,0 +1,3 @@
bin_PROGRAMS = gtp-echo-responder
gtp_echo_responder_SOURCES = gtp_echo_responder.c

470
utils/gtp_echo_responder.c Normal file
View File

@@ -0,0 +1,470 @@
/*
* MIT License
*
* Copyright (c) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* For more info see:
* 3GPP TS 29.060 (GTPv1 and GTPv0)
* 3GPP TS 29.274 (GTPv2C)
*/
#include "../config.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/socket.h>
#define GTP1C_PORT 2123
#define GTP_MSGTYPE_ECHO_REQ 1
#define GTP_MSGTYPE_ECHO_RSP 2
#define GTP1C_IE_RECOVERY 14
#define GTP2C_IE_RECOVERY 3
#define GTP2C_IE_NODE_FEATURES 152
struct gtp1_hdr {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t pn:1, s:1, e:1, spare:1, pt:1, version:3;
#else
uint8_t version:3, pt:1, spare:1, e:1, s:1, pn:1;
#endif
uint8_t type;
uint16_t length;
uint32_t tei;
uint16_t seq;
uint8_t npdu;
uint8_t next;
} __attribute__((packed));
struct gtp2_hdr {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t reserved:3, t:1, p:1, version:3;
#else
uint8_t version:3, p:1, t:1, reserved:1;
#endif
uint8_t type;
uint16_t length;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint32_t reserved2:8, seq:24;
#else
uint8_t seq:24, reserved2:1;
#endif
} __attribute__((packed));
struct gtp_echo_resp_state {
struct {
char laddr[INET6_ADDRSTRLEN];
uint8_t recovery_ctr;
uint8_t node_features;
} cfg;
struct sockaddr_storage laddr_gtpc;
int fd_gtpc;
};
struct gtp_echo_resp_state *g_st;
static void print_usage(void)
{
printf("Usage: gtp-echo-responder [-h] [-V] [-l listen_addr]\n");
}
static void print_help(void)
{
printf(" Some useful help...\n"
" -h --help This help text\n"
" -V --version Print the version of gtp-echo-responder\n"
" -l --listen-addr Listend address for GTPCv1 and GTPCv2\n"
" -R --recovery-counter GTP Recovery Counter to transmit in GTP Echo Response message\n"
" -n --node-features GTPCv2 Node Features bitmask to transmit in GTP Echo Response message\n"
);
}
static void print_version(void)
{
printf("gtp-echo-responder version %s\n", PACKAGE_VERSION);
}
static uint8_t parse_node_features_mask(const char *arg)
{
unsigned long res;
char *end;
errno = 0;
res = strtoul(arg, &end, 0);
if ((errno == ERANGE && res == ULONG_MAX) || (errno && !res) ||
arg == end || *end != '\0') {
fprintf(stderr, "Failed parsing Node Features bitmask: '%s'\n", arg);
exit(1);
}
if (res > 0xff) {
fprintf(stderr, "Failed parsing Node Features bitmask: '%s' > 0xFF\n", arg);
exit(1);
}
return (uint8_t)res;
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static struct option long_options[] = {
{ "help", 0, 0, 'h' },
{ "version", 0, 0, 'V' },
{ "listen-addr", 1, 0, 'l'},
{ "recovery-counter", 1, 0, 'R'},
{ "node-features", 1, 0, 'N'},
{ 0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "hVl:R:N:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_usage();
print_help();
exit(0);
case 'V':
print_version();
exit(0);
break;
case 'l':
strncpy(&g_st->cfg.laddr[0], optarg, sizeof(g_st->cfg.laddr));
g_st->cfg.laddr[sizeof(g_st->cfg.laddr) - 1] = '\0';
break;
case 'R':
g_st->cfg.recovery_ctr = (uint8_t)atoi(optarg);
break;
case 'N':
g_st->cfg.node_features = parse_node_features_mask(optarg);
break;
}
}
}
static int init_socket(void)
{
struct in_addr addr;
struct in6_addr addr6;
struct sockaddr_in *saddr;
struct sockaddr_in6 *saddr6;
int family;
if (inet_pton(AF_INET6, g_st->cfg.laddr, &addr6) == 1) {
family = AF_INET6;
saddr6 = (struct sockaddr_in6 *)&g_st->laddr_gtpc;
saddr6->sin6_family = family;
saddr6->sin6_port = htons(GTP1C_PORT);
memcpy(&saddr6->sin6_addr, &addr6, sizeof(addr6));
} else if (inet_pton(AF_INET, g_st->cfg.laddr, &addr) == 1) {
family = AF_INET;
saddr = (struct sockaddr_in *)&g_st->laddr_gtpc;
saddr->sin_family = family;
saddr->sin_port = htons(GTP1C_PORT);
memcpy(&saddr->sin_addr, &addr, sizeof(addr));
} else {
fprintf(stderr, "Failed parsing address %s\n", g_st->cfg.laddr);
return -1;
}
if ((g_st->fd_gtpc = socket(family, SOCK_DGRAM, 0)) < 0) {
fprintf(stderr, "socket() failed: %s\n", strerror(errno));
return -2;
}
if (bind(g_st->fd_gtpc, (struct sockaddr *)&g_st->laddr_gtpc, sizeof(g_st->laddr_gtpc)) < 0) {
fprintf(stderr, "bind() failed: %s\n", strerror(errno));
return -3;
}
return 0;
}
static const char *sockaddr2str(const struct sockaddr *saddr)
{
static char _rem_addr_str[INET6_ADDRSTRLEN];
struct sockaddr_in *saddr4;
struct sockaddr_in6 *saddr6;
switch (saddr->sa_family) {
case AF_INET6:
saddr6 = (struct sockaddr_in6 *)saddr;
if (!inet_ntop(saddr6->sin6_family, &saddr6->sin6_addr, _rem_addr_str, sizeof(_rem_addr_str)))
strcpy(_rem_addr_str, "unknown");
return _rem_addr_str;
case AF_INET:
saddr4 = (struct sockaddr_in *)saddr;
if (!inet_ntop(saddr4->sin_family, &saddr4->sin_addr, _rem_addr_str, sizeof(_rem_addr_str)))
strcpy(_rem_addr_str, "unknown");
return _rem_addr_str;
default:
strcpy(_rem_addr_str, "unknown-family");
return _rem_addr_str;
}
}
static int write_cb(int fd, const uint8_t *buf, size_t buf_len, const struct sockaddr *rem_saddr)
{
ssize_t rc;
rc = sendto(fd, buf, buf_len, 0, rem_saddr, sizeof(struct sockaddr_storage));
if (rc < 0) {
fprintf(stderr, "sendto() failed: %s\n", strerror(errno));
return -1;
}
if (rc != buf_len) {
fprintf(stderr, "sendto() short write: %zd vs exp %zu\n", rc, buf_len);
return -1;
}
return 0;
}
static int gen_gtpc1_echo_rsp(uint8_t *buf, struct gtp1_hdr *echo_req)
{
int offset = 0;
struct gtp1_hdr *echo_rsp = (struct gtp1_hdr *)buf;
unsigned exp_hdr_len = (echo_req->s || echo_req->pn || echo_req->e) ? 12 : 8;
memcpy(echo_rsp, echo_req, exp_hdr_len);
echo_rsp->type = GTP_MSGTYPE_ECHO_RSP;
offset = exp_hdr_len;
buf[offset++] = GTP1C_IE_RECOVERY;
buf[offset++] = g_st->cfg.recovery_ctr;
/* Update Length */
echo_rsp->length = htons(offset - 8);
return offset;
}
static int gen_gtpc2_echo_rsp(uint8_t *buf, struct gtp2_hdr *echo_req)
{
int offset = 0;
struct gtp1_hdr *echo_rsp = (struct gtp1_hdr *)buf;
unsigned exp_hdr_len = 8;
memcpy(echo_rsp, echo_req, exp_hdr_len);
echo_rsp->type = GTP_MSGTYPE_ECHO_RSP;
offset = exp_hdr_len;
/* 3GPP TS 29.274 sec 8.5 Recovery (Restart Counter) */
buf[offset++] = GTP2C_IE_RECOVERY;
buf[offset++] = 0; /* IE Length (high) */
buf[offset++] = 1; /* IE Length (low) */
buf[offset++] = 0; /* Spare=0 | Instance=0 (Table 7.1.1-1) */
buf[offset++] = g_st->cfg.recovery_ctr;
/* 3GPP TS 29.274 sec 8.83 Node Features */
if (g_st->cfg.node_features > 0) {
buf[offset++] = GTP2C_IE_NODE_FEATURES;
buf[offset++] = 0; /* IE Length (high) */
buf[offset++] = 1; /* IE Length (low) */
buf[offset++] = 0; /* Spare=0 | Instance=0 (Table 7.1.1-1) */
buf[offset++] = g_st->cfg.node_features;
}
/* Update Length */
echo_rsp->length = htons(offset - 4);
return offset;
}
static int rx_gtpc1_echo_req(struct gtp1_hdr *echo_req, unsigned buf_len, const struct sockaddr *rem_saddr)
{
int rc;
const size_t tx_buf_len = buf_len + 128; /* Leave some extra room */
uint8_t *tx_buf = alloca(tx_buf_len);
printf("Rx GTPCv1_ECHO_REQ from %s, Tx GTPCv1_ECHO_RSP\n", sockaddr2str(rem_saddr));
memset(tx_buf, 0, tx_buf_len);
rc = gen_gtpc1_echo_rsp(tx_buf, echo_req);
return write_cb(g_st->fd_gtpc, tx_buf, rc, rem_saddr);
}
static int rx_gtpc1(struct gtp1_hdr *hdr, unsigned buf_len, const struct sockaddr *rem_saddr)
{
unsigned exp_hdr_len = (hdr->s || hdr->pn || hdr->e) ? 12 : 8;
unsigned pdu_len;
if (buf_len < exp_hdr_len) {
fprintf(stderr, "GTPCv1 packet size smaller than header! %u < exp %u\n", buf_len, exp_hdr_len);
return -1;
}
pdu_len = ntohs(hdr->length);
if (buf_len < 8 + pdu_len) {
fprintf(stderr, "GTPCv1 packet size smaller than announced! %u < exp %u\n", buf_len, 8 + pdu_len);
return -1;
}
if (hdr->pt != 1) {
fprintf(stderr, "GTPCv1 Protocol Type GTP' not supported!\n");
return -1;
}
switch (hdr->type) {
case GTP_MSGTYPE_ECHO_REQ:
return rx_gtpc1_echo_req(hdr, buf_len, rem_saddr);
default:
fprintf(stderr, "Silently ignoring unexpected packet of type %u\n", hdr->type);
return 0;
}
}
static int rx_gtpc2_echo_req(struct gtp2_hdr *echo_req, unsigned buf_len, const struct sockaddr *rem_saddr)
{
int rc;
const size_t tx_buf_len = buf_len + 128; /* Leave some extra room */
uint8_t *tx_buf = alloca(tx_buf_len);
if (echo_req->t) {
fprintf(stderr, "GTPCv2 ECHO message should contain T=0!\n");
return -1;
}
printf("Rx GTPCv2_ECHO_REQ from %s, Tx GTPCv2_ECHO_RSP\n", sockaddr2str(rem_saddr));
memset(tx_buf, 0, tx_buf_len);
rc = gen_gtpc2_echo_rsp(tx_buf, echo_req);
return write_cb(g_st->fd_gtpc, tx_buf, rc, rem_saddr);
}
static int rx_gtpc2(struct gtp2_hdr *hdr, unsigned buf_len, const struct sockaddr *rem_saddr)
{
unsigned exp_hdr_len = hdr->t ? 12 : 8;
unsigned pdu_len;
if (hdr->p) {
fprintf(stderr, "GTPCv2 piggybacked message not supported!\n");
return -1;
}
if (buf_len < exp_hdr_len) {
fprintf(stderr, "GTPCv2 packet size smaller than header! %u < exp %u\n", buf_len, exp_hdr_len);
return -1;
}
pdu_len = ntohs(hdr->length);
/* 3GPP TS 29.274 sec 5.5.1: "Octets 3 to 4 represent the Message Length
* field. This field shall indicate the length of the message in octets
* excluding the mandatory part of the GTP-C header (the first 4
* octets). The TEID (if present) and the Sequence Number shall be
* included in the length count" */
if (buf_len < 4 + pdu_len) {
fprintf(stderr, "GTPCv2 packet size smaller than announced! %u < exp %u\n", buf_len, 4 + pdu_len);
return -1;
}
switch (hdr->type) {
case GTP_MSGTYPE_ECHO_REQ:
return rx_gtpc2_echo_req(hdr, buf_len, rem_saddr);
default:
fprintf(stderr, "Silently ignoring unexpected packet of type %u\n", hdr->type);
return 0;
}
}
static int read_cb(int fd)
{
ssize_t sz;
uint8_t buf[4096];
struct sockaddr_storage rem_saddr;
socklen_t rem_saddr_len = sizeof(rem_saddr);
struct gtp1_hdr *hdr1;
if ((sz = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&rem_saddr, &rem_saddr_len)) < 0) {
fprintf(stderr, "recvfrom() failed: %s\n", strerror(errno));
return -1;
}
if (sz == 0) {
fprintf(stderr, "recvfrom() read zero bytes!\n");
return -1;
}
hdr1 = (struct gtp1_hdr *)&buf[0];
switch (hdr1->version) {
case 1:
return rx_gtpc1(hdr1, sz, (const struct sockaddr *)&rem_saddr);
case 2:
return rx_gtpc2((struct gtp2_hdr *)&buf[0], sz, (const struct sockaddr *)&rem_saddr);
default:
fprintf(stderr, "Rx GTPv%u: not supported (flags=0x%x)\n", hdr1->version, buf[0]);
return -1;
}
}
static int loop(void)
{
int rc;
fd_set rfds;
int nfds;
while (true) {
FD_ZERO(&rfds);
FD_SET(g_st->fd_gtpc, &rfds);
nfds = g_st->fd_gtpc + 1;
rc = select(nfds, &rfds, NULL, NULL, NULL);
if (rc == 0)
continue;
if (rc < 0) {
fprintf(stderr, "select() failed: %s\n", strerror(errno));
return -1;
}
if (FD_ISSET(g_st->fd_gtpc, &rfds))
read_cb(g_st->fd_gtpc);
}
}
int main(int argc, char **argv)
{
g_st = calloc(1, sizeof(struct gtp_echo_resp_state));
strcpy(g_st->cfg.laddr, "::");
handle_options(argc, argv);
printf("Listening on: %s\n", g_st->cfg.laddr);
if (init_socket() < 0)
exit(1);
printf("Socket bound successfully, listening for requests...\n");
if (loop() < 0)
exit(1);
return 0;
}

111
utils/gtp_echo_responder_test.py Executable file
View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
# Author: Pau Espin Pedrol <pespin@sysmocom.de>
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import socket
import argparse
import struct
from ipaddress import ip_address, IPv4Address
GTP1C_PORT = 2123
BUF_SIZE = 4096
GTP_HDRv1_FLAG_PN = (1<<0)
GTP_HDRv1_FLAG_S = (1<<1)
GTP_HDRv1_FLAG_E = (1<<2)
GTP_HDRv1_PT_GTP = (1<<4)
GTP_HDRv1_VER_GTP1 = (1<<5)
GTP_HDRv2_FLAG_T = (1<<3)
GTP_HDRv2_FLAG_P = (1<<4)
GTP_HDRv2_VER_GTP2 = (2<<5)
def gen_gtpc_v1_hdr(flags, type, length, tei, seq=0, npdu=0, next=0):
spare = 0
if (flags & (GTP_HDRv1_FLAG_PN|GTP_HDRv1_FLAG_S|GTP_HDRv1_FLAG_E)):
#long format
length += 4
d = struct.pack('!BBHIHBB', flags, type, length, tei, seq, npdu, next)
else:
#short format
d = struct.pack('!BBHI', flags, type, length, tei)
return d
def gen_gtpc_v2_hdr(flags, type, length, tei=0, seq=0):
spare = 0
if (flags & (GTP_HDRv2_FLAG_T)):
#long format, with TEI
length += 4 + 4
d = struct.pack('!BBHIHBB', flags, type, length, tei, seq >> 8, seq & 0xff, spare)
else:
#short format
length += 4
d = struct.pack('!BBHHBB', flags, type, length, seq >> 8, seq & 0xff, spare)
return d
def gen_gtpc_v1_echo_req(tei=0, append_flags=0, seq=0, npdu=0, next=0):
return gen_gtpc_v1_hdr(GTP_HDRv1_VER_GTP1 | GTP_HDRv1_PT_GTP | append_flags, 1, 0, tei, seq, npdu, next)
def gen_gtpc_v2_echo_req(append_flags=0, seq=0, recovery=0, node_features=-1):
length = 0
payload = b''
if (recovery > 0):
recovery_ie = struct.pack('!BHBB', 3, 1, 0, recovery)
payload += recovery_ie
length += len(recovery_ie)
if (node_features > 0):
node_features_ie = struct.pack('!BHBB', 152, 1, 0, node_features)
payload += node_features_ie
length += len(node_features_ie)
return gen_gtpc_v2_hdr(GTP_HDRv2_VER_GTP2 | append_flags, 1, length, 0, seq) + payload
def tx_rx(sk, rem_addr, tx_buf, exp_rx = True):
print('Tx ECHO_REQ to %r: %r' % (repr(rem_addr), repr(tx_buf)))
sk.sendto(tx_buf, rem_addr)
if exp_rx:
rx_buf = sk.recvfrom(BUF_SIZE)
msg = "Message from Server {}".format(rx_buf)
print(msg)
if __name__ == '__main__':
p = argparse.ArgumentParser(description='Tester for gtp-echo-recorder.')
p.add_argument('-l', '--local-address', default='127.0.0.2', help="Local GTP address")
p.add_argument('-r', '--remote-address', default='127.0.0.1', help="Remote GTP address")
args = p.parse_args()
print('Binding socket on %r...' % repr((args.local_address, GTP1C_PORT)))
family = socket.AF_INET if type(ip_address(args.local_address)) is IPv4Address else socket.AF_INET6
sk = socket.socket(family=family, type=socket.SOCK_DGRAM)
sk.bind((args.local_address, GTP1C_PORT));
rem_addr = (args.remote_address, GTP1C_PORT)
tx_rx(sk, rem_addr, gen_gtpc_v1_echo_req())
tx_rx(sk, rem_addr, gen_gtpc_v1_echo_req(1, GTP_HDRv1_FLAG_S, seq=67))
tx_rx(sk, rem_addr, gen_gtpc_v2_echo_req(0, seq=300, recovery=-1, node_features=-1))
tx_rx(sk, rem_addr, gen_gtpc_v2_echo_req(0, seq=20, recovery=99, node_features=-1))
tx_rx(sk, rem_addr, gen_gtpc_v2_echo_req(0, seq=20, recovery=100, node_features=0xbb))