Change in osmo-sgsn[master]: Support forwarding RIM messages over GTPCv1 EUTRAN<->GERAN

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

pespin gerrit-no-reply at lists.osmocom.org
Wed May 19 11:37:15 UTC 2021


pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-sgsn/+/24164 )

Change subject: Support forwarding RIM messages over GTPCv1 EUTRAN<->GERAN
......................................................................

Support forwarding RIM messages over GTPCv1 EUTRAN<->GERAN

MMEs connect over Gn interface using GTPCv1 towards the SGSN in order to
exchange RIM PDUs by using "RAN Information Relay" GTPCv1 message type.
For more info, see 3GPP TS 29.060 sec 7.5.14.1 "RAN Information Relay"

In order to support it, this commit does the following:

* Uses new libgtp APIs to rx and tx RAN Information Relay messages. The
  same "gsn" object is reused, ie. the local GTPCv1 socket address used
  for exchanging messages against GGSN is reused.
* Adds a new "sgsn_mme_ctx" struct holding information about MMEs
  allowed by the SGSN, each one containing information about the GTP
  address it uses, the in/out routing based on TAI requests, etc. The
  set of MMEs and their config can be set up using new VTY node introduced
  in this commit.
* The RIM related code in SGSN is refactored to allow forwarding from
  and to several types of addresses/interfaces.

Depends: osmo-ggsn.git Change-Id Iea3eb032ccd4aed5187baca7f7719349d76039d4
Depends: libosmocore.git Change-Id I534db7d8bc5ceb19a2a6866f07d5f5c70e456c5c
Related: SYS#5314
Change-Id: I396450b8d8b66595dab8ff7bf41cbf964bb40d93
---
M TODO-RELEASE
M doc/manuals/chapters/configuration.adoc
M include/osmocom/sgsn/Makefile.am
A include/osmocom/sgsn/gtp_mme.h
M include/osmocom/sgsn/sgsn.h
M include/osmocom/sgsn/sgsn_rim.h
M include/osmocom/sgsn/vty.h
M src/sgsn/Makefile.am
M src/sgsn/gprs_sgsn.c
A src/sgsn/gtp_mme.c
M src/sgsn/sgsn_libgtp.c
M src/sgsn/sgsn_main.c
M src/sgsn/sgsn_rim.c
M src/sgsn/sgsn_vty.c
M tests/osmo-sgsn_test-nodes.vty
M tests/sgsn/Makefile.am
16 files changed, 696 insertions(+), 39 deletions(-)

Approvals:
  Jenkins Builder: Verified
  osmith: Looks good to me, but someone else must approve
  pespin: Looks good to me, approved



diff --git a/TODO-RELEASE b/TODO-RELEASE
index 1c5d61f..44ee421 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -1 +1,3 @@
 #component	what		description / commit summary line
+libosmogb > 1.5.1       bssgp_encode_rim_pdu    symbol was not exported previously
+libgtp > 1.7.1          use gtp_ran_info_relay_req(), gtp_set_cb_ran_info_relay_ind()
diff --git a/doc/manuals/chapters/configuration.adoc b/doc/manuals/chapters/configuration.adoc
index 7d3072e..c31761c 100644
--- a/doc/manuals/chapters/configuration.adoc
+++ b/doc/manuals/chapters/configuration.adoc
@@ -11,7 +11,8 @@
 administrator only has to ensure that the NS and BSSGP layer identities
 (NSEI, NSVCI, BVCI) are unique for each PCU connecting to the SGSN.
 
-=== Configuring the Gp interface
+[[gp-if-ggsn]]
+=== Configuring the Gp interface (towards GGSN)
 
 The Gp interface is the GTP-C and GTP-U based interface between the SGSN
 and the GGSNs.  It is implemented via UDP on well-known source and
@@ -67,6 +68,58 @@
 <2> Enable the dynamic GGSN resolving mode
 <3> Specify the IP address of a DNS server for APN resolution
 
+[[gp-if-mme]]
+=== Configuring the Gp interface (towards MME)
+
+The Gp interface also contains the GTP-C v1 based interface between the SGSN
+and the MMEs. This interface between SGSN and MMEs is used to transfer _RAN
+Information Relay_ GTP-C messages between them, which are used as containers to
+allow PCUs under the SGSN and eNodeBs under MMEs to  exchange cell information
+(RIM).
+
+In the SGSN, this interface re-uses the same socket local configuration as per
+the GGSN connections (see _gtp local-ip_ VTY command in <<gp-if-ggsn>>).
+
+Similarly as with GGSNs, (again see <<gp-if-ggsn>>), selection of destination
+peers for the _RAN Information Relay_ message can be configured statically or
+dynamically over GRX.
+
+
+==== Static MME/TAI configuration
+
+In this mode, there is a static list of MMEs and TAIs configured in
+OsmoSGSN via the VTY / config file. One MME in the list can be configured as the
+_default route_, where all unspecified TAIs are routed too.
+
+This is a non-standard method outside of the 3GPP specifications for the
+SGSN, and is typically only used in private/small GPRS networks without
+any access to a GRX.
+
+.Example: Static MME/TAI configuration (single catch-all GGSN)
+----
+sgsn
+...
+ gtp local-ip 192.168.0.10 <1>
+ mme test-mme0 <2>
+  gtp remote-ip 192.168.0.20 <3>
+  gtp ran-info-relay 262 42 3 <4>
+  gtp ran-info-relay 262 42 4
+ mme test-mme1 <5>
+  gtp remote-ip 192.168.0.30
+  gtp ran-info-relay default  <6>
+----
+<1> Configure the local IP address at the SGSN used for Gp/GTP
+<2> Configure an MME named "test-mme0"
+<3> Specify the remote IP address of the MME (for MME "test-mme0")
+<4> Route specified TAIs towards this MME
+<5> Configure an MME named "test-mme1"
+<6> Route all TAIs with an unspecified MME towards MM "test-mme1"
+
+==== Dynamic MME/TAI configuration
+
+Dynamic MME/TAI peer look up over GRX is not yet supported by OsmoSGSN.
+
+
 [[auth-pol]]
 === Authorization Policy
 
diff --git a/include/osmocom/sgsn/Makefile.am b/include/osmocom/sgsn/Makefile.am
index 51bdee8..289f502 100644
--- a/include/osmocom/sgsn/Makefile.am
+++ b/include/osmocom/sgsn/Makefile.am
@@ -22,6 +22,7 @@
 	gprs_subscriber.h \
 	gprs_utils.h \
 	gtphub.h \
+	gtp_mme.h \
 	sgsn.h \
 	sgsn_rim.h \
 	signal.h \
diff --git a/include/osmocom/sgsn/gtp_mme.h b/include/osmocom/sgsn/gtp_mme.h
new file mode 100644
index 0000000..ceea405
--- /dev/null
+++ b/include/osmocom/sgsn/gtp_mme.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gprs/protocol/gsm_24_301.h>
+
+struct gsn_t;
+
+struct mme_rim_route {
+	struct llist_head list; /* item in struct sgsn_mme_ctx */
+	struct osmo_eutran_tai tai;
+};
+
+struct sgsn_mme_ctx {
+	struct llist_head list; /* item in sgsn_mme_ctxts */
+	struct llist_head routes; /* list of struct mme_rim_route */
+	struct sgsn_instance *sgsn; /* backpointer */
+	char *name;
+	struct in_addr remote_addr;
+
+	/* is it the default route for outgoing message? are all incoming messages accepted? */
+	bool default_route;
+};
+struct sgsn_mme_ctx *sgsn_mme_ctx_alloc(struct sgsn_instance *sgsn, const char *name);
+struct sgsn_mme_ctx *sgsn_mme_ctx_find_alloc(struct sgsn_instance *sgsn, const char *name);
+void sgsn_mme_ctx_free(struct sgsn_mme_ctx *mme);
+
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_name(const struct sgsn_instance *sgsn, const char *name);
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_addr(const struct sgsn_instance *sgsn, const struct in_addr *addr);
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_route(const struct sgsn_instance *sgsn, const struct osmo_eutran_tai *tai);
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_default_route(const struct sgsn_instance *sgsn);
+
+void sgsn_mme_ctx_route_add(struct sgsn_mme_ctx *mme, const struct osmo_eutran_tai *tai);
+void sgsn_mme_ctx_route_del(struct sgsn_mme_ctx *mme, const struct osmo_eutran_tai *tai);
+
+#define LOGMME(mme, cat, level, fmt, args...) { \
+	char _buf[INET_ADDRSTRLEN]; \
+	LOGP(cat, level, "MME(%s:%s): " fmt, (mme)->name, inet_ntop(AF_INET, &(mme)->remote_addr, _buf, sizeof(_buf)), ## args); \
+	} while (0)
diff --git a/include/osmocom/sgsn/sgsn.h b/include/osmocom/sgsn/sgsn.h
index d9ef938..b686c7c 100644
--- a/include/osmocom/sgsn/sgsn.h
+++ b/include/osmocom/sgsn/sgsn.h
@@ -6,7 +6,10 @@
 #include <osmocom/core/select.h>
 #include <osmocom/crypt/gprs_cipher.h>
 #include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+
 #include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gtp_mme.h>
 #include <osmocom/gsm/oap_client.h>
 #include <osmocom/gsupclient/gsup_client.h>
 #include <osmocom/sgsn/common.h>
@@ -145,6 +148,8 @@
 	struct ares_addr_node *ares_servers;
 
 	struct rate_ctr_group *rate_ctrs;
+
+	struct llist_head mme_list; /* list of struct sgsn_mme_ctx */
 };
 
 extern struct sgsn_instance *sgsn;
@@ -169,6 +174,7 @@
 void sgsn_pdp_upd_gtp_u(struct sgsn_pdp_ctx *pdp, void *addr, size_t alen);
 void sgsn_ggsn_echo_req(struct sgsn_ggsn_ctx *ggc);
 int send_act_pdp_cont_acc(struct sgsn_pdp_ctx *pctx);
+int sgsn_mme_ran_info_req(struct sgsn_mme_ctx *mme, const struct bssgp_ran_information_pdu *pdu);
 
 /* gprs_sndcp.c */
 
diff --git a/include/osmocom/sgsn/sgsn_rim.h b/include/osmocom/sgsn/sgsn_rim.h
index ca3660b..aa5a726 100644
--- a/include/osmocom/sgsn/sgsn_rim.h
+++ b/include/osmocom/sgsn/sgsn_rim.h
@@ -1,3 +1,6 @@
 #pragma once
 
-int sgsn_rim_rx(struct osmo_bssgp_prim *bp, struct msgb *msg);
+struct sgsn_mme_ctx;
+
+int sgsn_rim_rx_from_gb(struct osmo_bssgp_prim *bp, struct msgb *msg);
+int sgsn_rim_rx_from_gtp(struct bssgp_ran_information_pdu *pdu, struct sgsn_mme_ctx *mme);
diff --git a/include/osmocom/sgsn/vty.h b/include/osmocom/sgsn/vty.h
index bd469ef..a273222 100644
--- a/include/osmocom/sgsn/vty.h
+++ b/include/osmocom/sgsn/vty.h
@@ -5,4 +5,5 @@
 enum bsc_vty_node {
 	SGSN_NODE = _LAST_OSMOVTY_NODE + 1,
 	GTPHUB_NODE,
+	MME_NODE,
 };
diff --git a/src/sgsn/Makefile.am b/src/sgsn/Makefile.am
index 8738986..9e4a342 100644
--- a/src/sgsn/Makefile.am
+++ b/src/sgsn/Makefile.am
@@ -53,6 +53,7 @@
 	gprs_sndcp_pcomp.c \
 	gprs_sndcp_vty.c \
 	gprs_sndcp_xid.c \
+	gtp_mme.c \
 	sgsn_main.c \
 	sgsn_vty.c \
 	sgsn_libgtp.c \
diff --git a/src/sgsn/gprs_sgsn.c b/src/sgsn/gprs_sgsn.c
index d4bc554..f744257 100644
--- a/src/sgsn/gprs_sgsn.c
+++ b/src/sgsn/gprs_sgsn.c
@@ -1042,6 +1042,8 @@
 	inst->cfg.auth_policy = SGSN_AUTH_POLICY_CLOSED;
 	inst->cfg.require_authentication = true; /* only applies if auth_policy is REMOTE */
 	inst->cfg.gsup_server_port = OSMO_GSUP_PORT;
+
+	INIT_LLIST_HEAD(&inst->mme_list);
 	return inst;
 }
 
diff --git a/src/sgsn/gtp_mme.c b/src/sgsn/gtp_mme.c
new file mode 100644
index 0000000..4fe804d
--- /dev/null
+++ b/src/sgsn/gtp_mme.c
@@ -0,0 +1,145 @@
+/* TS 29.060 § 7.5.14 RAN Information Management Messages */
+/*
+ * (C) 2021 by sysmocom - s.m.f.c. GmbH <info at sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Pau Espin Pedrol <pespin at sysmocom.de>
+ *
+ * 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 <talloc.h>
+
+#include <osmocom/sgsn/gtp_mme.h>
+#include <osmocom/sgsn/sgsn.h>
+
+extern void *tall_sgsn_ctx;
+
+static bool _eutran_tai_equal(const struct osmo_eutran_tai *t1, const struct osmo_eutran_tai *t2)
+{
+	return  t1->mcc == t2->mcc &&
+		t1->mnc == t2->mnc &&
+		t1->mnc_3_digits == t2->mnc_3_digits &&
+		t1->tac == t2->tac;
+}
+
+struct sgsn_mme_ctx *sgsn_mme_ctx_alloc(struct sgsn_instance *sgsn, const char *name)
+{
+	struct sgsn_mme_ctx *mme;
+	mme = talloc_zero(tall_sgsn_ctx, struct sgsn_mme_ctx);
+	if (!mme)
+		return NULL;
+
+	/* if we are called from config file parse, this gsn doesn't exist yet */
+	mme->sgsn = sgsn;
+
+	mme->name = talloc_strdup(mme, name);
+
+	INIT_LLIST_HEAD(&mme->routes);
+	llist_add_tail(&mme->list, &sgsn->mme_list);
+
+	return mme;
+}
+
+void sgsn_mme_ctx_free(struct sgsn_mme_ctx *mme)
+{
+	struct mme_rim_route *rt, *rt2;
+	llist_del(&mme->list);
+
+	llist_for_each_entry_safe(rt, rt2, &mme->routes, list) {
+		llist_del(&rt->list);
+		talloc_free(rt);
+	}
+
+	talloc_free(mme);
+}
+
+struct sgsn_mme_ctx *sgsn_mme_ctx_find_alloc(struct sgsn_instance *sgsn, const char *name)
+{
+	struct sgsn_mme_ctx *mme;
+
+	mme = sgsn_mme_ctx_by_name(sgsn, name);
+	if (!mme)
+		mme = sgsn_mme_ctx_alloc(sgsn, name);
+	return mme;
+}
+
+void sgsn_mme_ctx_route_add(struct sgsn_mme_ctx *mme, const struct osmo_eutran_tai *tai)
+{
+	struct mme_rim_route *rt = talloc_zero(mme, struct mme_rim_route);
+	rt->tai = *tai;
+	llist_add_tail(&rt->list, &mme->routes);
+}
+
+void sgsn_mme_ctx_route_del(struct sgsn_mme_ctx *mme, const struct osmo_eutran_tai *tai)
+{
+	struct mme_rim_route *rt;
+
+	llist_for_each_entry(rt, &mme->routes, list) {
+		if (_eutran_tai_equal(tai, &rt->tai)) {
+			llist_del(&rt->list);
+			talloc_free(rt);
+			return;
+		}
+	}
+}
+
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_name(const struct sgsn_instance *sgsn, const char *name)
+{
+	struct sgsn_mme_ctx *mme;
+
+	llist_for_each_entry(mme, &sgsn->mme_list, list) {
+		if (!strcmp(name, mme->name))
+			return mme;
+	}
+	return NULL;
+}
+
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_addr(const struct sgsn_instance *sgsn, const struct in_addr *addr)
+{
+	struct sgsn_mme_ctx *mme;
+
+	llist_for_each_entry(mme, &sgsn->mme_list, list) {
+		if (!memcmp(addr, &mme->remote_addr, sizeof(*addr)))
+			return mme;
+	}
+	return NULL;
+}
+
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_route(const struct sgsn_instance *sgsn, const struct osmo_eutran_tai *tai)
+{
+	struct sgsn_mme_ctx *mme;
+	llist_for_each_entry(mme, &sgsn->mme_list, list) {
+		struct mme_rim_route *rt;
+		llist_for_each_entry(rt, &mme->routes, list) {
+			if (_eutran_tai_equal(tai, &rt->tai)) {
+				return mme;
+			}
+		}
+	}
+	return NULL;
+}
+
+struct sgsn_mme_ctx *sgsn_mme_ctx_by_default_route(const struct sgsn_instance *sgsn)
+{
+	struct sgsn_mme_ctx *mme;
+
+	llist_for_each_entry(mme, &sgsn->mme_list, list) {
+		if (mme->default_route)
+			return mme;
+	}
+	return NULL;
+}
diff --git a/src/sgsn/sgsn_libgtp.c b/src/sgsn/sgsn_libgtp.c
index 8be7927..6229217 100644
--- a/src/sgsn/sgsn_libgtp.c
+++ b/src/sgsn/sgsn_libgtp.c
@@ -55,6 +55,8 @@
 #include <osmocom/sgsn/gprs_ranap.h>
 #include <osmocom/sgsn/gprs_gmm_fsm.h>
 #include <osmocom/sgsn/gprs_mm_state_gb_fsm.h>
+#include <osmocom/sgsn/gtp_mme.h>
+#include <osmocom/sgsn/sgsn_rim.h>
 
 #include <gtp.h>
 #include <pdp.h>
@@ -479,6 +481,43 @@
 	gtp_echo_req(ggc->gsn, ggc->gtp_version, ggc, &ggc->remote_addr);
 }
 
+int sgsn_mme_ran_info_req(struct sgsn_mme_ctx *mme, const struct bssgp_ran_information_pdu *pdu)
+{
+	char ri_src_str[64], ri_dest_str[64];
+	int ri_len;
+	struct msgb *msg;
+	struct bssgp_normal_hdr *bgph;
+	int rc;
+	uint8_t ri_buf[64];
+	uint8_t *ri_ptr = &ri_buf[0];
+	struct sockaddr_in sk_in = {
+		.sin_family = AF_INET,
+		.sin_port = htons(GTP1C_PORT),
+		.sin_addr = mme->remote_addr,
+	};
+
+	msg = bssgp_encode_rim_pdu(pdu);
+	if (!msg) {
+		LOGMME(mme, DRIM, LOGL_ERROR, "Tx GTP RAN Information Relay: failed to encode pdu\n");
+		return -EINVAL;
+	}
+	bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+	DEBUGP(DLBSSGP, "Tx GTP RAN Information Relay: RIM-PDU:%s, src=%s, dest=%s\n",
+	       bssgp_pdu_str(bgph->pdu_type),
+	       bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &pdu->routing_info_src),
+	       bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &pdu->routing_info_dest));
+
+	if ((ri_len = bssgp_create_rim_ri(ri_ptr, &pdu->routing_info_dest)) < 0) {
+		ri_ptr = NULL;
+		ri_len = 0;
+	}
+
+	rc = gtp_ran_info_relay_req(mme->sgsn->gsn,  &sk_in, msgb_data(msg), msgb_length(msg),
+				    ri_ptr, ri_len, pdu->routing_info_dest.discr);
+	msgb_free(msg);
+	return rc;
+}
+
 /* Confirmation of a PDP Context Update */
 static int update_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
 {
@@ -648,6 +687,42 @@
 	return 0;
 }
 
+static int cb_gtp_ran_info_relay_ind(struct sockaddr_in *peer, union gtpie_member **ie)
+{
+	char addrbuf[INET_ADDRSTRLEN];
+	struct sgsn_mme_ctx *mme = sgsn_mme_ctx_by_addr(sgsn, &peer->sin_addr);
+	if (!mme) {
+		LOGP(DGTP, LOGL_NOTICE, "Rx GTP RAN Information Relay from unknown MME %s\n",
+		     inet_ntop(AF_INET, &peer->sin_addr, addrbuf, sizeof(addrbuf)));
+		return -ECONNREFUSED;
+	}
+
+	LOGMME(mme, DGTP, LOGL_INFO, "Rx GTP RAN Information Relay\n");
+
+	unsigned int len = 0;
+	struct msgb *msg = msgb_alloc(4096, "gtpcv1_ran_info");
+	struct bssgp_ran_information_pdu pdu;
+
+	if (gtpie_gettlv(ie, GTPIE_RAN_T_CONTAIN, 0, &len, msgb_data(msg), 4096) || len <= 0) {
+		LOGMME(mme, DGTP, LOGL_ERROR, "Rx GTP RAN Information Relay: No Transparent Container IE found!\n");
+		goto ret_error;
+	}
+	msgb_put(msg, len);
+	msgb_bssgph(msg) = msg->data;
+	msgb_nsei(msg) = 0;
+	if (bssgp_parse_rim_pdu(&pdu, msg) < 0) {
+		LOGMME(mme, DGTP, LOGL_ERROR, "Rx GTP RAN Information Relay: Failed parsing Transparent Container IE!\n");
+		goto ret_error;
+	}
+
+	msgb_free(msg);
+	return sgsn_rim_rx_from_gtp(&pdu, mme);
+
+ret_error:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
 /* Called whenever we receive a DATA packet */
 static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len)
 {
@@ -837,6 +912,7 @@
 	gtp_set_cb_data_ind(gsn, cb_data_ind);
 	gtp_set_cb_unsup_ind(gsn, cb_unsup_ind);
 	gtp_set_cb_extheader_ind(gsn, cb_extheader_ind);
+	gtp_set_cb_ran_info_relay_ind(gsn, cb_gtp_ran_info_relay_ind);
 
 	return 0;
 }
diff --git a/src/sgsn/sgsn_main.c b/src/sgsn/sgsn_main.c
index 71f2efa..af9757a 100644
--- a/src/sgsn/sgsn_main.c
+++ b/src/sgsn/sgsn_main.c
@@ -122,7 +122,7 @@
 	case SAP_BSSGP_NM:
 		break;
 	case SAP_BSSGP_RIM:
-		return sgsn_rim_rx(bp, oph->msg);
+		return sgsn_rim_rx_from_gb(bp, oph->msg);
 	}
 	return 0;
 }
@@ -162,15 +162,36 @@
 	}
 }
 
+static int sgsn_vty_go_parent(struct vty *vty)
+{
+	switch (vty->node) {
+	case SGSN_NODE:
+		vty->node = CONFIG_NODE;
+		break;
+	case MME_NODE:
+		vty->node = SGSN_NODE;
+		vty->index = NULL;
+		break;
+	default:
+#if BUILD_IU
+		osmo_ss7_vty_go_parent(vty);
+#else
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+#endif
+		break;
+	}
+
+	return vty->node;
+}
+
 /* NSI that BSSGP uses when transmitting on NS */
 extern struct gprs_ns_inst *bssgp_nsi;
 
 static struct vty_app_info vty_info = {
 	.name 		= "OsmoSGSN",
 	.version	= PACKAGE_VERSION,
-#if BUILD_IU
-	.go_parent_cb	= osmo_ss7_vty_go_parent,
-#endif
+	.go_parent_cb	= sgsn_vty_go_parent,
 };
 
 static void print_help(void)
diff --git a/src/sgsn/sgsn_rim.c b/src/sgsn/sgsn_rim.c
index 71e6d98..f28ba60 100644
--- a/src/sgsn/sgsn_rim.c
+++ b/src/sgsn/sgsn_rim.c
@@ -11,57 +11,113 @@
 #include <osmocom/gprs/gprs_bssgp.h>
 #include <osmocom/gprs/gprs_bssgp_rim.h>
 #include <osmocom/sgsn/sgsn_rim.h>
+#include <osmocom/sgsn/gtp_mme.h>
 #include <osmocom/sgsn/debug.h>
+#include <osmocom/sgsn/sgsn.h>
 
-/* Find an NSEI for the destination cell, this function works only for GERAN! */
-static int find_dest_nsei_geran(struct bssgp_rim_routing_info *dest_rim_ri, uint16_t nsei)
+static int sgsn_bssgp_fwd_rim_to_geran(const struct bssgp_ran_information_pdu *pdu)
 {
 	struct bssgp_bvc_ctx *bvc_ctx;
+	OSMO_ASSERT(pdu->routing_info_dest.discr == BSSGP_RIM_ROUTING_INFO_GERAN);
 
-	OSMO_ASSERT(dest_rim_ri->discr == BSSGP_RIM_ROUTING_INFO_GERAN);
-
-	bvc_ctx = btsctx_by_raid_cid(&dest_rim_ri->geran.raid, dest_rim_ri->geran.cid);
+	bvc_ctx = btsctx_by_raid_cid(&pdu->routing_info_dest.geran.raid, pdu->routing_info_dest.geran.cid);
 	if (!bvc_ctx) {
-		LOGP(DRIM, LOGL_ERROR, "BSSGP RIM (NSEI=%u) cannot find NSEI for destination cell\n", nsei);
+		LOGP(DRIM, LOGL_ERROR, "Unable to find NSEI for destination cell %s\n",
+		       bssgp_rim_ri_name(&pdu->routing_info_dest));
 		return -EINVAL;
 	}
 
-	return bvc_ctx->nsei;
+	/* Forward PDU as it is to the correct interface */
+	return bssgp_tx_rim(pdu, bvc_ctx->nsei);
 }
 
-int sgsn_rim_rx(struct osmo_bssgp_prim *bp, struct msgb *msg)
+static int sgsn_bssgp_fwd_rim_to_eutran(const struct bssgp_ran_information_pdu *pdu)
 {
-	struct bssgp_ran_information_pdu *pdu = &bp->u.rim_pdu;
-	int d_nsei;
-	uint16_t nsei = msgb_nsei(msg);
+	struct sgsn_mme_ctx *mme;
+	OSMO_ASSERT(pdu->routing_info_dest.discr == BSSGP_RIM_ROUTING_INFO_EUTRAN);
 
-	/* At the moment we only support GERAN, so we block all other network
-	 * types here. */
-	if (pdu->routing_info_dest.discr != BSSGP_RIM_ROUTING_INFO_GERAN) {
-		LOGP(DRIM, LOGL_ERROR,
-		     "BSSGP RIM (NSEI=%u) only GERAN supported, destination cell is not a GERAN cell -- rejected.\n",
-		     nsei);
-		/* At the moment we can only handle GERAN addresses, any other
-		 * type of address will be consideres as an invalid address.
-		 * see also: 3GPP TS 48.018, section 8c.3.1.3 */
-		return bssgp_tx_status(BSSGP_CAUSE_UNKN_RIM_AI, NULL, msg);
+	mme = sgsn_mme_ctx_by_route(sgsn, &pdu->routing_info_dest.eutran.tai);
+	if (!mme) { /* See if we have a default route configured */
+		mme = sgsn_mme_ctx_by_default_route(sgsn);
+		if (!mme) {
+			LOGP(DRIM, LOGL_ERROR, "Unable to find MME for destination cell %s\n",
+			       bssgp_rim_ri_name(&pdu->routing_info_dest));
+			return -EINVAL;
+		}
 	}
+
+	return sgsn_mme_ran_info_req(mme, pdu);
+}
+
+/* Receive a RIM PDU from BSSGP (GERAN) */
+int sgsn_rim_rx_from_gb(struct osmo_bssgp_prim *bp, struct msgb *msg)
+{
+	uint16_t nsei = msgb_nsei(msg);
+	struct bssgp_ran_information_pdu *pdu = &bp->u.rim_pdu;
+
 	if (pdu->routing_info_src.discr != BSSGP_RIM_ROUTING_INFO_GERAN) {
 		LOGP(DRIM, LOGL_ERROR,
-		     "BSSGP RIM (NSEI=%u) only GERAN supported, source cell is not a GERAN cell -- rejected.\n", nsei);
-		/* See comment above */
-		return bssgp_tx_status(BSSGP_CAUSE_UNKN_RIM_AI, NULL, msg);
+		     "Rx BSSGP RIM (NSEI=%u): Expected src %s, got %s\n", nsei,
+		     bssgp_rim_routing_info_discr_str(BSSGP_RIM_ROUTING_INFO_GERAN),
+		     bssgp_rim_routing_info_discr_str(pdu->routing_info_src.discr));
+		goto err;
 	}
 
-	d_nsei = find_dest_nsei_geran(&pdu->routing_info_dest, nsei);
-	if (d_nsei < 0) {
-		LOGP(DRIM, LOGL_NOTICE, "BSSGP RIM (NSEI=%u) Cell %s unknown to this sgsn\n",
-		     nsei, bssgp_rim_ri_name(&pdu->routing_info_dest));
-		/* In case of an invalid destination address we respond with
-		 * a BSSGP STATUS PDU, see also: 3GPP TS 48.018, section 8c.3.1.3 */
-		return bssgp_tx_status(BSSGP_CAUSE_UNKN_RIM_AI, NULL, msg);
+	switch (pdu->routing_info_dest.discr) {
+	case BSSGP_RIM_ROUTING_INFO_GERAN:
+		return sgsn_bssgp_fwd_rim_to_geran(pdu);
+	case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+		return sgsn_bssgp_fwd_rim_to_eutran(pdu);
+	default:
+		/* At the moment we can only handle GERAN/EUTRAN addresses, any
+		 * other type of address will be considered as an invalid
+		 * address. see also: 3GPP TS 48.018, section 8c.3.1.3
+		 */
+		LOGP(DRIM, LOGL_ERROR,
+		     "Rx BSSGP RIM (NSEI=%u): Unsupported dst %s\n", nsei,
+		     bssgp_rim_routing_info_discr_str(pdu->routing_info_dest.discr));
 	}
 
-	/* Forward PDU as it is to the correct interface */
-	return bssgp_tx_rim(pdu, (uint16_t) d_nsei);
+	LOGP(DRIM, LOGL_INFO, "Rx BSSGP RIM (NSEI=%u): for dest cell %s\n", nsei,
+	     bssgp_rim_ri_name(&pdu->routing_info_dest));
+
+err:
+	/* In case of an invalid destination address we respond with
+	 * a BSSGP STATUS PDU, see also: 3GPP TS 48.018, section 8c.3.1.3 */
+	bssgp_tx_status(BSSGP_CAUSE_UNKN_RIM_AI, NULL, msg);
+	return -1;
+}
+
+/* Receive a RIM PDU from GTPvC1 (EUTRAN) */
+int sgsn_rim_rx_from_gtp(struct bssgp_ran_information_pdu *pdu, struct sgsn_mme_ctx *mme)
+{
+	struct sgsn_mme_ctx *mme_tmp;
+	if (pdu->routing_info_src.discr != BSSGP_RIM_ROUTING_INFO_EUTRAN) {
+		LOGMME(mme, DRIM, LOGL_ERROR, "Rx GTP RAN Information Relay: Expected src %s, got %s\n",
+		       bssgp_rim_routing_info_discr_str(BSSGP_RIM_ROUTING_INFO_EUTRAN),
+		       bssgp_rim_routing_info_discr_str(pdu->routing_info_src.discr));
+		return -EINVAL;
+	}
+
+	if (pdu->routing_info_dest.discr != BSSGP_RIM_ROUTING_INFO_GERAN) {
+		LOGMME(mme, DRIM, LOGL_ERROR, "Rx GTP RAN Information Relay: Expected dst %s, got %s\n",
+		       bssgp_rim_routing_info_discr_str(BSSGP_RIM_ROUTING_INFO_GERAN),
+		       bssgp_rim_routing_info_discr_str(pdu->routing_info_dest.discr));
+		return -EINVAL;
+	}
+
+	mme_tmp = sgsn_mme_ctx_by_route(sgsn, &pdu->routing_info_src.eutran.tai);
+	if (!mme_tmp)/* See if we have a default route configured */
+		mme_tmp = sgsn_mme_ctx_by_default_route(sgsn);
+	if (mme != mme_tmp) {
+		LOGP(DRIM, LOGL_ERROR, "Rx GTP RAN Information Relay: "
+		     "Source MME doesn't have RIM routing configured for TAI: %s\n",
+		     bssgp_rim_ri_name(&pdu->routing_info_src));
+		return -EINVAL;
+	}
+
+	LOGMME(mme, DRIM, LOGL_INFO, "Rx GTP RAN Information Relay for dest cell %s\n",
+	       bssgp_rim_ri_name(&pdu->routing_info_dest));
+
+	return sgsn_bssgp_fwd_rim_to_geran(pdu);
 }
diff --git a/src/sgsn/sgsn_vty.c b/src/sgsn/sgsn_vty.c
index 042bad5..76d5202 100644
--- a/src/sgsn/sgsn_vty.c
+++ b/src/sgsn/sgsn_vty.c
@@ -37,6 +37,7 @@
 #include <osmocom/gprs/gprs_ns2.h>
 #include <osmocom/sgsn/gprs_gmm.h>
 #include <osmocom/sgsn/gprs_sgsn.h>
+#include <osmocom/sgsn/gtp_mme.h>
 #include <osmocom/sgsn/vty.h>
 #include <osmocom/gsupclient/gsup_client.h>
 
@@ -176,12 +177,35 @@
 	1,
 };
 
+static struct cmd_node mme_node = {
+	MME_NODE,
+	"%s(config-sgsn-mme)# ",
+	1,
+};
+
+static void config_write_mme(struct vty *vty, const struct sgsn_mme_ctx *mme, const char *prefix)
+{
+	struct mme_rim_route *rt;
+
+	vty_out(vty, "%smme %s%s", prefix, mme->name, VTY_NEWLINE);
+
+	vty_out(vty, "%s gtp remote-ip %s%s", prefix, inet_ntoa(mme->remote_addr), VTY_NEWLINE);
+	if (mme->default_route)
+		vty_out(vty, "%s gtp ran-info-relay default%s", prefix, VTY_NEWLINE);
+	llist_for_each_entry(rt, &mme->routes, list) {
+		vty_out(vty, "%s gtp ran-info-relay %s %s %u%s", prefix,
+			osmo_mcc_name(rt->tai.mcc), osmo_mnc_name(rt->tai.mnc, rt->tai.mnc_3_digits),
+			rt->tai.tac, VTY_NEWLINE);
+	}
+}
+
 static int config_write_sgsn(struct vty *vty)
 {
 	struct sgsn_ggsn_ctx *gctx;
 	struct imsi_acl_entry *acl;
 	struct apn_ctx *actx;
 	struct ares_addr_node *server;
+	struct sgsn_mme_ctx *mme;
 
 	vty_out(vty, "sgsn%s", VTY_NEWLINE);
 
@@ -296,6 +320,10 @@
 	} else
 		vty_out(vty, " no compression v42bis%s", VTY_NEWLINE);
 
+	llist_for_each_entry(mme, &sgsn->mme_list, list) {
+		config_write_mme(vty, mme, " ");
+	}
+
 #ifdef BUILD_IU
 	vty_out(vty, " cs7-instance-iu %u%s", g_cfg->iu.cs7_instance,
 		VTY_NEWLINE);
@@ -1423,6 +1451,161 @@
 }
 #endif
 
+DEFUN(cfg_sgsn_mme, cfg_sgsn_mme_cmd,
+	"mme NAME",
+	"Configure an MME peer\n"
+	"Name identifying the MME peer\n")
+{
+	struct sgsn_mme_ctx *mme;
+
+	mme = sgsn_mme_ctx_find_alloc(sgsn, argv[0]);
+	if (!mme)
+		return CMD_WARNING;
+
+	vty->node = MME_NODE;
+	vty->index = mme;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_sgsn_no_mme, cfg_sgsn_no_mme_cmd,
+	"no mme NAME",
+	NO_STR "Delete an MME peer configuration\n"
+	"Name identifying the MME peer\n")
+{
+	struct sgsn_mme_ctx *mme;
+
+	mme = sgsn_mme_ctx_by_name(sgsn, argv[0]);
+	if (!mme) {
+		vty_out(vty, "%% MME %s doesn't exist.%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	sgsn_mme_ctx_free(mme);
+
+	return CMD_SUCCESS;
+}
+
+#define GTP_STR "Configure GTP connection\n"
+
+DEFUN(cfg_mme_remote_ip, cfg_mme_remote_ip_cmd,
+	"gtp remote-ip A.B.C.D",
+	GTP_STR "Set Remote GTP IP address\n" IP_STR)
+{
+	struct sgsn_mme_ctx *mme = (struct sgsn_mme_ctx *) vty->index;
+
+	inet_aton(argv[0], &mme->remote_addr);
+
+	return CMD_SUCCESS;
+}
+
+#define RAN_INFO_STR "Configure RAN Information Relay routing\n"
+#define TAI_DOC "MCC\n" "MNC\n" "TAC\n"
+
+DEFUN(cfg_mme_ran_info_relay_tai, cfg_mme_ran_info_relay_tai_cmd,
+	"gtp ran-info-relay <0-999> <0-999> <0-65535>",
+	GTP_STR RAN_INFO_STR TAI_DOC)
+{
+	struct sgsn_mme_ctx *mme = (struct sgsn_mme_ctx *) vty->index;
+	struct sgsn_mme_ctx *mme_tmp;
+	struct osmo_eutran_tai tai;
+
+	const char *mcc = argv[0];
+	const char *mnc = argv[1];
+	const char *tac = argv[2];
+
+	if (osmo_mcc_from_str(mcc, &tai.mcc)) {
+		vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (osmo_mnc_from_str(mnc, &tai.mnc, &tai.mnc_3_digits)) {
+		vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	tai.tac = atoi(tac);
+
+	if ((mme_tmp = sgsn_mme_ctx_by_route(sgsn, &tai))) {
+		if (mme_tmp != mme) {
+			vty_out(vty, "%% Another MME %s already contains this route%s",
+				mme_tmp->name, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		/* else: NO-OP, return */
+		return CMD_SUCCESS;
+	}
+
+	sgsn_mme_ctx_route_add(mme, &tai);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mme_no_ran_info_relay_tai, cfg_mme_no_ran_info_relay_tai_cmd,
+	"no gtp ran-info-relay <0-999> <0-999> <0-65535>",
+	NO_STR GTP_STR RAN_INFO_STR TAI_DOC)
+{
+	struct sgsn_mme_ctx *mme = (struct sgsn_mme_ctx *) vty->index;
+	struct sgsn_mme_ctx *mme_tmp;
+	struct osmo_eutran_tai tai;
+
+	const char *mcc = argv[0];
+	const char *mnc = argv[1];
+	const char *tac = argv[2];
+
+	if (osmo_mcc_from_str(mcc, &tai.mcc)) {
+		vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (osmo_mnc_from_str(mnc, &tai.mnc, &tai.mnc_3_digits)) {
+		vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	tai.tac = atoi(tac);
+
+	if ((mme_tmp = sgsn_mme_ctx_by_route(sgsn, &tai))) {
+		if (mme_tmp != mme) {
+			vty_out(vty, "%% Another MME %s contains this route%s",
+				mme_tmp->name, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		sgsn_mme_ctx_route_del(mme, &tai);
+		return CMD_SUCCESS;
+	} else {
+		vty_out(vty, "%% This route doesn't exist in current MME %s%s",
+			mme->name, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+}
+
+DEFUN(cfg_mme_ran_info_relay_default, cfg_mme_ran_info_relay_default_cmd,
+	"gtp ran-info-relay default",
+	GTP_STR RAN_INFO_STR "Set as default route")
+{
+	struct sgsn_mme_ctx *mme = (struct sgsn_mme_ctx *) vty->index;
+	struct sgsn_mme_ctx *default_mme;
+
+	if (mme->default_route)
+		return CMD_SUCCESS; /* NO-OP */
+
+	if ((default_mme = sgsn_mme_ctx_by_default_route(sgsn))) {
+		vty_out(vty, "%% Another MME %s is already set as default route, "
+			     "remove it before setting it here.%s",
+			     default_mme->name, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	mme->default_route = true;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mme_no_ran_info_relay_default, cfg_mme_no_ran_info_relay_default_cmd,
+	"no gtp ran-info-relay default",
+	NO_STR GTP_STR RAN_INFO_STR "Set as default route")
+{
+	struct sgsn_mme_ctx *mme = (struct sgsn_mme_ctx *) vty->index;
+	mme->default_route = false;
+	return CMD_SUCCESS;
+}
+
 int sgsn_vty_init(struct sgsn_config *cfg)
 {
 	g_cfg = cfg;
@@ -1486,6 +1669,15 @@
 	install_element(SGSN_NODE, &cfg_comp_v42bis_cmd);
 	install_element(SGSN_NODE, &cfg_comp_v42bisp_cmd);
 
+	install_element(SGSN_NODE, &cfg_sgsn_mme_cmd);
+	install_element(SGSN_NODE, &cfg_sgsn_no_mme_cmd);
+	install_node(&mme_node, NULL);
+	install_element(MME_NODE, &cfg_mme_remote_ip_cmd);
+	install_element(MME_NODE, &cfg_mme_ran_info_relay_default_cmd);
+	install_element(MME_NODE, &cfg_mme_no_ran_info_relay_default_cmd);
+	install_element(MME_NODE, &cfg_mme_ran_info_relay_tai_cmd);
+	install_element(MME_NODE, &cfg_mme_no_ran_info_relay_tai_cmd);
+
 #ifdef BUILD_IU
 	install_element(SGSN_NODE, &cfg_sgsn_cs7_instance_iu_cmd);
 	ranap_iu_vty_init(SGSN_NODE, &g_cfg->iu.rab_assign_addr_enc);
diff --git a/tests/osmo-sgsn_test-nodes.vty b/tests/osmo-sgsn_test-nodes.vty
index 953a30e..f889541 100644
--- a/tests/osmo-sgsn_test-nodes.vty
+++ b/tests/osmo-sgsn_test-nodes.vty
@@ -61,3 +61,58 @@
   compression v42bis active direction (ms|sgsn|both) codewords <512-65535> strlen <6-250>
   compression v42bis passive
 ...
+
+OsmoSGSN(config-sgsn)# mme test0
+OsmoSGSN(config-sgsn-mme)# gtp remote-ip 1.2.3.4
+OsmoSGSN(config-sgsn-mme)# gtp ran-info-relay 907 10 567
+OsmoSGSN(config-sgsn-mme)# gtp ran-info-relay 202 12 51
+OsmoSGSN(config-sgsn-mme)# gtp ran-info-relay 907 10 567
+OsmoSGSN(config-sgsn-mme)# exit
+OsmoSGSN(config-sgsn)# mme test1
+OsmoSGSN(config-sgsn-mme)# gtp remote-ip 5.6.7.8
+OsmoSGSN(config-sgsn-mme)# gtp ran-info-relay default
+OsmoSGSN(config-sgsn-mme)# exit
+OsmoSGSN(config-sgsn)# show running-config
+...
+sgsn
+...
+ mme test0
+  gtp remote-ip 1.2.3.4
+  gtp ran-info-relay 907 10 567
+  gtp ran-info-relay 202 12 51
+ mme test1
+  gtp remote-ip 5.6.7.8
+  gtp ran-info-relay default
+...
+OsmoSGSN(config-sgsn)# mme test0
+OsmoSGSN(config-sgsn-mme)# gtp ran-info-relay default
+% Another MME test1 is already set as default route, remove it before setting it here.
+OsmoSGSN(config-sgsn-mme)# exit
+OsmoSGSN(config-sgsn)# mme test1
+OsmoSGSN(config-sgsn-mme)# no gtp ran-info-relay default
+OsmoSGSN(config-sgsn-mme)# exit
+OsmoSGSN(config-sgsn)# mme test0
+OsmoSGSN(config-sgsn-mme)# gtp ran-info-relay default
+OsmoSGSN(config-sgsn-mme)# exit
+OsmoSGSN(config-sgsn)# show running-config
+...
+sgsn
+...
+ mme test0
+  gtp remote-ip 1.2.3.4
+  gtp ran-info-relay default
+  gtp ran-info-relay 907 10 567
+  gtp ran-info-relay 202 12 51
+ mme test1
+  gtp remote-ip 5.6.7.8
+...
+OsmoSGSN(config-sgsn)# no mme test0
+OsmoSGSN(config-sgsn)# show running-config
+...
+sgsn
+...
+ no compression v42bis
+ mme test1
+  gtp remote-ip 5.6.7.8
+...
+OsmoSGSN(config-sgsn)# no mme test1
diff --git a/tests/sgsn/Makefile.am b/tests/sgsn/Makefile.am
index b72c446..bdf941d 100644
--- a/tests/sgsn/Makefile.am
+++ b/tests/sgsn/Makefile.am
@@ -50,6 +50,7 @@
 	$(top_builddir)/src/sgsn/gprs_gmm_fsm.o \
 	$(top_builddir)/src/sgsn/gprs_mm_state_gb_fsm.o \
 	$(top_builddir)/src/sgsn/gprs_sgsn.o \
+	$(top_builddir)/src/sgsn/gtp_mme.o \
 	$(top_builddir)/src/sgsn/sgsn_vty.o \
 	$(top_builddir)/src/sgsn/sgsn_libgtp.o \
 	$(top_builddir)/src/sgsn/sgsn_auth.o \
@@ -62,6 +63,7 @@
         $(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/gprs_gb_parse.o \

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-sgsn/+/24164
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-sgsn
Gerrit-Branch: master
Gerrit-Change-Id: I396450b8d8b66595dab8ff7bf41cbf964bb40d93
Gerrit-Change-Number: 24164
Gerrit-PatchSet: 9
Gerrit-Owner: pespin <pespin at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: daniel <dwillmann at sysmocom.de>
Gerrit-Reviewer: dexter <pmaier at sysmocom.de>
Gerrit-Reviewer: fixeria <vyanitskiy at sysmocom.de>
Gerrit-Reviewer: laforge <laforge at osmocom.org>
Gerrit-Reviewer: lynxis lazus <lynxis at fe80.eu>
Gerrit-Reviewer: osmith <osmith at sysmocom.de>
Gerrit-Reviewer: pespin <pespin at sysmocom.de>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20210519/a93f9529/attachment.htm>


More information about the gerrit-log mailing list