[PATCH] osmo-bsc[master]: mgcp: use osmo-mgw to switch RTP streams

dexter gerrit-no-reply at lists.osmocom.org
Thu Oct 26 14:23:50 UTC 2017


Hello Neels Hofmeyr, Harald Welte, Jenkins Builder,

I'd like you to reexamine a change.  Please visit

    https://gerrit.osmocom.org/4334

to look at the new patch set (#6).

mgcp: use osmo-mgw to switch RTP streams

osmo-bsc currently negotiates the RTP stream directly with the
BTS and reports back the RTP IP/Port on the BTS. This works fine
for a single BTS, but for Handover the port/ip pointing to the
MSC side must not change, so an entity in between the BTSs and
the MSC is required.

Integrate the mgcp-client and use osmo-mgw to switch the RTP
streams.

TODO: Handover will not work yet, because there is no
functionality to update the connection with the port/ip of
the new BTS.

Change-Id: Ia2882b7ca31a3219c676986e85045fa08a425d7a
---
M configure.ac
M include/osmocom/bsc/Makefile.am
M include/osmocom/bsc/gsm_data.h
M include/osmocom/bsc/osmo_bsc.h
A include/osmocom/bsc/osmo_bsc_mgcp.h
M src/Makefile.am
M src/osmo-bsc/Makefile.am
M src/osmo-bsc/osmo_bsc_audio.c
M src/osmo-bsc/osmo_bsc_bssap.c
M src/osmo-bsc/osmo_bsc_main.c
A src/osmo-bsc/osmo_bsc_mgcp.c
M src/osmo-bsc/osmo_bsc_sigtran.c
M src/osmo-bsc/osmo_bsc_vty.c
13 files changed, 1,030 insertions(+), 65 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/osmo-bsc refs/changes/34/4334/6

diff --git a/configure.ac b/configure.ac
index 4edbb83..f73f33c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -47,8 +47,9 @@
 PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.0.1)
 PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran) # TODO version?
 PKG_CHECK_MODULES(LIBCRYPTO, libcrypto >= 0.9.5)
-PKG_CHECK_MODULES(LIBOSMOLEGACYMGCP, libosmo-legacy-mgcp >= 0.0.1)
 PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.0.2)
+PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.0.0)
+PKG_CHECK_MODULES(LIBOSMOLEGACYMGCP, libosmo-legacy-mgcp >= 1.0.0)
 
 dnl checks for header files
 AC_HEADER_STDC
@@ -135,7 +136,6 @@
     src/libfilter/Makefile
     src/libcommon-cs/Makefile
     src/osmo-bsc/Makefile
-    src/osmo-bsc_nat/Makefile
     src/ipaccess/Makefile
     src/utils/Makefile
     tests/Makefile
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index 8ad2b5d..1f7cd39 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -41,6 +41,7 @@
 	openbscdefines.h \
 	osmo_bsc.h \
 	osmo_bsc_grace.h \
+	osmo_bsc_mgcp.h \
 	osmo_bsc_rf.h \
 	osmo_bsc_sigtran.h \
 	bsc_msc_data.h \
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
index 51b2c98..5cb7efd 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -479,6 +479,11 @@
 	uint8_t t3212;
 
 	struct {
+		struct mgcp_client_conf *conf;
+		struct mgcp_client *client;
+	} mgw;
+
+	struct {
 		/* CS7 instance id number (set via VTY) */
 		uint32_t cs7_instance;
 		/* A list with the context information about
diff --git a/include/osmocom/bsc/osmo_bsc.h b/include/osmocom/bsc/osmo_bsc.h
index 5ebea50..8a5cd30 100644
--- a/include/osmocom/bsc/osmo_bsc.h
+++ b/include/osmocom/bsc/osmo_bsc.h
@@ -29,6 +29,20 @@
 	uint32_t rtp_ip;
 	int rtp_port;
 
+	/* RTP address of the remote end (assigned by MSC through assignment
+	 * request) */
+	struct sockaddr_storage aoip_rtp_addr_remote;
+
+	/* Local RTP address (reported back to the MSC by us with the
+	 * assignment complete message) */
+	struct sockaddr_storage aoip_rtp_addr_local;
+
+	/* storage to keep states of the MGCP connection handler, the
+	 * handler is created when an assignment request is received
+	 * and is terminated when the assignment complete message is
+	 * sent */
+	struct mgcp_ctx *mgcp_ctx;
+
 	/* for advanced ping/pong */
 	int send_ping;
 
@@ -72,4 +86,6 @@
 
 struct llist_head *bsc_access_lists(void);
 
+int bssmap_send_aoip_ass_compl(struct gsm_lchan *lchan);
+
 #endif
diff --git a/include/osmocom/bsc/osmo_bsc_mgcp.h b/include/osmocom/bsc/osmo_bsc_mgcp.h
new file mode 100644
index 0000000..7246dda
--- /dev/null
+++ b/include/osmocom/bsc/osmo_bsc_mgcp.h
@@ -0,0 +1,45 @@
+/* (C) 2017 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * 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/>.
+ *
+ */
+
+#pragma once
+
+/* MGCP state handler context (fsm etc..) */
+struct mgcp_ctx {
+	/* FSM instance, which handles the connection switching procedure */
+	struct osmo_fsm_inst *fsm;
+
+	/* RTP endpoint number */
+	uint16_t rtp_endpoint;
+
+	/* Copy of the pointer and the data with context information
+	 * needed to process the AoIP and MGCP requests (system data) */
+	struct mgcp_client *mgcp;
+
+	struct osmo_bsc_sccp_con *conn;
+	enum gsm48_chan_mode chan_mode;
+	bool full_rate;
+	struct gsm_lchan *lchan;
+	struct msgb *resp;
+};
+
+struct mgcp_ctx *mgcp_assignm_req(void *ctx, struct mgcp_client *mgcp, struct osmo_bsc_sccp_con *conn,
+				  enum gsm48_chan_mode chan_mode, bool full_rate);
+void mgcp_clear_complete(struct mgcp_ctx *mgcp_ctx, struct msgb *resp);
+void mgcp_ass_complete(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *lchan);
diff --git a/src/Makefile.am b/src/Makefile.am
index d04f025..dd1ad3d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -33,5 +33,4 @@
 	utils \
 	ipaccess \
 	osmo-bsc \
-	osmo-bsc_nat \
 	$(NULL)
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index dfc4def..7db698c 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -15,6 +15,7 @@
 	$(COVERAGE_CFLAGS) \
 	$(LIBOSMOABIS_CFLAGS) \
 	$(LIBOSMOSIGTRAN_CFLAGS) \
+	$(LIBOSMOMGCPCLIENT_CFLAGS) \
 	$(NULL)
 
 AM_LDFLAGS = \
@@ -30,6 +31,7 @@
 	osmo_bsc_vty.c \
 	osmo_bsc_api.c \
 	osmo_bsc_grace.c \
+	osmo_bsc_mgcp.c \
 	osmo_bsc_msc.c \
 	osmo_bsc_sigtran.c \
 	osmo_bsc_filter.c \
@@ -53,4 +55,5 @@
 	$(COVERAGE_LDFLAGS) \
 	$(LIBOSMOABIS_LIBS) \
 	$(LIBOSMOSIGTRAN_LIBS) \
+	$(LIBOSMOMGCPCLIENT_LIBS) \
 	$(NULL)
diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c
index 94aa350..326703d 100644
--- a/src/osmo-bsc/osmo_bsc_audio.c
+++ b/src/osmo-bsc/osmo_bsc_audio.c
@@ -29,46 +29,9 @@
 #include <osmocom/gsm/gsm0808.h>
 #include <osmocom/gsm/gsm0808_utils.h>
 #include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
 
 #include <arpa/inet.h>
-
-/* Generate and send assignment complete message */
-static int send_aoip_ass_compl(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan)
-{
-	struct msgb *resp;
-	struct sockaddr_storage rtp_addr;
-	struct sockaddr_in rtp_addr_in;
-	struct gsm0808_speech_codec sc;
-
-	OSMO_ASSERT(lchan->abis_ip.ass_compl.valid == true);
-
-	/* Package RTP-Address data */
-	memset(&rtp_addr_in, 0, sizeof(rtp_addr_in));
-	rtp_addr_in.sin_family = AF_INET;
-	rtp_addr_in.sin_port = htons(lchan->abis_ip.bound_port);
-	rtp_addr_in.sin_addr.s_addr = htonl(lchan->abis_ip.bound_ip);
-	memset(&rtp_addr, 0, sizeof(rtp_addr));
-	memcpy(&rtp_addr, &rtp_addr_in, sizeof(rtp_addr_in));
-
-	/* Extrapolate speech codec from speech mode */
-	gsm0808_speech_codec_from_chan_type(&sc, lchan->abis_ip.ass_compl.speech_mode);
-
-	/* Generate message */
-	resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause,
-					lchan->abis_ip.ass_compl.chosen_channel,
-					lchan->abis_ip.ass_compl.encr_alg_id,
-					lchan->abis_ip.ass_compl.speech_mode,
-					&rtp_addr,
-					&sc,
-					NULL);
-
-	if (!resp) {
-		LOGP(DMSC, LOGL_ERROR, "Failed to generate assignment completed message!\n"); \
-		return -EINVAL;
-	}
-
-	return osmo_bsc_sigtran_send(conn->sccp_con, resp);
-}
 
 static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
 				 void *handler_data, void *signal_data)
@@ -124,11 +87,9 @@
 			 * IPA based base stations. See also osmo_bsc_api.c,
 			 * function bsc_assign_compl() */
 			LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL (POSTPONED)\n");
-			if (send_aoip_ass_compl(con, lchan) != 0)
-				return -EINVAL;
+			mgcp_ass_complete(con->sccp_con->mgcp_ctx, lchan);
 		}
 		break;
-	break;
 	}
 
 	return 0;
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
index 4311250..e474b98 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -25,6 +25,7 @@
 #include <osmocom/bsc/debug.h>
 #include <osmocom/bsc/bsc_subscriber.h>
 #include <osmocom/legacy_mgcp/mgcp.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
 #include <osmocom/bsc/paging.h>
 #include <osmocom/bsc/gsm_04_08_utils.h>
 
@@ -321,14 +322,29 @@
 		conn->conn = NULL;
 	}
 
-	/* send the clear complete message */
+	/* generate the clear complete message */
 	resp = gsm0808_create_clear_complete();
 	if (!resp) {
 		LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n");
 		return -1;
 	}
 
-	osmo_bsc_sigtran_send(conn, resp);
+	if (conn->mgcp_ctx) {
+		/* NOTE: This is the AoIP case, osmo-bsc has to negotiate with
+		 * the MGCP-GW. For this an mgcp_ctx should be created that
+		 * contains the FSM and some system data. When the connection
+		 * is removed from the MGCP-GW, then osmo_bsc_sigtran_send()
+		 * calls osmo_bsc_sigtran_send(). */
+	        mgcp_clear_complete(conn->mgcp_ctx, resp);
+		conn->mgcp_ctx = NULL;
+	} else {
+		/* NOTE: This is the SCCP-Lite case, since we do not handle
+		 * the MGCP-GW switching ourselves, we may skip everything
+		 * that is MGCP-GW related and sent the clear complete message
+		 * directly */
+		osmo_bsc_sigtran_send(conn, resp);
+	}
+
 	return 0;
 }
 
@@ -426,7 +442,6 @@
 	int port, full_rate = -1;
 	bool aoip = false;
 	struct sockaddr_storage rtp_addr;
-	struct sockaddr_in *rtp_addr_in;
 	struct gsm0808_channel_type ct;
 	struct gsm0808_speech_codec_list scl;
 	struct gsm0808_speech_codec_list *scl_ptr = NULL;
@@ -531,28 +546,39 @@
 	       get_value_string(gsm48_chan_mode_names, chan_mode),
 	       ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len));
 
-	if (aoip == false) {
-		/* map it to a MGCP Endpoint and a RTP port */
+	/* Forward the assingment request to lower layers */
+	if (aoip) {
+		/* Store network side RTP connection information, we will
+		 * process this address later after we have established an RTP
+		 * connection to the BTS. This is just for organizational
+		 * reasons, functional wise it would not matter when exactly
+		 * the network side RTP connection is made, as long it is made
+		 * before we return with the assignment complete message. */
+		memcpy(&conn->aoip_rtp_addr_remote, &rtp_addr, sizeof(rtp_addr));
+
+		/* Create an assignment request using the MGCP fsm. This FSM
+		 * is directly started when its created (now) and will also
+		 * take care about the further processing (creating RTP
+		 * endpoints, calling gsm0808_assign_req(), rsponding to
+		 * the assignment request etc... */
+		conn->mgcp_ctx = mgcp_assignm_req(msc->network, msc->network->mgw.client, conn, chan_mode, full_rate);
+		if (!conn->mgcp_ctx) {
+			LOGP(DMSC, LOGL_ERROR, "MGCP GW failure, rejecting assignment... (id=%i)\n", conn->conn_id);
+			goto reject;
+		}
+
+		/* We now may return here, the FSM will do all further work */
+		return 0;
+	} else {
+		/* Note: In the sccp-lite case we to not perform any mgcp operation,
+		 * (the MSC does that for us). We set conn->rtp_ip to 0 and check
+		 * on this later. By this we know that we have to behave accordingly
+		 * to sccp-lite. */
 		port = mgcp_timeslot_to_endpoint(multiplex, timeslot);
 		conn->rtp_port = rtp_calculate_port(port, msc->rtp_base);
 		conn->rtp_ip = 0;
-	} else {
-		/* use address / port supplied with the AoIP
-		 * transport address element */
-		if (rtp_addr.ss_family == AF_INET) {
-			rtp_addr_in = (struct sockaddr_in *)&rtp_addr;
-			conn->rtp_port = osmo_ntohs(rtp_addr_in->sin_port);
-			memcpy(&conn->rtp_ip, &rtp_addr_in->sin_addr.s_addr,
-			       IP_V4_ADDR_LEN);
-			conn->rtp_ip = osmo_ntohl(conn->rtp_ip);
-		} else {
-			LOGP(DMSC, LOGL_ERROR,
-			     "Unsopported addressing scheme. (supports only IPV4)\n");
-			goto reject;
-		}
+		return gsm0808_assign_req(conn->conn, chan_mode, full_rate);
 	}
-
-	return gsm0808_assign_req(conn->conn, chan_mode, full_rate);
 
 reject:
 	resp =
@@ -729,3 +755,39 @@
 
 	return -1;
 }
+
+/* Generate and send assignment complete message */
+int bssmap_send_aoip_ass_compl(struct gsm_lchan *lchan)
+{
+	struct msgb *resp;
+	struct gsm0808_speech_codec sc;
+	struct gsm_subscriber_connection *conn;
+
+	conn = lchan->conn;
+
+	OSMO_ASSERT(lchan->abis_ip.ass_compl.valid);
+	OSMO_ASSERT(conn);
+	OSMO_ASSERT(conn->sccp_con);
+
+	LOGP(DMSC, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp_con->conn_id);
+
+	/* Extrapolate speech codec from speech mode */
+	gsm0808_speech_codec_from_chan_type(&sc, lchan->abis_ip.ass_compl.speech_mode);
+
+	/* Generate message */
+	resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause,
+					lchan->abis_ip.ass_compl.chosen_channel,
+					lchan->abis_ip.ass_compl.encr_alg_id,
+					lchan->abis_ip.ass_compl.speech_mode,
+					&conn->sccp_con->aoip_rtp_addr_local,
+					&sc,
+					NULL);
+
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n",
+		     conn->sccp_con->conn_id);
+		return -EINVAL;
+	}
+
+	return osmo_bsc_sigtran_send(conn->sccp_con, resp);
+}
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index 730e1db..5d25701 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -44,6 +44,7 @@
 #include <osmocom/abis/abis.h>
 
 #include <osmocom/sccp/sccp.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
 
 #define _GNU_SOURCE
 #include <getopt.h>
@@ -206,6 +207,9 @@
 		exit(1);
 	}
 
+	bsc_gsmnet->mgw.conf = talloc_zero(bsc_gsmnet, struct mgcp_client_conf);
+	mgcp_client_conf_init(bsc_gsmnet->mgw.conf);
+
 	bts_init();
 	libosmo_abis_init(tall_bsc_ctx);
 
@@ -274,6 +278,15 @@
 		}
 	}
 
+	bsc_gsmnet->mgw.client = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf);
+
+	if (mgcp_client_connect(bsc_gsmnet->mgw.client)) {
+		LOGP(DNM, LOGL_ERROR, "MGW connect failed at (%s:%u)\n",
+		     bsc_gsmnet->mgw.conf->remote_addr,
+		     bsc_gsmnet->mgw.conf->remote_port);
+		exit(1);
+	}
+
 	if (osmo_bsc_sigtran_init(&bsc_gsmnet->bsc_data->mscs) != 0) {
 		LOGP(DNM, LOGL_ERROR, "Failed to initalize sigtran backhaul.\n");
 		exit(1);
diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c
new file mode 100644
index 0000000..0ba021e
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_mgcp.c
@@ -0,0 +1,853 @@
+/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * 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/mgcp_client/mgcp_client.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/core/byteswap.h>
+#include <arpa/inet.h>
+
+#define CONN_ID_BTS 1
+#define CONN_ID_NET 2
+
+#define MGCP_MGW_TIMEOUT 4	/* in seconds */
+#define MGCP_MGW_TIMEOUT_TIMER_NR 1
+#define MGCP_BSS_TIMEOUT 4	/* in seconds */
+#define MGCP_BSS_TIMEOUT_TIMER_NR 2
+
+#define MGCP_ENDPOINT_FORMAT "%i at mgw"
+
+/* Some internal cause codes to indicate fault
+ * condition inside the FSM */
+enum int_cause_code {
+	MGCP_ERR_MGW_FAIL,
+	MGCP_ERR_MGW_INVAL_RESP,
+	MGCP_ERR_MGW_TX_FAIL,
+	MGCP_ERR_UNEXP_TEARDOWN,
+	MGCP_ERR_ASSGMNT_FAIL,
+	MGCP_ERR_UNSUPP_ADDR_FMT,
+	MGCP_ERR_BSS_TIMEOUT
+};
+
+/* Human readable respresentation of the faul codes,
+ * will be displayed by handle_error() */
+static const struct value_string int_cause_codes_str[] = {
+	{MGCP_ERR_MGW_FAIL, "operation failed on MGW"},
+	{MGCP_ERR_MGW_INVAL_RESP, "invalid / unparseable response from MGW"},
+	{MGCP_ERR_MGW_INVAL_RESP, "failed to transmit MGCP message to MGW"},
+	{MGCP_ERR_UNEXP_TEARDOWN, "unexpected connection teardown (BSS)"},
+	{MGCP_ERR_ASSGMNT_FAIL, "assignment failure (BSS)"},
+	{MGCP_ERR_UNSUPP_ADDR_FMT, "unsupported network address format used (BSS)"},
+	{MGCP_ERR_BSS_TIMEOUT, "assignment could not be completed in time (BSS)"},
+	{0, NULL}
+};
+
+enum fsm_states {
+	ST_CRCX_BTS,
+	ST_ASSIGN_PROC,
+	ST_MDCX_BTS,
+	ST_CRCX_NET,
+	ST_ASSIGN_COMPL,
+	ST_DLCX,
+	ST_HALT
+};
+
+static const struct value_string fsm_state_names[] = {
+	{ST_CRCX_BTS, "ST_CRCX_BTS (send CRCX for BTS)"},
+	{ST_ASSIGN_PROC, "ST_ASSIGN_PROC (continue assignment)"},
+	{ST_MDCX_BTS, "ST_MDCX_BTS (send MDCX for BTS)"},
+	{ST_CRCX_NET, "ST_CRCX_NET (send CRCX for NET)"},
+	{ST_ASSIGN_COMPL, "ST_ASSIGN_COMPL (complete assignment)"},
+	{ST_DLCX, "ST_DLCX (delete all rtp connections)"},
+	{ST_HALT, "ST_HALT (destroy state machine)"},
+	{0, NULL}
+};
+
+enum fsm_evt {
+	/* Initial event: start off the state machine */
+	EV_INIT,
+
+	/* External event: Assignment complete, event is issued shortly before
+	 * the assignment complete message is sent via the A-Interface */
+	EV_ASS_COMPLETE,
+
+	/* External event: Teardown event, this event is used to notify the end
+	 * of a. It is also issued in case of errors to teardown a half open
+	 * connection. */
+	EV_TEARDOWN,
+
+	/* Internal event: The mgcp_gw has sent its CRCX response for
+	 * the BTS side */
+	EV_CRCX_BTS_RESP,
+
+	/* Internal event: The mgcp_gw has sent its MDCX response for
+	 * the BTS side */
+	EV_MDCX_BTS_RESP,
+
+	/* Internal event: The mgcp_gw has sent its CRCX response for
+	 * the NET side */
+	EV_CRCX_NET_RESP,
+
+	/* Internal event: The mgcp_gw has sent its DLCX response for
+	 * the NET and BTS side */
+	EV_DLCX_ALL_RESP,
+};
+
+static const struct value_string fsm_evt_names[] = {
+	{EV_INIT, "EV_INIT (start state machine (send CRCX for BTS)"},
+	{EV_ASS_COMPLETE, "EV_ASS_COMPLETE (assignment complete)"},
+	{EV_TEARDOWN, "EV_TEARDOWN (teardown all connections)"},
+	{EV_CRCX_BTS_RESP, "EV_CRCX_BTS_RESP (got CRCX reponse for BTS)"},
+	{EV_MDCX_BTS_RESP, "EV_MDCX_BTS_RESP (got MDCX reponse for BTS)"},
+	{EV_CRCX_NET_RESP, "EV_CRCX_NET_RESP (got CRCX reponse for NET)"},
+	{EV_DLCX_ALL_RESP, "EV_DLCX_ALL_RESP (got DLCX reponse for BTS/NET)"},
+	{0, NULL}
+};
+
+/* A general error handler function. On error we still have an interest to
+ * remove a half open connection (if possible). This function will execute
+ * a controlled jump to the DLCX phase. From there, the FSM will then just
+ * continue like the call were ended normally */
+static void handle_error(struct mgcp_ctx *mgcp_ctx, enum int_cause_code cause)
+{
+	struct osmo_fsm_inst *fi;
+	struct osmo_bsc_sccp_con *conn;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+
+	fi = mgcp_ctx->fsm;
+	OSMO_ASSERT(fi);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "fsm-state: %s\n", get_value_string(fsm_state_names, fi->state));
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "%s -- graceful shutdown...\n",
+		 get_value_string(int_cause_codes_str, cause));
+
+	/* Set the VM into the state where it waits for the call end */
+	osmo_fsm_inst_state_chg(fi, ST_DLCX, 0, 0);
+
+	/* Simulate the call end by sending a teardown event, so that
+	 * the FSM proceeds directly with the DLCX */
+	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
+}
+
+static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_CRCX_BTS: startup state machine send out CRCX for BTS side */
+static void fsm_crcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+	struct osmo_bsc_sccp_con *conn;
+	struct msgb *msg;
+	struct mgcp_msg mgcp_msg;
+	struct mgcp_client *mgcp;
+	uint16_t rtp_endpoint;
+	int rc;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+	mgcp = mgcp_ctx->mgcp;
+	OSMO_ASSERT(mgcp);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "fsm-state: %s, fsm-event: %s\n",
+		 get_value_string(fsm_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+	rtp_endpoint = mgcp_client_next_endpoint(mgcp);
+	mgcp_ctx->rtp_endpoint = rtp_endpoint;
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "creating connection for the BTS side on " "MGW endpoint:%x...\n", rtp_endpoint);
+
+	/* Generate MGCP message string */
+	mgcp_msg = (struct mgcp_msg) {
+		.verb = MGCP_VERB_CRCX,
+		.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
+			     MGCP_MSG_PRESENCE_CONN_MODE),
+		.call_id = conn->conn_id,
+		.conn_id = CONN_ID_BTS,
+		.conn_mode = MGCP_CONN_LOOPBACK
+	};
+	snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, rtp_endpoint);
+	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+	OSMO_ASSERT(msg);
+
+	/* Transmit MGCP message to MGW */
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: transmitting MGCP message to MGW...\n");
+	rc = mgcp_client_tx(mgcp, msg, crcx_for_bts_resp_cb, mgcp_ctx);
+	if (rc < 0) {
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+		return;
+	}
+
+	osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_ASSIGN_PROC, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for BTS associated CRCX */
+static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv)
+{
+	struct mgcp_ctx *mgcp_ctx = priv;
+	int rc;
+	struct osmo_bsc_sccp_con *conn;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+
+	if (r->head.response_code != 200) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+			 "CRCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
+		return;
+	}
+
+	rc = mgcp_response_parse_params(r);
+	if (rc) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/BTS: Cannot parse response\n");
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
+		return;
+	}
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
+
+	/* Set the connection details in the conn struct. The code that
+	 * controls the BTS via RSL will take these values and signal them
+	 * to the BTS via RSL/IPACC */
+	conn->rtp_port = r->audio_port;
+	conn->rtp_ip = osmo_ntohl(inet_addr(r->audio_ip));
+
+	/* Notify the FSM that we got the response. */
+	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_BTS_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_ASSIGN_PROC: An mgcp response has been received, proceed
+ * with the assignment request */
+static void fsm_proc_assignmnent_req_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+	struct osmo_bsc_sccp_con *conn;
+	enum gsm48_chan_mode chan_mode;
+	bool full_rate;
+	int rc;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "fsm-state: %s, fsm-event: %s\n",
+		 get_value_string(fsm_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+	/* Bail on teardown */
+	if (event == EV_TEARDOWN) {
+		handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+		return;
+	}
+
+	OSMO_ASSERT(conn->conn);
+	chan_mode = mgcp_ctx->chan_mode;
+	full_rate = mgcp_ctx->full_rate;
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MGW proceeding assignment request...\n");
+	rc = gsm0808_assign_req(conn->conn, chan_mode, full_rate);
+
+	if (rc < 0) {
+		handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL);
+		return;
+	}
+
+	osmo_fsm_inst_state_chg(fi, ST_MDCX_BTS, MGCP_BSS_TIMEOUT, MGCP_BSS_TIMEOUT_TIMER_NR);
+}
+
+static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_MDCX_BTS: When the BSS has completed the assignment,
+ * proceed with updating the connection for the BTS side */
+static void fsm_mdcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+	struct osmo_bsc_sccp_con *conn;
+	struct gsm_lchan *lchan;
+	struct msgb *msg;
+	struct mgcp_msg mgcp_msg;
+	struct mgcp_client *mgcp;
+	uint16_t rtp_endpoint;
+	struct in_addr addr;
+	int rc;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "fsm-state: %s, fsm-event: %s\n",
+		 get_value_string(fsm_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+	/* Bail on teardown */
+	if (event == EV_TEARDOWN) {
+		handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+		return;
+	}
+
+	mgcp = mgcp_ctx->mgcp;
+	OSMO_ASSERT(mgcp);
+	lchan = mgcp_ctx->lchan;
+	OSMO_ASSERT(lchan);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "BSS has completed the assignment, now prceed with MDCX towards BTS...\n");
+
+	rtp_endpoint = mgcp_ctx->rtp_endpoint;
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "completing connection for the BTS side on " "MGW endpoint:%x...\n", rtp_endpoint);
+
+	addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip);
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "BTS expects RTP input on address %s:%u\n", inet_ntoa(addr), lchan->abis_ip.bound_port);
+
+	/* Generate MGCP message string */
+	mgcp_msg = (struct mgcp_msg) {
+		.verb = MGCP_VERB_MDCX,
+		.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
+			     MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
+		.call_id = conn->conn_id,
+		.conn_id = CONN_ID_BTS,.conn_mode = MGCP_CONN_RECV_SEND,
+		.audio_ip = inet_ntoa(addr),
+		.audio_port = lchan->abis_ip.bound_port
+	};
+	snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint);
+	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+	OSMO_ASSERT(msg);
+
+	/* Transmit MGCP message to MGW */
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS: transmitting MGCP message to MGW...\n");
+	rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_resp_cb, mgcp_ctx);
+	if (rc < 0) {
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+		return;
+	}
+
+	osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_CRCX_NET, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for BTS associated MDCX */
+static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv)
+{
+	struct mgcp_ctx *mgcp_ctx = priv;
+	int rc;
+	struct in_addr addr;
+	struct gsm_lchan *lchan;
+
+	OSMO_ASSERT(mgcp_ctx);
+	lchan = mgcp_ctx->lchan;
+	OSMO_ASSERT(lchan);
+
+	if (r->head.response_code != 200) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+			 "MDCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
+		return;
+	}
+
+	rc = mgcp_response_parse_params(r);
+	if (rc) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "MDCX/BTS: Cannot parse MDCX response\n");
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
+		return;
+	}
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
+
+	addr.s_addr = lchan->abis_ip.bound_ip;
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "MDCX/BTS: corresponding lchan has been bound to address %s:%u\n",
+		 inet_ntoa(addr), lchan->abis_ip.bound_port);
+
+	/* Notify the FSM that we got the response. */
+	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_RESP, mgcp_ctx);
+}
+
+static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_CRCX_NET: An mgcp response has been received, proceed... */
+static void fsm_crcx_net_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+	struct osmo_bsc_sccp_con *conn;
+	struct msgb *msg;
+	struct mgcp_msg mgcp_msg;
+	struct mgcp_client *mgcp;
+	uint16_t rtp_endpoint;
+	struct sockaddr_in *sin;
+	char *addr;
+	uint16_t port;
+	int rc;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+	mgcp = mgcp_ctx->mgcp;
+	OSMO_ASSERT(mgcp);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "fsm-state: %s, fsm-event: %s\n",
+		 get_value_string(fsm_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+	rtp_endpoint = mgcp_ctx->rtp_endpoint;
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "creating connection for the NET side on " "MGW endpoint:%x...\n", rtp_endpoint);
+
+	/* Currently we only have support for IPv4 in our MGCP software, the
+	 * AoIP part is ready to support IPv6 in theory, because the IE
+	 * parser/generator uses sockaddr_storage for the AoIP transport
+	 * identifier. However, the MGCP-GW does not support IPv6 yet. This is
+	 * why we stop here in case some MSC tries to signal IPv6 AoIP
+	 * transport identifiers */
+	if (conn->aoip_rtp_addr_remote.ss_family != AF_INET) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+			 "endpoint:%x MSC uses unsupported address format in AoIP transport identifier -- aborting...\n",
+			 rtp_endpoint);
+		handle_error(mgcp_ctx, MGCP_ERR_UNSUPP_ADDR_FMT);
+		return;
+	}
+
+	sin = (struct sockaddr_in *)&conn->aoip_rtp_addr_remote;
+	addr = inet_ntoa(sin->sin_addr);
+	port = osmo_ntohs(sin->sin_port);
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MSC expects RTP input on address %s:%u\n", addr, port);
+
+	/* Generate MGCP message string */
+	mgcp_msg = (struct mgcp_msg) {
+		.verb = MGCP_VERB_CRCX,
+		.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
+			     MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
+		.call_id = conn->conn_id,
+		.conn_id = CONN_ID_NET,
+		.conn_mode = MGCP_CONN_RECV_SEND,
+		.audio_ip = addr,
+		.audio_port = port
+	};
+	snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint);
+	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+	OSMO_ASSERT(msg);
+
+	/* Transmit MGCP message to MGW */
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: transmitting MGCP message to MGW...\n");
+	rc = mgcp_client_tx(mgcp, msg, crcx_for_net_resp_cb, mgcp_ctx);
+	if (rc < 0) {
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+		return;
+	}
+
+	osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_ASSIGN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for NET associated CRCX */
+static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv)
+{
+	struct mgcp_ctx *mgcp_ctx = priv;
+	int rc;
+	struct osmo_bsc_sccp_con *conn;
+	struct gsm_lchan *lchan;
+	struct sockaddr_in *sin;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+	lchan = mgcp_ctx->lchan;
+	OSMO_ASSERT(lchan);
+
+	if (r->head.response_code != 200) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+			 "CRCX/NET: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
+		return;
+	}
+
+	rc = mgcp_response_parse_params(r);
+	if (rc) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/NET: Cannot parse CRCX response\n");
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
+		return;
+	}
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
+
+	/* Store address */
+	sin = (struct sockaddr_in *)&conn->aoip_rtp_addr_local;
+	sin->sin_family = AF_INET;
+	sin->sin_addr.s_addr = inet_addr(r->audio_ip);
+	sin->sin_port = osmo_ntohs(r->audio_port);
+
+	/* Notify the FSM that we got the response. */
+	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_NET_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_ASSIGN_COMPL: Send back assignment complete and wait until the call ends */
+static void fsm_send_assignment_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+	struct gsm_lchan *lchan;
+
+	OSMO_ASSERT(mgcp_ctx);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "fsm-state: %s, fsm-event: %s\n",
+		 get_value_string(fsm_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+	/* Bail on teardown */
+	if (event == EV_TEARDOWN) {
+		handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+		return;
+	}
+
+	lchan = mgcp_ctx->lchan;
+	OSMO_ASSERT(lchan);
+
+	/* Send assignment completion message via AoIP, this will complete
+	 * the circuit. The message will also contain the port and IP-Address
+	 * where the MGW expects the RTP input from the MSC side */
+	bssmap_send_aoip_ass_compl(lchan);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "call in progress, waiting for call end...\n");
+
+	osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_DLCX, 0, 0);
+}
+
+static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_DLCX: Remove connection for the BTS and NET side. */
+static void fsm_dlcx_all_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct mgcp_ctx *mgcp_ctx = data;
+	struct osmo_bsc_sccp_con *conn;
+	struct msgb *msg;
+	struct mgcp_msg mgcp_msg;
+	struct mgcp_client *mgcp;
+	uint16_t rtp_endpoint;
+	int rc;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+	mgcp = mgcp_ctx->mgcp;
+	OSMO_ASSERT(mgcp);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "fsm-state: %s, fsm-event: %s\n",
+		 get_value_string(fsm_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+	rtp_endpoint = mgcp_ctx->rtp_endpoint;
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "removing connection for the BTS and NET side on " "MGW endpoint:%x...\n", rtp_endpoint);
+
+	/* We now relase the endpoint back to the pool in order to allow
+	 * other connections to use this endpoint */
+	mgcp_client_release_endpoint(rtp_endpoint, mgcp);
+
+	/* Generate MGCP message string */
+	mgcp_msg = (struct mgcp_msg) {
+		.verb = MGCP_VERB_DLCX,
+		.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID),
+		.call_id = conn->conn_id
+	};
+	snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint);
+	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+	OSMO_ASSERT(msg);
+
+	/* Transmit MGCP message to MGW */
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "DLCX: transmitting MGCP message to MGW...\n");
+	rc = mgcp_client_tx(mgcp, msg, dlcx_for_all_resp_cb, mgcp_ctx);
+	if (rc < 0) {
+		handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+		return;
+	}
+
+	osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_HALT, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for NET associated CRCX */
+static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv)
+{
+	struct mgcp_ctx *mgcp_ctx = priv;
+	struct osmo_bsc_sccp_con *conn;
+	struct mgcp_client *mgcp;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+	mgcp = mgcp_ctx->mgcp;
+	OSMO_ASSERT(mgcp);
+
+	/* Note: We check the return code, but in case of an error there is
+	 * not much that can be done to recover. However, at least we tryed
+	 * to remove the connection (if there was even any) */
+	if (r->head.response_code != 200) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+			 "DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+	}
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "DLCX: MGW has acknowledged the removal of the connections\n");
+
+	/* Notify the FSM that we got the response. */
+	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_DLCX_ALL_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_HALT: Terminate the state machine */
+static void fsm_halt_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+	struct osmo_bsc_sccp_con *conn;
+
+	OSMO_ASSERT(mgcp_ctx);
+	conn = mgcp_ctx->conn;
+	OSMO_ASSERT(conn);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+		 "fsm-state: %s, fsm-event: %s\n",
+		 get_value_string(fsm_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+	/* Send pending sigtran message */
+	if (mgcp_ctx->resp) {
+		LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "sending pending sigtran response message...\n");
+		osmo_bsc_sigtran_send(conn, mgcp_ctx->resp);
+		mgcp_ctx->resp = NULL;
+	}
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "state machine halted\n");
+
+	/* Destroy the state machine and all context information */
+	osmo_fsm_inst_free(mgcp_ctx->fsm);
+	talloc_free(mgcp_ctx);
+}
+
+/* Timer callback to shut down in case of connectivity problems */
+static int fsm_timeout_cb(struct osmo_fsm_inst *fi)
+{
+	struct mgcp_ctx *mgcp_ctx = fi->priv;
+	struct mgcp_client *mgcp;
+
+	OSMO_ASSERT(mgcp_ctx);
+	mgcp = mgcp_ctx->mgcp;
+	OSMO_ASSERT(mgcp);
+
+	LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+		 "timeout (T%i) in state %s, attempting graceful teardown...\n",
+		 fi->T, get_value_string(fsm_state_names, fi->state));
+
+	/* Ensure that no sigtran response, is present. Otherwiese we might try
+	 * to send a sigtran response when the sccp connection is already freed. */
+	mgcp_ctx->resp = NULL;
+
+	if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) {
+		/* Note: We were unable to communicate with the MGCP-GW,
+		 * unfortunately there is no meaningful action we can take
+		 * now other than giving up. */
+		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "graceful teardown not possible, terminating...\n");
+
+		/* At least release the occupied endpoint ID */
+		mgcp_client_release_endpoint(mgcp_ctx->rtp_endpoint, mgcp);
+
+		/* Initiate self destruction of the FSM */
+		osmo_fsm_inst_state_chg(fi, ST_HALT, 0, 0);
+		osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
+	} else if (fi->T == MGCP_BSS_TIMEOUT_TIMER_NR)
+		/* Note: If the logic that controls the BSS is unable to
+		 * negotiate a connection, we presumably still have a
+		 * working connection to the MGCP-GW, we will try to
+		 * shut down gracefully. */
+		handle_error(mgcp_ctx, MGCP_ERR_BSS_TIMEOUT);
+	else {
+		/* Note: Ther must not be any unsolicited timers
+		 * in this FSM. If so, we have serious problem. */
+		OSMO_ASSERT(false);
+	}
+
+	return 0;
+}
+
+static struct osmo_fsm_state fsm_states[] = {
+
+	/* Startup state machine, send CRCX to BTS. */
+	[ST_CRCX_BTS] = {
+			 .in_event_mask = (1 << EV_INIT),
+			 .out_state_mask = (1 << ST_ASSIGN_PROC),
+			 .name = "ST_CRCX_BTS",
+			 .action = fsm_crcx_bts_cb,
+			 },
+
+	/* When the CRCX response for the BTS side is received, then
+	 * proceed the assignment on the BSS side. */
+	[ST_ASSIGN_PROC] = {
+			    .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_CRCX_BTS_RESP),
+			    .out_state_mask = (1 << ST_DLCX) | (1 << ST_MDCX_BTS),
+			    .name = "ST_ASSIGN_PROC",
+			    .action = fsm_proc_assignmnent_req_cb,
+			    },
+
+	/* When the BSS has processed the assignment request,
+	 * then send the MDCX command for the BTS side in order to
+	 * update the connections with the actual PORT/IP where the
+	 * BTS expects the RTP input. */
+	[ST_MDCX_BTS] = {
+			 .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_ASS_COMPLETE),
+			 .out_state_mask = (1 << ST_DLCX) | (1 << ST_CRCX_NET),
+			 .name = "ST_MDCX_BTS",
+			 .action = fsm_mdcx_bts_cb,
+			 },
+
+	/* When the MDCX response for the BTS siede is received, then
+	 * directly proceed with sending the CRCX command to connect the
+	 * network side. This is done in one phase (no MDCX needed). */
+	[ST_CRCX_NET] = {
+			 .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_MDCX_BTS_RESP),
+			 .out_state_mask = (1 << ST_DLCX) | (1 << ST_ASSIGN_COMPL),
+			 .name = "ST_CRCX_NET",
+			 .action = fsm_crcx_net_cb,
+			 },
+
+	/* When the CRCX response for the NET side is received. Then
+	 * send the assignment complete message via the A-Interface and
+	 * enter wait state in order to wait for the end of the call. */
+	[ST_ASSIGN_COMPL] = {
+			     .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_CRCX_NET_RESP),
+			     .out_state_mask = (1 << ST_DLCX),
+			     .name = "ST_ASSIGN_COMPL",
+			     .action = fsm_send_assignment_complete,
+			     },
+
+	/* When the call ends, remove all RTP connections from the
+	 * MGCP-GW by sending a wildcarded DLCX. */
+	[ST_DLCX] = {
+		     .in_event_mask = (1 << EV_TEARDOWN),
+		     .out_state_mask = (1 << ST_HALT),
+		     .name = "ST_DLCX",
+		     .action = fsm_dlcx_all_cb,
+		     },
+
+	/* When the MGCP_GW confirms that the connections are terminated,
+	 * then halt the state machine. */
+	[ST_HALT] = {
+		     .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_DLCX_ALL_RESP),
+		     .out_state_mask = 0,
+		     .name = "ST_HALT",
+		     .action = fsm_halt_cb,
+		     },
+};
+
+/* State machine definition */
+static struct osmo_fsm fsm = {
+	.name = "MGW",
+	.states = fsm_states,
+	.num_states = ARRAY_SIZE(fsm_states),
+	.log_subsys = DMGCP,
+	.timer_cb = fsm_timeout_cb,
+};
+
+/* Notify that the a new call begins. This will create a connection for the
+ * BTS on the MGCP-GW and set up the port numbers in struct osmo_bsc_sccp_con.
+ * After that gsm0808_assign_req() to proceed.
+ * Parameter:
+ * ctx: talloc context
+ * network: associated gsm network
+ * conn: associated sccp connection
+ * chan_mode: channel mode (system data, passed through)
+ * full_rate: full rate flag (system data, passed through)
+ * Returns an mgcp_context that contains system data and the OSMO-FSM */
+struct mgcp_ctx *mgcp_assignm_req(void *ctx, struct mgcp_client *mgcp, struct osmo_bsc_sccp_con *conn,
+				  enum gsm48_chan_mode chan_mode, bool full_rate)
+{
+	struct mgcp_ctx *mgcp_ctx;
+	char name[32];
+	static bool fsm_registered = false;
+
+	OSMO_ASSERT(mgcp);
+	OSMO_ASSERT(conn);
+
+	/* Register the fsm description (if not already done) */
+	if (fsm_registered == false) {
+		osmo_fsm_register(&fsm);
+		fsm_registered = true;
+	}
+
+	/* Allocate and configure a new fsm instance */
+	mgcp_ctx = talloc_zero(ctx, struct mgcp_ctx);
+	OSMO_ASSERT(mgcp_ctx);
+
+	snprintf(name, sizeof(name), "MGW_%i", conn->conn_id);
+	mgcp_ctx->fsm = osmo_fsm_inst_alloc(&fsm, NULL, ctx, LOGL_DEBUG, name);
+	OSMO_ASSERT(mgcp_ctx->fsm);
+	mgcp_ctx->fsm->priv = mgcp_ctx;
+	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MGW handler fsm created\n");
+	mgcp_ctx->mgcp = mgcp;
+	mgcp_ctx->conn = conn;
+	mgcp_ctx->chan_mode = chan_mode;
+	mgcp_ctx->full_rate = full_rate;
+
+	/* start state machine */
+	OSMO_ASSERT(mgcp_ctx->fsm->state == ST_CRCX_BTS)
+	    osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_INIT, mgcp_ctx);
+
+	return mgcp_ctx;
+}
+
+/* Notify that the call has ended, remove all connections from the MGCP-GW,
+ * then send the clear complete message and destroy the FSM instance
+ * Parameter:
+ * mgcp_ctx: context information (FSM, and pointer to external system data)
+ * respmgcp_ctx: pending clear complete message to send via A-Interface */
+void mgcp_clear_complete(struct mgcp_ctx *mgcp_ctx, struct msgb *resp)
+{
+	OSMO_ASSERT(mgcp_ctx);
+	OSMO_ASSERT(resp);
+
+	mgcp_ctx->resp = resp;
+
+	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
+}
+
+/* Notify that the BSS ready, send the assingnment complete message when the
+ * mgcp connection is completed
+ * Parameter:
+ * mgcp_ctx: context information (FSM, and pointer to external system data)
+ * lchan: needed for sending the assignment complete message via A-Interface */
+void mgcp_ass_complete(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *lchan)
+{
+	OSMO_ASSERT(mgcp_ctx);
+	OSMO_ASSERT(lchan);
+
+	mgcp_ctx->lchan = lchan;
+
+	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASS_COMPLETE, mgcp_ctx);
+
+	return;
+}
diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c
index 951061a..59f5b3b 100644
--- a/src/osmo-bsc/osmo_bsc_sigtran.c
+++ b/src/osmo-bsc/osmo_bsc_sigtran.c
@@ -33,6 +33,7 @@
 #include <osmocom/bsc/osmo_bsc_sigtran.h>
 #include <osmocom/bsc/a_reset.h>
 #include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
 
 /* A pointer to a list with all involved MSCs
  * (a copy of the pointer location submitted with osmo_bsc_sigtran_init() */
diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c
index b5232c4..78fce4c 100644
--- a/src/osmo-bsc/osmo_bsc_vty.c
+++ b/src/osmo-bsc/osmo_bsc_vty.c
@@ -29,6 +29,8 @@
 #include <osmocom/core/talloc.h>
 #include <osmocom/vty/logging.h>
 #include <osmocom/sccp/sccp_types.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+
 
 #include <time.h>
 
@@ -972,6 +974,8 @@
 
 int bsc_vty_init_extra(void)
 {
+	struct gsm_network *net = bsc_gsmnet;
+	
 	install_element(CONFIG_NODE, &cfg_net_msc_cmd);
 	install_element(CONFIG_NODE, &cfg_net_bsc_cmd);
 
@@ -1036,5 +1040,7 @@
 
 	install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
 
+	mgcp_client_vty_init(net, MSC_NODE, net->mgw.conf);
+	
 	return 0;
 }

-- 
To view, visit https://gerrit.osmocom.org/4334
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newpatchset
Gerrit-Change-Id: Ia2882b7ca31a3219c676986e85045fa08a425d7a
Gerrit-PatchSet: 6
Gerrit-Project: osmo-bsc
Gerrit-Branch: master
Gerrit-Owner: dexter <pmaier at sysmocom.de>
Gerrit-Reviewer: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: Neels Hofmeyr <nhofmeyr at sysmocom.de>
Gerrit-Reviewer: dexter <pmaier at sysmocom.de>


More information about the gerrit-log mailing list