From: Pablo Neira Ayuso pablo@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@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@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, >p_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, >p_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
Hi Pablo,
I am currently entering the same trajectory as you have: I'm tweaking and forwarding GTP messages in the openbsc:neels/gtphub branch. The more I try to use the gtp.h API (from openggsn), the more I am forced to re-implement it. In that sense, a new and proper library for GTP messages is highly interesting to me. GTPv0 is in fact less interesting, as seemingly *all* messages I need to deal with are GTPv1. Adding v1 should actually be pretty quick, though.
Let me take a look at the patch, comments are inline... I'm not implying that you should work on it more. That would be great, but if not, they are remarks for whomever would like to go on with it. Probably me anyway.
Thank you loads for sharing this!
~Neels
comments follow:
On Thu, Oct 15, 2015 at 08:12:02AM +0200, pablo@gnumonks.org wrote:
From: Pablo Neira Ayuso pablo@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.
+1 !!!
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.
gtphub would be interesting testing grounds: it deals entirely with "real" GTP messages coming from elsewhere, but also recodes them.
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/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;
Hmm, endianness is about *byte* order, not *bit* order, right? I suggest to use one of the existing decoding functions for endianness instead: - ntohs()/ntohl() - osmo_loadXXbe() - decode_big_endian() (yet static in openbsc/src/gprs/gprs_gsup_messages.c, see also the first commit it openbsc:neels/sgsn-id-3) Anyway, this single octet should not be affected by endianness, but those uint16,32,64_t below are.
+#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 {
heh, amazing name :)
+#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);
I like how endianness is dealt with at the msgb put/get level. The same should probably happen with the header struct above, with msgb_v_get_beXX().
The old gtp.h also has a "big endian struct"... it's cumbersome as one cannot simply use the struct fields, but one needs to remember to ntohX() first. Might make for more optimized code, though, unless read often.
+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 @@
[...]
+enum osmo_gsn_proto {
- OSMO_GSN_IPV4 = 0,
- OSMO_GSN_IPV6,
+};
It would be great if IPv6 were handled implicitly, by passing the addr size around and let getaddrinfo() et al deal with it. Am I missing something that's standing in the way there?
[...]
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);
Ah, here it is: htons() stored in the header struct. I'd prefer a host-byte-order struct.
- 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));
With msgb_alloc_headroom(), it is possible to first write the IEs to the msgb, and then prepend the header with the correct size later on. See for example in openbsc, gprs_gsup_msgb_alloc() and ipa_msg_push_header().
[...]
+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;
+}
[...]
+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;
+}
Why are these two called *alloc?
+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 },- },
+};
I can't begin to express how much better this looks than the old gtpie.h!
+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, >p_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;- }
Oh, how pleasing to the openggsn ridden eye :)
- 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);
For gtphub, I would need to have the response composition separate from parsing. Well, not much left to do, is there, with a nice and clean API like this ;)
In fact, I'd like to have message decoding/encoding in an entirely separate header/c file pair from message handling.
- 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,- },
+};
nice. Though it seems to be an implementation detail for the client you were writing (I'm always thinking in terms of gtphub, which doesn't usually take own actions, but just forwards tweaked GTP data).
+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;- }
Heh, this is pretty much 1:1 what I've pasted/written for gtphub :)
- if ((gtp0h->type < GTP_TYPE_MAX) && gtp0_handler[gtp0h->type].handler)
reply = gtp0_handler[gtp0h->type].handler(gsn, msg, err);- return reply;
+}
[...]
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>
[...]
+void *msgb_tv_put_be24(struct msgb *msg, int type, uint32_t value) +{
- value = htonl(value & 0x00ffffff) >> 8;
heh, interesting :) This won't work on a BE system. You can't portably do a host CPU shift-right on a network byte order value.
Imagining a class: Big Endian 101, Professor asks: "Can anyone explain the results of this code on a big endian and a little endian system?" Half an hour of vigorous discussion follows.
- return msgb_tv_fixed_put(msg, type, 3, (uint8_t *)&value);
+}
[...]
Thanks again!
Hi Neels and Pablo,
first of all, it is great to see this code appear, I never liked libgtp much either...
On Thu, Oct 15, 2015 at 03:32:02PM +0200, Neels Hofmeyr wrote:
+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;Hmm, endianness is about *byte* order, not *bit* order, right?
Life would be too simple for that. It there is both byte-endinanness and bit-endianness. And if you define bit-fields using the c syntax, then (at least on all platforms I know), you have to split your definition like above. Look at the definitions of IP header and TCP header in your /usr/include/netinet/ip.h or /usr/include/netinet/tcp.h
to use one of the existing decoding functions for endianness instead:
- ntohs()/ntohl()
that doesn't help you with what pablo defined, as it is about an uint8_t ;)
- gtp0h->seq = htons(seq);
- gtp0h->tid = htobe64(tid);
Ah, here it is: htons() stored in the header struct. I'd prefer a host-byte-order struct.
The struct itself (for 16bit/32bit members of the struct) dosen't care. And the function you refer to takes host-byte-order input and then pushes the values as network byte ordre to the msgb, which I think is fine.
+void gtp0_header_end(struct gtp0_header *gtp0h, struct msgb *msg) +{
- gtp0h->length = htons(msg->len - sizeof(*gtp0h));
With msgb_alloc_headroom(), it is possible to first write the IEs to the msgb, and then prepend the header with the correct size later on. See for example in openbsc, gprs_gsup_msgb_alloc() and ipa_msg_push_header().
yes, that is the preferred method.
+static const struct tlv_definition gtp_pdp_create_req_attr_tlvdef = {
- .def = {
[GTPV0_IE_QOS_PROFILE] = { TLV_TYPE_FIXED, 3 },+};
I can't begin to express how much better this looks than the old gtpie.h!
Well, that's how we generaly deal with TLVs in libosmocore based programs :)
+void *msgb_tv_put_be24(struct msgb *msg, int type, uint32_t value) +{
- value = htonl(value & 0x00ffffff) >> 8;
heh, interesting :) This won't work on a BE system. You can't portably do a host CPU shift-right on a network byte order value.
On a BE system the htonl() will evaporate and you end up with doning only a shift-right by 8, which is probably not what you wanted, indeed.
Imagining a class: Big Endian 101, Professor asks: "Can anyone explain the results of this code on a big endian and a little endian system?" Half an hour of vigorous discussion follows.
Beware, Pablo is a CS Professor at Sevilla University ;)
On Sat, Oct 17, 2015 at 10:53:53AM +0200, Harald Welte wrote:
Hi Neels and Pablo,
first of all, it is great to see this code appear, I never liked libgtp much either...
On Thu, Oct 15, 2015 at 03:32:02PM +0200, Neels Hofmeyr wrote:
+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;/usr/include/netinet/ip.h or /usr/include/netinet/tcp.h
Learning never ends indeed. Thanks!
And then, I take it, the bits within each bitfield element are also reversed, but since it matches the host, they are correct and ready...
I'm confused by the fact that the bitfields' order is "reversed" on BE. Is that consistent with shift left and right operators?? Don't tell me that you have to reverse those as well on a BE system.
Either way, would be great if C had a way that doesn't need everything written twice...
[... TLVs]
Well, that's how we generaly deal with TLVs in libosmocore based programs :)
About that: I see that values are referenced from the struct tlv_parsed. That's mighty fast. But GTP does have duplicate IEs: more than one LV with the same T. I need to loop tlv_parse_one() myself then, right? :/
I noticed because I found code in libgtp that targets the second occurence of an IE type. The one instance I know of so far is in
etsi/GSM/by_chapter/29.060.pdf 7.3.2 Create PDP Context Request
which has NSAPI and Linked NSAPI both referencing 7.7.17, as well as two SGSN addresses both referencing 7.7.32 as IE description.
Beware, Pablo is a CS Professor at Sevilla University ;)
/me bows
I actually picked some oranges in the Sevilla Monasteria once - delicious! It was free, too. And I had una thervesa on the Alameda :)
~Neels
Hi Neels,
On Sat, Oct 17, 2015 at 10:52:07PM +0200, Neels Hofmeyr wrote:
[... TLVs]
Well, that's how we generaly deal with TLVs in libosmocore based programs :)
About that: I see that values are referenced from the struct tlv_parsed. That's mighty fast. But GTP does have duplicate IEs: more than one LV with the same T. I need to loop tlv_parse_one() myself then, right? :/
Indeed. At least for those messages where that might happen. I don't really see a clean way to handle that, other than having a function where the caller hands in multiple struct tlv_parsed (or an array of them), which is equally ugly.
On 17.10.2015 22:52, Neels Hofmeyr wrote:
On Sat, Oct 17, 2015 at 10:53:53AM +0200, Harald Welte wrote:
Hi Neels and Pablo,
first of all, it is great to see this code appear, I never liked libgtp much either...
On Thu, Oct 15, 2015 at 03:32:02PM +0200, Neels Hofmeyr wrote:
+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;/usr/include/netinet/ip.h or /usr/include/netinet/tcp.h
Learning never ends indeed. Thanks!
And then, I take it, the bits within each bitfield element are also reversed, but since it matches the host, they are correct and ready...
I'm confused by the fact that the bitfields' order is "reversed" on BE. Is that consistent with shift left and right operators?? Don't tell me that you have to reverse those as well on a BE system.
The ordering of bitfields is not necessarily tied to the byte ordering.
Either way, would be great if C had a way that doesn't need everything written twice...
In fact you have to write it two times only, because we know we are using gcc. The C99 spec leaves it open to the compiler implementer whether the bit fields within a unit are "high-order to low-order or low-order to high-order" [C99: 6.7.2.1 (10)]. Have a look at the definition of the 'ms_struct' and 'gcc_struct' variable attributes in the gcc documentation for further details. Supporting both ABIs would lead to four variants.
So this approach to do serialisation/deserialisation is not really portable with respect to the specification.
Jacob