aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Willmann <dwillmann@sysmocom.de>2024-01-26 16:36:20 +0100
committerDaniel Willmann <dwillmann@sysmocom.de>2024-03-06 11:26:19 +0100
commit6536376527cd10ce333b28f48c93443432295d26 (patch)
treecd6a4a96d2278f4ce7dd8cd8773cad458971e0c4
parent6d85d2a3dd535a4aa3e4e1f520069e2c34834f08 (diff)
gtp: WIP SGSN Context Request/Response/Ack support
* SGSN Context Request parsing * SGSN Context Response generation * Link to libosmo-gsm Change-Id: I3a5046b67f73a81eeb6b509777132857a9abcf0c
-rw-r--r--configure.ac1
-rw-r--r--gtp/Makefile.am3
-rw-r--r--gtp/gsn.c6
-rw-r--r--gtp/gsn.h8
-rw-r--r--gtp/gtp.c390
-rw-r--r--gtp/gtp.h6
6 files changed, 413 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index ae93974..43fe0a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -155,6 +155,7 @@ adl_FUNC_GETOPT_LONG
AM_INIT_AUTOMAKE([foreign])
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.9.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.9.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.9.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.9.0)
diff --git a/gtp/Makefile.am b/gtp/Makefile.am
index 666befe..13c7c46 100644
--- a/gtp/Makefile.am
+++ b/gtp/Makefile.am
@@ -13,6 +13,7 @@ AM_CFLAGS = \
-Wall \
-DSBINDIR='"$(sbindir)"' \
$(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
$(NULL)
libgtp_la_SOURCES = \
@@ -31,4 +32,4 @@ libgtp_la_SOURCES = \
$(NULL)
libgtp_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
-libgtp_la_LIBADD = $(LIBOSMOCORE_LIBS)
+libgtp_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS)
diff --git a/gtp/gsn.c b/gtp/gsn.c
index e66dc99..2b8d9c5 100644
--- a/gtp/gsn.c
+++ b/gtp/gsn.c
@@ -244,6 +244,12 @@ int gtp_set_cb_data_ind(struct gsn_t *gsn,
gsn->cb_data_ind = cb_data_ind;
return 0;
}
+int gtp_set_cb_sgsn_context_request_ind(struct gsn_t *gsn,
+ int (*cb) (struct gsn_t *gsn, struct sockaddr_in *peer, const struct osmo_routing_area_id *rai, uint32_t teic, struct osmo_mobile_identity *mi, union gtpie_member **ie))
+{
+ gsn->cb_sgsn_context_request_ind = cb;
+ return 0;
+}
static int queue_timer_retrans(struct gsn_t *gsn)
{
diff --git a/gtp/gsn.h b/gtp/gsn.h
index 8e47ce3..be7cd53 100644
--- a/gtp/gsn.h
+++ b/gtp/gsn.h
@@ -17,6 +17,8 @@
#include <osmocom/core/timer.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/gsm/gsm48.h>
#include "pdp.h"
@@ -107,6 +109,7 @@ struct gsn_t {
int (*cb_recovery) (struct sockaddr_in * peer, uint8_t recovery);
int (*cb_recovery2) (struct sockaddr_in * peer, struct pdp_t * pdp, uint8_t recovery);
int (*cb_recovery3) (struct gsn_t *gsn, struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery);
+ int (*cb_sgsn_context_request_ind) (struct gsn_t *gsn, struct sockaddr_in *peer, const struct osmo_routing_area_id *rai, uint32_t teic, struct osmo_mobile_identity *mi, union gtpie_member **ie); /* Pass RAI and TLLI/TMSI/IMSI directly */
/* Counters */
struct rate_ctr_group *ctrg;
@@ -130,6 +133,8 @@ extern int gtp_freepdp_teardown(struct gsn_t *gsn, struct pdp_t *pdp);
extern int gtp_create_context_req(struct gsn_t *gsn, struct pdp_t *pdp,
void *cbp);
+extern int gtp_create_sgsn_context_req(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp);
+
extern int gtp_set_cb_create_context_ind(struct gsn_t *gsn,
int (*cb_create_context_ind) (struct
pdp_t *
@@ -152,6 +157,9 @@ extern int gtp_set_cb_extheader_ind(struct gsn_t *gsn,
extern int gtp_set_cb_ran_info_relay_ind(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer, union gtpie_member **ie));
+extern int gtp_set_cb_sgsn_context_request_ind(struct gsn_t *gsn,
+ int (*cb) (struct gsn_t *gsn, struct sockaddr_in *peer, const struct osmo_routing_area_id *rai, uint32_t teic, struct osmo_mobile_identity *mi, union gtpie_member **ie));
+
extern int gtp_set_cb_conf(struct gsn_t *gsn,
int (*cb) (int type, int cause, struct pdp_t * pdp,
void *cbp));
diff --git a/gtp/gtp.c b/gtp/gtp.c
index 34215e2..bd777d9 100644
--- a/gtp/gtp.c
+++ b/gtp/gtp.c
@@ -25,6 +25,8 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm23003.h>
#if defined(__FreeBSD__)
#include <sys/endian.h>
@@ -836,6 +838,385 @@ int gtp_ran_info_relay_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
return gtp_notification(gsn, 1, &packet, length, peer, gsn->fd1c, 0);
}
+#define GSM_MI_TYPE_TLLI 0x05
+
+int gtp_sgsn_context_req(struct gsn_t *gsn, const struct in_addr *peer,
+ const struct osmo_mobile_identity *mi, uint32_t teic,
+ const struct ul16_t *sgsn_addr, const struct ul255_t *rai, void *cbp)
+{
+ union gtp_packet packet;
+
+ /* GTP 1 is the highest supported protocol */
+ unsigned int length = get_default_gtp(1, GTP_SGSN_CONTEXT_REQ, &packet);
+
+ gtpie_tv0(&packet, &length, GTP_MAX, GTPIE_RAI, rai->l, rai->v);
+ gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_C, teic);
+ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, sgsn_addr->l, sgsn_addr->v);
+
+ switch (mi->type) {
+ case GSM_MI_TYPE_IMSI:
+ gtpie_tv8(&packet, &length, GTP_MAX, GTPIE_IMSI, *(uint64_t *)mi->imsi); /* FIXME: proper decoding*/
+ break;
+ case GSM_MI_TYPE_TLLI:
+ gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TLLI, mi->tmsi);
+ break;
+ case GSM_MI_TYPE_TMSI:
+ gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_P_TMSI, mi->tmsi);
+ break;
+ default:
+ /* TODO: Error */
+ break;
+ }
+
+ return gtp_req(gsn, 1, NULL, &packet, length, peer, cbp);
+}
+
+/* Handle an SGSN Context Request */
+static int gtp_sgsn_context_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
+ void *pack, unsigned len)
+{
+ /* Check if we have a context with TLLI/P-TMSI/IMSI in the RAI */
+ struct osmo_mobile_identity mi = {0};
+ uint64_t imsi;
+
+ union gtpie_member *ie[GTPIE_SIZE];
+ uint32_t teic;
+ uint8_t seq = get_seq(pack);
+ struct ul16_t sgsn_addr;
+ struct ul255_t rai = { .l = 6 };
+ struct osmo_routing_area_id rai_parsed;
+
+ if (version != 1) {
+ LOGP(DLGTP, LOGL_NOTICE,
+ "SGSN Context Request expected only on GTPCv1: %u\n", version);
+ return -EINVAL;
+ }
+
+ int hlen = get_hlen(pack);
+
+ /* Decode information elements */
+ if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Invalid message format\n");
+ return -EINVAL;
+ }
+
+ /* RAI */
+ if (gtpie_gettv0(ie, GTPIE_RAI, 0, &rai.v, 6)) {
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Missing RAI\n");
+ goto missing_ie;
+ }
+ if (osmo_routing_area_id_decode(&rai_parsed, rai.v, 6) < 0) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Invalid RAI\n");
+ }
+
+ /* TEI-C */
+ if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &teic)) {
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Missing TEI-C\n");
+ goto missing_ie;
+ }
+
+ /* SGSN address for signalling (mandatory) */
+ if (gtpie_gettlv(ie, GTPIE_GSN_ADDR, 0, &sgsn_addr.l,
+ &sgsn_addr.v, sizeof(sgsn_addr.v))) {
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Missing GSN Addr\n");
+ goto missing_ie;
+ }
+
+ /* Get PTMSI, TLLI or IMSI - only one IE allowed */
+ if (!gtpie_gettv4(ie, GTPIE_TLLI, 0, &mi.tmsi)) {
+ mi.type = GSM_MI_TYPE_TLLI;
+ goto done;
+ }
+
+ if (!gtpie_gettv4(ie, GTPIE_P_TMSI, 0, &mi.tmsi)) {
+ mi.type = GSM_MI_TYPE_TMSI;
+ goto done;
+ }
+
+ if (!gtpie_gettv8(ie, GTPIE_IMSI, 0, &imsi)) {
+ imsi = ntoh64(imsi);
+ mi.type = GSM_MI_TYPE_IMSI;
+ const char *imsi_str = imsi_gtp2str(&imsi);
+ memcpy(mi.imsi, imsi_str, sizeof(mi.imsi));
+ } else {
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Missing Identity information\n");
+ goto missing_ie;
+ }
+
+done:
+ if (gsn->cb_sgsn_context_request_ind)
+ gsn->cb_sgsn_context_request_ind(gsn, peer, &rai_parsed, teic, &mi, ie);
+
+ return 0;
+
+missing_ie:
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_MISSING);
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Missing mandatory IE\n");
+ /* TODO: Send response with cause */
+ return -EINVAL;
+}
+
+static bool is_ext_ua(const struct pdp_t *pdp)
+{
+ return pdp->eua.l > 0 && pdp->eua.v[0] == PDP_EUA_TYPE_v4v6;
+}
+
+static int gtp_pdp_ctx(uint8_t *buf, unsigned int size, const struct pdp_t *pdp, uint16_t sapi)
+{
+ uint32_t tmp32;
+ uint16_t tmp16;
+ uint8_t *ptr = buf;
+#define CHECK_SPACE_ERR(bytes) \
+ if ((ptr - buf) + (bytes) > size) { \
+ printf("Failed size check: %lu + %lu > %lu\n", (ptr- buf), bytes, size); \
+ return -1; \
+ }
+#define MEMCPY_CHK(dst, src, len) \
+ CHECK_SPACE_ERR((len)) \
+ memcpy((dst), (uint8_t *)(src), (len)); \
+ (dst) += (len);
+
+ // Flags - FIXME: No ASI
+ *ptr++ = (is_ext_ua(pdp) << 7) | ((!!pdp->vplmn_allow) << 6) | ((!!pdp->reorder) << 4) | (pdp->nsapi & 0x0f);
+ // SAPI
+ *ptr++ = sapi & 0x0f;
+
+ // QoS Sub
+ if (pdp->qos_sub.l < 4) {
+ /* Work around qos_sub never being set */
+ *ptr++ = 4;
+ *ptr++ = 0;
+ *ptr++ = 0x23;
+ *ptr++ = 0x02;
+ *ptr++ = 0x00;
+ } else {
+ *ptr++ = pdp->qos_sub.l;
+ MEMCPY_CHK(ptr, pdp->qos_sub.v, pdp->qos_sub.l);
+ }
+ // QoS Req
+ *ptr++ = pdp->qos_req.l;
+ MEMCPY_CHK(ptr, pdp->qos_req.v, pdp->qos_req.l);
+ // QoS Neg
+ *ptr++ = pdp->qos_neg.l;
+ MEMCPY_CHK(ptr, pdp->qos_neg.v, pdp->qos_neg.l);
+
+ // SND
+ tmp16 = osmo_htons(pdp->pdcpsndd);
+ MEMCPY_CHK(ptr, &tmp16, sizeof(tmp16));
+ // SNU
+ tmp16 = osmo_htons(pdp->pdcpsndu);
+ MEMCPY_CHK(ptr, &tmp16, sizeof(tmp16));
+ // Send N-PDU
+ *ptr++ = pdp->gtpsntx;
+ // Recv N-PDU
+ *ptr++ = pdp->gtpsnrx;
+ // Uplink TEIC
+ tmp32 = osmo_htonl(pdp->teic_gn);
+ MEMCPY_CHK(ptr, &tmp32, sizeof(tmp32));
+ // Uplink TEIDI
+ tmp32 = osmo_htonl(pdp->teid_gn);
+ MEMCPY_CHK(ptr, &tmp32, sizeof(tmp32));
+ // PDP Ctx Id
+ *ptr++ = pdp->pdp_id;
+ // PDP Type Org
+ *ptr++ = PDP_EUA_ORG_IETF;
+ // PDP Type No.
+ // PDP Address
+
+ switch (pdp->eua.v[1]) {
+ case PDP_EUA_TYPE_v4:
+ case PDP_EUA_TYPE_v4v6:
+ /* v4v6 expects an IPv4 addr first followed by an IPv6 addr*/
+ *ptr++ = PDP_EUA_TYPE_v4;
+ if (pdp->eua.l < 6)
+ return -1;
+ *ptr++ = 4;
+ MEMCPY_CHK(ptr, &pdp->eua.v[2], 4);
+ break;
+ case PDP_EUA_TYPE_v6:
+ *ptr++ = PDP_EUA_TYPE_v6;
+ if (pdp->eua.l < 18)
+ return -1;
+ *ptr++ = 16;
+ MEMCPY_CHK(ptr, &pdp->eua.v[2], 16);
+ break;
+ default:
+ return -EINVAL;
+ //Panic
+ }
+ // GGSN Address Ctrl
+ *ptr++ = pdp->gsnrc.l;
+ MEMCPY_CHK(ptr, pdp->gsnrc.v, pdp->gsnrc.l);
+ // GGSN Address User
+ *ptr++ = pdp->gsnru.l;
+ MEMCPY_CHK(ptr, pdp->gsnru.v, pdp->gsnru.l);
+ // APN
+ *ptr++ = pdp->apn_use.l;
+ MEMCPY_CHK(ptr, pdp->apn_use.v, pdp->apn_use.l);
+ // TransId
+ *ptr++ = (pdp->ti >> 8) & 0x0f;
+ *ptr++ = pdp->ti & 0xff;
+
+ if (is_ext_ua(pdp)) {
+ *ptr++ = PDP_EUA_TYPE_v6;
+ if (pdp->eua.l < 22)
+ return -1;
+ *ptr++ = 16;
+ MEMCPY_CHK(ptr, &pdp->eua.v[6], 16);
+ }
+ return ptr - buf;
+#undef CHECK_SPACE_ERR
+#undef MEMCPY_CHK
+}
+
+int gtp_sgsn_context_conf(struct gsn_t *gsn, struct sockaddr_in *peer, uint16_t seq,
+ uint32_t teic, uint8_t cause, const struct in_addr *sgsn_addr, struct pdp_t *pdpctx, uint16_t sapi, struct ul255_t *mmctx, void *cbp)
+{
+ union gtp_packet packet;
+ struct ul255_t pdp;
+
+ /* GTP 1 is the highest supported protocol */
+ unsigned int length = get_default_gtp(1, GTP_SGSN_CONTEXT_RSP, &packet);
+
+ // Cause - TV1
+ gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_CAUSE, cause);
+ // IMSI - TV8
+ //gtpie_tv8(&packet, &length, GTP_MAX, GTPIE_IMSI, imsi);
+
+ // TEIC - TV4
+ gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_C, pdpctx->teic_own);
+
+ // MM Ctx - TLV
+ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_MM_CONTEXT, mmctx->l, mmctx->v);
+
+ // PDP Ctx - TLV
+ if (pdpctx) {
+ int rc = gtp_pdp_ctx(pdp.v, 255, pdpctx, sapi);
+ if (rc > 0)
+ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_PDP_CONTEXT, rc, pdp.v);
+ }
+
+ // SGSN Addr Ctrl - TLV
+ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, 4, &sgsn_addr->s_addr);
+
+ // Alt GGSN Addr Ctrl (per PDP Ctx)
+ // Alt GGSN Addr User (per PDP Ctx)
+ /* The encoding for this is a bit insane */
+
+ return gtp_resp2(gsn, &packet, length, peer, gsn->fd1c, seq, 0, 0, teic);
+}
+
+/* Handle an SGSN Context Response */
+static int gtp_sgsn_context_conf_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
+ void *pack, unsigned len)
+{
+ /** If cause is "Request accepted": Send ACK,
+ else: Log and handle error */
+
+ /* Check if we have a context with TLLI/P-TMSI/IMSI in the RAI */
+
+ void *cbp = NULL;
+ uint8_t type = 0;
+ uint8_t cause;
+ union gtpie_member *ie[GTPIE_SIZE];
+ struct pdp_t *pdp = NULL;
+ uint64_t imsi;
+ uint32_t teic;
+ struct ul16_t sgsn_addr;
+
+ if (version != 1) {
+ LOGP(DLGTP, LOGL_NOTICE,
+ "SGSN Context Request expected only on GTPCv1: %u\n", version);
+ return -EINVAL;
+ }
+
+ int hlen = get_hlen(pack);
+
+ /* Remove packet from queue */
+ if (gtp_conf(gsn, version, peer, pack, len, &type, &cbp))
+ return EOF;
+
+ /* Extract information elements into a pointer array */
+ if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Invalid message format\n");
+ if (gsn->cb_conf)
+ gsn->cb_conf(type, EOF, NULL, cbp);
+ return EOF;
+ }
+
+ /* Extract cause value (mandatory) */
+ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_MISSING);
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Missing mandatory information field\n");
+ if (gsn->cb_conf)
+ gsn->cb_conf(type, EOF, NULL, cbp);
+ return EOF;
+ }
+
+ /* Check all conditional information elements */
+ if (!gtp_cause_successful(cause)) {
+ GTP_LOGPKG(LOGL_NOTICE, peer, pack, len,
+ "SGSN Context response with cause (%u)\n", cause);
+ if (gsn->cb_conf)
+ gsn->cb_conf(type, cause, NULL, cbp);
+ }
+
+ /* Gather UE context and PDP data, create pdp context */
+ //gtp_pdp_newpdp(gsn, &pdp, imsi, nsapi, NULL)
+ if (gsn->cb_conf)
+ gsn->cb_conf(type, cause, pdp, cbp);
+
+ return 0;
+}
+
+/* Handle an SGSN Context Acknowledge */
+static int gtp_sgsn_context_ack(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
+ void *pack, unsigned len)
+{
+ /* If cause is != Request accepted, log error */
+ return -ENOTSUP;
+}
+
+/* Handle an SGSN Context request */
+int gtp_context_request(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
+ void *pack, unsigned len)
+{
+ union gtpie_member *ie[GTPIE_SIZE];
+
+ if (version != 1) {
+ LOGP(DLGTP, LOGL_NOTICE,
+ "SGSN Context Request expected only on GTPCv1: %u\n", version);
+ return -EINVAL;
+ }
+
+ int hlen = get_hlen(pack);
+
+ /* Decode information elements */
+ if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
+ "Invalid message format (AN Information Relay)\n");
+ return -EINVAL;
+ }
+
+ if (gsn->cb_ran_info_relay_ind)
+ gsn->cb_ran_info_relay_ind(peer, ie);
+
+ return 0;
+}
+
/* ***********************************************************
* Session management messages
* Messages: create, update and delete PDP context
@@ -2842,6 +3223,15 @@ int gtp_decaps1c(struct gsn_t *gsn)
case GTP_RAN_INFO_RELAY:
gtp_ran_info_relay_ind(gsn, version, &peer, buffer, status);
break;
+ case GTP_SGSN_CONTEXT_REQ:
+ gtp_sgsn_context_ind(gsn, version, &peer, buffer, status);
+ break;
+ case GTP_SGSN_CONTEXT_RSP:
+ gtp_sgsn_context_conf_ind(gsn, version, &peer, buffer, status);
+ break;
+ case GTP_SGSN_CONTEXT_ACK:
+ gtp_sgsn_context_ack(gsn, version, &peer, buffer, status);
+ break;
default:
rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_UNKNOWN);
GTP_LOGPKG(LOGL_ERROR, &peer, buffer, status,
diff --git a/gtp/gtp.h b/gtp/gtp.h
index 7465cf1..d3ddea0 100644
--- a/gtp/gtp.h
+++ b/gtp/gtp.h
@@ -262,6 +262,12 @@ extern int gtp_ran_info_relay_req(struct gsn_t *gsn, const struct sockaddr_in *p
const uint8_t *rim_route_addr, size_t rim_route_addr_len,
uint8_t rim_route_addr_discr);
+extern int gtp_sgsn_context_req(struct gsn_t *gsn, const struct in_addr *peer,
+ const struct osmo_mobile_identity *mi, uint32_t teic,
+ const struct ul16_t *sgsn_addr, const struct ul255_t *rai, void *cbp);
+extern int gtp_sgsn_context_conf(struct gsn_t *gsn, struct sockaddr_in *peer, uint16_t seq,
+ uint32_t teic, uint8_t cause, const struct in_addr *sgsn_addr, struct pdp_t *pdpctx, uint16_t sapi, struct ul255_t *mmctx, void *cbp);
+
extern int gtp_decaps0(struct gsn_t *gsn);
extern int gtp_decaps1c(struct gsn_t *gsn);
extern int gtp_decaps1u(struct gsn_t *gsn);