lynxis lazus has submitted this change. ( https://gerrit.osmocom.org/c/osmo-sgsn/+/37864?usp=email )
(
17 is the latest approved patch-set. No files were changed between the latest approved patch-set and the submitted one. )Change subject: Add Routing Areas ......................................................................
Add Routing Areas
Add a routing area layer which tracks routing area and cells within a routing area.
Change-Id: I2474b19a7471a1dea3c863ddf8372b16180211aa --- M configure.ac M include/osmocom/sgsn/Makefile.am M include/osmocom/sgsn/debug.h A include/osmocom/sgsn/gprs_routing_area.h M include/osmocom/sgsn/sgsn.h M src/sgsn/Makefile.am M src/sgsn/gprs_bssgp.c M src/sgsn/gprs_ns.c A src/sgsn/gprs_routing_area.c M src/sgsn/sgsn.c M src/sgsn/sgsn_main.c M tests/Makefile.am A tests/gprs_routing_area/Makefile.am A tests/gprs_routing_area/gprs_routing_area_test.c A tests/gprs_routing_area/gprs_routing_area_test.ok M tests/sgsn/Makefile.am M tests/testsuite.at 17 files changed, 987 insertions(+), 0 deletions(-)
Approvals: laforge: Looks good to me, approved pespin: Looks good to me, but someone else must approve daniel: Looks good to me, but someone else must approve Jenkins Builder: Verified
diff --git a/configure.ac b/configure.ac index 5286d2e..17a0236 100644 --- a/configure.ac +++ b/configure.ac @@ -243,6 +243,7 @@ tests/Makefile tests/atlocal tests/gprs/Makefile + tests/gprs_routing_area/Makefile tests/sgsn/Makefile tests/gtphub/Makefile tests/xid/Makefile diff --git a/include/osmocom/sgsn/Makefile.am b/include/osmocom/sgsn/Makefile.am index aa6cd0f..9f87fd8 100644 --- a/include/osmocom/sgsn/Makefile.am +++ b/include/osmocom/sgsn/Makefile.am @@ -14,6 +14,7 @@ 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 \ diff --git a/include/osmocom/sgsn/debug.h b/include/osmocom/sgsn/debug.h index 80c5d9f..04ba3c0 100644 --- a/include/osmocom/sgsn/debug.h +++ b/include/osmocom/sgsn/debug.h @@ -27,6 +27,7 @@ DGTP, DOBJ, DRIM, + DRA, /* Routing Area handling */ Debug_LastEntry, };
diff --git a/include/osmocom/sgsn/gprs_routing_area.h b/include/osmocom/sgsn/gprs_routing_area.h new file mode 100644 index 0000000..34e29cb --- /dev/null +++ b/include/osmocom/sgsn/gprs_routing_area.h @@ -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); diff --git a/include/osmocom/sgsn/sgsn.h b/include/osmocom/sgsn/sgsn.h index 9e09184..9190a61 100644 --- a/include/osmocom/sgsn/sgsn.h +++ b/include/osmocom/sgsn/sgsn.h @@ -152,6 +152,9 @@ 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 */ diff --git a/src/sgsn/Makefile.am b/src/sgsn/Makefile.am index 487dead..c363a48 100644 --- a/src/sgsn/Makefile.am +++ b/src/sgsn/Makefile.am @@ -47,6 +47,7 @@ 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 \ diff --git a/src/sgsn/gprs_bssgp.c b/src/sgsn/gprs_bssgp.c index 0a9bb91..fd82b15 100644 --- a/src/sgsn/gprs_bssgp.c +++ b/src/sgsn/gprs_bssgp.c @@ -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 @@ } 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); diff --git a/src/sgsn/gprs_ns.c b/src/sgsn/gprs_ns.c index eb447fa..3968128 100644 --- a/src/sgsn/gprs_ns.c +++ b/src/sgsn/gprs_ns.c @@ -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 @@ 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", diff --git a/src/sgsn/gprs_routing_area.c b/src/sgsn/gprs_routing_area.c new file mode 100644 index 0000000..977caa2 --- /dev/null +++ b/src/sgsn/gprs_routing_area.c @@ -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); +} diff --git a/src/sgsn/sgsn.c b/src/sgsn/sgsn.c index 57a2c38..1c637b3 100644 --- a/src/sgsn/sgsn.c +++ b/src/sgsn/sgsn.c @@ -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 @@ /* 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; }
diff --git a/src/sgsn/sgsn_main.c b/src/sgsn/sgsn_main.c index d6afdef..86c3561 100644 --- a/src/sgsn/sgsn_main.c +++ b/src/sgsn/sgsn_main.c @@ -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 @@ .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 = { diff --git a/tests/Makefile.am b/tests/Makefile.am index fc5af0b..9d429d7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,7 @@ SUBDIRS = \ gprs \ gtphub \ + gprs_routing_area \ sgsn \ xid \ sndcp_xid \ diff --git a/tests/gprs_routing_area/Makefile.am b/tests/gprs_routing_area/Makefile.am new file mode 100644 index 0000000..a61f866 --- /dev/null +++ b/tests/gprs_routing_area/Makefile.am @@ -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 diff --git a/tests/gprs_routing_area/gprs_routing_area_test.c b/tests/gprs_routing_area/gprs_routing_area_test.c new file mode 100644 index 0000000..d879f39 --- /dev/null +++ b/tests/gprs_routing_area/gprs_routing_area_test.c @@ -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(); +} diff --git a/tests/gprs_routing_area/gprs_routing_area_test.ok b/tests/gprs_routing_area/gprs_routing_area_test.ok new file mode 100644 index 0000000..dccbb5e --- /dev/null +++ b/tests/gprs_routing_area/gprs_routing_area_test.ok @@ -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 diff --git a/tests/sgsn/Makefile.am b/tests/sgsn/Makefile.am index 9cffdfd..db40f99 100644 --- a/tests/sgsn/Makefile.am +++ b/tests/sgsn/Makefile.am @@ -60,6 +60,7 @@ $(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 \ diff --git a/tests/testsuite.at b/tests/testsuite.at index 637d853..242ab26 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -14,6 +14,13 @@ 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])