lynxis lazus has submitted this change. (
https://gerrit.osmocom.org/c/osmo-ggsn/+/38938?usp=email )
Change subject: gtp: add support for SGSN Context Req/Resp/Ack
......................................................................
gtp: add support for SGSN Context Req/Resp/Ack
To handle both incoming and outgoing SGSN Context via GTPv1.
Introduce a FSM which handles the state of SGSN Context to simplify
the API for the SGSN which uses libgtp as well as the external SGSN
which communicates via GTP.
Change-Id: Idb8ed0bb87200a68bb8caedd734fc070df9179c0
---
M TODO-RELEASE
M configure.ac
M gtp/Makefile.am
M gtp/gsn.c
M gtp/gtp.c
A gtp/gtp_sgsn_ctx.c
A gtp/gtp_sgsn_ctx.h
M include/osmocom/gtp/gsn.h
M include/osmocom/gtp/gtp.h
9 files changed, 1,368 insertions(+), 1 deletion(-)
Approvals:
Jenkins Builder: Verified
pespin: Looks good to me, but someone else must approve
daniel: Looks good to me, approved
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 0ed7189..c2470bd 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -7,3 +7,5 @@
# If any interfaces have been added since the last public release: c:r:a + 1.
# If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line
+libgtp new API SGSN Ctx Req/Resp/Ack, gtp_encode_pdp_ctx, gtp_decode_pdp_ctx,
gtp_set_cb_sgsn_context_request_ind, ...
+libgtp new dependency libosmogsm
diff --git a/configure.ac b/configure.ac
index cc5a459..8d40d66 100644
--- a/configure.ac
+++ b/configure.ac
@@ -157,6 +157,7 @@
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0)
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
diff --git a/gtp/Makefile.am b/gtp/Makefile.am
index fe5abe8..af75fe5 100644
--- a/gtp/Makefile.am
+++ b/gtp/Makefile.am
@@ -12,6 +12,7 @@
-DSBINDIR='"$(sbindir)"' \
-I$(top_srcdir)/include \
$(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
$(NULL)
libgtp_la_SOURCES = \
@@ -19,6 +20,8 @@
gsn_internal.h \
gtp.c \
gtp_internal.h \
+ gtp_sgsn_ctx.c \
+ gtp_sgsn_ctx.h \
gtpie.c \
lookupa.c \
lookupa.h \
@@ -28,4 +31,4 @@
$(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 14c86ad..bfbc694 100644
--- a/gtp/gsn.c
+++ b/gtp/gsn.c
@@ -64,6 +64,7 @@
#include "queue.h"
#include "gsn_internal.h"
#include "gtp_internal.h"
+#include "gtp_sgsn_ctx.h"
/* Error reporting functions */
@@ -244,6 +245,30 @@
return 0;
}
+int gtp_set_cb_sgsn_context_request_ind(struct gsn_t *gsn,
+ int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size))
+{
+ gsn->cb_sgsn_context_request_ind = cb;
+ return 0;
+}
+
+int gtp_set_cb_sgsn_context_response_ind(struct gsn_t *gsn,
+ int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size))
+{
+ gsn->cb_sgsn_context_response_ind = cb;
+ return 0;
+}
+
+int gtp_set_cb_sgsn_context_ack_ind(struct gsn_t *gsn,
+ int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size))
+{
+ gsn->cb_sgsn_context_ack_ind = cb;
+ return 0;
+}
+
static int queue_timer_retrans(struct gsn_t *gsn)
{
/* Retransmit any outstanding packets */
@@ -525,6 +550,10 @@
/* Start internal queue timer */
gtp_queue_timer_start(*gsn);
+ (*gsn)->sgsn_ctx = sgsn_ctx_reqs_init(*gsn, 2048, 4095);
+ if (!(*gsn)->sgsn_ctx)
+ goto error;
+
return 0;
error:
gtp_free(*gsn);
diff --git a/gtp/gtp.c b/gtp/gtp.c
index f5d398e..fa65575 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>
@@ -61,6 +63,7 @@
#include "queue.h"
#include "gsn_internal.h"
+#include "gtp_sgsn_ctx.h"
#include "gtp_internal.h"
/* Error reporting functions */
@@ -393,6 +396,18 @@
return 0;
}
+static int gtp_reqv1c(struct gsn_t *gsn, uint32_t teic,
+ union gtp_packet *packet, int len,
+ const struct in_addr *inetaddr, void *cbp)
+{
+ packet->flags |= GTP1HDR_F_SEQ;
+ packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT);
+ packet->gtp1l.h.seq = hton16(gsn->seq_next);
+ packet->gtp1l.h.tei = hton32(teic);
+
+ return gtp_req_transmit(gsn, 1, inetaddr, packet, len, NULL, cbp);
+}
+
static int gtp_req(struct gsn_t *gsn, uint8_t version, struct pdp_t *pdp,
union gtp_packet *packet, int len,
const struct in_addr *inetaddr, void *cbp)
@@ -552,6 +567,21 @@
return gtp_resp(gsn, packet, len, peer, fd, seq, tid, flow, tei);
}
+static int gtp_respv1c(struct gsn_t *gsn, uint16_t seq, uint32_t remote_teic,
+ union gtp_packet *packet, int len,
+ const struct in_addr *inetaddr)
+{
+ struct sockaddr_in peer = {};
+ peer.sin_addr = *inetaddr;
+ peer.sin_port = htons(GTP1C_PORT);
+ peer.sin_family = AF_INET;
+
+ packet->flags |= GTP1HDR_F_SEQ | GTP1HDR_F_GTP1;
+ packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT);
+
+ return gtp_resp(gsn, packet, len, &peer, gsn->fd1c, seq, 0, 0, remote_teic);
+}
+
static int gtp_notification(struct gsn_t *gsn, uint8_t version,
union gtp_packet *packet, int len,
const struct sockaddr_in *peer, int fd, uint16_t seq)
@@ -851,6 +881,640 @@
return gtp_notification(gsn, 1, &packet, length, peer, gsn->fd1c, 0);
}
+#define GSM_MI_TYPE_TLLI 0x05
+
+/* Send an SGSN Context Request */
+int gtp_sgsn_context_req(struct gsn_t *gsn, uint32_t *local_ref,
+ const struct sockaddr_in *peer, union gtpie_member **ie)
+{
+ union gtp_packet packet = {};
+ unsigned int packet_len;
+ int rc;
+ uint32_t local_teic;
+ union gtpie_member req_ie_elem[2] = {};
+ void *pack;
+ unsigned encoded_len;
+
+ rc = sgsn_ctx_req_fsm_tx_req(gsn, peer, ie, GTPIE_SIZE, &local_teic,
gsn->seq_next);
+ if (rc)
+ return rc;
+
+ packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_REQ, &packet);
+ req_ie_elem[0].tv4.t = GTPIE_TEI_C;
+ req_ie_elem[0].tv4.v = htonl(local_teic);
+ ie[GTPIE_TEI_C] = &req_ie_elem[0];
+
+ req_ie_elem[1].tlv.t = GTPIE_GSN_ADDR;
+ req_ie_elem[1].tlv.l = htons(sizeof(gsn->gsnc));
+ memcpy(&req_ie_elem[1].tlv.v[0], &gsn->gsnc, sizeof(gsn->gsnc));
+ ie[GTPIE_GSN_ADDR] = &req_ie_elem[1];
+
+ pack = &packet;
+ pack += packet_len;
+ rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len);
+ if (rc)
+ return -EINVAL;
+
+ packet_len += encoded_len;
+ *local_ref = local_teic;
+ return gtp_reqv1c(gsn, 0, &packet, packet_len, &peer->sin_addr, NULL);
+}
+
+/* Send an SGSN Context Request, extends ies */
+int gtp_sgsn_context_resp(struct gsn_t *gsn, uint32_t local_ref,
+ union gtpie_member **ie)
+{
+ uint32_t local_teic = local_ref;
+ uint32_t remote_teic;
+ uint16_t seq;
+
+ union gtpie_member resp_ie_elem[2] = {};
+ union gtp_packet packet = {};
+ unsigned int packet_len;
+ int rc;
+ void *pack;
+ unsigned encoded_len;
+ struct sockaddr_in peer;
+
+ rc = sgsn_ctx_req_fsm_tx_resp(gsn, &peer, &seq, local_teic, &remote_teic,
ie, GTPIE_SIZE);
+ if (rc)
+ return rc;
+
+ packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_RSP, &packet);
+
+ resp_ie_elem[0].tv4.t = GTPIE_TEI_C;
+ resp_ie_elem[0].tv4.v = htonl(local_teic);
+ ie[GTPIE_TEI_C] = &resp_ie_elem[0];
+
+ resp_ie_elem[1].tlv.t = GTPIE_GSN_ADDR;
+ resp_ie_elem[1].tlv.l = htons(sizeof(gsn->gsnc));
+ memcpy(&resp_ie_elem[1].tlv.v[0], &gsn->gsnc, sizeof(gsn->gsnc));
+ ie[GTPIE_GSN_ADDR] = &resp_ie_elem[1];
+
+ pack = &packet;
+ pack += packet_len;
+ rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len);
+ if (rc)
+ return -EINVAL;
+
+ packet_len += encoded_len;
+ return gtp_respv1c(gsn, seq, remote_teic, &packet, packet_len, &peer.sin_addr);
+}
+
+int gtp_sgsn_context_resp_error(struct gsn_t *gsn, uint32_t local_ref,
+ uint8_t cause)
+{
+ union gtpie_member *ie[GTP_MAX] = {};
+ union gtpie_member causetlv = {};
+ causetlv.tv1.t = GTPIE_CAUSE;
+ causetlv.tv1.v = cause;
+ ie[0] = &causetlv;
+
+ return gtp_sgsn_context_resp(gsn, local_ref, ie);
+}
+
+/* Send an SGSN Context Ack/Nack.
+ * Argument **ie could be const, but the other functions take a non-const ie
+ * to be consistent and allow future modification, keep ie in this case non-const.
+ */
+int gtp_sgsn_context_ack(struct gsn_t *gsn, uint32_t local_ref,
+ union gtpie_member **ie)
+{
+ uint32_t local_teic = local_ref;
+ uint32_t remote_teic;
+ uint16_t seq;
+ union gtp_packet packet = {};
+ unsigned int packet_len;
+ int rc;
+ void *pack;
+ unsigned encoded_len;
+ struct sockaddr_in peer;
+
+ rc = sgsn_ctx_req_fsm_tx_ack(gsn, &peer, &seq, local_teic, &remote_teic, ie,
GTPIE_SIZE);
+ if (rc)
+ return rc;
+
+ packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_ACK, &packet);
+ pack = &packet;
+ pack += packet_len;
+
+ rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len);
+ if (rc)
+ return -EINVAL;
+
+ packet_len += encoded_len;
+ return gtp_respv1c(gsn, seq, remote_teic, &packet, packet_len, &peer.sin_addr);
+}
+
+int gtp_sgsn_context_ack_error(struct gsn_t *gsn, uint32_t local_ref,
+ uint8_t cause)
+{
+ union gtpie_member *ie[GTPIE_SIZE] = {};
+ union gtpie_member causetlv = {};
+ causetlv.tv1.t = GTPIE_CAUSE;
+ causetlv.tv1.v = cause;
+ ie[GTPIE_CAUSE] = &causetlv;
+
+ return gtp_sgsn_context_ack(gsn, local_ref, ie);
+}
+
+/* Handle an SGSN Context Request */
+static int gtp_sgsn_context_req_ind(struct gsn_t *gsn, int version, struct sockaddr_in
*peer,
+ void *pack, unsigned len)
+{
+ union gtpie_member *ie[GTPIE_SIZE] = {};
+ uint16_t seq;
+
+ if (version != 1) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer,
+ "Sent SGSN Context Request with wrong version (exp 1, rx %u)\n",
version);
+ return -EINVAL;
+ }
+
+ /* the GTP code ensures the package contains a full GTPv1 header */
+ 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,
+ "Sent SGSN Context Request with Invalid message format\n");
+ return -EINVAL;
+ }
+
+ seq = get_seq(pack);
+ return sgsn_ctx_req_fsm_rx_req(gsn, peer, seq, ie, GTPIE_SIZE);
+}
+
+static bool is_ext_ua(const struct pdp_t *pdp)
+{
+ return pdp->eua.l > 0 && pdp->eua.v[0] == PDP_EUA_TYPE_v4v6;
+}
+
+/* Check if length of the PDP */
+int decode_pdp_ctx_len_check(const uint8_t *buf, unsigned int size)
+{
+#define CHECK_SIZE() do { if (ptr >= end) return -EINVAL; } while (0)
+ uint8_t tmp1;
+ const uint8_t *ptr = buf;
+ const uint8_t *end = ptr + size;
+ bool ext_ua = false;
+
+ /* FIXME what is the minimal length ? */
+ if (size < 20)
+ return -EINVAL;
+
+ ext_ua = !!((*ptr) & 0x80);
+
+ ptr += 2;
+ /* Qos Sub Length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+
+ /* Qos Req Length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+
+ /* Qos Neg Length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+
+ ptr += 17;
+
+ /* PDP Address length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+
+ /* GGSN Address for control plane length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+
+ /* GGSN Address for User traffic plane length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+
+ /* APN length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+
+ /* Transaction Id */
+ ptr += 2;
+
+ /* Second PDP Address is only present in IPv4v6 PDP Context */
+ if (ext_ua) {
+ ptr++;
+ /* PDP Address Length */
+ CHECK_SIZE();
+ tmp1 = *ptr;
+ ptr += tmp1 + 1;
+ }
+
+ if (ptr == end)
+ return 0;
+
+#undef CHECK_SIZE
+ return 1;
+}
+
+/** Decode a PDP Ctx from a buffer into struct pdp_t
+ *
+ * @param buf input buffer
+ * @param size size of buf
+ * @param pdp the decoded pdp buffer
+ * @param sapi the decoded sapi
+ * @return 0 on success, 1 if buffer not completely consumed, < 0 on syntactical
errors
+ */
+int gtp_decode_pdp_ctx(const uint8_t *buf, unsigned int size, struct pdp_t *pdp, uint16_t
*sapi)
+{
+ uint8_t pdp_type, pdp_len;
+ const uint8_t *ptr = buf;
+ const uint8_t *end = ptr + size;
+
+ if (decode_pdp_ctx_len_check(buf, size)) {
+ LOGP(DLGTP, LOGL_ERROR, "GTP: PDP Context Decode: length check failed.");
+ return -EINVAL;
+ }
+
+ memset(pdp, 0, sizeof(*pdp));
+ bool ext_ua = (*ptr & (1 << 7));
+
+ pdp->version = 1;
+ pdp->vplmn_allow = (*ptr) & (1 << 6);
+ pdp->reorder = (*ptr) & (1 << 4);
+ pdp->nsapi = (*ptr) & 0x0f;
+ ptr++;
+
+ /* SAPI */
+ *sapi = *ptr & 0x0f;
+ ptr++;
+
+ /* QoS Sub */
+ pdp->qos_sub.l = *ptr;
+ ptr++;
+ memcpy(pdp->qos_sub.v, ptr, pdp->qos_sub.l);
+ ptr += pdp->qos_sub.l;
+
+ /* QoS Req */
+ pdp->qos_req.l = *ptr;
+ ptr++;
+ memcpy(pdp->qos_req.v, ptr, pdp->qos_req.l);
+ ptr += pdp->qos_req.l;
+
+ /* QoS Neg */
+ pdp->qos_neg.l = *ptr;
+ ptr++;
+ memcpy(pdp->qos_neg.v, ptr, pdp->qos_neg.l);
+ ptr += pdp->qos_neg.l;
+
+ /* SND */
+ pdp->pdcpsndd = osmo_load16be(ptr);
+ ptr += 2;
+
+ /* SNU */
+ pdp->pdcpsndu = osmo_load16be(ptr);
+ ptr += 2;
+
+ /* Send N-PDU */
+ pdp->gtpsntx = *ptr;
+ ptr++;
+
+ /* Recv N-PDU */
+ pdp->gtpsnrx = *ptr;
+ ptr++;
+
+ /* Uplink TEIC */
+ pdp->teic_gn = osmo_load32be(ptr);
+ ptr += 4;
+
+ /* Uplink TEIDI */
+ pdp->teid_gn = osmo_load32be(ptr);
+ ptr += 4;
+
+ /* PDP Ctx Id */
+ pdp->pdp_id = *ptr;
+ ptr++;
+
+ /* PDP Type Org */
+ pdp->eua.v[0] = (*ptr) & 0x0f;
+ ptr++;
+
+ /* PDP Type Number */
+ pdp_type = *ptr;
+ ptr++;
+
+ /* PDP Type length */
+ pdp_len = *ptr;
+ ptr++;
+
+ /* PDP Address */
+ switch (pdp_type) {
+ case PDP_EUA_TYPE_v4:
+ /* v4v6 expects an IPv4 addr first followed by an IPv6 addr*/
+ pdp->eua.v[1] = ext_ua ? PDP_EUA_TYPE_v4v6 : PDP_EUA_TYPE_v4;
+ pdp->eua.l = pdp_len + 2;
+ if (pdp_len != 4) {
+ LOGP(DLGTP, LOGL_ERROR,
+ "PDP Context Decode: Failed with invalid PDP length for IPv4 (%d != 4)",
pdp_len);
+ return -EINVAL;
+ }
+ memcpy(&pdp->eua.v[2], ptr, pdp_len);
+ break;
+ case PDP_EUA_TYPE_v6:
+ pdp->eua.v[1] = PDP_EUA_TYPE_v6;
+ pdp->eua.l = pdp_len + 2;
+ if (pdp_len != 16) {
+ LOGP(DLGTP, LOGL_ERROR,
+ "PDP Context Decode: Failed with invalid PDP length for IPv6 (%d != 16)",
pdp_len);
+ return -EINVAL;
+ }
+ memcpy(&pdp->eua.v[2], ptr, pdp_len);
+ break;
+ default:
+ return -EINVAL;
+ }
+ ptr += pdp_len;
+
+ /* GGSN Address Ctrl */
+ pdp->gsnrc.l = *ptr;
+ ptr++;
+ memcpy(pdp->gsnrc.v, ptr, pdp->gsnrc.l);
+ ptr += pdp->gsnrc.l;
+
+ /* GGSN Address User */
+ pdp->gsnru.l = *ptr;
+ ptr++;
+ memcpy(pdp->gsnru.v, ptr, pdp->gsnru.l);
+ ptr += pdp->gsnru.l;
+
+ /* APN */
+ pdp->apn_use.l = *ptr;
+ ptr++;
+ memcpy(pdp->apn_use.v, ptr, pdp->apn_use.l);
+ ptr += pdp->apn_use.l;
+
+ /* TransId 4 or 12 bit, if the second octet is 0x0, it is a 4 bit transaction id */
+ if (*(ptr + 1) == 0x00)
+ pdp->ti = (*ptr) & 0x0f;
+ else
+ pdp->ti = osmo_load16be(ptr) & 0x0fff;
+
+ ptr += 2;
+
+ if (is_ext_ua(pdp)) {
+ pdp_len = *ptr;
+ if (pdp_len != 16) {
+ LOGP(DLGTP, LOGL_ERROR,
+ "PDP Context Decode: Failed with invalid PDP address length for IPv6 (%d != 16)
in the second PDP address field", pdp_len);
+ return -EINVAL;
+ }
+
+ pdp->eua.l += pdp_len;
+ memcpy(&pdp->eua.v[6], ptr, pdp_len);
+ ptr += pdp_len;
+ }
+
+ if (ptr == end)
+ return 0;
+
+ return 1;
+}
+
+/** Encode a PDP Ctx into a buffer from a struct pdp_t
+ *
+ * @param buf output buffer
+ * @param size size of buf
+ * @param pdp to be encoded PDP context
+ * @param sapi to be encoded sapi
+ * @return on error < 0, on success the used bytes in buf
+ */
+int gtp_encode_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) do { \
+ if ((ptr - buf) + (bytes) > size) { \
+ LOGP(DLGTP, LOGL_ERROR, "PDP encode: Failed size check: %lu + %lu >
%lu\n", (ptr - buf), (unsigned long) (bytes), (unsigned long) size); \
+ return -1; \
+ } \
+ } while (0)
+
+#define MEMCPY_CHK(dst, src, len) do { \
+ CHECK_SPACE_ERR((len)); \
+ memcpy((dst), (uint8_t *)(src), (len)); \
+ (dst) += (len); \
+ } while (0)
+
+ /* 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;
+ }
+ /* 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
+}
+
+/* Handle an SGSN Context Response */
+static int gtp_sgsn_context_resp_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;
+ uint16_t seq;
+ uint32_t local_teic;
+ int hlen;
+ union gtpie_member *ie[GTPIE_SIZE] = {};
+ union gtp_packet *packet = (union gtp_packet *)pack;
+
+ if (version != 1) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer,
+ "Sent SGSN Context Response with wrong version (exp 1, rx %u)\n", version);
+ return -EINVAL;
+ }
+
+ if (!(packet->flags & GTP1HDR_F_SEQ)) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ LOGP(DLGTP, LOGL_NOTICE,
+ "Sent SGSN Context Response without Sequence flag set. Flags 0x%x\n",
packet->flags);
+ return -EINVAL;
+ }
+
+ /* the GTP code ensures the package contains a full GTPv1 header */
+ seq = get_seq(pack);
+ hlen = get_hlen(pack);
+ local_teic = get_tei(pack);
+
+ /* FIXME: retransmission need to be implemented:
+ * - Check if an Ack has been already transmitted (if so re-transmit) and return early
+ * - Check if a Req is in re-transmit queue, if, confirm it
+ */
+ /* 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,
+ "Sent SGSN Context Request with Invalid message format\n");
+ /* FIXME: Error Indication */
+ 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");
+ /* FIXME: Error Indication */
+ return EOF;
+ }
+
+ return sgsn_ctx_req_fsm_rx_resp(gsn, peer,
+ seq, local_teic,
+ ie, GTPIE_SIZE);
+}
+
+/* Handle an SGSN Context Acknowledge */
+static int gtp_sgsn_context_ack_ind(struct gsn_t *gsn, int version, struct sockaddr_in
*peer,
+ void *pack, unsigned len)
+{
+ union gtpie_member *ie[GTPIE_SIZE] = {};
+ uint16_t seq;
+ uint32_t local_teic;
+ const union gtp_packet *packet = (union gtp_packet *)pack;
+
+ if (version != 1) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer,
+ "Sent SGSN Context Ack with wrong version (exp 1, rx %u)\n",
version);
+ return -EINVAL;
+ }
+
+ if (!(packet->flags & GTP1HDR_F_SEQ)) {
+ rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
+ LOGP(DLGTP, LOGL_NOTICE,
+ "Sent SGSN Context Ack without Sequence flag set. Flags 0x%x\n",
packet->flags);
+ return -EINVAL;
+ }
+
+ /* the GTP code ensures the package contains a full GTPv1 header */
+ 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,
+ "Sent SGSN Context Ack with Invalid message format\n");
+ return -EINVAL;
+ }
+
+ seq = get_seq(pack);
+ local_teic = get_tei(pack);
+
+ return sgsn_ctx_req_fsm_rx_ack(gsn, peer,
+ seq, local_teic,
+ ie, GTPIE_SIZE);
+}
+
/* ***********************************************************
* Conversion functions
*************************************************************/
@@ -2949,6 +3613,15 @@
case GTP_RAN_INFO_RELAY:
gtp_ran_info_relay_ind(gsn, version, &peer, buffer, status);
break;
+ case GTP_SGSN_CONTEXT_REQ:
+ gtp_sgsn_context_req_ind(gsn, version, &peer, buffer, status);
+ break;
+ case GTP_SGSN_CONTEXT_RSP:
+ gtp_sgsn_context_resp_ind(gsn, version, &peer, buffer, status);
+ break;
+ case GTP_SGSN_CONTEXT_ACK:
+ gtp_sgsn_context_ack_ind(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_sgsn_ctx.c b/gtp/gtp_sgsn_ctx.c
new file mode 100644
index 0000000..6ade6ef
--- /dev/null
+++ b/gtp/gtp_sgsn_ctx.c
@@ -0,0 +1,512 @@
+/*
+ * OsmoGGSN - Gateway GPRS Support Node
+ * Copyright (C) 2024 sysmocom s.f.m.c. GmbH
+ *
+ * Author: Alexander Couzens <lynxis(a)fe80.eu>
+ *
+ * The contents of this file may be used under the terms of the GNU
+ * General Public License Version 2, provided that the above copyright
+ * notice and this permission notice is included in all copies or
+ * substantial portions of the software.
+ *
+ */
+
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/gtp/gsn.h>
+#include <osmocom/gtp/gtp.h>
+#include <osmocom/gtp/gtpie.h>
+
+#include "gtp_sgsn_ctx.h"
+
+#define X(s) (1 << (s))
+
+/* Handle the logic part of the SGSN Context Req/Resp/Ack */
+
+const struct value_string sgsn_ctx_event_names[] = {
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_REQ),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_RESP),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_RESP_FAIL),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_ACK),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_NACK),
+
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_REQ),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_RESP),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_RESP_FAIL),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_ACK),
+ OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_NACK),
+ { 0, NULL }
+};
+
+static const struct osmo_tdef_state_timeout sgsn_ctx_fsm_timeouts[32] = {
+ [SGSN_CTX_REQ_ST_START] = { },
+
+ [SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP] = { },
+ [SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK] = { },
+
+ [SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP] = { },
+ [SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK] = { },
+};
+
+struct osmo_tdef gtp_tdefs_sgsn_ctx[] = {
+ { /* terminator */ }
+};
+
+/* Used to pass all relevant data to the fsm via data */
+struct ctx_msg {
+ /* Message type */
+ uint8_t msg;
+ union gtpie_member * const *ie;
+ unsigned int ie_size;
+};
+
+#define sgsn_ctx_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, sgsn_ctx_fsm_timeouts, gtp_tdefs_sgsn_ctx,
10)
+
+/* Direction of the SGSN context.
+ * Outgoing Ctx: From local to remote.
+ * Incoming Ctx: From remote to local.
+ */
+static void sgsn_ctx_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct sgsn_ctx_req *req = fi->priv;
+ struct gsn_t *gsn = req->gsn;
+ struct ctx_msg *msg = data;
+
+ switch (event) {
+ case SGSN_CTX_REQ_E_RX_REQ:
+ /* remote SGSN ask this peer */
+ sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP);
+ if (gsn->cb_sgsn_context_request_ind)
+ gsn->cb_sgsn_context_request_ind(gsn, &req->peer, req->local_teic,
msg->ie, msg->ie_size);
+ break;
+ case SGSN_CTX_REQ_E_TX_REQ:
+ /* local SGSN ask remote peer */
+ sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* Outgoing Ctx: remote SGSN ask this peer, waiting for a resp from this node (SGSN) */
+static void sgsn_ctx_wait_local_resp(struct osmo_fsm_inst *fi, uint32_t event, void
*data)
+{
+ switch (event) {
+ case SGSN_CTX_REQ_E_TX_RESP:
+ sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK);
+ break;
+ case SGSN_CTX_REQ_E_TX_RESP_FAIL:
+ sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* Outgoing Ctx: This node (SGSN) answered with a Ctx Resp, waiting for a remote Ack or
Nack */
+static void sgsn_ctx_wait_remote_ack(struct osmo_fsm_inst *fi, uint32_t event, void
*data)
+{
+ struct sgsn_ctx_req *req = fi->priv;
+ struct gsn_t *gsn = req->gsn;
+ struct ctx_msg *msg = data;
+
+ switch (event) {
+ case SGSN_CTX_REQ_E_RX_ACK:
+ if (gsn->cb_sgsn_context_ack_ind)
+ gsn->cb_sgsn_context_ack_ind(gsn, &req->peer, req->local_teic,
msg->ie, msg->ie_size);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL);
+ break;
+ case SGSN_CTX_REQ_E_RX_NACK:
+ if (gsn->cb_sgsn_context_ack_ind)
+ gsn->cb_sgsn_context_ack_ind(gsn, &req->peer, req->local_teic,
msg->ie, msg->ie_size);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* Incoming Ctx: This node requested a remote SGSN. Waiting for a remote response */
+static void sgsn_ctx_wait_remote_resp(struct osmo_fsm_inst *fi, uint32_t event, void
*data)
+{
+ struct sgsn_ctx_req *req = fi->priv;
+ struct gsn_t *gsn = req->gsn;
+ struct ctx_msg *msg = data;
+
+ switch (event) {
+ case SGSN_CTX_REQ_E_RX_RESP:
+ sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK);
+ if (gsn->cb_sgsn_context_response_ind)
+ gsn->cb_sgsn_context_response_ind(gsn, &req->peer, req->local_teic,
msg->ie, msg->ie_size);
+ break;
+ case SGSN_CTX_REQ_E_RX_RESP_FAIL:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* Incoming Ctx: Received a Response, waiting for this node to respond with an Ack or
Nack */
+static void sgsn_ctx_local_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case SGSN_CTX_REQ_E_TX_ACK:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ case SGSN_CTX_REQ_E_TX_NACK:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+int sgsn_ctx_timer_cb(struct osmo_fsm_inst *fi)
+{
+ /* Terminating the FSM when a timeout happens */
+ return 1;
+}
+
+static struct osmo_fsm_state sgsn_ctx_req_states[] = {
+ [SGSN_CTX_REQ_ST_START] = {
+ .in_event_mask =
+ X(SGSN_CTX_REQ_E_RX_REQ) |
+ X(SGSN_CTX_REQ_E_TX_REQ),
+ .out_state_mask =
+ X(SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP) |
+ X(SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP),
+ .name = "Init",
+ .action = sgsn_ctx_init,
+ },
+
+ [SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP] = {
+ .in_event_mask =
+ X(SGSN_CTX_REQ_E_TX_RESP) |
+ X(SGSN_CTX_REQ_E_TX_RESP_FAIL),
+ .out_state_mask =
+ X(SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK),
+ .name = "Wait Local Response",
+ .action = sgsn_ctx_wait_local_resp,
+ },
+ [SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK] = {
+ .in_event_mask =
+ X(SGSN_CTX_REQ_E_RX_ACK) |
+ X(SGSN_CTX_REQ_E_RX_NACK),
+ .out_state_mask = 0,
+ .name = "Wait Remote Ack",
+ .action = sgsn_ctx_wait_remote_ack,
+ },
+
+ [SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP] = {
+ .in_event_mask =
+ X(SGSN_CTX_REQ_E_RX_RESP) |
+ X(SGSN_CTX_REQ_E_RX_RESP_FAIL),
+ .out_state_mask =
+ X(SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK),
+ .name = "Wait Remote Response",
+ .action = sgsn_ctx_wait_remote_resp,
+ },
+ [SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK] = {
+ .in_event_mask =
+ X(SGSN_CTX_REQ_E_TX_ACK) |
+ X(SGSN_CTX_REQ_E_TX_NACK),
+ .out_state_mask = 0,
+ .name = "Wait Local Ack",
+ .action = sgsn_ctx_local_ack,
+ },
+};
+
+struct osmo_fsm sgsn_ctx_req_fsm = {
+ .name = "SGSNCtxReq",
+ .states = sgsn_ctx_req_states,
+ .num_states = ARRAY_SIZE(sgsn_ctx_req_states),
+ .event_names = sgsn_ctx_event_names,
+ .log_subsys = DLGTP,
+ .timer_cb = sgsn_ctx_timer_cb,
+};
+
+static __attribute__((constructor)) void sgsn_ctx_register(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&sgsn_ctx_req_fsm) == 0);
+}
+
+struct sgsn_ctx_reqs *sgsn_ctx_reqs_init(void *ctx, uint32_t lowest_teic, uint32_t
highest_teic)
+{
+ struct sgsn_ctx_reqs *reqs;
+
+ if (lowest_teic >= highest_teic)
+ return NULL;
+
+ reqs = talloc_zero(ctx, struct sgsn_ctx_reqs);
+ if (!reqs)
+ return reqs;
+
+ reqs->lowest_teic = reqs->next_teic = lowest_teic;
+ reqs->high_teic = highest_teic;
+
+ INIT_LLIST_HEAD(&reqs->local_reqs);
+ INIT_LLIST_HEAD(&reqs->remote_reqs);
+
+ return reqs;
+}
+
+static uint32_t get_next_teic(struct gsn_t *gsn)
+{
+ uint32_t teic = gsn->sgsn_ctx->next_teic++;
+
+ if (gsn->sgsn_ctx->next_teic > gsn->sgsn_ctx->high_teic)
+ gsn->sgsn_ctx->next_teic = gsn->sgsn_ctx->lowest_teic;
+
+ /* FIXME: check if this is already assigned! */
+
+ return teic;
+}
+
+static struct sgsn_ctx_req *_sgsn_ctx_req_alloc(struct gsn_t *gsn)
+{
+ struct sgsn_ctx_req *req = talloc_zero(gsn, struct sgsn_ctx_req);
+
+ if (!req)
+ return NULL;
+
+ req->fi = osmo_fsm_inst_alloc(&sgsn_ctx_req_fsm, req, req, LOGL_INFO, NULL);
+ if (!req->fi)
+ goto out;
+
+ req->gsn = gsn;
+ req->local_teic = get_next_teic(gsn);
+ return req;
+
+out:
+ osmo_fsm_inst_free(req->fi);
+ talloc_free(req);
+ return NULL;
+}
+
+/* Alloc a SGSN_CTX for an outgoing Context (Remote SGSN requested) */
+static struct sgsn_ctx_req *sgsn_ctx_req_alloc_outgoing(struct gsn_t *gsn, const struct
sockaddr_in *peer, uint16_t seq)
+{
+ struct sgsn_ctx_req *req = _sgsn_ctx_req_alloc(gsn);
+ if (!req)
+ return req;
+
+ req->seq = seq;
+ req->peer = *peer;
+ llist_add_tail(&req->list, &gsn->sgsn_ctx->remote_reqs);
+
+ return req;
+}
+
+/* Alloc a SGSN_CTX for an incoming Context (Local SGSN requested) */
+static struct sgsn_ctx_req *sgsn_ctx_req_alloc_incoming(struct gsn_t *gsn, const struct
sockaddr_in *peer, uint16_t seq)
+{
+ struct sgsn_ctx_req *req = _sgsn_ctx_req_alloc(gsn);
+ if (!req)
+ return req;
+
+ req->seq = seq;
+ req->peer = *peer;
+ llist_add_tail(&req->list, &gsn->sgsn_ctx->local_reqs);
+
+ return req;
+}
+
+static void sgsn_ctx_free(struct sgsn_ctx_req *req)
+{
+ if (!req)
+ return;
+
+ osmo_fsm_inst_free(req->fi);
+ llist_del(&req->list);
+ talloc_free(req);
+}
+
+static struct sgsn_ctx_req *sgsn_ctx_by_teic(struct gsn_t *gsn, uint32_t teic, bool
local_req)
+{
+ struct sgsn_ctx_req *req;
+ struct llist_head *head;
+
+ if (local_req)
+ head = &gsn->sgsn_ctx->local_reqs;
+ else
+ head = &gsn->sgsn_ctx->remote_reqs;
+
+ llist_for_each_entry(req, head, list) {
+ if (req->local_teic == teic)
+ return req;
+ }
+
+ return NULL;
+}
+
+int sgsn_ctx_req_fsm_rx_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint16_t seq,
+ union gtpie_member * const *ie, unsigned int ie_size)
+{
+ struct sgsn_ctx_req *req = sgsn_ctx_req_alloc_outgoing(gsn, peer, seq);
+ struct ctx_msg ctx_msg = {
+ .msg = GTP_SGSN_CONTEXT_REQ,
+ .ie = ie,
+ .ie_size = ie_size,
+ };
+
+ if (!req)
+ return -ENOMEM;
+
+ if (!gsn->cb_sgsn_context_request_ind)
+ goto err;
+
+ if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &req->remote_teic))
+ goto err;
+
+ if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_RX_REQ, &ctx_msg))
+ goto err;
+
+ return 0;
+err:
+ sgsn_ctx_free(req);
+ return -EINVAL;
+}
+
+int sgsn_ctx_req_fsm_rx_resp(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint16_t seq, uint32_t local_teic,
+ union gtpie_member * const *ie, unsigned int ie_size)
+{
+ struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, true);
+ uint8_t cause;
+ struct ctx_msg ctx_msg = {
+ .msg = GTP_SGSN_CONTEXT_RSP,
+ .ie = ie,
+ .ie_size = ie_size,
+ };
+
+ if (!req)
+ return -ENOENT;
+
+ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause))
+ return -EINVAL;
+
+ if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &req->remote_teic) && cause ==
GTPCAUSE_ACC_REQ)
+ return -EINVAL;
+
+ if (osmo_fsm_inst_dispatch(req->fi,
+ cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_RX_RESP : SGSN_CTX_REQ_E_RX_RESP_FAIL,
+ &ctx_msg))
+ return -EINVAL;
+
+ return 0;
+}
+
+int sgsn_ctx_req_fsm_rx_ack(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint16_t seq, uint32_t local_teic,
+ union gtpie_member * const *ie, unsigned int ie_size)
+{
+ struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, false);
+ struct ctx_msg ctx_msg = {
+ .msg = GTP_SGSN_CONTEXT_ACK,
+ .ie = ie,
+ .ie_size = ie_size,
+ };
+
+ if (!req)
+ return -ENOENT;
+
+ if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_RX_ACK, &ctx_msg))
+ return -EINVAL;
+
+ return 0;
+}
+
+int sgsn_ctx_req_fsm_tx_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ union gtpie_member * const *ie, unsigned int ie_size,
+ uint32_t *local_teic, uint16_t seq)
+{
+ struct sgsn_ctx_req *req = sgsn_ctx_req_alloc_incoming(gsn, peer, seq);
+ struct ctx_msg ctx_msg = {
+ .msg = GTP_SGSN_CONTEXT_REQ,
+ .ie = ie,
+ .ie_size = ie_size,
+ };
+
+ if (!req)
+ return -ENOMEM;
+
+ /* TODO: move tx GTPv1 into the fsm */
+ *local_teic = req->local_teic;
+
+ if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_TX_REQ, &ctx_msg))
+ goto err;
+
+ return 0;
+
+err:
+ sgsn_ctx_free(req);
+ return -EINVAL;
+}
+
+int sgsn_ctx_req_fsm_tx_resp(struct gsn_t *gsn, struct sockaddr_in *peer,
+ uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic,
+ union gtpie_member * const *ie, unsigned int ie_size)
+{
+ struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, false);
+ uint8_t cause;
+ struct ctx_msg ctx_msg = {
+ .msg = GTP_SGSN_CONTEXT_RSP,
+ .ie = ie,
+ .ie_size = ie_size,
+ };
+
+ if (!req)
+ return -ENOENT;
+
+ /* TODO: move tx GTPv1 into the fsm */
+ *seq = req->seq;
+ *peer = req->peer;
+ *remote_teic = req->remote_teic;
+
+ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause))
+ return -EINVAL;
+
+ if (osmo_fsm_inst_dispatch(req->fi,
+ cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_TX_RESP : SGSN_CTX_REQ_E_TX_RESP_FAIL,
+ &ctx_msg))
+ return -EINVAL;
+
+ return 0;
+}
+
+int sgsn_ctx_req_fsm_tx_ack(struct gsn_t *gsn, struct sockaddr_in *peer,
+ uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic,
+ union gtpie_member * const *ie, unsigned int ie_size)
+{
+ struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, true);
+ uint8_t cause;
+ struct ctx_msg ctx_msg = {
+ .msg = GTP_SGSN_CONTEXT_ACK,
+ .ie = ie,
+ .ie_size = ie_size,
+ };
+
+ if (!req)
+ return -ENOENT;
+
+ /* TODO: move tx GTPv1 into the fsm */
+ *seq = req->seq;
+ *peer = req->peer;
+ *remote_teic = req->remote_teic;
+
+ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause))
+ return -EINVAL;
+
+ if (osmo_fsm_inst_dispatch(req->fi,
+ cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_TX_ACK : SGSN_CTX_REQ_E_TX_NACK,
+ &ctx_msg))
+ return -EINVAL;
+
+ return 0;
+}
diff --git a/gtp/gtp_sgsn_ctx.h b/gtp/gtp_sgsn_ctx.h
new file mode 100644
index 0000000..6d2ea0e
--- /dev/null
+++ b/gtp/gtp_sgsn_ctx.h
@@ -0,0 +1,104 @@
+/*
+ * OsmoGGSN - Gateway GPRS Support Node
+ * Copyright (C) 2024 sysmocom s.f.m.c. GmbH
+ *
+ * Author: Alexander Couzens <lynxis(a)fe80.eu>
+ *
+ * The contents of this file may be used under the terms of the GNU
+ * General Public License Version 2, provided that the above copyright
+ * notice and this permission notice is included in all copies or
+ * substantial portions of the software.
+ *
+ */
+
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+
+struct gsn_t;
+struct osmo_fsm_inst;
+union gtpie_member;
+
+
+enum gtp_sgsn_ctx_states {
+ SGSN_CTX_REQ_ST_START,
+
+ /* remote SGSN/MME request a Ctx from this peer */
+ SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP, /*! wait for this peer to tx a SGSN Ctx Respond */
+ SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK, /*! wait for remote peer SGSN Ctx Ack */
+
+ /* local SGSN request a Ctx from a remote peer (SGSN/MME) */
+ SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP, /*! wait for remote peer to send this peer a SGSN Ctx
Respond */
+ SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK, /*! wait for the local peer to ack */
+};
+
+enum gtp_sgsn_ctx_req_event {
+ /* remote SGSN/MME request a Ctx from this peer */
+ SGSN_CTX_REQ_E_RX_REQ,
+ SGSN_CTX_REQ_E_TX_RESP, /* a response with a success reason */
+ SGSN_CTX_REQ_E_TX_RESP_FAIL,
+ SGSN_CTX_REQ_E_RX_ACK,
+ SGSN_CTX_REQ_E_RX_NACK, /* a nack with a reason != success */
+
+ /* local SGSN requests a Context from a remote peer (SGSN/MME) */
+ SGSN_CTX_REQ_E_TX_REQ,
+ SGSN_CTX_REQ_E_RX_RESP,
+ SGSN_CTX_REQ_E_RX_RESP_FAIL,
+ SGSN_CTX_REQ_E_TX_ACK,
+ SGSN_CTX_REQ_E_TX_NACK,
+};
+
+struct sgsn_ctx_reqs {
+ /*! contains SGSN Context Request which this peer started by requesting a Ctx */
+ struct llist_head local_reqs;
+ /*! contains SGSN Context Requests which the remote peer started by sending a Ctx */
+ struct llist_head remote_reqs;
+
+ /* TEID-C */
+ uint32_t lowest_teic; /*! lowest valid teic for Gn interface */
+ uint32_t next_teic;
+ uint32_t high_teic; /*! highest valid teic for Gn interface */
+};
+
+struct sgsn_ctx_req {
+ /*! entry in sgsn_ctx_reqs local or remote reqs */
+ struct llist_head list;
+ struct gsn_t *gsn;
+
+ struct osmo_fsm_inst *fi;
+
+ struct sockaddr_in peer;
+
+ uint32_t local_teic;
+ uint32_t remote_teic;
+
+ uint16_t seq;
+};
+
+struct sgsn_ctx_reqs *sgsn_ctx_reqs_init(void *ctx, uint32_t lowest_teic, uint32_t
highest_teic);
+
+/*! Received a SGSN Context Request from a peeer */
+int sgsn_ctx_req_fsm_rx_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint16_t seq,
+ union gtpie_member * const *ie, unsigned int ie_size);
+
+int sgsn_ctx_req_fsm_rx_resp(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint16_t seq, uint32_t local_teic,
+ union gtpie_member * const *ie, unsigned int ie_size);
+
+int sgsn_ctx_req_fsm_rx_ack(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ uint16_t seq, uint32_t local_teic,
+ union gtpie_member * const *ie, unsigned int ie_size);
+
+
+int sgsn_ctx_req_fsm_tx_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
+ union gtpie_member * const *ie, unsigned int ie_size,
+ uint32_t *local_teic, uint16_t seq);
+
+int sgsn_ctx_req_fsm_tx_resp(struct gsn_t *gsn, struct sockaddr_in *peer,
+ uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic,
+ union gtpie_member * const *ie, unsigned int ie_size);
+
+int sgsn_ctx_req_fsm_tx_ack(struct gsn_t *gsn, struct sockaddr_in *peer,
+ uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic,
+ union gtpie_member * const *ie, unsigned int ie_size);
diff --git a/include/osmocom/gtp/gsn.h b/include/osmocom/gtp/gsn.h
index 772e845..8ccae14 100644
--- a/include/osmocom/gtp/gsn.h
+++ b/include/osmocom/gtp/gsn.h
@@ -17,7 +17,10 @@
#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 "gtpie.h"
#include "pdp.h"
#define GTP_MODE_GGSN 1
@@ -25,6 +28,8 @@
#define RESTART_FILE "gsn_restart"
+struct sgsn_ctx_reqs;
+
extern struct osmo_tdef gtp_T_defs[];
/* ***********************************************************
@@ -108,12 +113,17 @@
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, const struct sockaddr_in *peer,
uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size);
+ int (*cb_sgsn_context_response_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer,
uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size);
+ int (*cb_sgsn_context_ack_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer,
uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size);
/* Counters */
struct rate_ctr_group *ctrg;
/* Timers: */
struct osmo_tdef *tdef;
+
+ struct sgsn_ctx_reqs *sgsn_ctx;
};
/* External API functions */
@@ -152,6 +162,15 @@
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, const struct sockaddr_in *peer, uint32_t local_ref, union
gtpie_member * const *ie, unsigned int ie_size));
+
+extern int gtp_set_cb_sgsn_context_response_ind(struct gsn_t *gsn,
+ int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union
gtpie_member * const *ie, unsigned int ie_size));
+
+extern int gtp_set_cb_sgsn_context_ack_ind(struct gsn_t *gsn,
+ int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union
gtpie_member * const *ie, unsigned int ie_size));
+
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/include/osmocom/gtp/gtp.h b/include/osmocom/gtp/gtp.h
index 801837a..3e37e73 100644
--- a/include/osmocom/gtp/gtp.h
+++ b/include/osmocom/gtp/gtp.h
@@ -270,6 +270,27 @@
const uint8_t *rim_route_addr, size_t rim_route_addr_len,
uint8_t rim_route_addr_discr);
+/* Tx a SGSN Context Request */
+extern int gtp_sgsn_context_req(struct gsn_t *gsn, uint32_t *local_ref,
+ const struct sockaddr_in *peer, union gtpie_member **ie);
+
+/* Tx a SGSN Context Response */
+extern int gtp_sgsn_context_resp(struct gsn_t *gsn, uint32_t local_ref,
+ union gtpie_member **ie);
+
+/* Tx a SGSN Context Response, simplified when returning an error */
+int gtp_sgsn_context_resp_error(struct gsn_t *gsn, uint32_t local_ref,
+ uint8_t cause);
+
+/* Tx a SGSN Context Ack */
+extern int gtp_sgsn_context_ack(struct gsn_t *gsn, uint32_t local_ref,
+ union gtpie_member **ie);
+
+/* Tx a SGSN Context Ack, simplified when returning an error */
+int gtp_sgsn_context_ack_error(struct gsn_t *gsn, uint32_t local_ref,
+ uint8_t cause);
+
+
extern int gtp_decaps0(struct gsn_t *gsn);
extern int gtp_decaps1c(struct gsn_t *gsn);
extern int gtp_decaps1u(struct gsn_t *gsn);
@@ -279,6 +300,9 @@
extern int gsna2in_addr(struct in_addr *dst, struct ul16_t *gsna);
+extern int gtp_encode_pdp_ctx(uint8_t *buf, unsigned int size, const struct pdp_t *pdp,
uint16_t sapi);
+extern int gtp_decode_pdp_ctx(const uint8_t *buf, unsigned int size, struct pdp_t *pdp,
uint16_t *sapi);
+
extern const char *imsi_gtp2str(const uint64_t *imsi);
/*! Set the talloc context for internal objects */
--
To view, visit
https://gerrit.osmocom.org/c/osmo-ggsn/+/38938?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: osmo-ggsn
Gerrit-Branch: master
Gerrit-Change-Id: Idb8ed0bb87200a68bb8caedd734fc070df9179c0
Gerrit-Change-Number: 38938
Gerrit-PatchSet: 20
Gerrit-Owner: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: daniel <dwillmann(a)sysmocom.de>
Gerrit-Reviewer: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-CC: laforge <laforge(a)osmocom.org>