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
This commit is contained in:
Alexander Couzens
2025-01-07 13:53:43 +01:00
parent a564344d54
commit d46d0cc368
9 changed files with 1368 additions and 1 deletions

View File

@@ -7,3 +7,5 @@
# If any interfaces have been added since the last public release: c:r:a + 1. # 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. # If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line #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

View File

@@ -157,6 +157,7 @@ AM_INIT_AUTOMAKE([foreign])
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0)
AC_ARG_ENABLE(sanitize, AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING( [AS_HELP_STRING(

View File

@@ -12,6 +12,7 @@ AM_CFLAGS = \
-DSBINDIR='"$(sbindir)"' \ -DSBINDIR='"$(sbindir)"' \
-I$(top_srcdir)/include \ -I$(top_srcdir)/include \
$(LIBOSMOCORE_CFLAGS) \ $(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \
$(NULL) $(NULL)
libgtp_la_SOURCES = \ libgtp_la_SOURCES = \
@@ -19,6 +20,8 @@ libgtp_la_SOURCES = \
gsn_internal.h \ gsn_internal.h \
gtp.c \ gtp.c \
gtp_internal.h \ gtp_internal.h \
gtp_sgsn_ctx.c \
gtp_sgsn_ctx.h \
gtpie.c \ gtpie.c \
lookupa.c \ lookupa.c \
lookupa.h \ lookupa.h \
@@ -28,4 +31,4 @@ libgtp_la_SOURCES = \
$(NULL) $(NULL)
libgtp_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined libgtp_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libgtp_la_LIBADD = $(LIBOSMOCORE_LIBS) libgtp_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS)

View File

@@ -64,6 +64,7 @@
#include "queue.h" #include "queue.h"
#include "gsn_internal.h" #include "gsn_internal.h"
#include "gtp_internal.h" #include "gtp_internal.h"
#include "gtp_sgsn_ctx.h"
/* Error reporting functions */ /* Error reporting functions */
@@ -244,6 +245,30 @@ int gtp_set_cb_data_ind(struct gsn_t *gsn,
return 0; 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) static int queue_timer_retrans(struct gsn_t *gsn)
{ {
/* Retransmit any outstanding packets */ /* Retransmit any outstanding packets */
@@ -525,6 +550,10 @@ int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen,
/* Start internal queue timer */ /* Start internal queue timer */
gtp_queue_timer_start(*gsn); gtp_queue_timer_start(*gsn);
(*gsn)->sgsn_ctx = sgsn_ctx_reqs_init(*gsn, 2048, 4095);
if (!(*gsn)->sgsn_ctx)
goto error;
return 0; return 0;
error: error:
gtp_free(*gsn); gtp_free(*gsn);

673
gtp/gtp.c
View File

@@ -25,6 +25,8 @@
#include <osmocom/core/logging.h> #include <osmocom/core/logging.h>
#include <osmocom/core/utils.h> #include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm23003.h>
#if defined(__FreeBSD__) #if defined(__FreeBSD__)
#include <sys/endian.h> #include <sys/endian.h>
@@ -61,6 +63,7 @@
#include "queue.h" #include "queue.h"
#include "gsn_internal.h" #include "gsn_internal.h"
#include "gtp_sgsn_ctx.h"
#include "gtp_internal.h" #include "gtp_internal.h"
/* Error reporting functions */ /* Error reporting functions */
@@ -393,6 +396,18 @@ static int gtp_req_transmit(struct gsn_t *gsn, uint8_t version, const struct in_
return 0; 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, static int gtp_req(struct gsn_t *gsn, uint8_t version, struct pdp_t *pdp,
union gtp_packet *packet, int len, union gtp_packet *packet, int len,
const struct in_addr *inetaddr, void *cbp) const struct in_addr *inetaddr, void *cbp)
@@ -552,6 +567,21 @@ static int gtp_resp_pdp(uint8_t version, struct gsn_t *gsn, struct pdp_t *pdp,
return gtp_resp(gsn, packet, len, peer, fd, seq, tid, flow, tei); 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, static int gtp_notification(struct gsn_t *gsn, uint8_t version,
union gtp_packet *packet, int len, union gtp_packet *packet, int len,
const struct sockaddr_in *peer, int fd, uint16_t seq) const struct sockaddr_in *peer, int fd, uint16_t seq)
@@ -851,6 +881,640 @@ 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); 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 * Conversion functions
*************************************************************/ *************************************************************/
@@ -2949,6 +3613,15 @@ int gtp_decaps1c(struct gsn_t *gsn)
case GTP_RAN_INFO_RELAY: case GTP_RAN_INFO_RELAY:
gtp_ran_info_relay_ind(gsn, version, &peer, buffer, status); gtp_ran_info_relay_ind(gsn, version, &peer, buffer, status);
break; 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: default:
rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_UNKNOWN); rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_UNKNOWN);
GTP_LOGPKG(LOGL_ERROR, &peer, buffer, status, GTP_LOGPKG(LOGL_ERROR, &peer, buffer, status,

512
gtp/gtp_sgsn_ctx.c Normal file
View File

@@ -0,0 +1,512 @@
/*
* OsmoGGSN - Gateway GPRS Support Node
* Copyright (C) 2024 sysmocom s.f.m.c. GmbH
*
* Author: Alexander Couzens <lynxis@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;
}

104
gtp/gtp_sgsn_ctx.h Normal file
View File

@@ -0,0 +1,104 @@
/*
* OsmoGGSN - Gateway GPRS Support Node
* Copyright (C) 2024 sysmocom s.f.m.c. GmbH
*
* Author: Alexander Couzens <lynxis@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);

View File

@@ -17,7 +17,10 @@
#include <osmocom/core/timer.h> #include <osmocom/core/timer.h>
#include <osmocom/core/tdef.h> #include <osmocom/core/tdef.h>
#include <osmocom/core/rate_ctr.h> #include <osmocom/core/rate_ctr.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/gsm/gsm48.h>
#include "gtpie.h"
#include "pdp.h" #include "pdp.h"
#define GTP_MODE_GGSN 1 #define GTP_MODE_GGSN 1
@@ -25,6 +28,8 @@
#define RESTART_FILE "gsn_restart" #define RESTART_FILE "gsn_restart"
struct sgsn_ctx_reqs;
extern struct osmo_tdef gtp_T_defs[]; extern struct osmo_tdef gtp_T_defs[];
/* *********************************************************** /* ***********************************************************
@@ -108,12 +113,17 @@ struct gsn_t {
int (*cb_recovery)(struct sockaddr_in *peer, uint8_t recovery); 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_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_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 */ /* Counters */
struct rate_ctr_group *ctrg; struct rate_ctr_group *ctrg;
/* Timers: */ /* Timers: */
struct osmo_tdef *tdef; struct osmo_tdef *tdef;
struct sgsn_ctx_reqs *sgsn_ctx;
}; };
/* External API functions */ /* External API functions */
@@ -152,6 +162,15 @@ extern int gtp_set_cb_extheader_ind(struct gsn_t *gsn,
extern int gtp_set_cb_ran_info_relay_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)); 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, extern int gtp_set_cb_conf(struct gsn_t *gsn,
int (*cb)(int type, int cause, struct pdp_t *pdp, int (*cb)(int type, int cause, struct pdp_t *pdp,
void *cbp)); void *cbp));

View File

@@ -270,6 +270,27 @@ 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, const uint8_t *rim_route_addr, size_t rim_route_addr_len,
uint8_t rim_route_addr_discr); 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_decaps0(struct gsn_t *gsn);
extern int gtp_decaps1c(struct gsn_t *gsn); extern int gtp_decaps1c(struct gsn_t *gsn);
extern int gtp_decaps1u(struct gsn_t *gsn); extern int gtp_decaps1u(struct gsn_t *gsn);
@@ -279,6 +300,9 @@ extern int gtp_echo_req(struct gsn_t *gsn, int version, void *cbp,
extern int gsna2in_addr(struct in_addr *dst, struct ul16_t *gsna); 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); extern const char *imsi_gtp2str(const uint64_t *imsi);
/*! Set the talloc context for internal objects */ /*! Set the talloc context for internal objects */