mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn.git
synced 2025-11-03 05:33:23 +00:00
Compare commits
43 Commits
whyteks/ic
...
rhizomatic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea9044fe4 | ||
|
|
306e7fa853 | ||
|
|
59f1539ece | ||
|
|
eff88c08e7 | ||
|
|
92ac7249f9 | ||
|
|
5cf6b75dc9 | ||
|
|
4aa2e417c9 | ||
|
|
f14c056310 | ||
|
|
bf69ddbfef | ||
|
|
70a4e2e6f8 | ||
|
|
99afe979ef | ||
|
|
35066fb0b0 | ||
|
|
55fe62f634 | ||
|
|
4fac842826 | ||
|
|
97f60e3dca | ||
|
|
a727e6ed38 | ||
|
|
3a55b89777 | ||
|
|
9f1f747d8e | ||
|
|
b9036af7ca | ||
|
|
724ecc6680 | ||
|
|
0d3bd3435f | ||
|
|
3ed252b58e | ||
|
|
ac802e63d7 | ||
|
|
bc583d9763 | ||
|
|
ade4dc191b | ||
|
|
cd05da79e7 | ||
|
|
5545bcea5d | ||
|
|
c97286f839 | ||
|
|
f471800168 | ||
|
|
bdf0697a5a | ||
|
|
674a912fb5 | ||
|
|
1bf3b3d0f9 | ||
|
|
fb9303c610 | ||
|
|
0585769741 | ||
|
|
9b288b788e | ||
|
|
134ac7e7c8 | ||
|
|
46f04343a5 | ||
|
|
a3ca2d185b | ||
|
|
8cbdd21867 | ||
|
|
ae81195418 | ||
|
|
6ee5fa939a | ||
|
|
b6a0e3fd2e | ||
|
|
bd2b55679e |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
-------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
85
debian/changelog
vendored
@@ -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
2
debian/compat
vendored
@@ -1 +1 @@
|
||||
9
|
||||
10
|
||||
|
||||
23
debian/control
vendored
23
debian/control
vendored
@@ -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
5
debian/copyright
vendored
@@ -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
1
debian/gtp-echo-responder.install
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/usr/bin/gtp-echo-responder
|
||||
2
debian/libgtp6.shlibs
vendored
Normal file
2
debian/libgtp6.shlibs
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Most recent version of the package that added new symbols (OS#5318)
|
||||
libgtp 6 libgtp6 (>= 1.8.0)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
157
ggsn/ggsn.c
157
ggsn/ggsn.c
@@ -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;
|
||||
}
|
||||
|
||||
27
ggsn/ggsn.h
27
ggsn/ggsn.h
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
12
ggsn/sgsn.c
12
ggsn/sgsn.c
@@ -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;
|
||||
|
||||
@@ -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
596
gtp/gsn.c
Normal 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
181
gtp/gsn.h
Normal 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 */
|
||||
147
gtp/gtp.h
147
gtp/gtp.h
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
14
lib/icmpv6.h
14
lib/icmpv6.h
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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 \
|
||||
|
||||
@@ -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
3
utils/Makefile.am
Normal 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
470
utils/gtp_echo_responder.c
Normal 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
111
utils/gtp_echo_responder_test.py
Executable 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))
|
||||
Reference in New Issue
Block a user