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(a)sysmocom.de>
+ * Author: Alexander Couzens <lynxis(a)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(a)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])
--
To view, visit
https://gerrit.osmocom.org/c/osmo-sgsn/+/37864?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: osmo-sgsn
Gerrit-Branch: master
Gerrit-Change-Id: I2474b19a7471a1dea3c863ddf8372b16180211aa
Gerrit-Change-Number: 37864
Gerrit-PatchSet: 18
Gerrit-Owner: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: daniel <dwillmann(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>