Add Routing Areas

Add a routing area layer which tracks routing area and
cells within a routing area.

Change-Id: I2474b19a7471a1dea3c863ddf8372b16180211aa
This commit is contained in:
Alexander Couzens
2024-08-04 19:52:16 +02:00
parent 6213201b95
commit e8c82d9cd1
17 changed files with 987 additions and 0 deletions

View File

@@ -243,6 +243,7 @@ AC_OUTPUT(
tests/Makefile
tests/atlocal
tests/gprs/Makefile
tests/gprs_routing_area/Makefile
tests/sgsn/Makefile
tests/gtphub/Makefile
tests/xid/Makefile

View File

@@ -14,6 +14,7 @@ noinst_HEADERS = \
gprs_llc.h \
gprs_llc_xid.h \
gprs_ranap.h \
gprs_routing_area.h \
gprs_sm.h \
gprs_sndcp_comp.h \
gprs_sndcp_dcomp.h \

View File

@@ -27,6 +27,7 @@ enum {
DGTP,
DOBJ,
DRIM,
DRA, /* Routing Area handling */
Debug_LastEntry,
};

View File

@@ -0,0 +1,88 @@
/*! \file gprs_routing_area.h */
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/gsm23003.h>
struct sgsn_instance;
struct sgsn_ra_global {
/* list of struct sgsn_ra */
struct llist_head ra_list;
};
struct sgsn_ra {
/* Entry in sgsn_ra_global->ra_list */
struct llist_head list;
struct osmo_routing_area_id rai;
/* cells contains a list of sgsn_ra_cells which are alive */
struct llist_head cells;
};
enum sgsn_ra_ran_type {
RA_TYPE_GERAN_Gb,
RA_TYPE_UTRAN_Iu,
};
struct sgsn_ra_cell {
/* Entry in sgsn_ra->cells */
struct llist_head list;
/*! link back to the parent */
struct sgsn_ra *ra;
enum sgsn_ra_ran_type ran_type;
uint16_t cell_id;
union {
struct {
uint16_t nsei;
uint16_t bvci;
} geran;
struct {
/* TODO: unused */
uint16_t rncid;
uint16_t sac;
} utran;
} u;
};
void sgsn_ra_init(struct sgsn_instance *inst);
struct sgsn_ra *sgsn_ra_alloc(const struct osmo_routing_area_id *rai);
void sgsn_ra_free(struct sgsn_ra *ra);
struct sgsn_ra_cell *sgsn_ra_cell_alloc_geran(struct sgsn_ra *ra, uint16_t cell_id, uint16_t nsei, uint16_t bvci);
void sgsn_ra_cell_free(struct sgsn_ra_cell *cell);
/* Called by BSSGP layer to inform about a reset on a BVCI */
int sgsn_ra_bvc_reset_ind(uint16_t nsei, uint16_t bvci, struct osmo_cell_global_id_ps *cgi_ps);
/* FIXME: handle BVC BLOCK/UNBLOCK/UNAVAILABLE */
/* Called by NS-VC layer to inform about an unavailable NSEI (and all BVCI on them) */
int sgsn_ra_nsei_failure_ind(uint16_t nsei);
struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi_ps(const struct osmo_cell_global_id_ps *cgi_ps);
struct sgsn_ra_cell *sgsn_ra_get_cell_by_lai(const struct osmo_location_area_id *lai, uint16_t cell_id);
struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi(const struct osmo_cell_global_id *cgi);
struct sgsn_ra_cell *sgsn_ra_get_cell_by_ra(const struct sgsn_ra *ra, uint16_t cell_id);
struct sgsn_ra_cell *sgsn_ra_get_cell_by_gb(uint16_t nsei, uint16_t bvci);
struct sgsn_ra *sgsn_ra_get_ra(const struct osmo_routing_area_id *ra_id);
/*
* return value for callbacks.
* STOP: stop calling the callback for the remaining cells, sgsn_ra_foreach_ra() returns 0
* CONT: continue to call the callback for remaining cells
* ABORT: stop calling the callback for the remaining cells, sgsn_ra_foreach_ra() returns -1
*/
#define SGSN_RA_CB_STOP 1
#define SGSN_RA_CB_CONT 0
#define SGSN_RA_CB_ERROR -1
typedef int (sgsn_ra_cb_t)(struct sgsn_ra_cell *ra_cell, void *cb_data);
int sgsn_ra_foreach_cell(struct sgsn_ra *ra, sgsn_ra_cb_t *cb, void *cb_data);
int sgsn_ra_foreach_cell2(struct osmo_routing_area_id *rai, sgsn_ra_cb_t *cb, void *cb_data);

View File

@@ -152,6 +152,9 @@ struct sgsn_instance {
ares_channel ares_channel;
struct ares_addr_node *ares_servers;
/* Routing areas */
struct sgsn_ra_global *routing_area;
struct rate_ctr_group *rate_ctrs;
struct llist_head apn_list; /* list of struct sgsn_apn_ctx */

View File

@@ -47,6 +47,7 @@ osmo_sgsn_SOURCES = \
gprs_gmm_fsm.c \
gprs_mm_state_gb_fsm.c \
gprs_ns.c \
gprs_routing_area.c \
gprs_sm.c \
gprs_sndcp.c \
gprs_sndcp_comp.c \

View File

@@ -20,6 +20,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/prim.h>
#include <osmocom/core/rate_ctr.h>
@@ -30,9 +31,26 @@
#include <osmocom/sgsn/gprs_llc.h>
#include <osmocom/sgsn/gprs_gmm.h>
#include <osmocom/sgsn/gprs_routing_area.h>
#include <osmocom/sgsn/sgsn_rim.h>
#include <osmocom/sgsn/mmctx.h>
#include <osmocom/sgsn/debug.h>
static int bssgp_nm_bvc_reset_ind(struct osmo_bssgp_prim *bp)
{
struct osmo_cell_global_id_ps cgi_ps = {};
if (!bp->tp)
return -EINVAL;
if (!TLVP_PRES_LEN(bp->tp, BSSGP_IE_CELL_ID, 8))
return -EINVAL;
bssgp_parse_cell_id2(&cgi_ps.rai, &cgi_ps.cell_identity, TLVP_VAL(bp->tp, BSSGP_IE_CELL_ID), 8);
return sgsn_ra_bvc_reset_ind(bp->nsei, bp->bvci, &cgi_ps);
}
/* call-back function for the BSSGP protocol */
int sgsn_bssgp_rx_prim(struct osmo_prim_hdr *oph)
{
@@ -58,6 +76,18 @@ int sgsn_bssgp_rx_prim(struct osmo_prim_hdr *oph)
}
break;
case SAP_BSSGP_NM:
switch (oph->primitive) {
case PRIM_NM_BVC_RESET:
if (oph->operation == PRIM_OP_INDICATION)
bssgp_nm_bvc_reset_ind(bp);
break;
case PRIM_NM_BVC_BLOCK:
case PRIM_NM_BVC_UNBLOCK:
case PRIM_NM_STATUS:
case PRIM_NM_LLC_DISCARDED:
break;
}
break;
case SAP_BSSGP_RIM:
return sgsn_rim_rx_from_gb(bp, oph->msg);

View File

@@ -28,6 +28,7 @@
#include <osmocom/gprs/gprs_ns2.h>
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/sgsn/gprs_llc.h>
#include <osmocom/sgsn/gprs_routing_area.h>
#include "config.h"
@@ -52,6 +53,7 @@ void gprs_ns_prim_status_cb(struct osmo_gprs_ns2_prim *nsp)
break;
case GPRS_NS2_AFF_CAUSE_FAILURE:
LOGP(DGPRS, LOGL_NOTICE, "NS-E %d became unavailable\n", nsp->nsei);
sgsn_ra_nsei_failure_ind(nsp->nsei);
break;
default:
LOGP(DGPRS, LOGL_NOTICE, "NS: %s Unknown prim %d from NS\n",

View File

@@ -0,0 +1,340 @@
/* SGSN Routing Area for 2G */
/* (C) 2024 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <lynxis@fe80.eu>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/logging.h>
#include <osmocom/sgsn/debug.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/sgsn/sgsn.h>
#include <osmocom/sgsn/gprs_routing_area.h>
static void _sgsn_ra_cell_free(struct sgsn_ra_cell *cell, bool drop_empty_ra)
{
struct sgsn_ra *ra;
if (!cell)
return;
llist_del(&cell->list);
/* to prevent double free of the Cell when freeing a Routing Area */
if (!drop_empty_ra) {
talloc_free(cell);
return;
}
ra = cell->ra;
talloc_free(cell);
if (llist_empty(&ra->cells))
sgsn_ra_free(ra);
}
void sgsn_ra_cell_free(struct sgsn_ra_cell *cell)
{
_sgsn_ra_cell_free(cell, true);
}
void sgsn_ra_free(struct sgsn_ra *ra)
{
struct sgsn_ra_cell *cell, *cell2;
if (!ra)
return;
llist_for_each_entry_safe(cell, cell2, &ra->cells, list) {
_sgsn_ra_cell_free(cell, false);
}
llist_del(&ra->list);
talloc_free(ra);
}
struct sgsn_ra *sgsn_ra_alloc(const struct osmo_routing_area_id *rai)
{
struct sgsn_ra *ra;
ra = talloc_zero(sgsn->routing_area, struct sgsn_ra);
if (!ra)
return NULL;
INIT_LLIST_HEAD(&ra->cells);
ra->rai = *rai;
llist_add(&ra->list, &sgsn->routing_area->ra_list);
return ra;
}
struct sgsn_ra_cell *sgsn_ra_cell_alloc_geran(struct sgsn_ra *ra, uint16_t cell_id, uint16_t nsei, uint16_t bvci)
{
struct sgsn_ra_cell *cell;
cell = talloc_zero(ra, struct sgsn_ra_cell);
if (!cell)
return NULL;
cell->ra = ra;
cell->cell_id = cell_id;
cell->ran_type = RA_TYPE_GERAN_Gb;
cell->u.geran.bvci = bvci;
cell->u.geran.nsei = nsei;
llist_add(&cell->list, &ra->cells);
return cell;
}
struct sgsn_ra *sgsn_ra_get_ra(const struct osmo_routing_area_id *ra_id)
{
struct sgsn_ra *ra;
llist_for_each_entry(ra, &sgsn->routing_area->ra_list, list)
if (osmo_rai_cmp(&ra->rai, ra_id) == 0)
return ra;
return NULL;
}
struct sgsn_ra_cell *sgsn_ra_get_cell_by_gb(uint16_t nsei, uint16_t bvci)
{
struct sgsn_ra *ra;
struct sgsn_ra_cell *cell;
/* BVCI = 0 is invalid, only valid for signalling within the BSSGP, not for a single cell */
if (bvci == 0)
return NULL;
llist_for_each_entry(ra, &sgsn->routing_area->ra_list, list) {
llist_for_each_entry(cell, &ra->cells, list) {
if (cell->ran_type != RA_TYPE_GERAN_Gb)
continue;
if (cell->u.geran.bvci == bvci && cell->u.geran.nsei == nsei)
return cell;
}
}
return NULL;
}
int sgsn_ra_foreach_cell(struct sgsn_ra *ra, sgsn_ra_cb_t *cb, void *cb_data)
{
struct sgsn_ra_cell *cell, *tmp;
int ret = -ENOENT;
OSMO_ASSERT(cb);
llist_for_each_entry_safe(cell, tmp, &ra->cells, list) {
ret = cb(cell, cb_data);
switch (ret) {
case SGSN_RA_CB_CONT:
continue;
case SGSN_RA_CB_STOP:
return 0;
case SGSN_RA_CB_ERROR:
return -1;
default:
OSMO_ASSERT(0);
}
}
return ret;
}
int sgsn_ra_foreach_cell2(struct osmo_routing_area_id *ra_id, sgsn_ra_cb_t *cb, void *cb_data)
{
struct sgsn_ra *ra;
OSMO_ASSERT(ra_id);
OSMO_ASSERT(cb);
ra = sgsn_ra_get_ra(ra_id);
if (!ra)
return -ENOENT;
return sgsn_ra_foreach_cell(ra, cb, cb_data);
}
struct sgsn_ra_cell *sgsn_ra_get_cell_by_ra(const struct sgsn_ra *ra, uint16_t cell_id)
{
struct sgsn_ra_cell *cell;
llist_for_each_entry(cell, &ra->cells, list) {
if (cell->cell_id == cell_id)
return cell;
}
return NULL;
}
struct sgsn_ra_cell *sgsn_ra_get_cell_by_lai(const struct osmo_location_area_id *lai, uint16_t cell_id)
{
struct sgsn_ra *ra;
struct sgsn_ra_cell *cell;
/* This is a little bit in-efficient. A more performance way, but more complex would
* adding a llist for LAC on top of the routing areas */
llist_for_each_entry(ra, &sgsn->routing_area->ra_list, list) {
if (osmo_lai_cmp(&ra->rai.lac, lai) != 0)
continue;
llist_for_each_entry(cell, &ra->cells, list) {
if (cell->cell_id == cell_id)
return cell;
}
}
return NULL;
}
/*! Return the cell by searching for the RA, when found, search the cell within the RA
*
* \param cgi_ps
* \return the cell or NULL if not found
*/
struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi_ps(const struct osmo_cell_global_id_ps *cgi_ps)
{
struct sgsn_ra *ra;
OSMO_ASSERT(cgi_ps);
ra = sgsn_ra_get_ra(&cgi_ps->rai);
if (!ra)
return NULL;
return sgsn_ra_get_cell_by_ra(ra, cgi_ps->cell_identity);
}
struct sgsn_ra_cell *sgsn_ra_get_cell_by_cgi(const struct osmo_cell_global_id *cgi)
{
OSMO_ASSERT(cgi);
return sgsn_ra_get_cell_by_lai(&cgi->lai, cgi->cell_identity);
}
/*! Callback from the BSSGP layer on NM RESET IND
*
* \param nsei
* \param bvci
* \param cgi_ps
* \return 0 on success or -ENOMEM
*/
int sgsn_ra_bvc_reset_ind(uint16_t nsei, uint16_t bvci, struct osmo_cell_global_id_ps *cgi_ps)
{
struct sgsn_ra *ra;
struct sgsn_ra_cell *cell;
bool ra_created = false;
OSMO_ASSERT(cgi_ps);
/* TODO: do we have to move all MS to GMM IDLE state when this happens for a alive cell which got reseted? */
ra = sgsn_ra_get_ra(&cgi_ps->rai);
if (!ra) {
ra = sgsn_ra_alloc(&cgi_ps->rai);
if (!ra)
return -ENOMEM;
ra_created = true;
}
if (!ra_created) {
cell = sgsn_ra_get_cell_by_ra(ra, cgi_ps->cell_identity);
if (cell && cell->ran_type == RA_TYPE_GERAN_Gb) {
/* Cell already exist, update NSEI/BVCI */
if (cell->u.geran.bvci != bvci || cell->u.geran.nsei != nsei) {
LOGP(DRA, LOGL_INFO, "GERAN Cell changed DLCI. Old: nsei/bvci %05u/%05u New: nsei/bvci %05u/%05u\n",
cell->u.geran.nsei, cell->u.geran.bvci, nsei, bvci);
cell->u.geran.bvci = bvci;
cell->u.geran.nsei = nsei;
}
return 0;
}
if (cell && cell->ran_type != RA_TYPE_GERAN_Gb) {
/* How can we have here a RA change? Must be a configuration error. */
LOGP(DRA, LOGL_INFO, "CGI %s: RAN change detected to GERAN!", osmo_cgi_ps_name(cgi_ps));
_sgsn_ra_cell_free(cell, false);
cell = NULL;
}
if (!cell) {
char old_ra[32];
char new_ra[32];
/* check for the same cell id within the location area. The cell id is also unique for the cell within the LAC
* This should only happen when a Cell is changing routing areas */
cell = sgsn_ra_get_cell_by_lai(&cgi_ps->rai.lac, cgi_ps->cell_identity);
if (cell) {
LOGP(DRA, LOGL_INFO, "CGI %s: changed Routing Area. Old: %s, New: %s\n",
osmo_cgi_ps_name(cgi_ps),
osmo_rai_name2_buf(old_ra, sizeof(old_ra), &cell->ra->rai),
osmo_rai_name2_buf(new_ra, sizeof(new_ra), &cgi_ps->rai));
OSMO_ASSERT(cell->ra != ra);
/* the old RA is definitive not our ra! Drop the old ra */
_sgsn_ra_cell_free(cell, true);
cell = NULL;
}
}
}
cell = sgsn_ra_cell_alloc_geran(ra, cgi_ps->cell_identity, nsei, bvci);
if (!cell)
return -ENOMEM;
LOGP(DRA, LOGL_INFO, "New cell registered %s via nsei/bvci %05u/%05u\n", osmo_cgi_ps_name(cgi_ps), nsei, bvci);
return 0;
}
/* FIXME: call it on BSSGP BLOCK + unavailable with BVCI */
int sgsn_ra_nsei_failure_ind(uint16_t nsei)
{
struct sgsn_ra *ra, *ra2;
struct sgsn_ra_cell *cell, *cell2;
bool found = false;
llist_for_each_entry_safe(ra, ra2, &sgsn->routing_area->ra_list, list) {
llist_for_each_entry_safe(cell, cell2, &ra->cells, list) {
if (cell->ran_type != RA_TYPE_GERAN_Gb)
continue;
if (cell->u.geran.nsei == nsei) {
found = true;
_sgsn_ra_cell_free(cell, false);
}
}
if (llist_empty(&ra->cells))
sgsn_ra_free(ra);
}
return found ? 0 : -ENOENT;
}
void sgsn_ra_init(struct sgsn_instance *inst)
{
inst->routing_area = talloc_zero(inst, struct sgsn_ra_global);
OSMO_ASSERT(inst->routing_area);
INIT_LLIST_HEAD(&inst->routing_area->ra_list);
}

View File

@@ -55,6 +55,7 @@
#include <osmocom/sgsn/gtp_ggsn.h>
#include <osmocom/sgsn/gtp.h>
#include <osmocom/sgsn/pdpctx.h>
#include <osmocom/sgsn/gprs_routing_area.h>
#include <pdp.h>
@@ -189,6 +190,7 @@ struct sgsn_instance *sgsn_instance_alloc(void *talloc_ctx)
/* These are mostly setting up stuff not related to VTY cfg, so they can be set up here: */
sgsn_auth_init(inst);
sgsn_cdr_init(inst);
sgsn_ra_init(inst);
return inst;
}

View File

@@ -64,6 +64,7 @@
#include <osmocom/sgsn/gprs_ranap.h>
#include <osmocom/sgsn/gprs_ns.h>
#include <osmocom/sgsn/gprs_bssgp.h>
#include <osmocom/sgsn/gprs_routing_area.h>
#include <osmocom/sgsn/gprs_subscriber.h>
#include <osmocom/sgsn/gtp.h>
@@ -348,6 +349,11 @@ static struct log_info_cat gprs_categories[] = {
.description = "RAN Information Management (RIM)",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DRA] = {
.name = "DRA",
.description = "Routing Area",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
};
static const struct log_info gprs_log_info = {

View File

@@ -1,6 +1,7 @@
SUBDIRS = \
gprs \
gtphub \
gprs_routing_area \
sgsn \
xid \
sndcp_xid \

View File

@@ -0,0 +1,93 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
-Wall \
-ggdb3 \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOCTRL_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOGSUPCLIENT_CFLAGS) \
$(LIBCARES_CFLAGS) \
$(LIBGTP_CFLAGS) \
$(NULL)
if BUILD_IU
AM_CFLAGS += \
$(LIBASN1C_CFLAGS) \
$(LIBOSMOSIGTRAN_CFLAGS) \
$(LIBOSMORANAP_CFLAGS) \
$(NULL)
endif
AM_LDFLAGS = -no-install
EXTRA_DIST = \
gprs_routing_area_test.ok \
$(NULL)
check_PROGRAMS = \
gprs_routing_area_test \
$(NULL)
gprs_routing_area_test_SOURCES = \
gprs_routing_area_test.c \
$(NULL)
gprs_routing_area_test_LDADD = \
$(top_builddir)/src/sgsn/apn.o \
$(top_builddir)/src/sgsn/gprs_bssgp.o \
$(top_builddir)/src/sgsn/gprs_llc.o \
$(top_builddir)/src/sgsn/gprs_ns.o \
$(top_builddir)/src/sgsn/gprs_sndcp.o \
$(top_builddir)/src/sgsn/gprs_gmm_attach.o \
$(top_builddir)/src/sgsn/gprs_gmm.o \
$(top_builddir)/src/sgsn/gprs_gmm_fsm.o \
$(top_builddir)/src/sgsn/gprs_mm_state_gb_fsm.o \
$(top_builddir)/src/sgsn/gprs_routing_area.o \
$(top_builddir)/src/sgsn/gtp_ggsn.o \
$(top_builddir)/src/sgsn/gtp_mme.o \
$(top_builddir)/src/sgsn/mmctx.o \
$(top_builddir)/src/sgsn/pdpctx.o \
$(top_builddir)/src/sgsn/sgsn.o \
$(top_builddir)/src/sgsn/sgsn_cdr.o \
$(top_builddir)/src/sgsn/sgsn_ctrl.o \
$(top_builddir)/src/sgsn/sgsn_vty.o \
$(top_builddir)/src/sgsn/sgsn_libgtp.o \
$(top_builddir)/src/sgsn/sgsn_auth.o \
$(top_builddir)/src/sgsn/gprs_subscriber.o \
$(top_builddir)/src/sgsn/gprs_llc_xid.o \
$(top_builddir)/src/sgsn/gprs_sndcp_xid.o \
$(top_builddir)/src/sgsn/slhc.o \
$(top_builddir)/src/sgsn/gprs_sm.o \
$(top_builddir)/src/sgsn/gprs_sndcp_comp.o \
$(top_builddir)/src/sgsn/gprs_sndcp_pcomp.o \
$(top_builddir)/src/sgsn/v42bis.o \
$(top_builddir)/src/sgsn/gprs_sndcp_dcomp.o \
$(top_builddir)/src/sgsn/sgsn_rim.o \
$(top_builddir)/src/gprs/gprs_utils.o \
$(top_builddir)/src/gprs/gprs_llc_parse.o \
$(top_builddir)/src/gprs/crc24.o \
$(top_builddir)/src/gprs/sgsn_ares.o \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOGB_LIBS) \
$(LIBOSMOGSUPCLIENT_LIBS) \
$(LIBCARES_LIBS) \
$(LIBGTP_LIBS) \
-lrt \
-lm \
$(NULL)
if BUILD_IU
gprs_routing_area_test_LDADD += \
$(top_builddir)/src/sgsn/gprs_ranap.o \
$(top_builddir)/src/sgsn/gprs_mm_state_iu_fsm.o \
$(LIBOSMORANAP_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \
$(LIBASN1C_LIBS) \
$(NULL)
endif

View File

@@ -0,0 +1,404 @@
/* Test the SGSN routing ares */
/*
* (C) 2024 by sysmocom s.f.m.c. GmbH
* All Rights Reserved
* Author: Alexander Couzens <lynxis@fe80.eu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/application.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gprs/gprs_bssgp.h>
#include <osmocom/vty/vty.h>
#include <osmocom/gsupclient/gsup_client.h>
#include <osmocom/sgsn/gprs_llc.h>
#include <osmocom/sgsn/mmctx.h>
#include <osmocom/sgsn/sgsn.h>
#include <osmocom/sgsn/gprs_gmm.h>
#include <osmocom/sgsn/debug.h>
#include <osmocom/sgsn/gprs_routing_area.h>
#include <stdio.h>
void *tall_sgsn_ctx;
struct sgsn_instance *sgsn;
static void cleanup_test(void)
{
TALLOC_FREE(sgsn);
}
/* Create RA, free RA */
static void test_routing_area_create(void)
{
struct sgsn_ra *ra;
struct osmo_routing_area_id raid = {
.lac = {
.plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false },
.lac = 23
},
.rac = 42
};
printf("Testing Routing Area create/free\n");
sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
ra = sgsn_ra_alloc(&raid);
OSMO_ASSERT(ra);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
sgsn_ra_free(ra);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
/* Cleanup */
cleanup_test();
}
static void test_routing_area_free_empty(void)
{
struct sgsn_ra *ra;
struct sgsn_ra_cell *cell_a;
struct osmo_routing_area_id raid = {
.lac = {
.plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false },
.lac = 24
},
.rac = 43
};
uint16_t cell_id = 9999;
uint16_t nsei = 2, bvci = 3;
printf("Testing Routing Area create/free\n");
sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
ra = sgsn_ra_alloc(&raid);
OSMO_ASSERT(ra);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
cell_a = sgsn_ra_cell_alloc_geran(ra, cell_id, nsei, bvci);
OSMO_ASSERT(cell_a);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
OSMO_ASSERT(llist_count(&ra->cells) == 1);
sgsn_ra_free(ra);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
ra = sgsn_ra_alloc(&raid);
OSMO_ASSERT(ra);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
cell_a = sgsn_ra_cell_alloc_geran(ra, cell_id, nsei, bvci);
OSMO_ASSERT(cell_a);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
OSMO_ASSERT(llist_count(&ra->cells) == 1);
sgsn_ra_free(ra);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
cleanup_test();
}
/* Create RA, use different find functiosn, free RA */
static void test_routing_area_find(void)
{
struct sgsn_ra *ra_a, *ra_b;
struct sgsn_ra_cell *cell_a, *cell_b;
struct osmo_routing_area_id ra_id = {
.lac = {
.plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false },
.lac = 24
},
.rac = 43
};
uint16_t cell_id = 9999, cell_id_not_found = 44;
struct osmo_cell_global_id_ps cgi_ps = {
.rai = ra_id,
.cell_identity = cell_id,
};
struct osmo_cell_global_id cgi = {
.lai = ra_id.lac,
.cell_identity = cell_id
};
uint16_t nsei = 2, bvci = 3;
printf("Testing Routing Area find\n");
sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
ra_a = sgsn_ra_alloc(&ra_id);
OSMO_ASSERT(ra_a);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
ra_b = sgsn_ra_get_ra(&ra_id);
OSMO_ASSERT(ra_a == ra_b);
cell_a = sgsn_ra_cell_alloc_geran(ra_a, cell_id, nsei, bvci);
OSMO_ASSERT(cell_a);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
cell_b = sgsn_ra_get_cell_by_cgi_ps(&cgi_ps);
OSMO_ASSERT(cell_b);
OSMO_ASSERT(cell_b == cell_a);
cell_b = sgsn_ra_get_cell_by_ra(ra_a, cgi.cell_identity);
OSMO_ASSERT(cell_b);
OSMO_ASSERT(cell_b == cell_a);
cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
OSMO_ASSERT(cell_b);
OSMO_ASSERT(cell_b == cell_a);
cell_b = sgsn_ra_get_cell_by_lai(&cgi.lai, cgi.cell_identity);
OSMO_ASSERT(cell_b);
OSMO_ASSERT(cell_b == cell_a);
sgsn_ra_free(ra_a);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
/* try to search for a cell id which isn't present */
cgi.cell_identity = cell_id_not_found;
cgi_ps.cell_identity = cell_id_not_found;
ra_a = sgsn_ra_alloc(&ra_id);
OSMO_ASSERT(ra_a);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
cell_a = sgsn_ra_cell_alloc_geran(ra_a, cell_id, nsei, bvci);
OSMO_ASSERT(cell_a);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
cell_b = sgsn_ra_get_cell_by_cgi_ps(&cgi_ps);
OSMO_ASSERT(!cell_b);
cell_b = sgsn_ra_get_cell_by_ra(ra_a, cgi_ps.cell_identity);
OSMO_ASSERT(!cell_b);
cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
OSMO_ASSERT(!cell_b);
cell_b = sgsn_ra_get_cell_by_lai(&cgi.lai, cgi.cell_identity);
OSMO_ASSERT(!cell_b);
/* try to find for a different RAC */
cgi_ps.rai.rac = 45;
ra_id.rac = 46;
cell_b = sgsn_ra_get_cell_by_cgi_ps(&cgi_ps);
OSMO_ASSERT(!cell_b);
ra_b = sgsn_ra_get_ra(&ra_id);
OSMO_ASSERT(!ra_b);
/* try to find for different LAC */
cgi.lai.lac = 46;
cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
OSMO_ASSERT(!cell_b);
sgsn_ra_free(ra_a);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
cleanup_test();
}
static void test_routing_area_reset_ind(void)
{
struct sgsn_ra *ra_a;
struct sgsn_ra_cell *cell_a, *cell_b;
struct osmo_routing_area_id ra_id = {
.lac = {
.plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false },
.lac = 24
},
.rac = 43
};
uint16_t cell_id = 9999;
struct osmo_cell_global_id_ps cgi_ps = {
.rai = ra_id,
.cell_identity = cell_id,
};
struct osmo_cell_global_id cgi = {
.lai = ra_id.lac,
.cell_identity = cell_id
};
uint16_t nsei = 2, bvci = 3;
int rc;
printf("Testing Routing Area BSSGP BVC RESET IND\n");
sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
ra_a = sgsn_ra_alloc(&ra_id);
OSMO_ASSERT(ra_a);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
OSMO_ASSERT(llist_count(&ra_a->cells) == 0);
rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
OSMO_ASSERT(rc == 0);
OSMO_ASSERT(llist_count(&ra_a->cells) == 1);
cell_a = sgsn_ra_get_cell_by_cgi(&cgi);
OSMO_ASSERT(cell_a);
rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
OSMO_ASSERT(rc == 0);
cell_b = sgsn_ra_get_cell_by_cgi(&cgi);
OSMO_ASSERT(cell_b);
OSMO_ASSERT(cell_a == cell_b);
sgsn_ra_free(ra_a);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
OSMO_ASSERT(rc == 0);
OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1);
ra_a = sgsn_ra_get_ra(&cgi_ps.rai);
sgsn_ra_free(ra_a);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
cleanup_test();
}
void test_routing_area_nsei_free(void)
{
struct sgsn_ra *ra_a;
struct osmo_routing_area_id ra_id = {
.lac = {
.plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false },
.lac = 24
},
.rac = 43
};
uint16_t cell_id = 9999;
struct osmo_cell_global_id_ps cgi_ps = {
.rai = ra_id,
.cell_identity = cell_id,
};
uint16_t nsei = 2, bvci = 3;
int rc;
printf("Testing Routing Area nsei failure\n");
sgsn = sgsn_instance_alloc(tall_sgsn_ctx);
rc = sgsn_ra_bvc_reset_ind(nsei, bvci, &cgi_ps);
OSMO_ASSERT(rc == 0);
ra_a = sgsn_ra_get_ra(&cgi_ps.rai);
OSMO_ASSERT(llist_count(&ra_a->cells) == 1);
rc = sgsn_ra_nsei_failure_ind(nsei);
OSMO_ASSERT(rc == 0);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
rc = sgsn_ra_nsei_failure_ind(nsei);
OSMO_ASSERT(rc == -ENOENT);
OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list));
cleanup_test();
}
static struct log_info_cat gprs_categories[] = {
[DMM] = {
.name = "DMM",
.description = "Layer3 Mobility Management (MM)",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
[DPAG] = {
.name = "DPAG",
.description = "Paging Subsystem",
.color = "\033[1;38m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DREF] = {
.name = "DREF",
.description = "Reference Counting",
.enabled = 0, .loglevel = LOGL_NOTICE,
},
[DGPRS] = {
.name = "DGPRS",
.description = "GPRS Packet Service",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
[DLLC] = {
.name = "DLLC",
.description = "GPRS Logical Link Control Protocol (LLC)",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
[DRA] = {
.name = "DRA",
.description = "Routing Area",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
};
static struct log_info info = {
.cat = gprs_categories,
.num_cat = ARRAY_SIZE(gprs_categories),
};
static struct vty_app_info vty_info = {
.name = "testSGSN",
};
int main(int argc, char **argv)
{
void *osmo_sgsn_ctx;
void *msgb_ctx;
osmo_sgsn_ctx = talloc_named_const(NULL, 0, "osmo_sgsn");
osmo_init_logging2(osmo_sgsn_ctx, &info);
tall_sgsn_ctx = talloc_named_const(osmo_sgsn_ctx, 0, "sgsn");
msgb_ctx = msgb_talloc_ctx_init(osmo_sgsn_ctx, 0);
vty_init(&vty_info);
test_routing_area_create();
test_routing_area_find();
test_routing_area_free_empty();
test_routing_area_reset_ind();
test_routing_area_nsei_free();
printf("Done\n");
talloc_report_full(osmo_sgsn_ctx, stderr);
OSMO_ASSERT(talloc_total_blocks(msgb_ctx) == 1);
OSMO_ASSERT(talloc_total_blocks(tall_sgsn_ctx) == 1);
return 0;
}
/* stubs */
struct osmo_prim_hdr;
int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
{
abort();
}

View File

@@ -0,0 +1,6 @@
Testing Routing Area create/free
Testing Routing Area find
Testing Routing Area create/free
Testing Routing Area BSSGP BVC RESET IND
Testing Routing Area nsei failure
Done

View File

@@ -60,6 +60,7 @@ sgsn_test_LDADD = \
$(top_builddir)/src/sgsn/gprs_gmm.o \
$(top_builddir)/src/sgsn/gprs_gmm_fsm.o \
$(top_builddir)/src/sgsn/gprs_mm_state_gb_fsm.o \
$(top_builddir)/src/sgsn/gprs_routing_area.o \
$(top_builddir)/src/sgsn/gtp_ggsn.o \
$(top_builddir)/src/sgsn/gtp_mme.o \
$(top_builddir)/src/sgsn/mmctx.o \

View File

@@ -14,6 +14,13 @@ cat $abs_srcdir/sgsn/sgsn_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/sgsn/sgsn_test], [], [expout], [ignore])
AT_CLEANUP
AT_SETUP([gprs_routing_area])
AT_KEYWORDS([gprs_routing_area])
AT_CHECK([test "$enable_gprs_routing_area_test" != no || exit 77])
cat $abs_srcdir/gprs_routing_area/gprs_routing_area_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/gprs_routing_area/gprs_routing_area_test], [], [expout], [ignore])
AT_CLEANUP
AT_SETUP([gtphub])
AT_KEYWORDS([gtphub])
AT_CHECK([test "$enable_gtphub_test" != no || exit 77])