[PATCH] initial commit for libosmo-gtp

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.

pablo at gnumonks.org pablo at gnumonks.org
Thu Oct 15 06:12:02 UTC 2015


From: Pablo Neira Ayuso <pablo at gnumonks.org>

I started this libosmo-gtp library after spending quite some time trying to
clean up libgtp and add IPv6 support to it in some clean way.

The result was this library that I started from scratch in my spare time.

The basic idea behind this is to avoid mixing the carrier (socket) code with
the message building and parsing, and avoid overly the complicated callback
scheme of libgtp.

This implements minimal support for GTPv0, it also includes automated tests
by generating GTPv0 messages then hand it over to gtp0_recv() which generates
the reply.

I remember that I also validated this code by sending GTPv0 messages over UDP
to inspect them through wireshark, and they were OK.

It's fairly incomplete. The only client I have for this library is an initial
(fairly incomplete) osmo-gtp daemon using this library to replace opengtp.

BTW, there's a src/libosmocore.c file that contains helper functions to
provide a more consistent way to work with different TLV types.

Probably it can be uploaded to the osmocom repository even if incomplete?

Well, just wanted to get this code out there, it got stuck here and I didn't
find so far more time to make more progress on it, but we'll try to update
it when traveling or something.

 .gitignore                |  43 +++
 Makefile.am               |  13 +
 configure.ac              |  42 +++
 git-version-gen           | 151 +++++++++++
 include/Makefile.am       |   3 +
 include/gtp.h             | 102 +++++++
 include/libosmocore.h     |  20 ++
 include/osmocom/gtp/gtp.h |  49 ++++
 libosmogtp.pc.in          |  11 +
 src/Makefile.am           |  15 ++
 src/gtp.c                 | 669 ++++++++++++++++++++++++++++++++++++++++++++++
 src/libosmocore.c         |  70 +++++
 tests/Makefile.am         |  52 ++++
 tests/gtp/gtp_test.ok     |   0
 tests/gtp_test.c          | 121 +++++++++
 tests/gtp_test.ok         |   4 +
 tests/testsuite.at        |  18 ++
 17 files changed, 1383 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Makefile.am
 create mode 100644 configure.ac
 create mode 100755 git-version-gen
 create mode 100644 include/Makefile.am
 create mode 100644 include/gtp.h
 create mode 100644 include/libosmocore.h
 create mode 100644 include/osmocom/gtp/gtp.h
 create mode 100644 libosmogtp.pc.in
 create mode 100644 src/Makefile.am
 create mode 100644 src/gtp.c
 create mode 100644 src/libosmocore.c
 create mode 100644 tests/Makefile.am
 create mode 100644 tests/gtp/gtp_test.ok
 create mode 100644 tests/gtp_test.c
 create mode 100644 tests/gtp_test.ok
 create mode 100644 tests/testsuite.at

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ee984ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.pc
+aclocal.m4
+m4/*.m4
+autom4te.cache
+config.h*
+config.sub
+config.log
+config.status
+config.guess
+configure
+compile
+depcomp
+missing
+ltmain.sh
+install-sh
+stamp-h1
+libtool
+#libosmo-abis-*
+tests/*_test
+
+.tarball-version
+.version
+.dirstamp
+
+# tests
+tests/atconfig
+tests/package.m4
+tests/testsuite
+tests/testsuite.log
+tests/testsuite.dir/
+tests/gtp_test
+
+
+
+# vi/vim files
+*.sw?
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..5ab1484
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,13 @@
+ACLOCAL_AMFLAGS = -I m4
+
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+SUBDIRS = include src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libosmogtp.pc
+
+BUILT_SOURCES = $(top_srcdir)/.version
+$(top_srcdir)/.version:
+	echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+	echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..c22fab4
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,42 @@
+AC_INIT([libosmogtp],
+	m4_esyscmd([./git-version-gen .tarball-version]),
+	[gprs at lists.osmocom.org])
+
+AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip 1.6])
+AC_CONFIG_TESTDIR(tests)
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+LT_INIT([pic-only])
+
+AC_CONFIG_MACRO_DIR([m4])
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
+      [ AC_MSG_RESULT([yes])
+        SYMBOL_VISIBILITY="-fvisibility=hidden"],
+        AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+dnl Generate the output
+AM_CONFIG_HEADER(config.h)
+
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.3.0)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.3.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.3.10)
+
+AC_OUTPUT(
+	libosmogtp.pc
+	include/Makefile
+	src/Makefile
+	tests/Makefile
+	Makefile)
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 0000000..42cf3d2
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+#   produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+#   presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+#   a checked-out repository.  Created with contents that were learned at
+#   the last time autoconf was run, and used by git-version-gen.  Must not
+#   be present in either $(srcdir) or $(builddir) for git-version-gen to
+#   give accurate answers during normal development with a checked out tree,
+#   but must be present in a tarball when there is no version control system.
+#   Therefore, it cannot be used in any dependencies.  GNUmakefile has
+#   hooks to force a reconfigure at distribution time to get the value
+#   correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+#   tarball.  Usable in dependencies, particularly for files that don't
+#   want to depend on config.h but do want to track version changes.
+#   Delete this file prior to any autoconf run where you want to rebuild
+#   files to pick up a version string change; and leave it stale to
+#   minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+#         m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+#         [bug-project at example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+#	echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+#	echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+    1) ;;
+    *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+    v=`cat $tarball_version_file` || exit 1
+    case $v in
+	*$nl*) v= ;; # reject multi-line output
+	[0-9]*) ;;
+	*) v= ;;
+    esac
+    test -z "$v" \
+	&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+    : # use $v
+elif
+       v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+	  || git describe --abbrev=4 HEAD 2>/dev/null` \
+    && case $v in
+	 [0-9]*) ;;
+	 v[0-9]*) ;;
+	 *) (exit 1) ;;
+       esac
+then
+    # Is this a new git that lists number of commits since the last
+    # tag or the previous older version that did not?
+    #   Newer: v6.10-77-g0f8faeb
+    #   Older: v6.10-g0f8faeb
+    case $v in
+	*-*-*) : git describe is okay three part flavor ;;
+	*-*)
+	    : git describe is older two part flavor
+	    # Recreate the number of commits and rewrite such that the
+	    # result is the same as if we were using the newer version
+	    # of git describe.
+	    vtag=`echo "$v" | sed 's/-.*//'`
+	    numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+	    v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+	    ;;
+    esac
+
+    # Change the first '-' to a '.', so version-comparing tools work properly.
+    # Remove the "g" in git describe's output string, to save a byte.
+    v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+    v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+    '') ;;
+    *) # Append the suffix only if there isn't one already.
+	case $v in
+	  *-dirty) ;;
+	  *) v="$v-dirty" ;;
+	esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..d04cac0
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,3 @@
+noinst_HEADERS = gtp.h libosmocore.h
+
+nobase_include_HEADERS = osmocom/gtp/gtp.h
diff --git a/include/gtp.h b/include/gtp.h
new file mode 100644
index 0000000..3a47de6
--- /dev/null
+++ b/include/gtp.h
@@ -0,0 +1,102 @@
+#ifndef _GTP_PROTOCOL_H_
+#define _GTP_PROTOCOL_H_
+
+enum gtp_type {
+	GTP_UNUSED		= 0,	/* GSM 09.60 says for future use */
+	GTP_ECHO_REQ		= 1,	/* 7.4.1 GSM 09.60 */
+	GTP_ECHO_RESP		= 2,	/* 7.4.2 GSM 09.60 */
+	GTP_VERSION_NOTSUPP	= 3,	/* 7.4.3 GSM 09.60 */
+	GTP_PDP_CREATE_REQ	= 16,	/* 7.5.1 GSM 09.60 */
+	GTP_PDP_CREATE_RESP	= 17,	/* 7.5.2 GSM 09.60 */
+	GTP_PDP_UPDATE_REQ	= 18,	/* 7.5.3 GSM 09.60 */
+	GTP_PDP_UPDATE_RESP	= 19,	/* 7.5.4 GSM 09.60 */
+	GTP_PDP_DELETE_REQ	= 20,	/* 7.5.5 GSM 09.60 */
+	GTP_PDP_DELETE_RESP	= 21,	/* 7.5.6 GSM 09.60 */
+	GTP_TYPE_MAX,
+};
+
+struct gtp0_header {
+#if BYTE_ORDER == BIG_ENDIAN
+	uint8_t version:3,
+		pt:1,
+		spare:3,
+		snn:1;
+#elif BYTE_ORDER == LITTLE_ENDIAN
+	uint8_t snn:1,
+		spare:3,
+		pt:1,
+		version:3;
+#else
+#warn "BYTE_ORDER is not defined, please fix your headers"
+#endif
+	uint8_t type;
+	uint16_t length;
+	uint16_t seq;
+	uint16_t flow;
+	uint8_t number;
+	uint8_t spare1;
+	uint8_t spare2;
+	uint8_t spare3;
+	uint64_t tid;
+} __attribute__((packed));
+
+/*
+ * Information elements
+ */
+/* TV */
+#define GTPV0_IE_CAUSE			1   /* 8 bits */
+#define GTPV0_IE_QOS_PROFILE		6   /* 24 bits */
+#define GTPV0_IE_REORDERING_REQ		8   /* 1 bit */
+#define GTPV0_IE_RECOVERY		14  /* 8 bit */
+#define GTPV0_IE_SELECT_MODE		15  /* 16 bits */
+#define GTPV0_IE_FLOW_LABEL_DATA	16  /* 16 bits */
+#define GTPV0_IE_FLOW_LABEL_SIGNAL	17  /* 16 bits */
+#define GTPV0_IE_CHARGING_ID		127 /* 32 bits */
+/* TLV >= 128 */
+#define GTPV0_IE_ENDUSER_ADDR		128
+#define GTPV0_IE_AP_NAME		131
+#define GTPV0_IE_PROTO_CONF_OPTS	132
+#define GTPV0_IE_GSN_ADDR		133
+#define GTPV0_IE_MSISDN			134
+#define GTPV0_IE_CHARGING_GW_ADDR	251
+
+/*
+ * Other
+ */
+#define GTPV0_CAUSE_REQ_ACCEPTED	128	/* GSM 09.60 7.9.1 */
+
+#include <netinet/in.h>
+
+struct enduser_addr_ie_payload_ipv4 {
+#if BYTE_ORDER == BIG_ENDIAN
+	uint8_t spare:4,
+		pdp_org_type:4;
+#elif BYTE_ORDER == LITTLE_ENDIAN
+	uint8_t pdp_org_type:4,
+		spare:4;
+#else
+#warn "BYTE_ORDER is not defined, please fix your headers"
+#endif
+	uint8_t pdp_type_number;
+	struct in_addr addr;
+} __attribute__((packed));
+
+struct enduser_addr_ie_payload_ipv6 {
+#if BYTE_ORDER == BIG_ENDIAN
+	uint8_t spare:4,
+		pdp_org_type:4;
+#elif BYTE_ORDER == LITTLE_ENDIAN
+	uint8_t pdp_org_type:4,
+		spare:4;
+#else
+#warn "BYTE_ORDER is not defined, please fix your headers"
+#endif
+	uint8_t pdp_type_number;
+	struct in6_addr addr;
+} __attribute__((packed));
+
+#define PDP_ORG_IETF	1
+#define PDP_TYPE_IPV4	0x21
+#define PDP_TYPE_IPV6	0x57
+
+#endif
diff --git a/include/libosmocore.h b/include/libosmocore.h
new file mode 100644
index 0000000..dab9265
--- /dev/null
+++ b/include/libosmocore.h
@@ -0,0 +1,20 @@
+#ifndef _GTP_LIBOSMOCORE_H_
+#define _GTP_LIBOSMOCORE_H_
+
+#include <osmocom/gsm/tlv.h>
+
+void *msgb_tv_put_be8(struct msgb *msg, int type, uint8_t value);
+void *msgb_tv_put_be16(struct msgb *msg, int type, uint16_t value);
+void *msgb_tv_put_be24(struct msgb *msg, int type, uint32_t value);
+void *msgb_tv_put_be32(struct msgb *msg, int type, uint32_t value);
+void *msgb_tlv_put_be32(struct msgb *msg, int type, uint32_t data);
+void *msgb_tlv_put_data(struct msgb *msg, int type, int len, void *data);
+
+uint8_t tv_get_be8(struct tlv_parsed *tp, int type);
+uint16_t tv_get_be16(struct tlv_parsed *tp, int type);
+uint32_t tv_get_be24(struct tlv_parsed *tp, int type);
+uint32_t tv_get_be32(struct tlv_parsed *tp, int type);
+const char *tv_get_strdup(struct tlv_parsed *tp, int type);
+const char *tlv_get_strdup(struct tlv_parsed *tp, int type);
+
+#endif
diff --git a/include/osmocom/gtp/gtp.h b/include/osmocom/gtp/gtp.h
new file mode 100644
index 0000000..cb1b362
--- /dev/null
+++ b/include/osmocom/gtp/gtp.h
@@ -0,0 +1,49 @@
+#ifndef _OSMO_GTP_H_
+#define _OSMO_GTP_H_
+
+enum osmo_gtp_event {
+	OSMO_GTP_PDP_NEW,
+	OSMO_GTP_PDP_UPD,
+	OSMO_GTP_PDP_DEL,
+	/* XXX add more events here */
+	OSMO_GTP_EVENT_MAX,
+};
+
+enum gtp_err_type {
+	GTP_ENOSUPP	= 0,
+	GTP_ETOOSHORT,
+	GTP_ETRUNCATED,
+	GTP_EMALFORMED,
+	GTP_EMAX
+};
+
+struct osmo_gtp_err {
+	const char		*file;
+	int			line;
+	enum gtp_err_type	type;
+};
+
+struct osmo_gsn;
+
+struct osmo_gsn *osmo_gsn_alloc(void);
+
+struct msgb *gtp0_recv(struct osmo_gsn *gsn, struct msgb *msg,
+		       struct osmo_gtp_err *err);
+
+enum {
+	OSMO_GTP_DEFAULT = 0,
+	OSMO_GTP_HEX,
+};
+
+void osmo_gtp0_fprintf(FILE *fd, struct msgb *msg, uint32_t type);
+
+struct gtp0_header *gtp0_header_put(struct msgb *msg, uint32_t type,
+				    uint16_t seq, uint16_t flow, uint64_t tid);
+void gtp0_header_end(struct gtp0_header *gtp0h, struct msgb *msg);
+
+enum osmo_gsn_proto {
+	OSMO_GSN_IPV4	= 0,
+	OSMO_GSN_IPV6,
+};
+
+#endif
diff --git a/libosmogtp.pc.in b/libosmogtp.pc.in
new file mode 100644
index 0000000..f8998ea
--- /dev/null
+++ b/libosmogtp.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: GTP Core Library
+Description: C Utility Library
+Version: @VERSION@
+Libs: -L${libdir} -losmogtp
+Cflags: -I${includedir}/
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..cad0c75
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,15 @@
+# This is _NOT_ the library release version, it's an API version.
+# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification
+LIBVERSION=0:0:0
+
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS= -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+COMMONLIBS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS)
+
+lib_LTLIBRARIES = libosmogtp.la
+
+libosmogtp_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBVERSION)
+libosmogtp_la_LIBADD = $(COMMONLIBS)
+libosmogtp_la_SOURCES = gtp.c		\
+			libosmocore.c
diff --git a/src/gtp.c b/src/gtp.c
new file mode 100644
index 0000000..672af5a
--- /dev/null
+++ b/src/gtp.c
@@ -0,0 +1,669 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <arpa/inet.h>
+
+#include <gtp.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gtp/gtp.h>
+#include <osmocom/gsm/tlv.h>
+
+#include "libosmocore.h"
+
+/* Generic context pointer to data in the event context */
+struct osmo_gtp_ctx {
+	struct osmo_gtp_pdp *pdp;
+	/* XXX Add more event context here */
+};
+
+/* Events are called from the GTP stack to notify upper layer application */
+struct gtp_event_cb {
+	int (*cb)(enum osmo_gtp_event event, struct osmo_gtp_ctx *ctx);
+};
+
+struct osmo_gsn {
+	struct {
+		enum osmo_gsn_proto	proto;
+		bool			reordering_required;
+		union {
+			struct in_addr	ip_addr;
+			struct in6_addr	ip6_addr;
+		} ggsn;
+	} cfg;
+
+	struct gtp_event_cb	event[OSMO_GTP_EVENT_MAX];
+
+	uint8_t restart_counter;
+};
+
+struct osmo_gtp_pdp {
+	uint32_t		qos_profile:24;
+	uint16_t		flow_label_data;
+	uint16_t		flow_label_signal;
+	uint8_t			sel_mode;
+	const char		*ap_name;
+	union {
+		struct in_addr	ip_addr;
+		struct in6_addr	ip6_addr;
+	} enduser;
+	union {
+		struct in_addr	ip_addr;
+		struct in6_addr	ip6_addr;
+	} sgsn;
+	const char		*msisdn;
+};
+
+#define OSMO_GTP_MTU		1500
+
+#define GTPV0			0	/* GSM 09.60 */
+#define GTP_PRIME		0	/* GSM 09.60 */
+#define GTP_NON_PRIME		1	/* GSM 12.15 */
+#define GTP_SNDCP_NPDU_UNSET	0
+
+struct gtp0_header *gtp0_header_put(struct msgb *msg, uint32_t type,
+				    uint16_t seq, uint16_t flow, uint64_t tid)
+{
+	struct gtp0_header *gtp0h = (struct gtp0_header *)msg->data;
+
+	gtp0h->version	= GTPV0;
+	gtp0h->pt	= GTP_NON_PRIME;
+	gtp0h->spare	= 0x7;
+	gtp0h->snn	= GTP_SNDCP_NPDU_UNSET;
+	gtp0h->type	= type;
+	gtp0h->seq	= htons(seq);
+	gtp0h->tid	= htobe64(tid);
+	gtp0h->spare1	= 0xff;
+	gtp0h->spare2	= 0xff;
+	gtp0h->spare3	= 0xff;
+	gtp0h->number	= 0xff;
+	gtp0h->flow	= flow;
+	msgb_put(msg, sizeof(*gtp0h));
+
+	return gtp0h;
+}
+
+void gtp0_header_end(struct gtp0_header *gtp0h, struct msgb *msg)
+{
+	gtp0h->length = htons(msg->len - sizeof(*gtp0h));
+}
+
+uint64_t gtp0h_tid(const struct msgb *msg)
+{
+	const struct gtp0_header *gtp0h =
+		(const struct gtp0_header *)msg->data;
+
+	return be64toh(gtp0h->tid);
+}
+
+uint16_t gtp0h_seq(const struct msgb *msg)
+{
+	const struct gtp0_header *gtp0h =
+		(const struct gtp0_header *)msg->data;
+
+	return ntohs(gtp0h->seq);
+}
+
+struct osmo_gsn *osmo_gsn_alloc(void)
+{
+	return calloc(1, sizeof(struct osmo_gsn));
+}
+
+static struct msgb *gtp_unsupp(struct msgb *msg)
+{
+	struct msgb *reply;
+	struct gtp0_header *gtp0h;
+
+	reply = msgb_alloc(OSMO_GTP_MTU, "gtp");
+	if (reply == NULL)
+		return NULL;
+
+	gtp0h = gtp0_header_put(reply, GTP_VERSION_NOTSUPP, gtp0h_seq(msg), 0,
+				gtp0h_tid(msg));
+	gtp0_header_end(gtp0h, reply);
+
+	return reply;
+}
+
+struct osmo_gtp_handler {
+	bool request;
+	struct msgb *(*handler)(struct osmo_gsn *gsn, struct msgb *msg,
+				struct osmo_gtp_err *err);
+};
+
+static struct msgb *gtp_echo_req_handler(struct osmo_gsn *gsn,
+					 struct msgb *msg,
+					 struct osmo_gtp_err *err)
+{
+	struct msgb *reply;
+	struct gtp0_header *gtp0h;
+
+	reply = msgb_alloc(OSMO_GTP_MTU, "gtp");
+	if (reply == NULL)
+		return NULL;
+
+	gtp0h = gtp0_header_put(reply, GTP_ECHO_RESP, gtp0h_seq(msg), 0,
+				gtp0h_tid(msg));
+	msgb_tv_put(reply, GTPV0_IE_RECOVERY, gsn->restart_counter);
+
+	/* TODO: Private extension. 7.9.26 (Optional) */
+	/* TODO: IE T=255 Length ExtensionID=(8 bits) ExtValue=(variable) */
+
+	gtp0_header_end(gtp0h, reply);
+
+	return reply;
+}
+
+static int ip_alloc(struct osmo_gtp_pdp *pdp)
+{
+	pdp->enduser.ip_addr.s_addr= 0x01020304;
+	return 0;
+}
+
+static int ip6_alloc(struct osmo_gtp_pdp *pdp)
+{
+	return 0;
+}
+
+static void build_enduser_addr(struct msgb *reply, struct osmo_gsn *gsn,
+			       struct osmo_gtp_pdp *pdp)
+{
+	switch (gsn->cfg.proto) {
+	case OSMO_GSN_IPV4: {
+		struct enduser_addr_ie_payload_ipv4 ie_ipv4;
+
+		ip_alloc(pdp);
+
+		ie_ipv4.spare = 0xf;
+		ie_ipv4.pdp_org_type = PDP_ORG_IETF;
+		ie_ipv4.pdp_type_number = PDP_TYPE_IPV4;
+		ie_ipv4.addr = pdp->enduser.ip_addr;
+
+		msgb_tlv_put_data(reply, GTPV0_IE_ENDUSER_ADDR,
+				  sizeof(ie_ipv4), &ie_ipv4);
+		break;
+		}
+	case OSMO_GSN_IPV6: {
+		struct enduser_addr_ie_payload_ipv6 ie_ipv6;
+
+		ip6_alloc(pdp);
+
+		ie_ipv6.spare = 0xf;
+		ie_ipv6.pdp_org_type = PDP_ORG_IETF;
+		ie_ipv6.pdp_type_number = PDP_TYPE_IPV6;
+		ie_ipv6.addr = pdp->enduser.ip6_addr;
+
+		msgb_tlv_put_data(reply, GTPV0_IE_ENDUSER_ADDR,
+				  sizeof(ie_ipv6), &ie_ipv6);
+		}
+		break;
+	}
+}
+
+static void build_gsn_addr(struct msgb *reply, struct osmo_gsn *gsn,
+			   struct osmo_gtp_pdp *pdp)
+{
+	switch (gsn->cfg.proto) {
+	case OSMO_GSN_IPV4:
+		msgb_tlv_put_data(reply, GTPV0_IE_GSN_ADDR,
+				  sizeof(struct in_addr),
+				  &gsn->cfg.ggsn.ip_addr);
+		break;
+	case OSMO_GSN_IPV6:
+		msgb_tlv_put_data(reply, GTPV0_IE_GSN_ADDR,
+			     	  sizeof(struct in6_addr),
+				  &gsn->cfg.ggsn.ip6_addr);
+		break;
+	}
+}
+
+static struct msgb *gtp_pdp_ctx_create_resp(struct osmo_gsn *gsn,
+					    const struct msgb *msg,
+					    struct osmo_gtp_pdp *pdp)
+{
+	struct msgb *reply;
+	struct gtp0_header *gtp0h;
+
+	reply = msgb_alloc(OSMO_GTP_MTU, "gtp");
+	if (reply == NULL)
+		return NULL;
+
+	gtp0h = gtp0_header_put(reply, GTP_PDP_CREATE_RESP, gtp0h_seq(msg), 0,
+				gtp0h_tid(msg));
+
+	/* TODO: implement reject PDP request + reason */
+	msgb_tv_put_be8(reply, GTPV0_IE_CAUSE, GTPV0_CAUSE_REQ_ACCEPTED);
+
+	if (GTPV0_CAUSE_REQ_ACCEPTED == GTPV0_CAUSE_REQ_ACCEPTED) {
+		/* Quality of service profile. 7.9.6 (Conditional) */
+
+		/* broken u24 */
+		msgb_tv_put_be24(reply, GTPV0_IE_QOS_PROFILE, pdp->qos_profile);
+
+		/* Reordering required. 7.9.7 (Conditional) */
+		msgb_tv_put_be8(reply, GTPV0_IE_REORDERING_REQ,
+				gsn->cfg.reordering_required | 0xfe);
+
+		/* Recovery. 7.9.12 (Optional) */
+		msgb_tv_put_be8(reply, GTPV0_IE_RECOVERY, gsn->restart_counter);
+
+		int val = 0xa;
+
+		/* Flow label signalling. 7.9.15 (Conditional) */
+		msgb_tv_put_be16(reply, GTPV0_IE_FLOW_LABEL_DATA, val);
+		/* Flow label data I. 7.9.14 (Conditional) */
+		msgb_tv_put_be16(reply, GTPV0_IE_FLOW_LABEL_SIGNAL, val);
+
+		/* Charging ID. 7.9.17 (Conditional) */
+		msgb_tv_put_be32(reply, GTPV0_IE_CHARGING_ID, val);
+
+		/* End user address. 7.9.18 (Conditional) */
+		build_enduser_addr(reply, gsn, pdp);
+
+		/* TODO: Protocol configuration options. 7.9.22 (Optional) */
+
+		/* TODO: GGSN Address for signalling. 7.9.23 (Conditional) */
+		/* TODO: GGSN Address for user traffic. 7.9.23 (Conditional) */
+		build_gsn_addr(reply, gsn, pdp);
+
+		/* TODO: Charging Gateway Address. 7.9.25 (Optional) */
+		msgb_tlv_put_data(reply, GTPV0_IE_CHARGING_GW_ADDR, sizeof(val), &val);
+
+		/* TODO: Private extension. 7.9.26 (Optional) */
+	}
+
+	gtp0_header_end(gtp0h, reply);
+
+	return reply;
+}
+
+static const struct tlv_definition gtp_pdp_create_req_attr_tlvdef = {
+	.def = {
+		[GTPV0_IE_QOS_PROFILE]		= { TLV_TYPE_FIXED, 3 },
+		[GTPV0_IE_RECOVERY]		= { TLV_TYPE_FIXED, 1 },
+		[GTPV0_IE_SELECT_MODE]		= { TLV_TYPE_FIXED, 1 },
+		[GTPV0_IE_FLOW_LABEL_DATA]	= { TLV_TYPE_FIXED, 2 },
+		[GTPV0_IE_FLOW_LABEL_SIGNAL]	= { TLV_TYPE_FIXED, 2 },
+		[GTPV0_IE_ENDUSER_ADDR]		= { TLV_TYPE_TL16V },
+		[GTPV0_IE_AP_NAME]		= { TLV_TYPE_TL16V },
+		[GTPV0_IE_PROTO_CONF_OPTS]	= { TLV_TYPE_TL16V },
+		[GTPV0_IE_GSN_ADDR]		= { TLV_TYPE_TL16V },
+		[GTPV0_IE_MSISDN]		= { TLV_TYPE_TL16V },
+	},
+};
+
+struct enduser_addr_ie_payload {
+#if BYTE_ORDER == BIG_ENDIAN
+	uint8_t spare:4,
+		pdp_org_type:4;
+#elif BYTE_ORDER == LITTLE_ENDIAN
+	uint8_t pdp_org_type:4,
+		spare:4;
+#else
+#warn "BYTE_ORDER is not defined, please fix your headers"
+#endif
+	uint8_t pdp_type_number;
+	char addr[0];
+} __attribute__((packed));
+
+static int gtp0_parse_enduser_addr(struct tlv_parsed *tp,
+				   struct osmo_gtp_pdp *pdp)
+{
+	struct enduser_addr_ie_payload *ie =
+		(struct enduser_addr_ie_payload *)
+			TLVP_VAL(tp, GTPV0_IE_ENDUSER_ADDR);
+	int attrlen = TLVP_LEN(tp, GTPV0_IE_ENDUSER_ADDR);
+	int hdrlen = sizeof(struct enduser_addr_ie_payload);
+
+	if (ie->pdp_org_type != PDP_ORG_IETF) {
+		LOGP(DLINP, LOGL_ERROR, "Unsupported PDP org type %d",
+		     ie->pdp_org_type);
+		return -1;
+	}
+
+	switch (ie->pdp_type_number) {
+	case PDP_TYPE_IPV4:
+		if (attrlen != hdrlen + sizeof(struct in_addr)) {
+			LOGP(DLINP, LOGL_ERROR,
+			     "bad enduser address length %d for IPv4",
+			     attrlen);
+			return -1;
+		}
+		memcpy(&pdp->enduser.ip_addr, ie->addr, sizeof(struct in_addr));
+		break;
+	case PDP_TYPE_IPV6:
+		if (attrlen != hdrlen + sizeof(struct in6_addr)) {
+			LOGP(DLINP, LOGL_ERROR,
+			     "bad enduser address length %d for IPv6",
+			     attrlen);
+			return -1;
+		}
+		memcpy(&pdp->enduser.ip6_addr, ie->addr, sizeof(struct in6_addr));
+		break;
+	default:
+		LOGP(DLINP, LOGL_ERROR, "Unsupported PDP type number %d\n",
+		     ie->pdp_type_number);
+		break;
+	}
+
+	return 0;
+}
+
+static int gtp0_parse_gsn_addr(struct tlv_parsed *tp,
+			       struct osmo_gtp_pdp *pdp)
+{
+	switch (TLVP_LEN(tp, GTPV0_IE_GSN_ADDR)) {
+	case sizeof(struct in_addr):
+		memcpy(&pdp->sgsn, TLVP_VAL(tp, GTPV0_IE_GSN_ADDR),
+		       sizeof(struct in_addr));
+		break;
+	case sizeof(struct in6_addr):
+		memcpy(&pdp->sgsn, TLVP_VAL(tp, GTPV0_IE_GSN_ADDR),
+		       sizeof(struct in6_addr));
+		break;
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+static struct msgb *gtp_pdp_create_req_handler(struct osmo_gsn *gsn,
+					       struct msgb *msg,
+					       struct osmo_gtp_err *err)
+{
+	struct msgb *reply;
+	struct tlv_parsed tp;
+	struct osmo_gtp_pdp _pdp, *pdp = &_pdp;
+	int ret;
+
+	memset(pdp, 0, sizeof(_pdp));
+
+	ret = tlv_parse(&tp, &gtp_pdp_create_req_attr_tlvdef,
+			msg->data + sizeof(struct gtp0_header),
+			msg->len - sizeof(struct gtp0_header), 0, 0);
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "cannot parse TLVs: %u\n", ret);
+		return NULL;
+	}
+
+	/* Mandatory attributes:
+	 *
+	 * 1) 7.9.6. QoS profile. 32 bits. GSM 04.08
+	 * 2) 7.9.13. Selection mode. 8 bits & 0x3.
+	 * 3) 7.9.14. Flow label data. 16 bits
+	 * 4) 7.9.15. Flow label signalling. 16 bits
+	 * 5) 7.9.18. End User Address (see struct enduser_addr_ie_payload).
+	 * 6) 7.9.21. Access Point (AP) Name (variable length)
+	 * 7) 7.9.23. GSN address (variable length)
+	 * 8) 7.9.24. MSISDN (variable length)
+	 */
+	if (!TLVP_PRESENT(&tp, GTPV0_IE_QOS_PROFILE) 		||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_SELECT_MODE) 		||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_FLOW_LABEL_DATA)	||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_FLOW_LABEL_SIGNAL)	||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_ENDUSER_ADDR)		||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_AP_NAME)		||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_GSN_ADDR)		||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_MSISDN)) {
+		LOGP(DLINP, LOGL_ERROR, "missing mandatory TLV\n");
+		return NULL;
+	}
+
+	pdp->qos_profile = tv_get_be24(&tp, GTPV0_IE_QOS_PROFILE);
+
+	/* Validate spare 6 bits to one (7.9.13)? */
+	pdp->sel_mode = tv_get_be8(&tp, GTPV0_IE_SELECT_MODE) & 0x03;
+	pdp->flow_label_data = tv_get_be16(&tp, GTPV0_IE_FLOW_LABEL_DATA);
+	pdp->flow_label_signal = tv_get_be16(&tp, GTPV0_IE_FLOW_LABEL_SIGNAL);
+	pdp->ap_name = tv_get_strdup(&tp, GTPV0_IE_AP_NAME);
+
+	if (gtp0_parse_enduser_addr(&tp, pdp) < 0 ||
+	    gtp0_parse_gsn_addr(&tp, pdp) < 0)
+		return NULL;
+
+	pdp->msisdn = tlv_get_strdup(&tp, GTPV0_IE_MSISDN);
+
+	/* TODO: Optional attributes:
+	 * 1) 7.9.21. Protocol configuration options. GSM 04.08.
+	 */
+	if (TLVP_PRESENT(&tp, GTPV0_IE_PROTO_CONF_OPTS)) {
+	}
+
+	reply = gtp_pdp_ctx_create_resp(gsn, msg, pdp);
+	if (reply == NULL)
+		return NULL;
+
+	return reply;
+}
+
+static const struct tlv_definition gtp_pdp_update_req_attr_tlvdef = {
+	.def = {
+		[GTPV0_IE_QOS_PROFILE]		= { TLV_TYPE_FIXED, 3 },
+		[GTPV0_IE_RECOVERY]		= { TLV_TYPE_FIXED, 1 },
+		[GTPV0_IE_FLOW_LABEL_DATA]	= { TLV_TYPE_FIXED, 2 },
+		[GTPV0_IE_FLOW_LABEL_SIGNAL]	= { TLV_TYPE_FIXED, 2 },
+		[GTPV0_IE_GSN_ADDR]		= { TLV_TYPE_TL16V },
+	},
+};
+
+static struct msgb *gtp_pdp_ctx_update_resp(struct osmo_gsn *gsn,
+					    const struct msgb *msg,
+					    struct osmo_gtp_pdp *pdp)
+{
+	struct msgb *reply;
+	struct gtp0_header *gtp0h;
+
+	reply = msgb_alloc(OSMO_GTP_MTU, "gtp");
+	if (reply == NULL)
+		return NULL;
+
+	gtp0h = gtp0_header_put(reply, GTP_PDP_UPDATE_RESP, gtp0h_seq(msg), 0,
+				gtp0h_tid(msg));
+
+	/* TODO: implement reject PDP request + reason */
+	msgb_tv_put_be8(reply, GTPV0_IE_CAUSE, GTPV0_CAUSE_REQ_ACCEPTED);
+
+	if (GTPV0_CAUSE_REQ_ACCEPTED == GTPV0_CAUSE_REQ_ACCEPTED) {
+		/* Quality of service profile. 7.9.6 (Conditional) */
+
+		/* broken u24 */
+		msgb_tv_put_be24(reply, GTPV0_IE_QOS_PROFILE, pdp->qos_profile);
+
+		/* Recovery. 7.9.12 (Optional) */
+		msgb_tv_put_be8(reply, GTPV0_IE_RECOVERY, gsn->restart_counter);
+
+		int val = 0xa;
+
+		/* Flow label signalling. 7.9.15 (Conditional) */
+		msgb_tv_put_be16(reply, GTPV0_IE_FLOW_LABEL_DATA, val);
+		/* Flow label data I. 7.9.14 (Conditional) */
+		msgb_tv_put_be16(reply, GTPV0_IE_FLOW_LABEL_SIGNAL, val);
+
+		/* Charging ID. 7.9.17 (Conditional) */
+		msgb_tv_put_be32(reply, GTPV0_IE_CHARGING_ID, val);
+
+		/* TODO: Protocol configuration options. 7.9.22 (Optional) */
+		/* TODO: GGSN Address for signalling. 7.9.23 (Conditional) */
+		/* TODO: GGSN Address for user traffic. 7.9.23 (Conditional) */
+		build_gsn_addr(reply, gsn, pdp);
+
+		/* TODO: Charging Gateway Address. 7.9.25 (Optional) */
+		msgb_tlv_put_data(reply, GTPV0_IE_CHARGING_GW_ADDR, sizeof(val), &val);
+
+		/* TODO: Private extension. 7.9.26 (Optional) */
+	}
+
+	gtp0_header_end(gtp0h, reply);
+
+	return reply;
+}
+
+static struct msgb *gtp_pdp_update_req_handler(struct osmo_gsn *gsn,
+					       struct msgb *msg,
+					       struct osmo_gtp_err *err)
+{
+	struct msgb *reply;
+	struct tlv_parsed tp;
+	struct osmo_gtp_pdp _pdp, *pdp = &_pdp;
+	int ret;
+
+	memset(pdp, 0, sizeof(_pdp));
+
+	ret = tlv_parse(&tp, &gtp_pdp_update_req_attr_tlvdef,
+			msg->data + sizeof(struct gtp0_header),
+			msg->len - sizeof(struct gtp0_header), 0, 0);
+	if (ret < 0) {
+		LOGP(DLINP, LOGL_ERROR, "cannot parse TLVs: %u\n", ret);
+		return NULL;
+	}
+
+	/* Mandatory attributes:
+	 *
+	 * 1) 7.9.6. QoS profile. 32 bits. GSM 04.08
+	 * 2) 7.9.14. Flow label data. 16 bits
+	 * 3) 7.9.15. Flow label signalling. 16 bits
+	 * 4) 7.9.23. GSN address (variable length)
+	 * 5) 7.9.24. MSISDN (variable length)
+	 */
+	if (!TLVP_PRESENT(&tp, GTPV0_IE_QOS_PROFILE) 		||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_FLOW_LABEL_DATA)	||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_FLOW_LABEL_SIGNAL)	||
+	    !TLVP_PRESENT(&tp, GTPV0_IE_GSN_ADDR)) {
+		LOGP(DLINP, LOGL_ERROR, "missing mandatory TLV\n");
+		return NULL;
+	}
+
+	pdp->qos_profile = tv_get_be24(&tp, GTPV0_IE_QOS_PROFILE);
+
+	pdp->flow_label_data = tv_get_be16(&tp, GTPV0_IE_FLOW_LABEL_DATA);
+	pdp->flow_label_signal = tv_get_be16(&tp, GTPV0_IE_FLOW_LABEL_SIGNAL);
+	if (gtp0_parse_gsn_addr(&tp, pdp) < 0)
+		return NULL;
+
+	reply = gtp_pdp_ctx_update_resp(gsn, msg, pdp);
+	if (reply == NULL)
+		return NULL;
+
+	return reply;
+}
+
+static struct msgb *gtp_pdp_ctx_delete_resp(struct osmo_gsn *gsn,
+					    const struct msgb *msg,
+					    struct osmo_gtp_pdp *pdp)
+{
+	struct msgb *reply;
+	struct gtp0_header *gtp0h;
+
+	reply = msgb_alloc(OSMO_GTP_MTU, "gtp");
+	if (reply == NULL)
+		return NULL;
+
+	gtp0h = gtp0_header_put(reply, GTP_PDP_DELETE_RESP, gtp0h_seq(msg), 0,
+				gtp0h_tid(msg));
+
+	/* TODO: implement reject PDP request + reason */
+	msgb_tv_put_be8(reply, GTPV0_IE_CAUSE, GTPV0_CAUSE_REQ_ACCEPTED);
+
+	gtp0_header_end(gtp0h, reply);
+
+	return reply;
+}
+
+static struct msgb *gtp_pdp_delete_req_handler(struct osmo_gsn *gsn,
+					       struct msgb *msg,
+					       struct osmo_gtp_err *err)
+{
+	struct msgb *reply;
+	struct osmo_gtp_pdp _pdp, *pdp = &_pdp;
+
+	reply = gtp_pdp_ctx_delete_resp(gsn, msg, pdp);
+	if (reply == NULL)
+		return NULL;
+
+	return reply;
+}
+
+static struct osmo_gtp_handler gtp0_handler[GTP_TYPE_MAX] = {
+	[GTP_ECHO_REQ]		= {
+		.request	= true,
+		.handler	= gtp_echo_req_handler,
+	},
+	[GTP_PDP_CREATE_REQ]	= {
+		.request	= true,
+		.handler	= gtp_pdp_create_req_handler,
+	},
+	[GTP_PDP_UPDATE_REQ]	= {
+		.request	= true,
+		.handler	= gtp_pdp_update_req_handler,
+	},
+	[GTP_PDP_DELETE_REQ]	= {
+		.request	= true,
+		.handler	= gtp_pdp_delete_req_handler,
+	},
+};
+
+struct msgb *gtp0_recv(struct osmo_gsn *gsn, struct msgb *msg,
+		       struct osmo_gtp_err *err)
+{
+	struct gtp0_header *gtp0h = (struct gtp0_header *)msg->data;
+	struct msgb *reply = NULL;
+
+	if (gtp0h->version != 0) {
+		LOGP(DLINP, LOGL_ERROR, "wrong GTP packet version %u\n",
+		     gtp0h->version);
+		return gtp_unsupp(msg);
+	}
+	if (msg->len < sizeof(*gtp0h)) {
+		LOGP(DLINP, LOGL_ERROR, "GTPv0 packet too short msg->len %u\n",
+		     msg->len);
+		return NULL;
+	}
+	if (msg->len != ntohs(gtp0h->length) + sizeof(*gtp0h)) {
+		LOGP(DLINP, LOGL_ERROR,
+		     "truncated GTPv0 header msg->len %u != %u\n",
+		     msg->len, ntohs(gtp0h->length) + (int)sizeof(*gtp0h));
+		return NULL;
+	}
+
+	if ((gtp0h->type < GTP_TYPE_MAX) && gtp0_handler[gtp0h->type].handler)
+		reply = gtp0_handler[gtp0h->type].handler(gsn, msg, err);
+
+	return reply;
+}
+
+struct osmo_gtp_pdp *osmo_gtp_get_pdp(struct osmo_gtp_ctx *ctx)
+{
+	return ctx->pdp;
+}
+
+void osmo_gtp0_fprintf(FILE *fd, struct msgb *msg, uint32_t type)
+{
+	struct gtp0_header *gtp0h = (struct gtp0_header *)msg->data;
+	int i;
+
+	if (msg->len < sizeof(*gtp0h))
+		return;
+
+	switch (type) {
+	case OSMO_GTP_DEFAULT:
+		printf("version pt spare snn\n%x %x %x %x\n",
+		       gtp0h->version, gtp0h->pt, gtp0h->spare, gtp0h->snn);
+		printf("type seq tid flow\n%u %x %llx %x\n",
+		       gtp0h->type, ntohs(gtp0h->seq),
+		       (unsigned long long)be64toh(gtp0h->tid), gtp0h->flow);
+
+		for (i = sizeof(*gtp0h); i < msg->len; i++) {
+			printf("(%d) %.2x ", i, msg->data[i] & 0xff);
+		}
+		printf("\n");
+		break;
+	case OSMO_GTP_HEX:
+		for (i = 0; i < msg->len; i++)
+			printf("\\x%.2x", msg->data[i] & 0xff);
+
+		printf("\n");
+		break;
+	}
+}
diff --git a/src/libosmocore.c b/src/libosmocore.c
new file mode 100644
index 0000000..28b60d0
--- /dev/null
+++ b/src/libosmocore.c
@@ -0,0 +1,70 @@
+#include <osmocom/gsm/tlv.h>
+#include "libosmocore.h"
+#include <arpa/inet.h>
+
+void *msgb_tv_put_be8(struct msgb *msg, int type, uint8_t value)
+{
+	return msgb_tv_fixed_put(msg, type, 1, (uint8_t *)&value);
+}
+
+void *msgb_tv_put_be16(struct msgb *msg, int type, uint16_t value)
+{
+	value = htons(value);
+	return msgb_tv_fixed_put(msg, type, 2, (uint8_t *)&value);
+}
+
+void *msgb_tv_put_be24(struct msgb *msg, int type, uint32_t value)
+{
+	value = htonl(value & 0x00ffffff) >> 8;
+	return msgb_tv_fixed_put(msg, type, 3, (uint8_t *)&value);
+}
+
+void *msgb_tv_put_be32(struct msgb *msg, int type, uint32_t value)
+{
+	value = htonl(value);
+	return msgb_tv_fixed_put(msg, type, 4, (uint8_t *)&value);
+}
+
+void *msgb_tlv_put_be32(struct msgb *msg, int type, uint32_t value)
+{
+	value = htonl(value);
+	return msgb_tlv_put(msg, type, sizeof(uint32_t), (uint8_t *)&value);
+}
+
+void *msgb_tlv_put_data(struct msgb *msg, int type, int len, void *data)
+{
+	return msgb_tl16v_put(msg, type, len, data);
+}
+
+uint8_t tv_get_be8(struct tlv_parsed *tp, int type)
+{
+	return *((uint8_t *)TLVP_VAL(tp, type));
+}
+
+uint16_t tv_get_be16(struct tlv_parsed *tp, int type)
+{
+	return ntohs(*((uint16_t *)TLVP_VAL(tp, type)));
+}
+
+uint32_t tv_get_be24(struct tlv_parsed *tp, int type)
+{
+	uint32_t tmp = *((uint32_t *)TLVP_VAL(tp, type));
+
+	return ntohl(tmp) >> 8;
+}
+
+uint32_t tv_get_be32(struct tlv_parsed *tp, int type)
+{
+	return ntohl(*((uint32_t *)TLVP_VAL(tp, type)));
+}
+
+const char *tv_get_strdup(struct tlv_parsed *tp, int type)
+{
+	return strndup((const char *)TLVP_VAL(tp, type), TLVP_LEN(tp, type));
+}
+
+const char *tlv_get_strdup(struct tlv_parsed *tp, int type)
+{
+	/* XXX validate length */
+	return strndup((const char *)TLVP_VAL(tp, type), TLVP_LEN(tp, type));
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..9dfe830
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,52 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+check_PROGRAMS = gtp_test
+
+gtp_test_SOURCES = gtp_test.c
+gtp_test_LDADD = $(top_builddir)/src/libosmogtp.la \
+		 $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS)
+
+# boilerplate for the tests
+# The `:;' works around a Bash 3.2 bug when the output is not writeable.
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	:;{ \
+               echo '# Signature of the current package.' && \
+               echo 'm4_define([AT_PACKAGE_NAME],' && \
+               echo '  [$(PACKAGE_NAME)])' && \
+               echo 'm4_define([AT_PACKAGE_TARNAME],' && \
+               echo '  [$(PACKAGE_TARNAME)])' && \
+               echo 'm4_define([AT_PACKAGE_VERSION],' && \
+               echo '  [$(PACKAGE_VERSION)])' && \
+               echo 'm4_define([AT_PACKAGE_STRING],' && \
+               echo '  [$(PACKAGE_STRING)])' && \
+               echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \
+               echo '  [$(PACKAGE_BUGREPORT)])'; \
+               echo 'm4_define([AT_PACKAGE_URL],' && \
+               echo '  [$(PACKAGE_URL)])'; \
+             } >'$(srcdir)/package.m4'
+
+EXTRA_DIST	= testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
+		  gtp/gtp_test.ok
+
+TESTSUITE = $(srcdir)/testsuite
+
+DISTCLEANFILES = atconfig
+
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
+
+installcheck-local: atconfig $(TESTSUITE)
+	$(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \
+		$(TESTSUITEFLAGS)
+
+clean-local:
+	test ! -f '$(TESTSUITE)' || \
+		$(SHELL) '$(TESTSUITE)' --clean
+
+AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4
+	$(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at
+	mv $@.tmp $@
diff --git a/tests/gtp/gtp_test.ok b/tests/gtp/gtp_test.ok
new file mode 100644
index 0000000..e69de29
diff --git a/tests/gtp_test.c b/tests/gtp_test.c
new file mode 100644
index 0000000..53be1da
--- /dev/null
+++ b/tests/gtp_test.c
@@ -0,0 +1,121 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <gtp.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gtp/gtp.h>
+#include <osmocom/gsm/tlv.h>
+
+#include "libosmocore.h"
+
+int main(void)
+{
+	struct osmo_gsn *gsn;
+	struct msgb *msg, *reply;
+	struct osmo_gtp_err err;
+	struct gtp0_header *gtp0h;
+
+	gsn = osmo_gsn_alloc();
+
+	msg = msgb_alloc(1500, "gtp-test");
+
+	/*
+	 * GTP echo request
+	 */
+	gtp0h = gtp0_header_put(msg, GTP_ECHO_REQ, 0x1234, 0, 0);
+	gtp0_header_end(gtp0h, msg);
+
+	reply = gtp0_recv(gsn, msg, &err);
+	if (reply)
+		osmo_gtp0_fprintf(stdout, reply, OSMO_GTP_HEX);
+	else
+		printf("failure\n");
+
+	msgb_free(reply);
+	msgb_reset(msg);
+
+	/*
+	 * GTP PDP context create request
+	 */
+	int val = 0x654321;
+
+	gtp0h = gtp0_header_put(msg, GTP_PDP_CREATE_REQ, 0x1234, 0, 10);
+	msgb_tv_put_be24(msg, GTPV0_IE_QOS_PROFILE, val);
+	msgb_tv_put_be8(msg, GTPV0_IE_SELECT_MODE, 0);
+	msgb_tv_put_be16(msg, GTPV0_IE_FLOW_LABEL_DATA, 1);
+	msgb_tv_put_be16(msg, GTPV0_IE_FLOW_LABEL_SIGNAL, 2);
+
+	const char *test = "test";
+
+	struct enduser_addr_ie_payload_ipv4 ie_ipv4 = {
+		.spare			= 0xf,
+		.pdp_org_type		= PDP_ORG_IETF,
+		.pdp_type_number 	= PDP_TYPE_IPV4,
+		.addr			= {
+			.s_addr = 0x04030201,
+		},
+	};
+	msgb_tlv_put_data(msg, GTPV0_IE_ENDUSER_ADDR, sizeof(ie_ipv4),
+			  &ie_ipv4);
+	msgb_tlv_put_data(msg, GTPV0_IE_AP_NAME, strlen(test), (uint8_t *)test);
+	val = 0x01020304;
+	msgb_tlv_put_data(msg, GTPV0_IE_GSN_ADDR, sizeof(val), (uint8_t *)&val);
+	val = 0x0a0b0c0d;
+
+	const char *msisdn = "650102030";
+	msgb_tlv_put_data(msg, GTPV0_IE_MSISDN, strlen(msisdn), (uint8_t *)msisdn);
+
+	gtp0_header_end(gtp0h, msg);
+
+	reply = gtp0_recv(gsn, msg, &err);
+	if (reply)
+		osmo_gtp0_fprintf(stdout, reply, OSMO_GTP_HEX);
+	else
+		printf("failure\n");
+
+	msgb_free(reply);
+	msgb_reset(msg);
+
+	/*
+	 * GTP Update PDP context request
+	 */
+	val = 0x654321;
+
+	gtp0h = gtp0_header_put(msg, GTP_PDP_UPDATE_REQ, 0x1234, 0, 10);
+	msgb_tv_put_be24(msg, GTPV0_IE_QOS_PROFILE, val);
+	/* Recovery 7.9.12 optional */
+	msgb_tv_put_be16(msg, GTPV0_IE_FLOW_LABEL_DATA, 1);
+	msgb_tv_put_be16(msg, GTPV0_IE_FLOW_LABEL_SIGNAL, 2);
+	val = 0x01020304;
+	msgb_tlv_put_data(msg, GTPV0_IE_GSN_ADDR, sizeof(val), (uint8_t *)&val);
+
+	gtp0_header_end(gtp0h, msg);
+
+	reply = gtp0_recv(gsn, msg, &err);
+	if (reply)
+		osmo_gtp0_fprintf(stdout, reply, OSMO_GTP_HEX);
+	else
+		printf("failure\n");
+
+	msgb_free(reply);
+	msgb_reset(msg);
+
+	/*
+	 * GTP delete PDP context request
+	 */
+	val = 0x654321;
+
+	gtp0h = gtp0_header_put(msg, GTP_PDP_DELETE_REQ, 0x1234, 0, 10);
+	gtp0_header_end(gtp0h, msg);
+
+	reply = gtp0_recv(gsn, msg, &err);
+	if (reply)
+		osmo_gtp0_fprintf(stdout, reply, OSMO_GTP_HEX);
+	else
+		printf("failure\n");
+
+	msgb_free(reply);
+	msgb_reset(msg);
+
+	return EXIT_SUCCESS;
+}
diff --git a/tests/gtp_test.ok b/tests/gtp_test.ok
new file mode 100644
index 0000000..6860a40
--- /dev/null
+++ b/tests/gtp_test.ok
@@ -0,0 +1,4 @@
+version pt spare snn
+0 1 7 0
+ntype seq tid flow
+2 3412 0 0(20) e (21) 0 
diff --git a/tests/testsuite.at b/tests/testsuite.at
new file mode 100644
index 0000000..fb55669
--- /dev/null
+++ b/tests/testsuite.at
@@ -0,0 +1,18 @@
+AT_INIT
+AT_BANNER([Regression tests.])
+
+# Example for tests.. copy and uncomment. This creates a new category
+# and test. It will copy the expected output to expout and then run
+# the given test. The stdout will be compared with the expout to determine
+# if the test was successfull.
+# AT_SETUP([NAME])
+# AT_KEYWORDS([NAME])
+# cat $abs_srcdir/NAME/NAME_test.ok > expout
+# AT_CHECK([$abs_top_builddir/tests/NAME/NAME_test], [], [expout])
+# AT_CLEANUP
+
+AT_SETUP([gtp_test])
+AT_KEYWORDS([gtp_test])
+cat $abs_srcdir/gtp_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gtp_test], [], [expout], [ignore])
+AT_CLEANUP
-- 
2.1.4




More information about the OpenBSC mailing list