mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-ggsn.git
				synced 2025-11-03 21:53:25 +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 removed or changed since the last public release: c:r:0.
 | 
			
		||||
#library	what			description / commit summary line
 | 
			
		||||
libgtp    new API    SGSN Ctx Req/Resp/Ack, gtp_encode_pdp_ctx, gtp_decode_pdp_ctx, gtp_set_cb_sgsn_context_request_ind, ...
 | 
			
		||||
libgtp    new dependency libosmogsm
 | 
			
		||||
 
 | 
			
		||||
@@ -157,6 +157,7 @@ AM_INIT_AUTOMAKE([foreign])
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0)
 | 
			
		||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0)
 | 
			
		||||
 | 
			
		||||
AC_ARG_ENABLE(sanitize,
 | 
			
		||||
	[AS_HELP_STRING(
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ AM_CFLAGS = \
 | 
			
		||||
	    -DSBINDIR='"$(sbindir)"' \
 | 
			
		||||
	    -I$(top_srcdir)/include \
 | 
			
		||||
	    $(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
	    $(LIBOSMOGSM_CFLAGS) \
 | 
			
		||||
	    $(NULL)
 | 
			
		||||
 | 
			
		||||
libgtp_la_SOURCES = \
 | 
			
		||||
@@ -19,6 +20,8 @@ libgtp_la_SOURCES = \
 | 
			
		||||
		    gsn_internal.h \
 | 
			
		||||
		    gtp.c \
 | 
			
		||||
		    gtp_internal.h \
 | 
			
		||||
		    gtp_sgsn_ctx.c \
 | 
			
		||||
		    gtp_sgsn_ctx.h \
 | 
			
		||||
		    gtpie.c \
 | 
			
		||||
		    lookupa.c \
 | 
			
		||||
		    lookupa.h \
 | 
			
		||||
@@ -28,4 +31,4 @@ libgtp_la_SOURCES = \
 | 
			
		||||
		    $(NULL)
 | 
			
		||||
 | 
			
		||||
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 "gsn_internal.h"
 | 
			
		||||
#include "gtp_internal.h"
 | 
			
		||||
#include "gtp_sgsn_ctx.h"
 | 
			
		||||
 | 
			
		||||
/* Error reporting functions */
 | 
			
		||||
 | 
			
		||||
@@ -244,6 +245,30 @@ int gtp_set_cb_data_ind(struct gsn_t *gsn,
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int gtp_set_cb_sgsn_context_request_ind(struct gsn_t *gsn,
 | 
			
		||||
					int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer,
 | 
			
		||||
						  uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size))
 | 
			
		||||
{
 | 
			
		||||
	gsn->cb_sgsn_context_request_ind = cb;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int gtp_set_cb_sgsn_context_response_ind(struct gsn_t *gsn,
 | 
			
		||||
					 int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer,
 | 
			
		||||
						   uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size))
 | 
			
		||||
{
 | 
			
		||||
	gsn->cb_sgsn_context_response_ind = cb;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int gtp_set_cb_sgsn_context_ack_ind(struct gsn_t *gsn,
 | 
			
		||||
				    int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer,
 | 
			
		||||
					      uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size))
 | 
			
		||||
{
 | 
			
		||||
	gsn->cb_sgsn_context_ack_ind = cb;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int queue_timer_retrans(struct gsn_t *gsn)
 | 
			
		||||
{
 | 
			
		||||
	/* Retransmit any outstanding packets */
 | 
			
		||||
@@ -525,6 +550,10 @@ int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen,
 | 
			
		||||
	/* Start internal queue timer */
 | 
			
		||||
	gtp_queue_timer_start(*gsn);
 | 
			
		||||
 | 
			
		||||
	(*gsn)->sgsn_ctx = sgsn_ctx_reqs_init(*gsn, 2048, 4095);
 | 
			
		||||
	if (!(*gsn)->sgsn_ctx)
 | 
			
		||||
		goto error;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
error:
 | 
			
		||||
	gtp_free(*gsn);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										673
									
								
								gtp/gtp.c
									
									
									
									
									
								
							
							
						
						
									
										673
									
								
								gtp/gtp.c
									
									
									
									
									
								
							@@ -25,6 +25,8 @@
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/gsm/gsm48.h>
 | 
			
		||||
#include <osmocom/gsm/gsm23003.h>
 | 
			
		||||
 | 
			
		||||
#if defined(__FreeBSD__)
 | 
			
		||||
#include <sys/endian.h>
 | 
			
		||||
@@ -61,6 +63,7 @@
 | 
			
		||||
 | 
			
		||||
#include "queue.h"
 | 
			
		||||
#include "gsn_internal.h"
 | 
			
		||||
#include "gtp_sgsn_ctx.h"
 | 
			
		||||
#include "gtp_internal.h"
 | 
			
		||||
 | 
			
		||||
/* Error reporting functions */
 | 
			
		||||
@@ -393,6 +396,18 @@ static int gtp_req_transmit(struct gsn_t *gsn, uint8_t version, const struct in_
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gtp_reqv1c(struct gsn_t *gsn, uint32_t teic,
 | 
			
		||||
		      union gtp_packet *packet, int len,
 | 
			
		||||
		      const struct in_addr *inetaddr, void *cbp)
 | 
			
		||||
{
 | 
			
		||||
	packet->flags |= GTP1HDR_F_SEQ;
 | 
			
		||||
	packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT);
 | 
			
		||||
	packet->gtp1l.h.seq = hton16(gsn->seq_next);
 | 
			
		||||
	packet->gtp1l.h.tei = hton32(teic);
 | 
			
		||||
 | 
			
		||||
	return gtp_req_transmit(gsn, 1, inetaddr, packet, len, NULL, cbp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gtp_req(struct gsn_t *gsn, uint8_t version, struct pdp_t *pdp,
 | 
			
		||||
	    union gtp_packet *packet, int len,
 | 
			
		||||
	    const struct in_addr *inetaddr, void *cbp)
 | 
			
		||||
@@ -552,6 +567,21 @@ 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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gtp_respv1c(struct gsn_t *gsn, uint16_t seq, uint32_t remote_teic,
 | 
			
		||||
		       union gtp_packet *packet, int len,
 | 
			
		||||
		       const struct in_addr *inetaddr)
 | 
			
		||||
{
 | 
			
		||||
	struct sockaddr_in peer = {};
 | 
			
		||||
	peer.sin_addr = *inetaddr;
 | 
			
		||||
	peer.sin_port = htons(GTP1C_PORT);
 | 
			
		||||
	peer.sin_family = AF_INET;
 | 
			
		||||
 | 
			
		||||
	packet->flags |= GTP1HDR_F_SEQ | GTP1HDR_F_GTP1;
 | 
			
		||||
	packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT);
 | 
			
		||||
 | 
			
		||||
	return gtp_resp(gsn, packet, len, &peer, gsn->fd1c, seq, 0, 0, remote_teic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gtp_notification(struct gsn_t *gsn, uint8_t version,
 | 
			
		||||
		     union gtp_packet *packet, int len,
 | 
			
		||||
		     const struct sockaddr_in *peer, int fd, uint16_t seq)
 | 
			
		||||
@@ -851,6 +881,640 @@ int gtp_ran_info_relay_req(struct gsn_t *gsn, const struct sockaddr_in *peer,
 | 
			
		||||
	return gtp_notification(gsn, 1, &packet, length, peer, gsn->fd1c, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define GSM_MI_TYPE_TLLI 0x05
 | 
			
		||||
 | 
			
		||||
/* Send an SGSN Context Request */
 | 
			
		||||
int gtp_sgsn_context_req(struct gsn_t *gsn, uint32_t *local_ref,
 | 
			
		||||
			 const struct sockaddr_in *peer, union gtpie_member **ie)
 | 
			
		||||
{
 | 
			
		||||
	union gtp_packet packet = {};
 | 
			
		||||
	unsigned int packet_len;
 | 
			
		||||
	int rc;
 | 
			
		||||
	uint32_t local_teic;
 | 
			
		||||
	union gtpie_member req_ie_elem[2] = {};
 | 
			
		||||
	void *pack;
 | 
			
		||||
	unsigned encoded_len;
 | 
			
		||||
 | 
			
		||||
	rc = sgsn_ctx_req_fsm_tx_req(gsn, peer, ie, GTPIE_SIZE, &local_teic, gsn->seq_next);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_REQ, &packet);
 | 
			
		||||
	req_ie_elem[0].tv4.t = GTPIE_TEI_C;
 | 
			
		||||
	req_ie_elem[0].tv4.v = htonl(local_teic);
 | 
			
		||||
	ie[GTPIE_TEI_C] = &req_ie_elem[0];
 | 
			
		||||
 | 
			
		||||
	req_ie_elem[1].tlv.t = GTPIE_GSN_ADDR;
 | 
			
		||||
	req_ie_elem[1].tlv.l = htons(sizeof(gsn->gsnc));
 | 
			
		||||
	memcpy(&req_ie_elem[1].tlv.v[0], &gsn->gsnc, sizeof(gsn->gsnc));
 | 
			
		||||
	ie[GTPIE_GSN_ADDR] = &req_ie_elem[1];
 | 
			
		||||
 | 
			
		||||
	pack = &packet;
 | 
			
		||||
	pack += packet_len;
 | 
			
		||||
	rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	packet_len += encoded_len;
 | 
			
		||||
	*local_ref = local_teic;
 | 
			
		||||
	return gtp_reqv1c(gsn, 0, &packet, packet_len, &peer->sin_addr, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Send an SGSN Context Request, extends ies */
 | 
			
		||||
int gtp_sgsn_context_resp(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
			  union gtpie_member **ie)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t local_teic = local_ref;
 | 
			
		||||
	uint32_t remote_teic;
 | 
			
		||||
	uint16_t seq;
 | 
			
		||||
 | 
			
		||||
	union gtpie_member resp_ie_elem[2] = {};
 | 
			
		||||
	union gtp_packet packet = {};
 | 
			
		||||
	unsigned int packet_len;
 | 
			
		||||
	int rc;
 | 
			
		||||
	void *pack;
 | 
			
		||||
	unsigned encoded_len;
 | 
			
		||||
	struct sockaddr_in peer;
 | 
			
		||||
 | 
			
		||||
	rc = sgsn_ctx_req_fsm_tx_resp(gsn, &peer, &seq, local_teic, &remote_teic, ie, GTPIE_SIZE);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_RSP, &packet);
 | 
			
		||||
 | 
			
		||||
	resp_ie_elem[0].tv4.t = GTPIE_TEI_C;
 | 
			
		||||
	resp_ie_elem[0].tv4.v = htonl(local_teic);
 | 
			
		||||
	ie[GTPIE_TEI_C] = &resp_ie_elem[0];
 | 
			
		||||
 | 
			
		||||
	resp_ie_elem[1].tlv.t = GTPIE_GSN_ADDR;
 | 
			
		||||
	resp_ie_elem[1].tlv.l = htons(sizeof(gsn->gsnc));
 | 
			
		||||
	memcpy(&resp_ie_elem[1].tlv.v[0], &gsn->gsnc, sizeof(gsn->gsnc));
 | 
			
		||||
	ie[GTPIE_GSN_ADDR] = &resp_ie_elem[1];
 | 
			
		||||
 | 
			
		||||
	pack = &packet;
 | 
			
		||||
	pack += packet_len;
 | 
			
		||||
	rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	packet_len += encoded_len;
 | 
			
		||||
	return gtp_respv1c(gsn, seq, remote_teic, &packet, packet_len, &peer.sin_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int gtp_sgsn_context_resp_error(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
				uint8_t cause)
 | 
			
		||||
{
 | 
			
		||||
	union gtpie_member *ie[GTP_MAX] = {};
 | 
			
		||||
	union gtpie_member causetlv = {};
 | 
			
		||||
	causetlv.tv1.t = GTPIE_CAUSE;
 | 
			
		||||
	causetlv.tv1.v = cause;
 | 
			
		||||
	ie[0] = &causetlv;
 | 
			
		||||
 | 
			
		||||
	return gtp_sgsn_context_resp(gsn, local_ref, ie);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Send an SGSN Context Ack/Nack.
 | 
			
		||||
 * Argument **ie could be const, but the other functions take a non-const ie
 | 
			
		||||
 * to be consistent and allow future modification, keep ie in this case non-const.
 | 
			
		||||
 */
 | 
			
		||||
int gtp_sgsn_context_ack(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
			 union gtpie_member **ie)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t local_teic = local_ref;
 | 
			
		||||
	uint32_t remote_teic;
 | 
			
		||||
	uint16_t seq;
 | 
			
		||||
	union gtp_packet packet = {};
 | 
			
		||||
	unsigned int packet_len;
 | 
			
		||||
	int rc;
 | 
			
		||||
	void *pack;
 | 
			
		||||
	unsigned encoded_len;
 | 
			
		||||
	struct sockaddr_in peer;
 | 
			
		||||
 | 
			
		||||
	rc = sgsn_ctx_req_fsm_tx_ack(gsn, &peer, &seq, local_teic, &remote_teic, ie, GTPIE_SIZE);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_ACK, &packet);
 | 
			
		||||
	pack = &packet;
 | 
			
		||||
	pack += packet_len;
 | 
			
		||||
 | 
			
		||||
	rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	packet_len += encoded_len;
 | 
			
		||||
	return gtp_respv1c(gsn, seq, remote_teic, &packet, packet_len, &peer.sin_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int gtp_sgsn_context_ack_error(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
			       uint8_t cause)
 | 
			
		||||
{
 | 
			
		||||
	union gtpie_member *ie[GTPIE_SIZE] = {};
 | 
			
		||||
	union gtpie_member causetlv = {};
 | 
			
		||||
	causetlv.tv1.t = GTPIE_CAUSE;
 | 
			
		||||
	causetlv.tv1.v = cause;
 | 
			
		||||
	ie[GTPIE_CAUSE] = &causetlv;
 | 
			
		||||
 | 
			
		||||
	return gtp_sgsn_context_ack(gsn, local_ref, ie);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Handle an SGSN Context Request */
 | 
			
		||||
static int gtp_sgsn_context_req_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
 | 
			
		||||
			void *pack, unsigned len)
 | 
			
		||||
{
 | 
			
		||||
	union gtpie_member *ie[GTPIE_SIZE] = {};
 | 
			
		||||
	uint16_t seq;
 | 
			
		||||
 | 
			
		||||
	if (version != 1) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer,
 | 
			
		||||
			       "Sent SGSN Context Request with wrong version (exp 1, rx %u)\n", version);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* the GTP code ensures the package contains a full GTPv1 header */
 | 
			
		||||
	int hlen = get_hlen(pack);
 | 
			
		||||
 | 
			
		||||
	/* Decode information elements */
 | 
			
		||||
	if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
 | 
			
		||||
			    "Sent SGSN Context Request with Invalid message format\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	seq = get_seq(pack);
 | 
			
		||||
	return sgsn_ctx_req_fsm_rx_req(gsn, peer, seq, ie, GTPIE_SIZE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool is_ext_ua(const struct pdp_t *pdp)
 | 
			
		||||
{
 | 
			
		||||
	return pdp->eua.l > 0 && pdp->eua.v[0] == PDP_EUA_TYPE_v4v6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Check if length of the PDP */
 | 
			
		||||
int decode_pdp_ctx_len_check(const uint8_t *buf, unsigned int size)
 | 
			
		||||
{
 | 
			
		||||
#define CHECK_SIZE() do { if (ptr >= end) return -EINVAL; } while (0)
 | 
			
		||||
	uint8_t tmp1;
 | 
			
		||||
	const uint8_t *ptr = buf;
 | 
			
		||||
	const uint8_t *end = ptr + size;
 | 
			
		||||
	bool ext_ua = false;
 | 
			
		||||
 | 
			
		||||
	/* FIXME what is the minimal length ? */
 | 
			
		||||
	if (size < 20)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	ext_ua = !!((*ptr) & 0x80);
 | 
			
		||||
 | 
			
		||||
	ptr += 2;
 | 
			
		||||
	/* Qos Sub Length */
 | 
			
		||||
	CHECK_SIZE();
 | 
			
		||||
	tmp1 = *ptr;
 | 
			
		||||
	ptr += tmp1 + 1;
 | 
			
		||||
 | 
			
		||||
	/* Qos Req Length */
 | 
			
		||||
	CHECK_SIZE();
 | 
			
		||||
	tmp1 = *ptr;
 | 
			
		||||
	ptr += tmp1 + 1;
 | 
			
		||||
 | 
			
		||||
	/* Qos Neg Length */
 | 
			
		||||
	CHECK_SIZE();
 | 
			
		||||
	tmp1 = *ptr;
 | 
			
		||||
	ptr += tmp1 + 1;
 | 
			
		||||
 | 
			
		||||
	ptr += 17;
 | 
			
		||||
 | 
			
		||||
	/* PDP Address length */
 | 
			
		||||
	CHECK_SIZE();
 | 
			
		||||
	tmp1 = *ptr;
 | 
			
		||||
	ptr += tmp1 + 1;
 | 
			
		||||
 | 
			
		||||
	/* GGSN Address for control plane length */
 | 
			
		||||
	CHECK_SIZE();
 | 
			
		||||
	tmp1 = *ptr;
 | 
			
		||||
	ptr += tmp1 + 1;
 | 
			
		||||
 | 
			
		||||
	/* GGSN Address for User traffic plane length */
 | 
			
		||||
	CHECK_SIZE();
 | 
			
		||||
	tmp1 = *ptr;
 | 
			
		||||
	ptr += tmp1 + 1;
 | 
			
		||||
 | 
			
		||||
	/* APN length */
 | 
			
		||||
	CHECK_SIZE();
 | 
			
		||||
	tmp1 = *ptr;
 | 
			
		||||
	ptr += tmp1 + 1;
 | 
			
		||||
 | 
			
		||||
	/* Transaction Id */
 | 
			
		||||
	ptr += 2;
 | 
			
		||||
 | 
			
		||||
	/* Second PDP Address is only present in IPv4v6 PDP Context */
 | 
			
		||||
	if (ext_ua) {
 | 
			
		||||
		ptr++;
 | 
			
		||||
		/* PDP Address Length */
 | 
			
		||||
		CHECK_SIZE();
 | 
			
		||||
		tmp1 = *ptr;
 | 
			
		||||
		ptr += tmp1 + 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ptr == end)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
#undef CHECK_SIZE
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Decode a PDP Ctx from a buffer into struct pdp_t
 | 
			
		||||
 *
 | 
			
		||||
 * @param buf input buffer
 | 
			
		||||
 * @param size size of buf
 | 
			
		||||
 * @param pdp the decoded pdp buffer
 | 
			
		||||
 * @param sapi the decoded sapi
 | 
			
		||||
 * @return 0 on success, 1 if buffer not completely consumed, < 0 on syntactical errors
 | 
			
		||||
 */
 | 
			
		||||
int gtp_decode_pdp_ctx(const uint8_t *buf, unsigned int size, struct pdp_t *pdp, uint16_t *sapi)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t pdp_type, pdp_len;
 | 
			
		||||
	const uint8_t *ptr = buf;
 | 
			
		||||
	const uint8_t *end = ptr + size;
 | 
			
		||||
 | 
			
		||||
	if (decode_pdp_ctx_len_check(buf, size)) {
 | 
			
		||||
		LOGP(DLGTP, LOGL_ERROR, "GTP: PDP Context Decode: length check failed.");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	memset(pdp, 0, sizeof(*pdp));
 | 
			
		||||
	bool ext_ua = (*ptr & (1 << 7));
 | 
			
		||||
 | 
			
		||||
	pdp->version = 1;
 | 
			
		||||
	pdp->vplmn_allow = (*ptr) & (1 << 6);
 | 
			
		||||
	pdp->reorder = (*ptr) & (1 << 4);
 | 
			
		||||
	pdp->nsapi = (*ptr) & 0x0f;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* SAPI */
 | 
			
		||||
	*sapi = *ptr & 0x0f;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* QoS Sub */
 | 
			
		||||
	pdp->qos_sub.l = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
	memcpy(pdp->qos_sub.v, ptr, pdp->qos_sub.l);
 | 
			
		||||
	ptr += pdp->qos_sub.l;
 | 
			
		||||
 | 
			
		||||
	/* QoS Req */
 | 
			
		||||
	pdp->qos_req.l = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
	memcpy(pdp->qos_req.v, ptr, pdp->qos_req.l);
 | 
			
		||||
	ptr += pdp->qos_req.l;
 | 
			
		||||
 | 
			
		||||
	/* QoS Neg */
 | 
			
		||||
	pdp->qos_neg.l = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
	memcpy(pdp->qos_neg.v, ptr, pdp->qos_neg.l);
 | 
			
		||||
	ptr += pdp->qos_neg.l;
 | 
			
		||||
 | 
			
		||||
	/* SND */
 | 
			
		||||
	pdp->pdcpsndd = osmo_load16be(ptr);
 | 
			
		||||
	ptr += 2;
 | 
			
		||||
 | 
			
		||||
	/* SNU */
 | 
			
		||||
	pdp->pdcpsndu = osmo_load16be(ptr);
 | 
			
		||||
	ptr += 2;
 | 
			
		||||
 | 
			
		||||
	/* Send N-PDU */
 | 
			
		||||
	pdp->gtpsntx = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* Recv N-PDU */
 | 
			
		||||
	pdp->gtpsnrx = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* Uplink TEIC */
 | 
			
		||||
	pdp->teic_gn = osmo_load32be(ptr);
 | 
			
		||||
	ptr += 4;
 | 
			
		||||
 | 
			
		||||
	/* Uplink TEIDI */
 | 
			
		||||
	pdp->teid_gn = osmo_load32be(ptr);
 | 
			
		||||
	ptr += 4;
 | 
			
		||||
 | 
			
		||||
	/* PDP Ctx Id */
 | 
			
		||||
	pdp->pdp_id = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* PDP Type Org */
 | 
			
		||||
	pdp->eua.v[0] = (*ptr) & 0x0f;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* PDP Type Number */
 | 
			
		||||
	pdp_type = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* PDP Type length */
 | 
			
		||||
	pdp_len = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
 | 
			
		||||
	/* PDP Address */
 | 
			
		||||
	switch (pdp_type) {
 | 
			
		||||
	case PDP_EUA_TYPE_v4:
 | 
			
		||||
		/* v4v6 expects an IPv4 addr first followed by an IPv6 addr*/
 | 
			
		||||
		pdp->eua.v[1] = ext_ua ? PDP_EUA_TYPE_v4v6 : PDP_EUA_TYPE_v4;
 | 
			
		||||
		pdp->eua.l = pdp_len + 2;
 | 
			
		||||
		if (pdp_len != 4) {
 | 
			
		||||
			LOGP(DLGTP, LOGL_ERROR,
 | 
			
		||||
				"PDP Context Decode: Failed with invalid PDP length for IPv4 (%d != 4)", pdp_len);
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
		memcpy(&pdp->eua.v[2], ptr, pdp_len);
 | 
			
		||||
		break;
 | 
			
		||||
	case PDP_EUA_TYPE_v6:
 | 
			
		||||
		pdp->eua.v[1] = PDP_EUA_TYPE_v6;
 | 
			
		||||
		pdp->eua.l = pdp_len + 2;
 | 
			
		||||
		if (pdp_len != 16) {
 | 
			
		||||
			LOGP(DLGTP, LOGL_ERROR,
 | 
			
		||||
				"PDP Context Decode: Failed with invalid PDP length for IPv6 (%d != 16)", pdp_len);
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
		memcpy(&pdp->eua.v[2], ptr, pdp_len);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
	ptr += pdp_len;
 | 
			
		||||
 | 
			
		||||
	/* GGSN Address Ctrl */
 | 
			
		||||
	pdp->gsnrc.l = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
	memcpy(pdp->gsnrc.v, ptr, pdp->gsnrc.l);
 | 
			
		||||
	ptr += pdp->gsnrc.l;
 | 
			
		||||
 | 
			
		||||
	/* GGSN Address User */
 | 
			
		||||
	pdp->gsnru.l = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
	memcpy(pdp->gsnru.v, ptr, pdp->gsnru.l);
 | 
			
		||||
	ptr += pdp->gsnru.l;
 | 
			
		||||
 | 
			
		||||
	/* APN */
 | 
			
		||||
	pdp->apn_use.l = *ptr;
 | 
			
		||||
	ptr++;
 | 
			
		||||
	memcpy(pdp->apn_use.v, ptr, pdp->apn_use.l);
 | 
			
		||||
	ptr += pdp->apn_use.l;
 | 
			
		||||
 | 
			
		||||
	/* TransId 4 or 12 bit, if the second octet is 0x0, it is a 4 bit transaction id */
 | 
			
		||||
	if (*(ptr + 1) == 0x00)
 | 
			
		||||
		pdp->ti = (*ptr) & 0x0f;
 | 
			
		||||
	else
 | 
			
		||||
		pdp->ti = osmo_load16be(ptr) & 0x0fff;
 | 
			
		||||
 | 
			
		||||
	ptr += 2;
 | 
			
		||||
 | 
			
		||||
	if (is_ext_ua(pdp)) {
 | 
			
		||||
		pdp_len = *ptr;
 | 
			
		||||
		if (pdp_len != 16) {
 | 
			
		||||
			LOGP(DLGTP, LOGL_ERROR,
 | 
			
		||||
				"PDP Context Decode: Failed with invalid PDP address length for IPv6 (%d != 16) in the second PDP address field", pdp_len);
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pdp->eua.l += pdp_len;
 | 
			
		||||
		memcpy(&pdp->eua.v[6], ptr, pdp_len);
 | 
			
		||||
		ptr += pdp_len;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ptr == end)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Encode a PDP Ctx into a buffer from a struct pdp_t
 | 
			
		||||
 *
 | 
			
		||||
 * @param buf output buffer
 | 
			
		||||
 * @param size size of buf
 | 
			
		||||
 * @param pdp  to be encoded PDP context
 | 
			
		||||
 * @param sapi to be encoded sapi
 | 
			
		||||
 * @return on error < 0, on success the used bytes in buf
 | 
			
		||||
 */
 | 
			
		||||
int gtp_encode_pdp_ctx(uint8_t *buf, unsigned int size, const struct pdp_t *pdp, uint16_t sapi)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t tmp32;
 | 
			
		||||
	uint16_t tmp16;
 | 
			
		||||
	uint8_t *ptr = buf;
 | 
			
		||||
#define CHECK_SPACE_ERR(bytes) do { \
 | 
			
		||||
		if ((ptr - buf) + (bytes) > size) { \
 | 
			
		||||
			LOGP(DLGTP, LOGL_ERROR, "PDP encode: Failed size check: %lu + %lu > %lu\n", (ptr - buf),  (unsigned long) (bytes), (unsigned long) size); \
 | 
			
		||||
			return -1; \
 | 
			
		||||
		} \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
#define MEMCPY_CHK(dst, src, len) do { \
 | 
			
		||||
		CHECK_SPACE_ERR((len)); \
 | 
			
		||||
		memcpy((dst), (uint8_t *)(src), (len)); \
 | 
			
		||||
		(dst) += (len); \
 | 
			
		||||
	} while (0)
 | 
			
		||||
 | 
			
		||||
	/* Flags - FIXME: No ASI */
 | 
			
		||||
	*ptr++ = (is_ext_ua(pdp) << 7) | ((!!pdp->vplmn_allow) << 6) | ((!!pdp->reorder) << 4) | (pdp->nsapi & 0x0f);
 | 
			
		||||
	/* SAPI */
 | 
			
		||||
	*ptr++ = sapi & 0x0f;
 | 
			
		||||
 | 
			
		||||
	/* QoS Sub */
 | 
			
		||||
	if (pdp->qos_sub.l < 4) {
 | 
			
		||||
		/* Work around qos_sub never being set */
 | 
			
		||||
		*ptr++ = 4;
 | 
			
		||||
		*ptr++ = 0;
 | 
			
		||||
		*ptr++ = 0x23;
 | 
			
		||||
		*ptr++ = 0x02;
 | 
			
		||||
		*ptr++ = 0x00;
 | 
			
		||||
	} else {
 | 
			
		||||
		*ptr++ = pdp->qos_sub.l;
 | 
			
		||||
		MEMCPY_CHK(ptr, pdp->qos_sub.v, pdp->qos_sub.l);
 | 
			
		||||
	}
 | 
			
		||||
	/* QoS Req */
 | 
			
		||||
	*ptr++ = pdp->qos_req.l;
 | 
			
		||||
	MEMCPY_CHK(ptr, pdp->qos_req.v, pdp->qos_req.l);
 | 
			
		||||
	/* QoS Neg  */
 | 
			
		||||
	*ptr++ = pdp->qos_neg.l;
 | 
			
		||||
	MEMCPY_CHK(ptr, pdp->qos_neg.v, pdp->qos_neg.l);
 | 
			
		||||
 | 
			
		||||
	/* SND */
 | 
			
		||||
	tmp16 = osmo_htons(pdp->pdcpsndd);
 | 
			
		||||
	MEMCPY_CHK(ptr, &tmp16, sizeof(tmp16));
 | 
			
		||||
	/* SNU */
 | 
			
		||||
	tmp16 = osmo_htons(pdp->pdcpsndu);
 | 
			
		||||
	MEMCPY_CHK(ptr, &tmp16, sizeof(tmp16));
 | 
			
		||||
	/* Send N-PDU */
 | 
			
		||||
	*ptr++ = pdp->gtpsntx;
 | 
			
		||||
	/* Recv N-PDU */
 | 
			
		||||
	*ptr++ = pdp->gtpsnrx;
 | 
			
		||||
	/* Uplink TEIC */
 | 
			
		||||
	tmp32 = osmo_htonl(pdp->teic_gn);
 | 
			
		||||
	MEMCPY_CHK(ptr, &tmp32, sizeof(tmp32));
 | 
			
		||||
	/* Uplink TEIDI */
 | 
			
		||||
	tmp32 = osmo_htonl(pdp->teid_gn);
 | 
			
		||||
	MEMCPY_CHK(ptr, &tmp32, sizeof(tmp32));
 | 
			
		||||
	/* PDP Ctx Id */
 | 
			
		||||
	*ptr++ = pdp->pdp_id;
 | 
			
		||||
	/* PDP Type Org */
 | 
			
		||||
	*ptr++ = PDP_EUA_ORG_IETF;
 | 
			
		||||
 | 
			
		||||
	/* PDP Type No. */
 | 
			
		||||
	/* PDP Address */
 | 
			
		||||
	switch (pdp->eua.v[1]) {
 | 
			
		||||
	case PDP_EUA_TYPE_v4:
 | 
			
		||||
	case PDP_EUA_TYPE_v4v6:
 | 
			
		||||
		/* v4v6 expects an IPv4 addr first followed by an IPv6 addr */
 | 
			
		||||
		*ptr++ = PDP_EUA_TYPE_v4;
 | 
			
		||||
		if (pdp->eua.l < 6)
 | 
			
		||||
			return -1;
 | 
			
		||||
		*ptr++ = 4;
 | 
			
		||||
		MEMCPY_CHK(ptr, &pdp->eua.v[2], 4);
 | 
			
		||||
		break;
 | 
			
		||||
	case PDP_EUA_TYPE_v6:
 | 
			
		||||
		*ptr++ = PDP_EUA_TYPE_v6;
 | 
			
		||||
		if (pdp->eua.l < 18)
 | 
			
		||||
			return -1;
 | 
			
		||||
		*ptr++ = 16;
 | 
			
		||||
		MEMCPY_CHK(ptr, &pdp->eua.v[2], 16);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
	/* GGSN Address Ctrl */
 | 
			
		||||
	*ptr++ = pdp->gsnrc.l;
 | 
			
		||||
	MEMCPY_CHK(ptr, pdp->gsnrc.v, pdp->gsnrc.l);
 | 
			
		||||
	/* GGSN Address User */
 | 
			
		||||
	*ptr++ = pdp->gsnru.l;
 | 
			
		||||
	MEMCPY_CHK(ptr, pdp->gsnru.v, pdp->gsnru.l);
 | 
			
		||||
	/* APN */
 | 
			
		||||
	*ptr++ = pdp->apn_use.l;
 | 
			
		||||
	MEMCPY_CHK(ptr, pdp->apn_use.v, pdp->apn_use.l);
 | 
			
		||||
	/* TransId */
 | 
			
		||||
	*ptr++ = (pdp->ti >> 8) & 0x0f;
 | 
			
		||||
	*ptr++ = pdp->ti & 0xff;
 | 
			
		||||
 | 
			
		||||
	if (is_ext_ua(pdp)) {
 | 
			
		||||
		*ptr++ = PDP_EUA_TYPE_v6;
 | 
			
		||||
		if (pdp->eua.l < 22)
 | 
			
		||||
			return -1;
 | 
			
		||||
		*ptr++ = 16;
 | 
			
		||||
		MEMCPY_CHK(ptr, &pdp->eua.v[6], 16);
 | 
			
		||||
	}
 | 
			
		||||
	return ptr - buf;
 | 
			
		||||
#undef CHECK_SPACE_ERR
 | 
			
		||||
#undef MEMCPY_CHK
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Handle an SGSN Context Response */
 | 
			
		||||
static int gtp_sgsn_context_resp_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
 | 
			
		||||
			void *pack, unsigned len)
 | 
			
		||||
{
 | 
			
		||||
	/** If cause is "Request accepted": Send ACK,
 | 
			
		||||
	    else: Log and handle error */
 | 
			
		||||
 | 
			
		||||
	/* Check if we have a context with TLLI/P-TMSI/IMSI in the RAI */
 | 
			
		||||
 | 
			
		||||
	void *cbp = NULL;
 | 
			
		||||
	uint8_t type = 0;
 | 
			
		||||
	uint8_t cause;
 | 
			
		||||
	uint16_t seq;
 | 
			
		||||
	uint32_t local_teic;
 | 
			
		||||
	int hlen;
 | 
			
		||||
	union gtpie_member *ie[GTPIE_SIZE] = {};
 | 
			
		||||
	union gtp_packet *packet = (union gtp_packet *)pack;
 | 
			
		||||
 | 
			
		||||
	if (version != 1) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer,
 | 
			
		||||
			"Sent SGSN Context Response with wrong version (exp 1, rx %u)\n", version);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!(packet->flags & GTP1HDR_F_SEQ)) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		LOGP(DLGTP, LOGL_NOTICE,
 | 
			
		||||
		     "Sent SGSN Context Response without Sequence flag set. Flags 0x%x\n", packet->flags);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* the GTP code ensures the package contains a full GTPv1 header */
 | 
			
		||||
	seq = get_seq(pack);
 | 
			
		||||
	hlen = get_hlen(pack);
 | 
			
		||||
	local_teic = get_tei(pack);
 | 
			
		||||
 | 
			
		||||
	/* FIXME: retransmission need to be implemented:
 | 
			
		||||
	 * - Check if an Ack has been already transmitted (if so re-transmit) and return early
 | 
			
		||||
	 * - Check if a Req is in re-transmit queue, if, confirm it
 | 
			
		||||
	 */
 | 
			
		||||
	/* Remove packet from queue */
 | 
			
		||||
	if (gtp_conf(gsn, version, peer, pack, len, &type, &cbp))
 | 
			
		||||
		return EOF;
 | 
			
		||||
 | 
			
		||||
	/* Extract information elements into a pointer array */
 | 
			
		||||
	if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
 | 
			
		||||
			    "Sent SGSN Context Request with Invalid message format\n");
 | 
			
		||||
		/* FIXME: Error Indication */
 | 
			
		||||
		return EOF;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Extract cause value (mandatory) */
 | 
			
		||||
	if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_MISSING);
 | 
			
		||||
		GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
 | 
			
		||||
			    "Missing mandatory information field\n");
 | 
			
		||||
		/* FIXME: Error Indication */
 | 
			
		||||
		return EOF;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sgsn_ctx_req_fsm_rx_resp(gsn, peer,
 | 
			
		||||
					seq, local_teic,
 | 
			
		||||
					ie, GTPIE_SIZE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Handle an SGSN Context Acknowledge */
 | 
			
		||||
static int gtp_sgsn_context_ack_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
 | 
			
		||||
			void *pack, unsigned len)
 | 
			
		||||
{
 | 
			
		||||
	union gtpie_member *ie[GTPIE_SIZE] = {};
 | 
			
		||||
	uint16_t seq;
 | 
			
		||||
	uint32_t local_teic;
 | 
			
		||||
	const union gtp_packet *packet = (union gtp_packet *)pack;
 | 
			
		||||
 | 
			
		||||
	if (version != 1) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer,
 | 
			
		||||
			       "Sent SGSN Context Ack with wrong version (exp 1, rx %u)\n", version);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!(packet->flags & GTP1HDR_F_SEQ)) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		LOGP(DLGTP, LOGL_NOTICE,
 | 
			
		||||
		     "Sent SGSN Context Ack without Sequence flag set. Flags 0x%x\n", packet->flags);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* the GTP code ensures the package contains a full GTPv1 header */
 | 
			
		||||
	int hlen = get_hlen(pack);
 | 
			
		||||
 | 
			
		||||
	/* Decode information elements */
 | 
			
		||||
	if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) {
 | 
			
		||||
		rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID);
 | 
			
		||||
		GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
 | 
			
		||||
			   "Sent SGSN Context Ack with Invalid message format\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	seq = get_seq(pack);
 | 
			
		||||
	local_teic = get_tei(pack);
 | 
			
		||||
 | 
			
		||||
	return sgsn_ctx_req_fsm_rx_ack(gsn, peer,
 | 
			
		||||
					seq, local_teic,
 | 
			
		||||
					ie, GTPIE_SIZE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ***********************************************************
 | 
			
		||||
 * Conversion functions
 | 
			
		||||
 *************************************************************/
 | 
			
		||||
@@ -2949,6 +3613,15 @@ int gtp_decaps1c(struct gsn_t *gsn)
 | 
			
		||||
		case GTP_RAN_INFO_RELAY:
 | 
			
		||||
			gtp_ran_info_relay_ind(gsn, version, &peer, buffer, status);
 | 
			
		||||
			break;
 | 
			
		||||
		case GTP_SGSN_CONTEXT_REQ:
 | 
			
		||||
			gtp_sgsn_context_req_ind(gsn, version, &peer, buffer, status);
 | 
			
		||||
			break;
 | 
			
		||||
		case GTP_SGSN_CONTEXT_RSP:
 | 
			
		||||
			gtp_sgsn_context_resp_ind(gsn, version, &peer, buffer, status);
 | 
			
		||||
			break;
 | 
			
		||||
		case GTP_SGSN_CONTEXT_ACK:
 | 
			
		||||
			gtp_sgsn_context_ack_ind(gsn, version, &peer, buffer, status);
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_UNKNOWN);
 | 
			
		||||
			GTP_LOGPKG(LOGL_ERROR, &peer, buffer, status,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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/tdef.h>
 | 
			
		||||
#include <osmocom/core/rate_ctr.h>
 | 
			
		||||
#include <osmocom/gsm/gsm23003.h>
 | 
			
		||||
#include <osmocom/gsm/gsm48.h>
 | 
			
		||||
 | 
			
		||||
#include "gtpie.h"
 | 
			
		||||
#include "pdp.h"
 | 
			
		||||
 | 
			
		||||
#define GTP_MODE_GGSN 1
 | 
			
		||||
@@ -25,6 +28,8 @@
 | 
			
		||||
 | 
			
		||||
#define RESTART_FILE "gsn_restart"
 | 
			
		||||
 | 
			
		||||
struct sgsn_ctx_reqs;
 | 
			
		||||
 | 
			
		||||
extern struct osmo_tdef gtp_T_defs[];
 | 
			
		||||
 | 
			
		||||
/* ***********************************************************
 | 
			
		||||
@@ -108,12 +113,17 @@ struct gsn_t {
 | 
			
		||||
	int (*cb_recovery)(struct sockaddr_in *peer, uint8_t recovery);
 | 
			
		||||
	int (*cb_recovery2)(struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery);
 | 
			
		||||
	int (*cb_recovery3)(struct gsn_t *gsn, struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery);
 | 
			
		||||
	int (*cb_sgsn_context_request_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size);
 | 
			
		||||
	int (*cb_sgsn_context_response_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size);
 | 
			
		||||
	int (*cb_sgsn_context_ack_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size);
 | 
			
		||||
 | 
			
		||||
	/* Counters */
 | 
			
		||||
	struct rate_ctr_group *ctrg;
 | 
			
		||||
 | 
			
		||||
	/* Timers: */
 | 
			
		||||
	struct osmo_tdef *tdef;
 | 
			
		||||
 | 
			
		||||
	struct sgsn_ctx_reqs *sgsn_ctx;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* External API functions */
 | 
			
		||||
@@ -152,6 +162,15 @@ extern int gtp_set_cb_extheader_ind(struct gsn_t *gsn,
 | 
			
		||||
extern int gtp_set_cb_ran_info_relay_ind(struct gsn_t *gsn,
 | 
			
		||||
				    int (*cb)(struct sockaddr_in *peer, union gtpie_member **ie));
 | 
			
		||||
 | 
			
		||||
extern int gtp_set_cb_sgsn_context_request_ind(struct gsn_t *gsn,
 | 
			
		||||
	int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size));
 | 
			
		||||
 | 
			
		||||
extern int gtp_set_cb_sgsn_context_response_ind(struct gsn_t *gsn,
 | 
			
		||||
	int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size));
 | 
			
		||||
 | 
			
		||||
extern int gtp_set_cb_sgsn_context_ack_ind(struct gsn_t *gsn,
 | 
			
		||||
	int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size));
 | 
			
		||||
 | 
			
		||||
extern int gtp_set_cb_conf(struct gsn_t *gsn,
 | 
			
		||||
			   int (*cb)(int type, int cause, struct pdp_t *pdp,
 | 
			
		||||
				      void *cbp));
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
				  uint8_t rim_route_addr_discr);
 | 
			
		||||
 | 
			
		||||
/* Tx a SGSN Context Request */
 | 
			
		||||
extern int gtp_sgsn_context_req(struct gsn_t *gsn, uint32_t *local_ref,
 | 
			
		||||
				const struct sockaddr_in *peer, union gtpie_member **ie);
 | 
			
		||||
 | 
			
		||||
/* Tx a SGSN Context Response */
 | 
			
		||||
extern int gtp_sgsn_context_resp(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
				 union gtpie_member **ie);
 | 
			
		||||
 | 
			
		||||
/* Tx a SGSN Context Response, simplified when returning an error */
 | 
			
		||||
int gtp_sgsn_context_resp_error(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
				uint8_t cause);
 | 
			
		||||
 | 
			
		||||
/* Tx a SGSN Context Ack */
 | 
			
		||||
extern int gtp_sgsn_context_ack(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
				union gtpie_member **ie);
 | 
			
		||||
 | 
			
		||||
/* Tx a SGSN Context Ack, simplified when returning an error */
 | 
			
		||||
int gtp_sgsn_context_ack_error(struct gsn_t *gsn, uint32_t local_ref,
 | 
			
		||||
				uint8_t cause);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
extern int gtp_decaps0(struct gsn_t *gsn);
 | 
			
		||||
extern int gtp_decaps1c(struct gsn_t *gsn);
 | 
			
		||||
extern int gtp_decaps1u(struct gsn_t *gsn);
 | 
			
		||||
@@ -279,6 +300,9 @@ extern int 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 gtp_encode_pdp_ctx(uint8_t *buf, unsigned int size, const struct pdp_t *pdp, uint16_t sapi);
 | 
			
		||||
extern int gtp_decode_pdp_ctx(const uint8_t *buf, unsigned int size, struct pdp_t *pdp, uint16_t *sapi);
 | 
			
		||||
 | 
			
		||||
extern const char *imsi_gtp2str(const uint64_t *imsi);
 | 
			
		||||
 | 
			
		||||
/*! Set the talloc context for internal objects */
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user