mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn.git
synced 2025-10-23 08:22:07 +00:00
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:
@@ -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
|
||||||
|
@@ -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(
|
||||||
|
@@ -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)
|
||||||
|
29
gtp/gsn.c
29
gtp/gsn.c
@@ -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
673
gtp/gtp.c
@@ -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
512
gtp/gtp_sgsn_ctx.c
Normal 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
104
gtp/gtp_sgsn_ctx.h
Normal 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);
|
@@ -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));
|
||||||
|
@@ -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 */
|
||||||
|
Reference in New Issue
Block a user