[PATCH] osmo-bsc[master]: introduce an osmo_fsm for gsm_subscriber_connection

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/.

dexter gerrit-no-reply at lists.osmocom.org
Mon Mar 12 15:22:35 UTC 2018


Hello Jenkins Builder,

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

    https://gerrit.osmocom.org/7142

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

introduce an osmo_fsm for gsm_subscriber_connection

In the current implementation of osmo-bsc, the subscriber connection is
not handled (very) statefully. However, there is some state keeping in the
code that handles the mgcp connection, but there are still to much loose ends
which allow odd situations to happen, which then lead severe error situations
(see also closes tags at the end) This commit adds a number of improvements
to fix those problems.

- Use an osmo-fsm to control the gsm_subscriber_connection state and
  make sure that certain operations can only take place at certain states
  (e.g let connection oriented SCCP traffic only pass when an SCCP connection
  actually exists.

  Remove the old osmo_bsc_mgcp.c code. Use the recently developed MGCP client
  FSM to handle the MGCP connections.

  Also make sure that stuff that already works does not break. This in
  particular refers to the internal handover capability and the respective
  unit-tests.

  See also OS#2823, OS#2768 and OS#2898

- Fix logic to permit assignment to a signalling channel. (OS#2762)

- Introduce T993210 to release lchan + subscr_conn if MSC fails to respond

  The GSM specs don't have an explicit timer for this, so let's introdcue
  a custom timer (hence starting with 99).

  This timeout catches the following situation:
  * we send a SCCP CR with COMPL_L3_INFO from the MS to the MSC,
  * the MSC doesn't respond (e.g. SCCP routing failure, program down, ...)

  The MS is supposed to timeout with T3210, 3220 or 3230.  But the BSC
  shouldn't trust the MS but have some timer on its own.

  SCCP would have a timer T(conn est), but that one is specified to be
  1-2min and hence rather long.

  See also: OS#2775

- Terminate bsc_subscr_conn_fsm on SCCP N-DISC.ind from MSC

  If the MSC is disconnecting the SCCP channel, we must terminate the FSM
  which in turn will release all lchan's and other state.

  This makes TC_chan_rel_hard_rlsd pass, see also OS#2731

  As a side-effect, this fixes TC_chan_act_ack_est_ind_refused(),
  where the MSC is answering with CREF to our CR/COMPL_L3.

- Release subscriber connection on RLL RELEASE IND of SAPI0 on main DCCH

  The subscriber connection isn't really useful for anything after the
  SAPI0 main signalling link has been released.  We could try to
  re-establish, but our best option is probably simply releasing the
  subscriber_conn and anything related to it.

  This will make TC_chan_rel_rll_rel_ind pass, see also OS#2730

This commit has been tested using the BSC_Tests TTCN3 testsuit and the
following tests were passed:

TC_chan_act_noreply
TC_chan_act_ack_noest
TC_chan_act_ack_est_ind_noreply
TC_chan_act_ack_est_ind_refused
TC_chan_act_nack
TC_chan_exhaustion
TC_ctrl
TC_chan_rel_conn_fail
TC_chan_rel_hard_clear
TC_chan_rel_hard_rlsd
TC_chan_rel_a_reset
TC_rll_est_ind_inact_lchan
TC_rll_est_ind_inval_sapi1
TC_rll_est_ind_inval_sapi3
TC_rll_est_ind_inval_sacch
TC_assignment_cic_only
TC_assignment_csd
TC_assignment_ctm
TC_assignment_fr_a5_0
TC_assignment_fr_a5_1_codec_missing
TC_assignment_fr_a5_1
TC_assignment_fr_a5_3
TC_assignment_fr_a5_4
TC_paging_imsi_nochan
TC_paging_tmsi_nochan
TC_paging_tmsi_any
TC_paging_tmsi_sdcch
TC_paging_tmsi_tch_f
TC_paging_tmsi_tch_hf
TC_paging_imsi_nochan_cgi
TC_paging_imsi_nochan_lac_ci
TC_paging_imsi_nochan_ci
TC_paging_imsi_nochan_lai
TC_paging_imsi_nochan_lac
TC_paging_imsi_nochan_all
TC_paging_imsi_nochan_plmn_lac_rnc
TC_paging_imsi_nochan_rnc
TC_paging_imsi_nochan_lac_rnc
TC_paging_imsi_nochan_lacs
TC_paging_imsi_nochan_lacs_empty
TC_paging_imsi_a_reset
TC_paging_counter
TC_rsl_drop_counter
TC_classmark
TC_unsol_ass_fail
TC_unsol_ass_compl
TC_unsol_ho_fail
TC_err_82_short_msg
TC_ho_int

Authors:
Harald Welte <laforge at gnumonks.org>
Philipp Maier <pmaier at sysmocom.de>
Neels Hofmeyr <neels at hofmeyr.de>

Closes: OS#2730
Closes: OS#2731
Closes: OS#2762
Closes: OS#2768
Closes: OS#2775
Closes: OS#2823
Closes: OS#2898
Closes: OS#2936
Change-Id: I68286d26e2014048b054f39ef29c35fef420cc97
---
M include/osmocom/bsc/Makefile.am
M include/osmocom/bsc/bsc_api.h
A include/osmocom/bsc/bsc_subscr_conn_fsm.h
M include/osmocom/bsc/gsm_data.h
M include/osmocom/bsc/handover.h
M include/osmocom/bsc/osmo_bsc.h
D include/osmocom/bsc/osmo_bsc_mgcp.h
M include/osmocom/bsc/osmo_bsc_sigtran.h
M src/ipaccess/Makefile.am
M src/libbsc/Makefile.am
M src/libbsc/abis_rsl.c
M src/libbsc/bsc_api.c
A src/libbsc/bsc_subscr_conn_fsm.c
M src/libbsc/handover_decision_2.c
M src/libbsc/handover_logic.c
M src/osmo-bsc/Makefile.am
M src/osmo-bsc/osmo_bsc_api.c
M src/osmo-bsc/osmo_bsc_audio.c
M src/osmo-bsc/osmo_bsc_bssap.c
M src/osmo-bsc/osmo_bsc_main.c
D src/osmo-bsc/osmo_bsc_mgcp.c
M src/osmo-bsc/osmo_bsc_sigtran.c
M src/utils/Makefile.am
M tests/abis/abis_test.c
M tests/bsc-nat/bsc_nat_test.c
M tests/bsc/bsc_test.c
M tests/bssap/Makefile.am
M tests/bssap/bssap_test.c
M tests/channel/channel_test.c
M tests/gsm0408/gsm0408_test.c
M tests/handover/Makefile.am
M tests/handover/handover_test.c
M tests/nanobts_omlattr/nanobts_omlattr_test.c
33 files changed, 1,625 insertions(+), 1,729 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/osmo-bsc refs/changes/42/7142/3

diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index 17e8bd3..b25dfd8 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -12,6 +12,7 @@
 	bsc_nat_sccp.h \
 	bsc_rll.h \
 	bsc_subscriber.h \
+	bsc_subscr_conn_fsm.h \
 	bss.h \
 	bts_ipaccess_nanobts_omlattr.h \
 	chan_alloc.h \
@@ -36,7 +37,6 @@
 	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/bsc_api.h b/include/osmocom/bsc/bsc_api.h
index 6ee0562..01f90b1 100644
--- a/include/osmocom/bsc/bsc_api.h
+++ b/include/osmocom/bsc/bsc_api.h
@@ -46,6 +46,9 @@
 	void (*conn_cleanup)(struct gsm_subscriber_connection *conn);
 };
 
+uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan);
+uint8_t chan_mode_to_speech(struct gsm_lchan *lchan);
+
 int bsc_api_init(struct gsm_network *network, struct bsc_api *api);
 int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, int link_id, int allow_sacch);
 int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate);
diff --git a/include/osmocom/bsc/bsc_subscr_conn_fsm.h b/include/osmocom/bsc/bsc_subscr_conn_fsm.h
new file mode 100644
index 0000000..234161f
--- /dev/null
+++ b/include/osmocom/bsc/bsc_subscr_conn_fsm.h
@@ -0,0 +1,74 @@
+#pragma once
+#include <osmocom/core/fsm.h>
+
+enum gscon_fsm_event {
+	/* local SCCP stack tells us incoming conn from MSC */
+	GSCON_EV_A_CONN_IND,
+	/* RSL side requests CONNECT to MSC */
+	GSCON_EV_A_CONN_REQ,
+	/* MSC confirms the SCCP connection */
+	GSCON_EV_A_CONN_CFM,
+	/* MSC requests assignment */
+	GSCON_EV_A_ASSIGNMENT_CMD,
+	/* MSC has sent BSSMAP CLEAR CMD */
+	GSCON_EV_A_CLEAR_CMD,
+	/* MSC SCCP disconnect indication */
+	GSCON_EV_A_DISC_IND,
+	/* MSC sends Handover Request (in CR) */
+	GSCON_EV_A_HO_REQ,
+
+	/* RR ASSIGNMENT COMPLETE received */
+	GSCON_EV_RR_ASS_COMPL,
+	/* RR ASSIGNMENT FAIL received */
+	GSCON_EV_RR_ASS_FAIL,
+	/* RR MODE MODIFY ACK received */
+	GSCON_EV_RR_MODE_MODIFY_ACK,
+
+	/* RR HO ACC (access burst on ext HO) */
+	GSCON_EV_RR_HO_ACC,	/* NOT USED YET! */
+	/* RR HANDOVER COMPLETE received */
+	GSCON_EV_RR_HO_COMPL,	/* NOT USED YET! */
+	GSCON_EV_RR_HO_FAIL,	/* NOT USED YET! */
+
+	/* RSL RLL Release Indication */
+	GSCON_EV_RLL_REL_IND,
+	/* RSL CONNection FAILure Indication */
+	GSCON_EV_RSL_CONN_FAIL,
+
+	/* RSL/lchan tells us clearing is complete */
+	GSCON_EV_RSL_CLEAR_COMPL,
+
+	/* Mobile-originated DTAP (from MS) */
+	GSCON_EV_MO_DTAP,
+	/* Mobile-terminated DTAP (from MSC) */
+	GSCON_EV_MT_DTAP,
+
+	/* Transmit custom SCCP message */
+	GSCON_EV_TX_SCCP,
+
+	/* MGW is indicating failure (BTS) */
+	GSCON_EV_MGW_FAIL_BTS,
+	/* MGW is indicating failure (MSC) */
+	GSCON_EV_MGW_FAIL_MSC,
+	/* CRCX response received (BTS) */
+	GSCON_EV_MGW_CRCX_RESP_BTS,
+	/* MDCX response received (BTS) */
+	GSCON_EV_MGW_MDCX_RESP_BTS,
+	/* CRCX response received (MSC) */
+	GSCON_EV_MGW_CRCX_RESP_MSC,
+
+	/* Internal handover request (intra-BSC handover) */
+	GSCON_EV_HO_START,
+	/* Handover timed out (T3103 in handover_logic.c) */
+	GSCON_EV_HO_TIMEOUT,
+	/* Handover failed (handover_logic.c) */
+	GSCON_EV_HO_FAIL,
+	/* Handover completed successfully (handover_logic.c) */
+	GSCON_EV_HO_COMPL,
+};
+
+struct gsm_subscriber_connection;
+struct gsm_network;
+
+/* Allocate a subscriber connection and its associated FSM */
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net);
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
index 74009a9..1f4f234 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -15,10 +15,12 @@
 #include <osmocom/gsm/bts_features.h>
 #include <osmocom/gsm/protocol/gsm_08_08.h>
 #include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/fsm.h>
 
 #include <osmocom/crypt/auth.h>
 
 #include <osmocom/bsc/rest_octets.h>
+#include <osmocom/bsc/handover.h>
 
 #include <osmocom/core/bitvec.h>
 #include <osmocom/gsm/gsm_utils.h>
@@ -95,6 +97,9 @@
 	/* global linked list of subscriber_connections */
 	struct llist_head entry;
 
+	/* FSM instance to control the subscriber connection state (RTP, A) */
+	struct osmo_fsm_inst *fi;
+
 	/* libbsc subscriber information (if available) */
 	struct bsc_subscr *bsub;
 
@@ -103,8 +108,9 @@
 
 	/* the primary / currently active lchan to the BTS/subscriber */
 	struct gsm_lchan *lchan;
-	/* the future/allocated but not yet used lchan during HANDOVER */
-	struct gsm_lchan *ho_lchan;
+
+	/* handover information, if a handover is pending for this conn. */
+	struct bsc_handover *ho;
 
 	/* timer for assignment handling */
 	struct osmo_timer_list T10;
@@ -166,11 +172,30 @@
 		 * 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;
+		/* FSM instance to control the BTS sided RTP connection */
+		struct osmo_fsm_inst *fi_bts;
+
+		/* FSM instance to control the MSC sided RTP connection */
+		struct osmo_fsm_inst *fi_msc;
+
+		/* Endpoint identifier of the MGCP endpoint the connection uses */
+		char *mgw_endpoint;
+
+		/* Channel rate flag, FR=1, HR=0, Invalid=-1 */
+		int full_rate;
+
+		/* Channel mode flage (signaling or voice channel) */
+		enum gsm48_chan_mode chan_mode;
+
+		/* FIXME: These members are for caching the handover
+		 * information when the FSM gets the handover start
+		 * signal. This is probably misplaced and should
+		 * be its own struct. (see also handover_logic.c) */
+		enum hodec_id from_hodec_id;
+		struct gsm_lchan *ho_old_lchan;
+		struct gsm_bts *ho_new_bts;
+		enum gsm_chan_t ho_new_lchan_type;
+		
 	} user_plane;
 };
 
@@ -1340,8 +1365,7 @@
 
 int gsm_bts_model_register(struct gsm_bts_model *model);
 
-struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_lchan *lchan);
-void bsc_subscr_con_free(struct gsm_subscriber_connection *conn);
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *network);
 
 struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network);
 void msc_subscr_con_free(struct gsm_subscriber_connection *conn);
diff --git a/include/osmocom/bsc/handover.h b/include/osmocom/bsc/handover.h
index 0fbfaf6..eb03f6a 100644
--- a/include/osmocom/bsc/handover.h
+++ b/include/osmocom/bsc/handover.h
@@ -4,10 +4,12 @@
 
 #include <osmocom/core/linuxlist.h>
 #include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
 
 struct gsm_lchan;
 struct gsm_bts;
 struct gsm_subscriber_connection;
+struct gsm_meas_rep mr;
 
 #define LOGPHOLCHANTOLCHAN(old_lchan, new_lchan, level, fmt, args...) \
 	LOGP(DHODEC, level, "(BTS %u trx %u arfcn %u ts %u lchan %u %s)->(BTS %u trx %u arfcn %u ts %u lchan %u %s) (subscr %s) " fmt, \
@@ -38,21 +40,23 @@
 struct bsc_handover {
 	struct llist_head list;
 
-	enum hodec_id from_hodec_id;
-
+	/* Initial details of what is requested */
 	struct gsm_lchan *old_lchan;
-	struct gsm_lchan *new_lchan;
-
-	struct osmo_timer_list T3103;
-
-	uint8_t ho_ref;
-
-	bool inter_cell;
+	struct gsm_bts *new_bts;
+	enum gsm_chan_t new_lchan_type;
 	bool async;
+
+	/* Derived and resulting state */
+	bool inter_cell;
+	uint8_t ho_ref;
+	enum hodec_id from_hodec_id;
+	struct gsm_lchan *new_lchan;
+	struct osmo_timer_list T3103;
 };
 
 int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
 		       enum gsm_chan_t new_lchan_type);
+int bsc_handover_start_gscon(struct gsm_subscriber_connection *conn);
 void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan);
 struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan);
 
diff --git a/include/osmocom/bsc/osmo_bsc.h b/include/osmocom/bsc/osmo_bsc.h
index 678ac41..ed5698d 100644
--- a/include/osmocom/bsc/osmo_bsc.h
+++ b/include/osmocom/bsc/osmo_bsc.h
@@ -39,6 +39,4 @@
 
 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
deleted file mode 100644
index 15039f7..0000000
--- a/include/osmocom/bsc/osmo_bsc_mgcp.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/* (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/>.
- *
- */
-
-#pragma once
-
-#include <osmocom/mgcp_client/mgcp_common.h>
-#include <osmocom/mgcp_client/mgcp_client.h>
-
-/* MGCP state handler context. This context information stores all information
- * to handle the direction of the RTP streams via MGCP. There is one instance
- * of this context struct per subscriber connection.
- * (see also struct osmo_bsc_sccp_con) */
-struct mgcp_ctx {
-	/* FSM instance, which handles the connection switching procedure */
-	struct osmo_fsm_inst *fsm;
-
-	/* RTP endpoint number. This number number identifies the endpoint
-	 * on the MGW on which the BTS and NET connection is created. This
-	 * endpoint number is assigned and released automatically. */
-	uint16_t rtp_endpoint;
-
-	/* RTP connection identifiers */
-	char conn_id_bts[MGCP_CONN_ID_LENGTH];
-	char conn_id_net[MGCP_CONN_ID_LENGTH];
-
-	/* 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 gsm_subscriber_connection *conn;
-	enum gsm48_chan_mode chan_mode;
-	bool full_rate;
-	struct gsm_lchan *lchan;
-	struct gsm_lchan *ho_lchan;
-	struct msgb *resp;
-	mgcp_trans_id_t mgw_pending_trans;
-};
-
-void mgcp_init(struct gsm_network *net);
-
-struct mgcp_ctx *mgcp_assignm_req(void *ctx, struct mgcp_client *mgcp,
-				  struct gsm_subscriber_connection *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);
-void mgcp_free_ctx(struct mgcp_ctx *mgcp_ctx);
diff --git a/include/osmocom/bsc/osmo_bsc_sigtran.h b/include/osmocom/bsc/osmo_bsc_sigtran.h
index 5cb7230..80d4f5b 100644
--- a/include/osmocom/bsc/osmo_bsc_sigtran.h
+++ b/include/osmocom/bsc/osmo_bsc_sigtran.h
@@ -33,11 +33,6 @@
 /* Send data to MSC */
 int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg);
 
-/* Delete a connection from the list with open connections
- * (called by osmo_bsc_api.c on failing open connections and
- * locally, when a connection is closed by the MSC */
-int osmo_bsc_sigtran_del_conn(struct gsm_subscriber_connection *sccp);
-
 /* Initalize osmo sigtran backhaul */
 int osmo_bsc_sigtran_init(struct llist_head *mscs);
 
diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am
index a6195b9..9b2a83e 100644
--- a/src/ipaccess/Makefile.am
+++ b/src/ipaccess/Makefile.am
@@ -24,7 +24,6 @@
 
 bin_PROGRAMS = \
 	abisip-find \
-	ipaccess-config \
 	ipaccess-proxy \
 	$(NULL)
 
diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am
index 805a7ee..8956363 100644
--- a/src/libbsc/Makefile.am
+++ b/src/libbsc/Makefile.am
@@ -13,6 +13,7 @@
 	$(LIBOSMOMGCP_CFLAGS) \
 	$(LIBOSMOSIGTRAN_CFLAGS) \
 	$(COVERAGE_CFLAGS) \
+	$(LIBOSMOMGCPCLIENT_CFLAGS) \
 	$(NULL)
 
 noinst_LIBRARIES = \
@@ -62,5 +63,6 @@
 	handover_cfg.c \
 	penalty_timers.c \
 	handover_decision_2.c \
+	bsc_subscr_conn_fsm.c \
 	$(NULL)
 
diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c
index 7400f89..2017d2c 100644
--- a/src/libbsc/abis_rsl.c
+++ b/src/libbsc/abis_rsl.c
@@ -45,6 +45,7 @@
 #include <osmocom/core/talloc.h>
 #include <osmocom/bsc/pcu_if.h>
 #include <osmocom/bsc/bsc_api.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
 
 #define RSL_ALLOC_SIZE		1024
 #define RSL_ALLOC_HEADROOM	128
@@ -1357,21 +1358,28 @@
 static int rsl_rx_conn_fail(struct msgb *msg)
 {
 	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct gsm_lchan *lchan = msg->lchan;
 	struct tlv_parsed tp;
+	uint8_t cause = 0;
 
-	LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING state %s ",
+	LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL in state %s ",
 	     gsm_lchan_name(msg->lchan),
 	     gsm_lchans_name(msg->lchan->state));
 
 	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
 
-	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) {
 		print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE),
 				TLVP_LEN(&tp, RSL_IE_CAUSE));
+		cause = *TLVP_VAL(&tp, RSL_IE_CAUSE);
+	}
 
 	LOGPC(DRSL, LOGL_NOTICE, "\n");
-	rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]);
-	return rsl_rf_chan_release_err(msg->lchan);
+	rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]);
+
+	osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, &cause);
+
+	return 0;
 }
 
 static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru,
@@ -2232,6 +2240,9 @@
 		rll_indication(msg->lchan, rllh->link_id,
 				  BSC_RLLR_IND_REL_IND);
 		rsl_handle_release(msg->lchan);
+		/* if it was the main signalling link, let the subscr_conn_fsm know */
+		if (msg->lchan->conn && sapi == 0 && (rllh->link_id >> 6) == 0)
+			osmo_fsm_inst_dispatch(msg->lchan->conn->fi, GSCON_EV_RLL_REL_IND, msg);
 		break;
 	case RSL_MT_REL_CONF:
 		/* BTS informs us of having received UA from MS,
diff --git a/src/libbsc/bsc_api.c b/src/libbsc/bsc_api.c
index d792b58..c476547 100644
--- a/src/libbsc/bsc_api.c
+++ b/src/libbsc/bsc_api.c
@@ -2,7 +2,7 @@
 
 /* (C) 2010-2011 by Holger Hans Peter Freyther
  * (C) 2010-2011 by On-Waves
- * (C) 2009 by Harald Welte <laforge at gnumonks.org>
+ * (C) 2009,2017 by Harald Welte <laforge at gnumonks.org>
  *
  * All Rights Reserved
  *
@@ -51,7 +51,7 @@
 static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
 
 /* GSM 08.08 3.2.2.33 */
-static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan)
+uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan)
 {
 	uint8_t channel_mode = 0, channel = 0;
 
@@ -100,7 +100,7 @@
 	return channel_mode << 4 | channel;
 }
 
-static uint8_t chan_mode_to_speech(struct gsm_lchan *lchan)
+uint8_t chan_mode_to_speech(struct gsm_lchan *lchan)
 {
 	int mode = 0;
 
@@ -131,27 +131,6 @@
 		mode |= 0x4;
 
         return mode;
-}
-
-static void assignment_t10_timeout(void *_conn)
-{
-	struct bsc_api *api;
-	struct gsm_subscriber_connection *conn =
-		(struct gsm_subscriber_connection *) _conn;
-
-	LOGP(DMSC, LOGL_ERROR, "Assignment T10 timeout on %p\n", conn);
-
-	/*
-	 * normal release on the secondary channel but only if the
-	 * secondary_channel has not been released by the handle_chan_nack.
-	 */
-	if (conn->secondary_lchan)
-		lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
-	conn->secondary_lchan = NULL;
-
-	/* inform them about the failure */
-	api = conn->network->bsc_api;
-	api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
 }
 
 /*! \brief Determine and apply AMR multi-rate configuration to lchan
@@ -265,24 +244,6 @@
 	return 0;
 }
 
-struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_lchan *lchan)
-{
-	struct gsm_subscriber_connection *conn;
-	struct gsm_network *net = lchan->ts->trx->bts->network;
-
-	conn = talloc_zero(net, struct gsm_subscriber_connection);
-	if (!conn)
-		return NULL;
-
-	conn->network = net;
-	conn->lchan = lchan;
-	lchan->conn = conn;
-	INIT_LLIST_HEAD(&conn->ho_dtap_cache);
-	conn->sccp.conn_id = -1;
-	llist_add_tail(&conn->entry, &net->subscr_conns);
-	return conn;
-}
-
 static void ho_dtap_cache_add(struct gsm_subscriber_connection *conn, struct msgb *msg,
 			      int link_id, bool allow_sacch)
 {
@@ -301,12 +262,12 @@
 	msgb_enqueue(&conn->ho_dtap_cache, msg);
 }
 
-static void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send)
+void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send)
 {
 	struct msgb *msg;
 	unsigned int flushed_count = 0;
 
-	if (conn->secondary_lchan || conn->ho_lchan) {
+	if (conn->secondary_lchan || conn->ho) {
 		LOGP(DHO, LOGL_ERROR, "%s: Cannot send cached DTAP messages, handover/assignment is still ongoing\n",
 		     bsc_subscr_name(conn->bsub));
 		send = 0;
@@ -324,38 +285,6 @@
 		} else
 			msgb_free(msg);
 	}
-}
-
-void bsc_subscr_con_free(struct gsm_subscriber_connection *conn)
-{
-	if (!conn)
-		return;
-
-	if (conn->network->bsc_api->conn_cleanup)
-		conn->network->bsc_api->conn_cleanup(conn);
-
-	if (conn->ho_lchan) {
-		LOGP(DNM, LOGL_ERROR, "The ho_lchan should have been cleared.\n");
-		conn->ho_lchan->conn = NULL;
-	}
-
-	if (conn->lchan) {
-		LOGP(DNM, LOGL_ERROR, "The lchan should have been cleared.\n");
-		conn->lchan->conn = NULL;
-	}
-
-	if (conn->secondary_lchan) {
-		LOGP(DNM, LOGL_ERROR, "The secondary_lchan should have been cleared.\n");
-		conn->secondary_lchan->conn = NULL;
-	}
-
-	/* drop pending messages */
-	ho_dtap_cache_flush(conn, 0);
-
-	penalty_timers_free(&conn->hodec2.penalty_timers);
-
-	llist_del(&conn->entry);
-	talloc_free(conn);
 }
 
 int bsc_api_init(struct gsm_network *network, struct bsc_api *api)
@@ -379,7 +308,7 @@
 	}
 
 	/* buffer message during assignment / handover */
-	if (conn->secondary_lchan || conn->ho_lchan) {
+	if (conn->secondary_lchan || conn->ho) {
 		ho_dtap_cache_add(conn, msg, link_id, !! allow_sacch);
 		return 0;
 	}
@@ -478,9 +407,7 @@
 		gsm48_lchan_modify(conn->lchan, chan_mode);
 	}
 
-	/* we will now start the timer to complete the assignment */
-	osmo_timer_setup(&conn->T10, assignment_t10_timeout, conn);
-	osmo_timer_schedule(&conn->T10, GSM0808_T10_VALUE);
+	/* we expect the caller will manage T10 */
 	return 0;
 
 error:
@@ -500,7 +427,7 @@
 	struct gsm48_hdr *gh;
 	struct bsc_api *api = conn->network->bsc_api;
 
-	if (conn->ho_lchan) {
+	if (conn->ho) {
 		struct lchan_signal_data sig;
 		struct gsm48_hdr *gh = msgb_l3(msg);
 
@@ -556,7 +483,7 @@
 	uint8_t *rr_failure;
 	struct gsm48_hdr *gh;
 
-	if (conn->ho_lchan) {
+	if (conn->ho) {
 		struct lchan_signal_data sig;
 		struct gsm48_hdr *gh = msgb_l3(msg);
 
@@ -796,19 +723,18 @@
 	} else {
 		/* allocate a new connection */
 		rc = BSC_API_CONN_POL_REJECT;
-		lchan->conn = bsc_subscr_con_allocate(msg->lchan);
+		lchan->conn = bsc_subscr_con_allocate(msg->lchan->ts->trx->bts->network);
 		if (!lchan->conn) {
 			lchan_release(lchan, 1, RSL_REL_NORMAL);
 			return -1;
 		}
+		lchan->conn->lchan = lchan;
 
 		/* fwd via bsc_api to send COMPLETE L3 INFO to MSC */
 		rc = api->compl_l3(lchan->conn, msg, 0);
 
 		if (rc != BSC_API_CONN_POL_ACCEPT) {
-			lchan->conn->lchan = NULL;
-			bsc_subscr_con_free(lchan->conn);
-			lchan_release(lchan, 1, RSL_REL_NORMAL);
+			//osmo_fsm_inst_dispatch(lchan->conn->fi, FIXME, NULL);
 		}
 	}
 
@@ -846,7 +772,7 @@
  */
 int gsm0808_clear(struct gsm_subscriber_connection *conn)
 {
-	if (conn->ho_lchan)
+	if (conn->ho)
 		bsc_clear_handover(conn, 1);
 
 	if (conn->secondary_lchan)
@@ -857,7 +783,6 @@
 
 	conn->lchan = NULL;
 	conn->secondary_lchan = NULL;
-	conn->ho_lchan = NULL;
 
 	osmo_timer_del(&conn->T10);
 
@@ -959,16 +884,9 @@
 	/* now give up all channels */
 	if (conn->lchan == lchan)
 		conn->lchan = NULL;
-	if (conn->ho_lchan == lchan) {
+	if (conn->ho && conn->ho->new_lchan == lchan)
 		bsc_clear_handover(conn, 0);
-		conn->ho_lchan = NULL;
-	}
 	lchan->conn = NULL;
-
-	gsm0808_clear(conn);
-
-	if (destruct)
-		bsc_subscr_con_free(conn);
 }
 
 static void handle_chan_ack(struct gsm_subscriber_connection *conn,
diff --git a/src/libbsc/bsc_subscr_conn_fsm.c b/src/libbsc/bsc_subscr_conn_fsm.c
new file mode 100644
index 0000000..e89edd3
--- /dev/null
+++ b/src/libbsc/bsc_subscr_conn_fsm.c
@@ -0,0 +1,1050 @@
+/* (C) 2017 by Harald Welte <laforge at gnumonks.org>
+ * 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/fsm.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_api.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/penalty_timers.h>
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+#include <osmocom/core/byteswap.h>
+
+#define S(x)	(1 << (x))
+
+#define MGCP_MGW_TIMEOUT 4	/* in seconds */
+#define MGCP_MGW_TIMEOUT_TIMER_NR 1
+
+#define MGCP_MGW_HO_TIMEOUT 4	/* in seconds */
+#define MGCP_MGW_HO_TIMEOUT_TIMER_NR 2
+
+#define GSM0808_T10_TIMER_NR 10
+#define GSM0808_T10_VALUE 6
+
+#define ENDPOINT_ID "rtpbridge/*@mgw"
+
+enum gscon_fsm_states {
+	ST_INIT,
+	/* waiting for CC from MSC */
+	ST_WAIT_CC,
+	/* active connection */
+	ST_ACTIVE,
+	/* during assignment; waiting for ASS_CMPL */
+	ST_WAIT_ASS_CMPL,
+	/* during assignment; waiting for MODE_MODIFY_ACK */
+	ST_WAIT_MODE_MODIFY_ACK,
+	/* BSSMAP CLEAR has been received */
+	ST_CLEARING,
+
+/* MGW handling */
+	/* during assignment; waiting for MGW response to CRCX for BTS */
+	ST_WAIT_CRCX_BTS,
+	/* during assignment; waiting for MGW response to MDCX for BTS */
+	ST_WAIT_MDCX_BTS,
+	/* during assignment; waiting for MGW response to CRCX for MSC */
+	ST_WAIT_CRCX_MSC,
+
+/* MT (inbound) handover */
+	/* Wait for Handover Access from MS/BTS */
+	ST_WAIT_MT_HO_ACC,
+	/* Wait for RR Handover Complete from MS/BTS */
+	ST_WAIT_MT_HO_COMPL,
+
+/* MO (outbound) handover */
+	/* Wait for Handover Command / Handover Required Reject from MSC */
+	ST_WAIT_MO_HO_CMD,
+	/* Wait for Clear Command from MSC */
+	ST_MO_HO_PROCEEDING,
+
+/* Internal HO handling */
+	/* Wait for the handover logic to complete the handover */
+	ST_WAIT_HO_COMPL,
+	/* during handover; waiting for MGW response to MDCX for BTS */
+	ST_WAIT_MDCX_BTS_HO,
+};
+
+static const struct value_string gscon_fsm_event_names[] = {
+	{GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"},
+	{GSCON_EV_A_CONN_REQ, "MO-CONNECT.req"},
+	{GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"},
+	{GSCON_EV_A_ASSIGNMENT_CMD, "ASSIGNMENT_CMD"},
+	{GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"},
+	{GSCON_EV_A_DISC_IND, "DISCONNET.ind"},
+	{GSCON_EV_A_HO_REQ, "HANDOVER_REQUEST"},
+
+	{GSCON_EV_RR_ASS_COMPL, "RR_ASSIGN_COMPL"},
+	{GSCON_EV_RR_ASS_FAIL, "RR_ASSIGN_FAIL"},
+	{GSCON_EV_RR_MODE_MODIFY_ACK, "RR_MODE_MODIFY_ACK"},
+	{GSCON_EV_RR_HO_ACC, "RR_HO_ACCESS"},
+	{GSCON_EV_RR_HO_COMPL, "RR_HO_COMPLETE"},
+	{GSCON_EV_RLL_REL_IND, "RLL_RELEASE.ind"},
+	{GSCON_EV_RSL_CONN_FAIL, "RSL_CONN_FAIL.ind"},
+	{GSCON_EV_RSL_CLEAR_COMPL, "RSL_CLEAR_COMPLETE"},
+
+	{GSCON_EV_MO_DTAP, "MO-DTAP"},
+	{GSCON_EV_MT_DTAP, "MT-DTAP"},
+	{GSCON_EV_TX_SCCP, "TX_SCCP"},
+
+	{GSCON_EV_MGW_FAIL_BTS, "MGW_FAILURE_BTS"},
+	{GSCON_EV_MGW_FAIL_MSC, "MGW_FAILURE_MSC"},
+	{GSCON_EV_MGW_CRCX_RESP_BTS, "MGW_CRCX_RESPONSE_BTS"},
+	{GSCON_EV_MGW_MDCX_RESP_BTS, "MGW_MDCX_RESPONSE_BTS"},
+	{GSCON_EV_MGW_CRCX_RESP_MSC, "MGW_CRCX_RESPONSE_MSC"},
+
+	{GSCON_EV_HO_START, "HO_START"},
+	{GSCON_EV_HO_TIMEOUT, "HO_TIMEOUT"},
+	{GSCON_EV_HO_FAIL, "HO_FAIL"},
+	{GSCON_EV_HO_COMPL, "HO_COMPL"},
+
+	{0, NULL}
+};
+
+/* Send data SCCP message through SCCP connection. All sigtran messages
+ * that are send from this FSM must use this function. Never use
+ * osmo_bsc_sigtran_send() directly since this would defeat the checks
+ * provided by this function. */
+static void sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi)
+{
+	int rc;
+
+	/* Make sure that we only attempt to send SCCP messages if we have
+	 * a life SCCP connection. Otherwise drop the message. */
+	if (fi->state == ST_INIT || fi->state == ST_WAIT_CC) {
+		LOGPFSML(fi, LOGL_ERROR, "No active SCCP connection, dropping message!\n");
+		msgb_free(msg);
+		return;
+	}
+
+	rc = osmo_bsc_sigtran_send(conn, msg);
+	if (rc < 0)
+		LOGPFSML(fi, LOGL_ERROR, "Unable to deliver SCCP message!\n");
+}
+
+/* Generate and send assignment complete message */
+static void send_ass_compl(struct gsm_lchan *lchan, struct osmo_fsm_inst *fi)
+{
+	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);
+
+	LOGPFSML(fi, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.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->user_plane.aoip_rtp_addr_local, &sc, NULL);
+
+	if (!resp) {
+		LOGPFSML(fi, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n",
+			 conn->sccp.conn_id);
+	}
+
+	sigtran_send(conn, resp, fi);
+}
+
+/* forward MT DTAP from BSSAP side to RSL side */
+static void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi)
+{
+	int rc;
+	struct msgb *resp = NULL;
+
+	OSMO_ASSERT(fi);
+	OSMO_ASSERT(msg);
+	OSMO_ASSERT(conn);
+
+	rc = gsm0808_submit_dtap(conn, msg, OBSC_LINKID_CB(msg), 1);
+	if (rc != 0) {
+		LOGPFSML(fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n");
+		resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE);
+		sigtran_send(conn, resp, fi);
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		return;
+	}
+}
+
+/* forward MO DTAP from RSL side to BSSAP side */
+/* FIXME: move fi parameter to the beginning */
+static void forward_dtap(struct msgb *msg, struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi)
+{
+	struct msgb *resp = NULL;
+
+	OSMO_ASSERT(msg);
+	OSMO_ASSERT(conn);
+
+	resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg));
+	sigtran_send(conn, resp, fi);
+}
+
+/* In case there are open MGCP connections, toss
+ * those connections */
+static void toss_mgcp_conn(struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi)
+{
+	LOGPFSML(fi, LOGL_ERROR, "tossing all MGCP connections...\n");
+
+	if (conn->user_plane.fi_bts) {
+		mgcp_conn_delete(conn->user_plane.fi_bts);
+		conn->user_plane.fi_bts = NULL;
+	}
+
+	if (conn->user_plane.fi_msc) {
+		mgcp_conn_delete(conn->user_plane.fi_msc);
+		conn->user_plane.fi_msc = NULL;
+	}
+
+	if (conn->user_plane.mgw_endpoint) {
+		talloc_free(conn->user_plane.mgw_endpoint);
+		conn->user_plane.mgw_endpoint = NULL;
+	}
+}
+
+static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct osmo_scu_prim *scu_prim = NULL;
+	struct msgb *msg = NULL;
+	int rc;
+
+	switch (event) {
+	case GSCON_EV_A_CONN_REQ:
+		/* RLL ESTABLISH IND with initial L3 Message */
+		msg = data;
+		/* FIXME: Extract Mobile ID and update FSM using osmo_fsm_inst_set_id() */
+		rc = osmo_bsc_sigtran_open_conn(conn, msg);
+		if (rc < 0) {
+			osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+		} else {
+			/* SCCP T(conn est) is 1-2 minutes, way too long. The MS will timeout
+			 * using T3210 (20s), T3220 (5s) or T3230 (10s) */
+			osmo_fsm_inst_state_chg(fi, ST_WAIT_CC, 20, 993210);
+		}
+		break;
+	case GSCON_EV_A_CONN_IND:
+		scu_prim = data;
+		if (!conn->sccp.msc) {
+			LOGPFSML(fi, LOGL_NOTICE, "N-CONNECT.ind from unknown MSC %s\n",
+				 osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr));
+			osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
+					     &scu_prim->u.connect.called_addr, 0);
+			osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+		}
+		/* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() */
+		LOGPFSML(fi, LOGL_NOTICE, "No support for MSC-originated SCCP Connections yet\n");
+		osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
+				     &scu_prim->u.connect.called_addr, 0);
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* We've sent the CONNECTION.req to the SCCP provider and are waiting for CC from MSC */
+static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case GSCON_EV_A_CONN_CFM:
+		/* MSC has confirmed the connection, we now change into the
+		 * active state and wait there for further operations */
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		/* if there's user payload, forward it just like EV_MT_DTAP */
+		/* FIXME: Question: if there's user payload attached to the CC, forward it like EV_MT_DTAP? */
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* We're on an active subscriber connection, passing DTAP back and forth */
+static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct msgb *resp = NULL;
+	struct mgcp_conn_peer conn_peer;
+	int rc;
+
+	switch (event) {
+	case GSCON_EV_A_ASSIGNMENT_CMD:
+		/* MSC requests us to perform assignment, this code section is
+		 * triggered via signal GSCON_EV_A_ASSIGNMENT_CMD from
+		 * bssmap_handle_assignm_req() in osmo_bsc_bssap.c, which does
+		 * the parsing of incoming assignment requests. */
+
+		LOGPFSML(fi, LOGL_NOTICE, "Channel assignment: chan_mode=%s, full_rate=%i\n",
+			 get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode),
+			 conn->user_plane.full_rate);
+
+		/* FIXME: We need to check if current channel is sufficient. If
+		 * yes, do MODIFY. If not, do assignment (see commented lines below) */
+
+		/* FIXME: At the moment, the FSM is constructed in an
+		 * unfortunate way. In case of a voice channel assignment
+		 * we first go through a couple of MGCP related states,
+		 * then reach the state where the actual channel assignment
+		 * happens and then again we perform some MGCP related
+		 * actions and eventually end up in ST_ACTIVE again. This
+		 * should be restructured */
+
+		switch (conn->user_plane.chan_mode) {
+		case GSM48_CMODE_SPEECH_V1:
+		case GSM48_CMODE_SPEECH_EFR:
+		case GSM48_CMODE_SPEECH_AMR:
+			/* A voice channel is requested, so we run down the
+			 * mgcp-ass-mgcp state-chain (see FIXME above) */
+			memset(&conn_peer, 0, sizeof(conn_peer));
+			conn_peer.call_id = conn->sccp.conn_id;
+			osmo_strlcpy(conn_peer.endpoint, ENDPOINT_ID, sizeof(conn_peer.endpoint));
+
+			/* (Pre)Change state and create the connection */
+			osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+			conn->user_plane.fi_bts =
+			    mgcp_conn_create(conn->network->mgw.client, fi, GSCON_EV_MGW_FAIL_BTS,
+					     GSCON_EV_MGW_CRCX_RESP_BTS, &conn_peer);
+			if (!conn->user_plane.fi_bts) {
+				resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL);
+				sigtran_send(conn, resp, fi);
+				osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+				return;
+			}
+			break;
+		case GSM48_CMODE_SIGN:
+			/* A signalling channel is requested, so we perform the
+			 * channel assignment directly without performing any
+			 * MGCP actions. ST_WAIT_ASS_CMPL will see by the
+			 * conn->user_plane.chan_mode parameter that this
+			 * assignment is for a signalling channel and will then
+			 * change back to ST_ACTIVE (here) immediately. */
+			rc = gsm0808_assign_req(conn, conn->user_plane.full_rate, conn->user_plane.chan_mode);
+			if (rc != 0) {
+				resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL);
+				sigtran_send(conn, resp, fi);
+				return;
+			}
+
+			osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR);
+			break;
+		default:
+			/* An unsupported channel is requested, so we have to
+			 * reject this request by sending an assignment failure
+			 * message immediately */
+			LOGPFSML(fi, LOGL_ERROR, "Requested channel mode is not supported!\n",
+				 get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode),
+				 conn->user_plane.full_rate);
+
+			/* The requested channel mode is not supported  */
+			resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, NULL);
+			sigtran_send(conn, resp, fi);
+			break;
+		}
+		break;
+	case GSCON_EV_HO_START:
+		rc = bsc_handover_start_gscon(conn);
+		if (rc) {
+			resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE);
+			sigtran_send(conn, resp, fi);
+			osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0);
+			return;
+		}
+
+		/* Note: No timeout is set here, T3103 in handover_logic.c
+		 * will generate a GSCON_EV_HO_TIMEOUT event should the
+		 * handover time out, so we do not need another timeout
+		 * here (maybe its worth to think about giving GSCON
+		 * more power over the actual handover process). */
+		osmo_fsm_inst_state_chg(fi, ST_WAIT_HO_COMPL, 0, 0);
+		break;
+	case GSCON_EV_A_HO_REQ:
+		/* FIXME: reject any handover requests with HO FAIL until implemented */
+		break;
+	case GSCON_EV_MO_DTAP:
+		forward_dtap((struct msgb *)data, conn, fi);
+		break;
+	case GSCON_EV_MT_DTAP:
+		submit_dtap(conn, (struct msgb *)data, fi);
+		break;
+	case GSCON_EV_TX_SCCP:
+		sigtran_send(conn, (struct msgb *)data, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* Before we may start the channel assignment we need to get an IP/Port for the
+ * RTP connection from the MGW */
+static void gscon_fsm_wait_crcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct mgcp_conn_peer *conn_peer = NULL;
+	struct msgb *resp = NULL;
+	int rc;
+
+	switch (event) {
+	case GSCON_EV_MGW_CRCX_RESP_BTS:
+		conn_peer = data;
+
+		/* Check if the MGW has assigned an enpoint to us, we can not
+		 * proceed */
+		if (strlen(conn_peer->endpoint) <= 0) {
+			resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL);
+			sigtran_send(conn, resp, fi);
+			osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+			return;
+		}
+
+		/* Memorize the endpoint name we got assigned from the MGW.
+		 * When the BTS sided connection is done, we need to create
+		 * a second connection on that same endpoint, so we need
+		 * to know its ID */
+		if (!conn->user_plane.mgw_endpoint)
+			conn->user_plane.mgw_endpoint = talloc_zero_size(conn, MGCP_ENDPOINT_MAXLEN);
+		OSMO_ASSERT(conn->user_plane.mgw_endpoint);
+		osmo_strlcpy(conn->user_plane.mgw_endpoint, conn_peer->endpoint, MGCP_ENDPOINT_MAXLEN);
+
+		/* Store the IP-Address and the port the MGW assigned to us,
+		 * then start the channel assignment. */
+		conn->user_plane.rtp_port = conn_peer->port;
+		conn->user_plane.rtp_ip = osmo_ntohl(inet_addr(conn_peer->addr));
+		rc = gsm0808_assign_req(conn, conn->user_plane.full_rate, conn->user_plane.chan_mode);
+		if (rc != 0) {
+			resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE, NULL);
+			sigtran_send(conn, resp, fi);
+			osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+			return;
+		}
+
+		osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR);
+		break;
+	case GSCON_EV_MO_DTAP:
+		forward_dtap((struct msgb *)data, conn, fi);
+		break;
+	case GSCON_EV_MT_DTAP:
+		submit_dtap(conn, (struct msgb *)data, fi);
+		break;
+	case GSCON_EV_TX_SCCP:
+		sigtran_send(conn, (struct msgb *)data, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* We're waiting for an ASSIGNMENT COMPLETE from MS */
+static void gscon_fsm_wait_ass_cmpl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct gsm_lchan *lchan = conn->lchan;
+	struct mgcp_conn_peer conn_peer;
+	struct in_addr addr;
+	struct msgb *resp = NULL;
+	int rc;
+
+	switch (event) {
+	case GSCON_EV_RR_ASS_COMPL:
+		switch (conn->user_plane.chan_mode) {
+		case GSM48_CMODE_SPEECH_V1:
+		case GSM48_CMODE_SPEECH_EFR:
+		case GSM48_CMODE_SPEECH_AMR:
+			/* FIXME: What if we are using SCCP-Lite? */
+
+			/* We are dealing with a voice channel, so we can not
+			 * confirm the assignment directly. We must first do
+			 * some final steps on the MGCP side. */
+
+			/* Prepare parameters with the information we got during the assignment */
+			memset(&conn_peer, 0, sizeof(conn_peer));
+			addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip);
+			osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr));
+			conn_peer.port = lchan->abis_ip.bound_port;
+
+			/* (Pre)Change state and modify the connection */
+			osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+			rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer);
+			if (rc != 0) {
+				resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL);
+				sigtran_send(conn, resp, fi);
+				osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+				return;
+			}
+			break;
+		case GSM48_CMODE_SIGN:
+			/* Confirm the successful assignment on BSSMAP and
+			 * change back into active state */
+			send_ass_compl(lchan, fi);
+			osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+			break;
+		default:
+			/* Unsupported modes should have been already filtered
+			 * by gscon_fsm_active(). If we reach the default
+			 * section here anyway than some unsupported mode must
+			 * have made it into the FSM, this would be a bug, so
+			 * we fire an assertion here */
+			OSMO_ASSERT(false);
+			break;
+		}
+
+		break;
+	case GSCON_EV_RR_ASS_FAIL:
+		resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE, NULL);
+		sigtran_send(conn, resp, fi);
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		break;
+	case GSCON_EV_MO_DTAP:
+		forward_dtap((struct msgb *)data, conn, fi);
+		break;
+	case GSCON_EV_MT_DTAP:
+		submit_dtap(conn, (struct msgb *)data, fi);
+		break;
+	case GSCON_EV_TX_SCCP:
+		sigtran_send(conn, (struct msgb *)data, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* We are waiting for the MGW response to the MDCX */
+static void gscon_fsm_wait_mdcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct mgcp_conn_peer conn_peer;
+	struct sockaddr_in *sin = NULL;
+	struct msgb *resp = NULL;
+
+	switch (event) {
+	case GSCON_EV_MGW_MDCX_RESP_BTS:
+
+		/* Prepare parameters with the connection information we got
+		 * with the assignment command */
+		memset(&conn_peer, 0, sizeof(conn_peer));
+		conn_peer.call_id = conn->sccp.conn_id;
+		sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote;
+		conn_peer.port = osmo_ntohs(sin->sin_port);
+		osmo_strlcpy(conn_peer.addr, inet_ntoa(sin->sin_addr), sizeof(conn_peer.addr));
+
+		/* Make sure we use the same endpoint where we created the
+		 * BTS connection. */
+		osmo_strlcpy(conn_peer.endpoint, conn->user_plane.mgw_endpoint, sizeof(conn_peer.endpoint));
+
+		/* (Pre)Change state and create the connection */
+		osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_MSC, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+		conn->user_plane.fi_msc =
+		    mgcp_conn_create(conn->network->mgw.client, fi, GSCON_EV_MGW_FAIL_MSC, GSCON_EV_MGW_CRCX_RESP_MSC,
+				     &conn_peer);
+		if (!conn->user_plane.fi_bts) {
+			resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL);
+			sigtran_send(conn, resp, fi);
+			osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+			return;
+		}
+
+		break;
+	case GSCON_EV_MO_DTAP:
+		forward_dtap((struct msgb *)data, conn, fi);
+		break;
+	case GSCON_EV_MT_DTAP:
+		submit_dtap(conn, (struct msgb *)data, fi);
+		break;
+	case GSCON_EV_TX_SCCP:
+		sigtran_send(conn, (struct msgb *)data, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+static void gscon_fsm_wait_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct mgcp_conn_peer *conn_peer = NULL;
+	struct gsm_lchan *lchan = conn->lchan;
+	struct sockaddr_in *sin = NULL;
+
+	switch (event) {
+	case GSCON_EV_MGW_CRCX_RESP_MSC:
+		conn_peer = data;
+
+		/* Store address information we got in response from the CRCX command. */
+		sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_local;
+		sin->sin_family = AF_INET;
+		sin->sin_addr.s_addr = inet_addr(conn_peer->addr);
+		sin->sin_port = osmo_ntohs(conn_peer->port);
+
+		/* Send assignment complete message to the MSC */
+		send_ass_compl(lchan, fi);
+
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+
+		break;
+	case GSCON_EV_MO_DTAP:
+		forward_dtap((struct msgb *)data, conn, fi);
+		break;
+	case GSCON_EV_MT_DTAP:
+		submit_dtap(conn, (struct msgb *)data, fi);
+		break;
+	case GSCON_EV_TX_SCCP:
+		sigtran_send(conn, (struct msgb *)data, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* We're waiting for a MODE MODIFY ACK from MS + BTS */
+static void gscon_fsm_wait_mode_modify_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct gsm_lchan *lchan = conn->lchan;
+
+	switch (event) {
+	case GSCON_EV_RR_MODE_MODIFY_ACK:
+		/* we assume that not only have we received the RR MODE_MODIFY_ACK, but
+		 * actually that also the BTS side of the channel mode has been changed accordingly */
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+
+		/* FIXME: Check if this requires special handling. For now I assume that the send_ass_compl()
+		 * can be used. But I am not sure. */
+		send_ass_compl(lchan, fi);
+
+		break;
+		/* FIXME: Do we need to handle DTAP traffic in this state? Maybe yes? Needs to be checked. */
+	case GSCON_EV_MO_DTAP:
+		forward_dtap((struct msgb *)data, conn, fi);
+		break;
+	case GSCON_EV_MT_DTAP:
+		submit_dtap(conn, (struct msgb *)data, fi);
+		break;
+	case GSCON_EV_TX_SCCP:
+		sigtran_send(conn, (struct msgb *)data, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+static void gscon_fsm_clearing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct msgb *resp;
+
+	switch (event) {
+	case GSCON_EV_RSL_CLEAR_COMPL:
+		resp = gsm0808_create_clear_complete();
+		sigtran_send(conn, resp, fi);
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* Wait for the handover logic to tell us whether the handover completed,
+ * failed or has timed out */
+static void gscon_fsm_wait_ho_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct mgcp_conn_peer conn_peer;
+	struct gsm_lchan *lchan = conn->lchan;
+	struct in_addr addr;
+	struct msgb *resp;
+	int rc;
+
+	switch (event) {
+	case GSCON_EV_HO_COMPL:
+		/* The handover logic informs us that the handover has been
+		 * completet. Now we have to tell the MGW the IP/Port on the
+		 * new BTS so that the uplink RTP traffic can be redirected
+		 * there. */
+
+		/* Prepare parameters with the information we got during the
+		 * handover procedure (via IPACC) */
+		memset(&conn_peer, 0, sizeof(conn_peer));
+		addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip);
+		osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr));
+		conn_peer.port = lchan->abis_ip.bound_port;
+
+		/* (Pre)Change state and modify the connection */
+		osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_HO_TIMEOUT_TIMER_NR);
+		rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer);
+		if (rc != 0) {
+			resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE);
+			sigtran_send(conn, resp, fi);
+			osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0);
+			return;
+		}
+		break;
+	case GSCON_EV_HO_TIMEOUT:
+	case GSCON_EV_HO_FAIL:
+		/* The handover logic informs us that the handover failed for
+		 * some reason. This means the phone stays on the TS/BTS on
+		 * which it currently is. We will change back to the active
+		 * state again as there are no further operations needed */
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+/* Wait for the MGW to confirm handover related modification of the connection
+ * parameters */
+static void gscon_fsm_wait_mdcx_bts_ho(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+
+	switch (event) {
+	case GSCON_EV_MGW_MDCX_RESP_BTS:
+		/* The MGW has confirmed the handover MDCX, and the handover
+		 * is now also done on the RTP side. We may now change back
+		 * to the active state. */
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		break;
+	case GSCON_EV_MO_DTAP:
+		forward_dtap((struct msgb *)data, conn, fi);
+		break;
+	case GSCON_EV_MT_DTAP:
+		submit_dtap(conn, (struct msgb *)data, fi);
+		break;
+	case GSCON_EV_TX_SCCP:
+		sigtran_send(conn, (struct msgb *)data, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+#define EV_TRANSPARENT_SCCP S(GSCON_EV_TX_SCCP) | S(GSCON_EV_MO_DTAP) | S(GSCON_EV_MT_DTAP)
+
+static const struct osmo_fsm_state gscon_fsm_states[] = {
+	[ST_INIT] = {
+		.name = OSMO_STRINGIFY(INIT),
+		.in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND),
+		.out_state_mask = S(ST_WAIT_CC),
+		.action = gscon_fsm_init,
+	},
+	[ST_WAIT_CC] = {
+		.name = OSMO_STRINGIFY(WAIT_CC),
+		.in_event_mask = S(GSCON_EV_A_CONN_CFM),
+		.out_state_mask = S(ST_ACTIVE),
+		.action = gscon_fsm_wait_cc,
+	},
+	[ST_ACTIVE] = {
+		.name = OSMO_STRINGIFY(ACTIVE),
+		.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_A_ASSIGNMENT_CMD) | S(GSCON_EV_A_HO_REQ) |
+				 S(GSCON_EV_HO_START),
+		.out_state_mask = S(ST_CLEARING) | S(ST_WAIT_CRCX_BTS) | S(ST_WAIT_ASS_CMPL) |
+				  S(ST_WAIT_MODE_MODIFY_ACK) | S(ST_WAIT_MO_HO_CMD) | S(ST_WAIT_HO_COMPL),
+		.action = gscon_fsm_active,
+	},
+	[ST_WAIT_CRCX_BTS] = {
+		.name = OSMO_STRINGIFY(WAIT_CRCX_BTS),
+		.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_BTS),
+		.out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_ASS_CMPL),
+		.action = gscon_fsm_wait_crcx_bts,
+	},
+	[ST_WAIT_ASS_CMPL] = {
+		.name = OSMO_STRINGIFY(WAIT_ASS_CMPL),
+		.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_RR_ASS_COMPL) | S(GSCON_EV_RR_ASS_FAIL),
+		.out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS),
+		.action = gscon_fsm_wait_ass_cmpl,
+	},
+	[ST_WAIT_MDCX_BTS] = {
+		.name = OSMO_STRINGIFY(WAIT_MDCX_BTS),
+		.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS),
+		.out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CRCX_MSC),
+		.action = gscon_fsm_wait_mdcx_bts,
+	},
+	[ST_WAIT_CRCX_MSC] = {
+		.name = OSMO_STRINGIFY(WAIT_CRCX_MSC),
+		.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_MSC),
+		.out_state_mask = S(ST_ACTIVE),
+		.action = gscon_fsm_wait_crcx_msc,
+	},
+	[ST_WAIT_MODE_MODIFY_ACK] = {
+		.name = OSMO_STRINGIFY(WAIT_MODE_MODIFY_ACK),
+		.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_RR_MODE_MODIFY_ACK),
+		.out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING),
+		.action = gscon_fsm_wait_mode_modify_ack,
+	},
+	[ST_CLEARING] = {
+		.name = OSMO_STRINGIFY(CLEARING),
+		.in_event_mask = S(GSCON_EV_RSL_CLEAR_COMPL),
+		.action = gscon_fsm_clearing,
+	},
+
+	/* TODO: external handover, probably it makes sense to break up the
+	 * program flow in handover_logic.c a bit and handle some of the logic
+	 * here? */
+	[ST_WAIT_MT_HO_ACC] = {
+		.name = OSMO_STRINGIFY(WAIT_MT_HO_ACC),
+	},
+	[ST_WAIT_MT_HO_COMPL] = {
+		.name = OSMO_STRINGIFY(WAIT_MT_HO_COMPL),
+	},
+	[ST_WAIT_MO_HO_CMD] = {
+		.name = OSMO_STRINGIFY(WAIT_MO_HO_CMD),
+	},
+	[ST_MO_HO_PROCEEDING] = {
+		.name = OSMO_STRINGIFY(MO_HO_PROCEEDING),
+	},
+
+	/* Internal handover */
+	[ST_WAIT_HO_COMPL] = {
+		.name = OSMO_STRINGIFY(WAIT_HO_COMPL),
+		.in_event_mask = S(GSCON_EV_HO_COMPL) | S(GSCON_EV_HO_FAIL) | S(GSCON_EV_HO_TIMEOUT),
+		.out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS_HO),
+		.action = gscon_fsm_wait_ho_compl,
+	},
+	[ST_WAIT_MDCX_BTS_HO] = {
+		.name = OSMO_STRINGIFY(WAIT_MDCX_BTS_HO),
+		.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS),
+		.action = gscon_fsm_wait_mdcx_bts_ho,
+		.out_state_mask = S(ST_ACTIVE),
+	},
+};
+
+static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct msgb *resp = NULL;
+
+	/* When a connection on the MGW fails, make sure that the reference
+	 * in our book-keeping is erased. */
+	switch (event) {
+	case GSCON_EV_MGW_FAIL_BTS:
+		conn->user_plane.fi_bts = NULL;
+		break;
+	case GSCON_EV_MGW_FAIL_MSC:
+		conn->user_plane.fi_msc = NULL;
+		break;
+	}
+
+	/* Regular allstate event processing */
+	switch (event) {
+	case GSCON_EV_MGW_FAIL_BTS:
+	case GSCON_EV_MGW_FAIL_MSC:
+		/* Note: An MGW connection die per definition at any time.
+		 * However, if it dies during the assignment we must return
+		 * with an assignment failure */
+		OSMO_ASSERT(fi->state != ST_INIT && fi->state != ST_WAIT_CC)
+		    if (fi->state == ST_WAIT_CRCX_BTS || fi->state == ST_WAIT_ASS_CMPL || fi->state == ST_WAIT_MDCX_BTS
+			|| fi->state == ST_WAIT_CRCX_MSC) {
+			resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL);
+			sigtran_send(conn, resp, fi);
+			osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		}
+		break;
+	case GSCON_EV_A_CLEAR_CMD:
+		/* MSC tells us to cleanly shut down */
+		osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0);
+		gsm0808_clear(conn);
+		/* FIXME: Release all terestrial resources in ST_CLEARING */
+		/* According to 3GPP 48.008 3.1.9.1. "The BSS need not wait for the radio channel
+		 * release to be completed or for the guard timer to expire before returning the
+		 * CLEAR COMPLETE message" */
+
+		/* Close MGCP connections */
+		toss_mgcp_conn(conn, fi);
+
+		/* FIXME: Question: Is this a hack to force a clear complete from internel?
+		 * nobody seems to send the event from outside? */
+		osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RSL_CLEAR_COMPL, NULL);
+		break;
+	case GSCON_EV_A_DISC_IND:
+		/* MSC or SIGTRAN network has hard-released SCCP connection,
+		 * terminate the FSM now. */
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data);
+		break;
+	case GSCON_EV_RLL_REL_IND:
+		/* BTS reports that one of the LAPDm data links was released */
+		/* send proper clear request to MSC */
+		LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n");
+		resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE);
+		sigtran_send(conn, resp, fi);
+		break;
+	case GSCON_EV_RSL_CONN_FAIL:
+		LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n");
+		resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
+		sigtran_send(conn, resp, fi);
+		break;
+	default:
+		OSMO_ASSERT(false);
+		break;
+	}
+}
+
+void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send);
+
+static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+
+	if (conn->ho) {
+		LOGPFSML(fi, LOGL_DEBUG, "Releasing handover state\n");
+		bsc_clear_handover(conn, 1);
+		conn->ho = NULL;
+	}
+
+	if (conn->secondary_lchan) {
+		LOGPFSML(fi, LOGL_DEBUG, "Releasing secondary_lchan\n");
+		lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
+		conn->secondary_lchan = NULL;
+	}
+	if (conn->lchan) {
+		LOGPFSML(fi, LOGL_DEBUG, "Releasing lchan\n");
+		lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END);
+		conn->lchan = NULL;
+	}
+
+	if (conn->bsub) {
+		LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n");
+		bsc_subscr_put(conn->bsub);
+		conn->bsub = NULL;
+	}
+
+	if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) {
+		LOGPFSML(fi, LOGL_DEBUG, "Disconnecting SCCP\n");
+		struct bsc_msc_data *msc = conn->sccp.msc;
+		/* FIXME: include a proper cause value / error message? */
+		osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn_id, &msc->a.bsc_addr, 0);
+		conn->sccp.state = SUBSCR_SCCP_ST_NONE;
+	}
+
+	/* drop pending messages */
+	ho_dtap_cache_flush(conn, 0);
+
+	penalty_timers_free(&conn->hodec2.penalty_timers);
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+	fi->priv = NULL;
+}
+
+static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+
+	/* Make sure all possibly still open MGCP connections get closed */
+	toss_mgcp_conn(conn, fi);
+}
+
+static int gscon_timer_cb(struct osmo_fsm_inst *fi)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	struct msgb *resp = NULL;
+
+	switch (fi->T) {
+	case 993210:
+		/* MSC has not responded/confirmed connection witH CC */
+		/* N-DISCONNET.req is sent in gscon_cleanup() above */
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+		break;
+	case GSM0808_T10_TIMER_NR:	/* Assignment Failed */
+		resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, NULL);
+		sigtran_send(conn, resp, fi);
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		break;
+	case MGCP_MGW_TIMEOUT_TIMER_NR:	/* Assignment failed (no response from MGW) */
+		resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL);
+		sigtran_send(conn, resp, fi);
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		break;
+	case MGCP_MGW_HO_TIMEOUT_TIMER_NR:	/* Handover failed (no response from MGW) */
+		osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+		break;
+	default:
+		OSMO_ASSERT(false);
+	}
+	return 0;
+}
+
+static struct osmo_fsm gscon_fsm = {
+	.name = "SUBSCR_CONN",
+	.states = gscon_fsm_states,
+	.num_states = ARRAY_SIZE(gscon_fsm_states),
+	.allstate_event_mask = S(GSCON_EV_A_DISC_IND) | S(GSCON_EV_A_CLEAR_CMD) | S(GSCON_EV_RSL_CONN_FAIL) |
+	    S(GSCON_EV_RLL_REL_IND) | S(GSCON_EV_MGW_FAIL_BTS) | S(GSCON_EV_MGW_FAIL_MSC),
+	.allstate_action = gscon_fsm_allstate,
+	.cleanup = gscon_cleanup,
+	.pre_term = gscon_pre_term,
+	.timer_cb = gscon_timer_cb,
+	.log_subsys = DMSC,
+	.event_names = gscon_fsm_event_names,
+};
+
+/* Allocate a subscriber connection and its associated FSM */
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net)
+{
+	struct gsm_subscriber_connection *conn;
+	static bool g_initialized = false;
+
+	if (!g_initialized) {
+		osmo_fsm_register(&gscon_fsm);
+		g_initialized = true;
+	}
+
+	conn = talloc_zero(net, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
+
+	conn->network = net;
+	INIT_LLIST_HEAD(&conn->ho_dtap_cache);
+	/* BTW, penalty timers will be initialized on-demand. */
+	conn->sccp.conn_id = -1;
+
+	/* don't allocate from 'conn' context, as gscon_cleanup() will call talloc_free(conn) before
+	 * libosmocore will call talloc_free(conn->fi), i.e. avoid use-after-free during cleanup */
+	conn->fi = osmo_fsm_inst_alloc(&gscon_fsm, net, conn, LOGL_NOTICE, NULL);
+	if (!conn->fi) {
+		talloc_free(conn);
+		return NULL;
+	}
+
+	llist_add_tail(&conn->entry, &net->subscr_conns);
+	return conn;
+}
diff --git a/src/libbsc/handover_decision_2.c b/src/libbsc/handover_decision_2.c
index 467a19d..7ac54df 100644
--- a/src/libbsc/handover_decision_2.c
+++ b/src/libbsc/handover_decision_2.c
@@ -1150,7 +1150,7 @@
 		LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Initial Assignment is still ongoing\n");
 		return;
 	}
-	if (lchan->conn->ho_lchan) {
+	if (lchan->conn->ho) {
 		LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Handover already triggered\n");
 		return;
 	}
@@ -1349,7 +1349,7 @@
 					break;
 				/* omit if there is an ongoing ho/as */
 				if (!lc->conn || lc->conn->secondary_lchan
-				    || lc->conn->ho_lchan)
+				    || lc->conn->ho)
 					break;
 				/* We desperately want to resolve congestion, ignore rxlev when
 				 * collecting candidates by passing include_weaker_rxlev=true. */
@@ -1365,7 +1365,7 @@
 					/* omit of there is an ongoing ho/as */
 					if (!lc->conn
 					    || lc->conn->secondary_lchan
-					    || lc->conn->ho_lchan)
+					    || lc->conn->ho)
 						continue;
 					/* We desperately want to resolve congestion, ignore rxlev when
 					 * collecting candidates by passing include_weaker_rxlev=true. */
diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c
index cdc21f5..8de0fd6 100644
--- a/src/libbsc/handover_logic.c
+++ b/src/libbsc/handover_logic.c
@@ -40,6 +40,7 @@
 #include <osmocom/bsc/gsm_04_08_utils.h>
 #include <osmocom/bsc/handover.h>
 #include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
 
 static LLIST_HEAD(bsc_handovers);
 static LLIST_HEAD(handover_decision_callbacks);
@@ -81,70 +82,105 @@
 int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
 		       enum gsm_chan_t new_lchan_type)
 {
-	struct gsm_network *network;
-	struct gsm_lchan *new_lchan;
+	struct gsm_subscriber_connection *conn;
 	struct bsc_handover *ho;
 	static uint8_t ho_ref = 0;
-	int rc;
-	bool do_assignment = false;
+	bool do_assignment;
+
+	OSMO_ASSERT(old_lchan);
 
 	/* don't attempt multiple handovers for the same lchan at
 	 * the same time */
 	if (bsc_ho_by_old_lchan(old_lchan))
 		return -EBUSY;
 
-	if (!new_bts)
-		new_bts = old_lchan->ts->trx->bts;
-	do_assignment = (new_bts == old_lchan->ts->trx->bts);
-
-	network = new_bts->network;
-
-	rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
-
-	if (!old_lchan->conn) {
+	conn = old_lchan->conn;
+	if (!conn) {
 		LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n");
 		return -ENOSPC;
 	}
 
-	DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Beginning with handover operation...\n",
+	if (!new_bts)
+		new_bts = old_lchan->ts->trx->bts;
+	OSMO_ASSERT(new_bts);	
+
+	do_assignment = (new_bts == old_lchan->ts->trx->bts);
+
+	ho = talloc_zero(conn, struct bsc_handover);
+	if (!ho) {
+		LOGP(DHO, LOGL_FATAL, "Out of Memory\n");
+		return -ENOMEM;
+	}
+	ho->from_hodec_id = from_hodec_id;
+	ho->old_lchan = old_lchan;
+	ho->new_bts = new_bts;
+	ho->new_lchan_type = new_lchan_type;
+	ho->ho_ref = ho_ref++;
+	ho->inter_cell = !do_assignment;
+	ho->async = true;
+	llist_add(&ho->list, &bsc_handovers);
+
+	conn->ho = ho;
+
+	DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Initiating %s...\n",
 	       old_lchan->ts->trx->bts->nr,
 	       old_lchan->ts->trx->nr,
 	       old_lchan->ts->nr,
 	       old_lchan->nr,
 	       gsm_pchan_name(old_lchan->ts->pchan),
 	       new_bts->nr,
-	       gsm_lchant_name(new_lchan_type));
+	       gsm_lchant_name(new_lchan_type),
+	       do_assignment ? "Assignment" : "Handover");
 
-	new_lchan = lchan_alloc(new_bts, new_lchan_type, 0);
-	if (!new_lchan) {
-		LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(new_lchan_type));
+	return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HO_START, NULL);
+}
+
+/*! Start actual handover. Call bsc_handover_start() instead; The only legal caller is the GSCON FSM in
+ * bsc_subscr_conn_fsm.c. */
+int bsc_handover_start_gscon(struct gsm_subscriber_connection *conn)
+{
+	int rc;
+	struct gsm_network *network = conn->network;
+	struct bsc_handover *ho = conn->ho;
+	struct gsm_lchan *old_lchan;
+	struct gsm_lchan *new_lchan;
+
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "%s: Requested to start handover, but conn->ho is NULL\n",
+		     bsc_subscr_name(conn->bsub));
+		return -EINVAL;
+	}
+
+	OSMO_ASSERT(ho->old_lchan && ho->new_bts);
+
+	if (ho->old_lchan->conn != conn) {
+		LOGP(DHO, LOGL_ERROR,
+		     "%s: Requested to start handover, but the lchan does not belong to this conn\n",
+		     bsc_subscr_name(conn->bsub));
+		return -EINVAL;
+	}
+
+	rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
+
+	ho->new_lchan = lchan_alloc(ho->new_bts, ho->new_lchan_type, 0);
+	if (!ho->new_lchan) {
+		LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(ho->new_lchan_type));
 		rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]);
 		return -ENOSPC;
 	}
 
-	ho = talloc_zero(tall_bsc_ctx, struct bsc_handover);
-	if (!ho) {
-		LOGP(DHO, LOGL_FATAL, "Out of Memory\n");
-		lchan_free(new_lchan);
-		return -ENOMEM;
-	}
-	ho->from_hodec_id = from_hodec_id;
-	ho->old_lchan = old_lchan;
-	ho->new_lchan = new_lchan;
-	ho->ho_ref = ho_ref++;
-	ho->inter_cell = !do_assignment;
-	ho->async = true;
-
-	LOGPHO(ho, LOGL_INFO, "Triggering %s\n", do_assignment? "Assignment" : "Handover");
+	LOGPHO(ho, LOGL_INFO, "Triggering %s\n", ho->inter_cell? "Handover" : "Assignment");
 
 	/* copy some parameters from old lchan */
+	old_lchan = ho->old_lchan;
+	new_lchan = ho->new_lchan;
 	memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr));
-	if (do_assignment) {
+	if (!ho->inter_cell) {
 		new_lchan->ms_power = old_lchan->ms_power;
 		new_lchan->rqd_ta = old_lchan->rqd_ta;
 	} else {
 		new_lchan->ms_power =
-			ms_pwr_ctl_lvl(new_bts->band, new_bts->ms_max_power);
+			ms_pwr_ctl_lvl(ho->new_bts->band, ho->new_bts->ms_max_power);
 		/* FIXME: do we have a better idea of the timing advance? */
 		//new_lchan->rqd_ta = old_lchan->rqd_ta;
 	}
@@ -154,24 +190,21 @@
 	memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, sizeof(new_lchan->mr_ms_lv));
 	memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, sizeof(new_lchan->mr_bts_lv));
 
-	new_lchan->conn = old_lchan->conn;
-	new_lchan->conn->ho_lchan = new_lchan;
+	new_lchan->conn = conn;
 
 	rc = rsl_chan_activate_lchan(new_lchan,
 				     ho->async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC,
 				     ho->ho_ref);
 	if (rc < 0) {
 		LOGPHO(ho, LOGL_INFO, "%s Failure: activate lchan rc = %d\n",
-		       do_assignment? "Assignment" : "Handover", rc);
-		new_lchan->conn->ho_lchan = NULL;
-		new_lchan->conn = NULL;
-		talloc_free(ho);
+		       ho->inter_cell? "Handover" : "Assignment", rc);
 		lchan_free(new_lchan);
+		ho->new_lchan = NULL;
+		bsc_clear_handover(conn, 0);
 		return rc;
 	}
 
 	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
-	llist_add(&ho->list, &bsc_handovers);
 	/* we continue in the SS_LCHAN handler / ho_chan_activ_ack */
 
 	return 0;
@@ -180,26 +213,20 @@
 /* clear any operation for this connection */
 void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan)
 {
-	struct bsc_handover *ho;
+	struct bsc_handover *ho = conn->ho;
 
-	ho = bsc_ho_by_new_lchan(conn->ho_lchan);
-
-
-	if (!ho && conn->ho_lchan)
-		LOGP(DHO, LOGL_ERROR, "BUG: We lost some state.\n");
-
-	if (!ho) {
-		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+	if (!ho)
 		return;
+
+	if (ho->new_lchan) {
+		ho->new_lchan->conn = NULL;
+		if (free_lchan)
+			lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
+		ho->new_lchan = NULL;
 	}
 
-	conn->ho_lchan->conn = NULL;
-	conn->ho_lchan = NULL;
-
-	if (free_lchan)
-		lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
-
 	handover_free(ho);
+	conn->ho = NULL;
 }
 
 /* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */
@@ -211,10 +238,10 @@
 	DEBUGP(DHO, "HO T3103 expired\n");
 	rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]);
 
-	ho->new_lchan->conn->ho_lchan = NULL;
-	ho->new_lchan->conn = NULL;
-	lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
-	handover_free(ho);
+	/* Inform the GSCON FSM about the timed out handover */
+	osmo_fsm_inst_dispatch(ho->old_lchan->conn->fi, GSCON_EV_HO_TIMEOUT, NULL);
+
+	bsc_clear_handover(ho->old_lchan->conn, 1);
 }
 
 /* RSL has acknowledged activation of the new lchan */
@@ -263,12 +290,7 @@
 	if (hdc && hdc->on_ho_chan_activ_nack)
 		hdc->on_ho_chan_activ_nack(ho);
 
-	new_lchan->conn->ho_lchan = NULL;
-	new_lchan->conn = NULL;
-	handover_free(ho);
-
-	/* FIXME: maybe we should try to allocate a new LCHAN here? */
-
+	bsc_clear_handover(new_lchan->conn, 0);
 	return 0;
 }
 
@@ -296,16 +318,19 @@
 	if (ho->old_lchan != new_lchan->conn->lchan)
 		LOGPHO(ho, LOGL_ERROR, "Primary lchan changed during handover.\n");
 
-	if (new_lchan != new_lchan->conn->ho_lchan)
+	if (new_lchan->conn->ho != ho)
 		LOGPHO(ho, LOGL_ERROR, "Handover channel changed during this handover.\n");
 
-	new_lchan->conn->ho_lchan = NULL;
 	new_lchan->conn->lchan = new_lchan;
 	ho->old_lchan->conn = NULL;
 
 	lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END);
 
 	handover_free(ho);
+	new_lchan->conn->ho = NULL;
+
+	/* Inform the GSCON FSM that the handover is complete */
+	osmo_fsm_inst_dispatch(new_lchan->conn->fi, GSCON_EV_HO_COMPL, NULL);
 	return 0;
 }
 
@@ -314,7 +339,6 @@
 {
 	struct gsm_network *net = old_lchan->ts->trx->bts->network;
 	struct bsc_handover *ho;
-	struct gsm_lchan *new_lchan;
 	struct handover_decision_callbacks *hdc;
 
 	ho = bsc_ho_by_old_lchan(old_lchan);
@@ -329,16 +353,10 @@
 
 	rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]);
 
-	new_lchan = ho->new_lchan;
+	bsc_clear_handover(ho->new_lchan->conn, 1);
 
-	/* release the channel and forget about it */
-	ho->new_lchan->conn->ho_lchan = NULL;
-	ho->new_lchan->conn = NULL;
-	handover_free(ho);
-
-	lchan_release(new_lchan, 0, RSL_REL_LOCAL_END);
-
-
+	/* Inform the GSCON FSM that the handover failed */
+	osmo_fsm_inst_dispatch(old_lchan->conn->fi, GSCON_EV_HO_FAIL, NULL);
 	return 0;
 }
 
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index 3019470..cc96743 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -30,7 +30,6 @@
 	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 \
diff --git a/src/osmo-bsc/osmo_bsc_api.c b/src/osmo-bsc/osmo_bsc_api.c
index 75dae12..cdfd7cf 100644
--- a/src/osmo-bsc/osmo_bsc_api.c
+++ b/src/osmo-bsc/osmo_bsc_api.c
@@ -17,6 +17,7 @@
  *
  */
 
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
 #include <osmocom/bsc/osmo_bsc.h>
 #include <osmocom/bsc/bsc_msc_data.h>
 #include <osmocom/bsc/debug.h>
@@ -31,24 +32,23 @@
 
 #include <osmocom/bsc/osmo_bsc_sigtran.h>
 
-#define return_when_not_connected(conn) \
-	if (conn->sccp.state != SUBSCR_SCCP_ST_CONNECTED) {\
-		LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
-		return; \
-	}
+/* Check if we have a proper connection to the MSC */
+static bool msc_connected(struct gsm_subscriber_connection *conn)
+{
+	/* No subscriber conn at all */
+	if (!conn)
+		return false;
 
-#define return_when_not_connected_val(conn, ret) \
-	if (conn->sccp.state != SUBSCR_SCCP_ST_CONNECTED) {\
-		LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
-		return ret; \
-	}
+	/* Connection to MSC not established */
+	if (!conn->sccp.msc)
+		return false;
 
-#define queue_msg_or_return(resp) \
-	if (!resp) { \
-		LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); \
-		return; \
-	} \
-	osmo_bsc_sigtran_send(conn, resp);
+	/* Reset procedure not (yet) executed */
+	if (a_reset_conn_ready(conn->sccp.msc->a.reset) == false)
+		return false;
+
+	return true;
+}
 
 static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause);
 static int complete_layer3(struct gsm_subscriber_connection *conn,
@@ -136,24 +136,33 @@
 
 static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
 {
+	int rc;
 	struct msgb *resp;
-	return_when_not_connected(conn);
 
+	if (!msc_connected(conn))
+		return;
+	
 	LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci);
-
 	resp = gsm0808_create_sapi_reject(dlci);
-	queue_msg_or_return(resp);
+	rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+	if (rc != 0)
+		msgb_free(resp);
 }
 
 static void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn,
 				  struct msgb *msg, uint8_t chosen_encr)
 {
+	int rc;
 	struct msgb *resp;
-	return_when_not_connected(conn);
 
+	if (!msc_connected(conn))
+		return;
+	
 	LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n");
 	resp = gsm0808_create_cipher_complete(msg, chosen_encr);
-	queue_msg_or_return(resp);
+	rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+	if (rc != 0)
+		msgb_free(resp);
 }
 
 static void bsc_send_ussd_no_srv(struct gsm_subscriber_connection *conn,
@@ -274,15 +283,11 @@
 	resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), NULL);
 	if (!resp) {
 		LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n");
-		osmo_bsc_sigtran_del_conn(conn);
+		//osmo_bsc_sigtran_del_conn(conn);
 		return BSC_API_CONN_POL_REJECT;
 	}
 
-	if (osmo_bsc_sigtran_open_conn(conn, resp) != 0) {
-		osmo_bsc_sigtran_del_conn(conn);
-		msgb_free(resp);
-		return BSC_API_CONN_POL_REJECT;
-	}
+	osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_REQ, resp);
 
 	return BSC_API_CONN_POL_ACCEPT;
 }
@@ -307,7 +312,7 @@
 	 */
 	if (complete_layer3(_conn, msg, msc) != BSC_API_CONN_POL_ACCEPT) {
 		gsm0808_clear(_conn);
-		bsc_subscr_con_free(_conn);
+		//bsc_subscr_con_free(_conn);
 		return 1;
 	}
 
@@ -376,8 +381,9 @@
 static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
 {
 	int lu_cause;
-	struct msgb *resp;
-	return_when_not_connected(conn);
+
+	if (!msc_connected(conn))
+		return;
 
 	LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id);
 
@@ -399,16 +405,22 @@
 
 	bsc_scan_bts_msg(conn, msg);
 
-	resp = gsm0808_create_dtap(msg, link_id);
-	queue_msg_or_return(resp);
+	/* Store link_id in msg->cb */
+	OBSC_LINKID_CB(msg) = link_id;
+	osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MO_DTAP, msg);
 }
 
 static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause,
 			     uint8_t chosen_channel, uint8_t encr_alg_id,
 			     uint8_t speech_model)
 {
-	struct msgb *resp;
-	return_when_not_connected(conn);
+	if (!msc_connected(conn))
+		return;
+	
+	conn->lchan->abis_ip.ass_compl.rr_cause = rr_cause;
+	conn->lchan->abis_ip.ass_compl.chosen_channel = chosen_channel;
+	conn->lchan->abis_ip.ass_compl.encr_alg_id = encr_alg_id;
+	conn->lchan->abis_ip.ass_compl.speech_mode = speech_model;
 
 	if (is_ipaccess_bts(conn_get_bts(conn)) && conn->user_plane.rtp_ip) {
 		/* NOTE: In a network that makes use of an IPA base station
@@ -419,38 +431,30 @@
 		 * postpone the AoIP assignment completed message until we
 		 * know the RTP IP/Port combination. */
 		LOGP(DMSC, LOGL_INFO, "POSTPONE MSC ASSIGN COMPL\n");
-		conn->lchan->abis_ip.ass_compl.rr_cause = rr_cause;
-		conn->lchan->abis_ip.ass_compl.chosen_channel = chosen_channel;
-		conn->lchan->abis_ip.ass_compl.encr_alg_id = encr_alg_id;
-		conn->lchan->abis_ip.ass_compl.speech_mode = speech_model;
 		conn->lchan->abis_ip.ass_compl.valid = true;
 
 	} else {
 		/* NOTE: Send the A assignment complete message immediately. */
 		LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL\n");
-		resp = gsm0808_create_assignment_completed(rr_cause, chosen_channel,
-							   encr_alg_id, speech_model);
-		queue_msg_or_return(resp);
+		osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RR_ASS_COMPL, NULL);
 	}
 }
 
 static void bsc_assign_fail(struct gsm_subscriber_connection *conn,
 			    uint8_t cause, uint8_t *rr_cause)
 {
-	struct msgb *resp;
-	return_when_not_connected(conn);
-
 	LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN FAIL\n");
-
-	resp = gsm0808_create_assignment_failure(cause, rr_cause);
-	queue_msg_or_return(resp);
+	osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RR_ASS_FAIL, NULL);
 }
 
 static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
 {
+	int rc;
 	struct msgb *resp;
-	return_when_not_connected_val(conn, 1);
 
+	if (!msc_connected(conn))
+		return 1;
+	
 	LOGP(DMSC, LOGL_INFO, "Tx MSC CLEAR REQUEST\n");
 
 	resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
@@ -459,7 +463,10 @@
 		return 1;
 	}
 
-	osmo_bsc_sigtran_send(conn, resp);
+	rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+	if (rc != 0)
+		msgb_free(resp);
+	
 	return 1;
 }
 
@@ -467,12 +474,16 @@
 			  const uint8_t *cm2, uint8_t cm2_len,
 			  const uint8_t *cm3, uint8_t cm3_len)
 {
+	int rc;
 	struct msgb *resp;
-	return_when_not_connected(conn);
 
+	if (!msc_connected(conn))
+		return;
+	
 	resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len);
-
-	queue_msg_or_return(resp);
+	rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+	if (rc != 0)
+		msgb_free(resp);
 }
 
 static void bsc_mr_config(struct gsm_subscriber_connection *conn,
diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c
index 295d854..23fd8c9 100644
--- a/src/osmo-bsc/osmo_bsc_audio.c
+++ b/src/osmo-bsc/osmo_bsc_audio.c
@@ -29,7 +29,8 @@
 #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 <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/bsc_subscriber.h>
 
 #include <arpa/inet.h>
 
@@ -71,13 +72,17 @@
 		break;
 
 	case S_ABISIP_MDCX_ACK:
-		if (con->ho_lchan) {
-			LOGP(DHO, LOGL_DEBUG, "%s -> %s BTS sent MDCX ACK\n", gsm_lchan_name(lchan),
-			     gsm_lchan_name(con->ho_lchan));
+		if (con->ho) {
+			LOGPHO(con->ho, LOGL_DEBUG, "BTS sent MDCX ACK\n");
 			/* No need to do anything for handover here. As soon as a HANDOVER DETECT
 			 * happens, osmo_bsc_mgcp.c will trigger the MGCP MDCX towards MGW by
-			 * receiving an S_LCHAN_HANDOVER_DETECT signal. */
+			 * receiving an S_LCHAN_HANDOVER_DETECT signal. 
+			 * 
+			 * FIXME: This will not work, osmo_bsc_mgcp.c is now removed. The
+			 * switchover must be handled by the GSCON FSM because there we
+			 * we instantiate the child FSMs which handle the MGCP traffic. */
 #if 0
+/* FIXME: This does not work anymore, we will have to implement this in the GSCON FSM */
 			/* NOTE: When an ho_lchan exists, the MDCX is part of an
 			 * handover operation (intra-bsc). This means we will not
 			 * inform the MSC about the event, which means that no
@@ -92,7 +97,8 @@
 			 * IPA based base stations. See also osmo_bsc_api.c,
 			 * function bsc_assign_compl() */
 			LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL (POSTPONED)\n");
-			mgcp_ass_complete(con->user_plane.mgcp_ctx, lchan);
+			osmo_fsm_inst_dispatch(con->fi, GSCON_EV_RR_ASS_COMPL, NULL);
+
 		}
 		break;
 	}
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
index db749a3..14d10de 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -1,6 +1,7 @@
 /* GSM 08.08 BSSMAP handling						*/
 /* (C) 2009-2012 by Holger Hans Peter Freyther <zecke at selfish.org>
  * (C) 2009-2012 by On-Waves
+ * (C) 2017 by Harald Welte <laforge at gnumonks.org>
  * All Rights Reserved
  *
  * This program is free software; you can redistribute it and/or modify
@@ -24,9 +25,9 @@
 #include <osmocom/bsc/bsc_msc_data.h>
 #include <osmocom/bsc/debug.h>
 #include <osmocom/bsc/bsc_subscriber.h>
-#include <osmocom/bsc/osmo_bsc_mgcp.h>
 #include <osmocom/bsc/paging.h>
 #include <osmocom/bsc/gsm_04_08_utils.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
 
 #include <osmocom/gsm/protocol/gsm_08_08.h>
 #include <osmocom/gsm/gsm0808.h>
@@ -573,45 +574,6 @@
 }
 
 /*
- * GSM 08.08 § 3.1.9.1 and 3.2.1.21...
- * release our gsm_subscriber_connection and send message
- */
-static int bssmap_handle_clear_command(struct gsm_subscriber_connection *conn,
-				       struct msgb *msg, unsigned int payload_length)
-{
-	struct msgb *resp;
-
-	/* TODO: handle the cause of this package */
-
-	LOGP(DMSC, LOGL_INFO, "Releasing all transactions on %p\n", conn);
-	gsm0808_clear(conn);
-
-	/* generate the clear complete message */
-	resp = gsm0808_create_clear_complete();
-	if (!resp) {
-		LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n");
-		return -1;
-	}
-
-	if (conn->user_plane.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->user_plane.mgcp_ctx, resp);
-	} 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;
-}
-
-/*
  * GSM 08.08 § 3.4.7 cipher mode handling. We will have to pick
  * the cipher to be used for this. In case we are already using
  * a cipher we will have to send cipher mode reject to the MSC,
@@ -706,7 +668,7 @@
 		return -1;
 	}
 
-	osmo_bsc_sigtran_send(conn, resp);
+	osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
 	return -1;
 }
 
@@ -772,118 +734,129 @@
 	}
 
 	/* Currently we only support a limited subset of all
-	 * possible channel types. The limitation ends by not using
-	 * multi-slot, limiting the channel coding to speech */
-	if (ct.ch_indctr != GSM0808_CHAN_SPEECH) {
+	 * possible channel types, such as multi-slot or CSD */
+	switch (ct.ch_indctr) {
+	case GSM0808_CHAN_DATA:
 		LOGP(DMSC, LOGL_ERROR, "Unsupported channel type, currently only speech is supported!\n");
 		cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP;
 		goto reject;
-	}
-
-	/* Detect if a CIC code is present, if so, we use the classic ip.access
-	 * method to calculate the RTP port */
-	if (TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
-		conn->user_plane.cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
-		timeslot = conn->user_plane.cic & 0x1f;
-		multiplex = (conn->user_plane.cic & ~0x1f) >> 5;
-	} else if (TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
-		/* Decode AoIP transport address element */
-		rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr, TLVP_VAL(&tp, GSM0808_IE_AOIP_TRASP_ADDR),
-						 TLVP_LEN(&tp, GSM0808_IE_AOIP_TRASP_ADDR));
-		if (rc < 0) {
-			LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n");
-			cause = GSM0808_CAUSE_INCORRECT_VALUE;
-			goto reject;
-		}
-		aoip = true;
-	} else {
-		LOGP(DMSC, LOGL_ERROR, "AoIP transport address and CIC missing. Audio will not work.\n");
-		cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
-		goto reject;
-	}
-
-	/* Decode speech codec list (AoIP) */
-	conn->codec_list_present = false;
-	if (aoip) {
-		/* Check for speech codec list element */
-		if (!TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) {
-			LOGP(DMSC, LOGL_ERROR, "Mandatory speech codec list not present.\n");
+	case GSM0808_CHAN_SPEECH:
+		/* Detect if a CIC code is present, if so, we use the classic ip.access method to
+		 * calculate the RTP port */
+		if (TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+			conn->user_plane.cic =
+				osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+			timeslot = conn->user_plane.cic & 0x1f;
+			multiplex = (conn->user_plane.cic & ~0x1f) >> 5;
+		} else if (TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
+			/* Decode AoIP transport address element */
+			rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr,
+							 TLVP_VAL(&tp, GSM0808_IE_AOIP_TRASP_ADDR),
+							 TLVP_LEN(&tp, GSM0808_IE_AOIP_TRASP_ADDR));
+			if (rc < 0) {
+				LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n");
+				cause = GSM0808_CAUSE_INCORRECT_VALUE;
+				goto reject;
+			}
+			aoip = true;
+		} else {
+			LOGP(DMSC, LOGL_ERROR, "AoIP transport address and CIC missing. "
+			     "Audio will not work.\n");
 			cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
 			goto reject;
 		}
 
-		/* Decode Speech Codec list */
-		rc = gsm0808_dec_speech_codec_list(&conn->codec_list,
-						   TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST),
-						   TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST));
-		if (rc < 0) {
-			LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n");
-			cause = GSM0808_CAUSE_INCORRECT_VALUE;
+		/* FIXME: At the moment osmo-bsc does not support any other
+		 * A-Interface other than AoIP. So we must reject all
+		 * assignment requests that are not AoIP compliant. However,
+		 * might support other A-Interface dialects lateron again,
+		 * thats why we preserve the logic around the AoIP detection
+		 * here. */
+		if (!aoip) {
+			LOGP(DMSC, LOGL_ERROR, "Requested A-Interface type is not supported! (AoIP only!)\n");
+			cause = GSM0808_CAUSE_REQ_A_IF_TYPE_NOT_SUPP;
 			goto reject;
 		}
-		conn->codec_list_present = true;
-		scl_ptr = &conn->codec_list;
-	}
 
-	/* Match codec information from the assignment command against the
-	 * local preferences of the BSC */
-	rc = match_codec_pref(&full_rate, &chan_mode, &ct, scl_ptr, msc);
-	if (rc < 0) {
-		LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type ="
-		     " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
-		     ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len));
-		/* TODO: actually output codec names, e.g. implement gsm0808_permitted_speech_names[] and
-		 * iterate perm_spch. */
-		cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+		/* Decode speech codec list (AoIP) */
+		conn->codec_list_present = false;
+		if (aoip) {
+			
+			/* Check for speech codec list element */
+			if (!TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) {
+				LOGP(DMSC, LOGL_ERROR, "Mandatory speech codec list not present.\n");
+				cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+				goto reject;
+			}
+
+			/* Decode Speech Codec list */
+			rc = gsm0808_dec_speech_codec_list(&conn->codec_list,
+							   TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST),
+							   TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST));
+			if (rc < 0) {
+				LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n");
+				cause = GSM0808_CAUSE_INCORRECT_VALUE;
+				goto reject;
+			}
+			conn->codec_list_present = true;
+			scl_ptr = &conn->codec_list;
+		}
+
+		/* Match codec information from the assignment command against the
+		 * local preferences of the BSC */
+		rc = match_codec_pref(&full_rate, &chan_mode, &ct, scl_ptr, msc);
+		if (rc < 0) {
+			LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type ="
+			     " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
+			     ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len));
+			/* TODO: actually output codec names, e.g. implement
+			 * gsm0808_permitted_speech_names[] and iterate perm_spch. */
+			cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+			goto reject;
+		}
+
+		DEBUGP(DMSC, "Found matching audio type: %s %s for channel_type ="
+		       " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
+		       full_rate? "full rate" : "half rate",
+		       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));
+
+		/* Forward the assignment 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->user_plane.aoip_rtp_addr_remote, &rtp_addr, sizeof(rtp_addr));
+		} 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. */
+			conn->user_plane.rtp_port = mgcp_timeslot_to_port(multiplex, timeslot, msc->rtp_base);
+			conn->user_plane.rtp_ip = 0;
+		}
+		conn->user_plane.chan_mode = chan_mode;
+		conn->user_plane.full_rate = full_rate;
+		osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_ASSIGNMENT_CMD, NULL);
+		break;
+	case GSM0808_CHAN_SIGN:
+		conn->user_plane.chan_mode = GSM48_CMODE_SIGN;
+		osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_ASSIGNMENT_CMD, NULL);
+		break;
+	default:
+		cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
 		goto reject;
 	}
-	DEBUGP(DMSC, "Found matching audio type: %s %s for channel_type ="
-	       " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
-	       full_rate? "full rate" : "half rate",
-	       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));
 
-	/* Forward the assignment 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->user_plane.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(), responding to
-		 * the assignment request etc... */
-		conn->user_plane.mgcp_ctx = mgcp_assignm_req(msc->network, msc->network->mgw.client,
-								conn, chan_mode, full_rate);
-		if (!conn->user_plane.mgcp_ctx) {
-			LOGP(DMSC, LOGL_ERROR, "MGCP / MGW failure, rejecting assignment... (id=%i)\n",
-				conn->sccp.conn_id);
-			cause = GSM0808_CAUSE_EQUIPMENT_FAILURE;
-			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. */
-		conn->user_plane.rtp_port = mgcp_timeslot_to_port(multiplex, timeslot, msc->rtp_base);
-		conn->user_plane.rtp_ip = 0;
-		return gsm0808_assign_req(conn, chan_mode, full_rate);
-	}
-
+	return 0;
 reject:
 	resp = gsm0808_create_assignment_failure(cause, NULL);
 	OSMO_ASSERT(resp);
 
-	osmo_bsc_sigtran_send(conn, resp);
+	osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
 	return -1;
 }
 
@@ -934,7 +907,7 @@
 
 	switch (msg->l4h[0]) {
 	case BSS_MAP_MSG_CLEAR_CMD:
-		ret = bssmap_handle_clear_command(conn, msg, length);
+		osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, msg);
 		break;
 	case BSS_MAP_MSG_CIPHER_MODE_CMD:
 		ret = bssmap_handle_cipher_mode(conn, msg, length);
@@ -995,7 +968,9 @@
 
 	/* pass it to the filter for extra actions */
 	rc = bsc_scan_msc_msg(conn, gsm48);
-	dtap_rc = gsm0808_submit_dtap(conn, gsm48, header->link_id, 1);
+	/* Store link_id in msgb->cb */
+	OBSC_LINKID_CB(msg) = header->link_id;
+	dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48);
 	if (rc == BSS_SEND_USSD)
 		bsc_send_welcome_ussd(conn);
 	return dtap_rc;
@@ -1052,39 +1027,4 @@
 	}
 
 	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);
-
-	LOGP(DMSC, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.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->user_plane.aoip_rtp_addr_local,
-					&sc,
-					NULL);
-
-	if (!resp) {
-		LOGP(DMSC, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n",
-		     conn->sccp.conn_id);
-		return -EINVAL;
-	}
-
-	return osmo_bsc_sigtran_send(conn, resp);
 }
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index af8f83d..27c5640 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -14,7 +14,7 @@
  * 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/>.
+ * along with this program.  If not, see <http://www.gnu.org/lienses/>.
  *
  */
 
@@ -29,7 +29,6 @@
 #include <osmocom/bsc/ipaccess.h>
 #include <osmocom/bsc/ctrl.h>
 #include <osmocom/bsc/osmo_bsc_sigtran.h>
-#include <osmocom/bsc/osmo_bsc_mgcp.h>
 #include <osmocom/bsc/handover_decision.h>
 #include <osmocom/bsc/handover_decision_2.h>
 
@@ -510,8 +509,6 @@
 		LOGP(DMSC, LOGL_ERROR, "Failed to register audio support.\n");
 		exit(1);
 	}
-
-	mgcp_init(bsc_gsmnet);
 
 	handover_decision_1_init();
 	hodec2_init(bsc_gsmnet);
diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c
deleted file mode 100644
index 4b6420e..0000000
--- a/src/osmo-bsc/osmo_bsc_mgcp.c
+++ /dev/null
@@ -1,1149 +0,0 @@
-/* (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/bsc/signal.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 S(x)	(1 << (x))
-
-#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 "%x at mgw"
-
-/* Some internal cause codes to indicate fault
- * condition inside the FSM */
-enum bsc_mgcp_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,
-	MGCP_ERR_NOMEM
-};
-
-/* Human readable respresentation of the faul codes,
- * will be displayed by handle_error() */
-static const struct value_string bsc_mgcp_cause_codes_names[] = {
-	{MGCP_ERR_MGW_FAIL, "operation failed on MGW"},
-	{MGCP_ERR_MGW_INVAL_RESP, "invalid / unparseable response from MGW"},
-	{MGCP_ERR_MGW_TX_FAIL, "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 (MSC)"},
-	{MGCP_ERR_BSS_TIMEOUT, "assignment could not be completed in time (BSS)"},
-	{MGCP_ERR_NOMEM, "out of memory"},
-	{0, NULL}
-};
-
-enum fsm_bsc_mgcp_states {
-	ST_CRCX_BTS,
-	ST_ASSIGN_PROC,
-	ST_MDCX_BTS,
-	ST_CRCX_NET,
-	ST_ASSIGN_COMPL,
-	ST_CALL,
-	ST_MDCX_BTS_HO,
-	ST_HALT
-};
-
-enum bsc_mgcp_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 call. It is also issued in case of errors to teardown a half
-	 * open connection. */
-	EV_TEARDOWN,
-
-	/* External event: Handover event, this event notifies the FSM that a
-	 * handover is required. The FSM will then perform an extra MDCX to
-	 * configure the new connection data at the MGW. The only valid state
-	 * where a Handover event can be received is ST_CALL. */
-	EV_HANDOVER,
-
-	/* 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,
-
-	/* Internal event: The mgcp_gw has responded to the (Handover-)
-	   MDCX that has been send to update the BTS connection. */
-	EV_MDCX_BTS_HO_RESP,
-};
-
-/* 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 */
-#define handle_error(mgcp_ctx, cause) \
-	_handle_error(mgcp_ctx, cause, __FILE__, __LINE__)
-static void _handle_error(struct mgcp_ctx *mgcp_ctx, enum bsc_mgcp_cause_code cause,
-			  const char *file, int line)
-{
-	struct osmo_fsm_inst *fi;
-
-	OSMO_ASSERT(mgcp_ctx);
-	fi = mgcp_ctx->fsm;
-	OSMO_ASSERT(fi);
-
-	LOGPFSMLSRC(mgcp_ctx->fsm, LOGL_ERROR, file, line, "%s -- graceful shutdown...\n",
-		    get_value_string(bsc_mgcp_cause_codes_names, cause));
-
-	/* Set the VM into the state where it waits for the call end */
-	osmo_fsm_inst_state_chg(fi, ST_CALL, 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 = data;
-	struct gsm_subscriber_connection *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);
-
-	rtp_endpoint = mgcp_client_next_endpoint(mgcp);
-	mgcp_ctx->rtp_endpoint = rtp_endpoint;
-
-	LOGPFSML(fi, LOGL_DEBUG,
-		 "CRCX/BTS: creating connection for the BTS side on MGW endpoint:0x%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_MODE),
-		.call_id = conn->sccp.conn_id,
-		.conn_mode = MGCP_CONN_LOOPBACK
-	};
-	if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
-	    MGCP_ENDPOINT_MAXLEN) {
-		handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
-		return;
-	}
-	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
-	OSMO_ASSERT(msg);
-
-	/* Transmit MGCP message to MGW */
-	mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
-	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(fi, 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 gsm_subscriber_connection *conn;
-	uint32_t addr;
-
-	OSMO_ASSERT(mgcp_ctx);
-	conn = mgcp_ctx->conn;
-	OSMO_ASSERT(conn);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR,
-		     "CRCX/BTS: late MGW response, FSM already terminated -- ignoring...\n");
-		return;
-	}
-
-	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;
-	}
-
-	/* memorize connection identifier */
-	osmo_strlcpy(mgcp_ctx->conn_id_bts, r->head.conn_id, sizeof(mgcp_ctx->conn_id_bts));
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: MGW responded with CI: %s\n", mgcp_ctx->conn_id_bts);
-
-	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;
-	}
-
-	addr = inet_addr(r->audio_ip);
-	if (addr == INADDR_NONE) {
-		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/BTS: Cannot parse response (invalid IP-address)\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->user_plane.rtp_port = r->audio_port;
-	conn->user_plane.rtp_ip = osmo_ntohl(addr);
-
-	/* 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 = data;
-	struct gsm_subscriber_connection *conn;
-	enum gsm48_chan_mode chan_mode;
-	bool full_rate;
-	int rc;
-
-	OSMO_ASSERT(mgcp_ctx);
-	conn = mgcp_ctx->conn;
-	OSMO_ASSERT(conn);
-
-	switch (event) {
-	case EV_CRCX_BTS_RESP:
-		break;
-	default:
-		handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
-		return;
-	}
-
-	OSMO_ASSERT(conn);
-	chan_mode = mgcp_ctx->chan_mode;
-	full_rate = mgcp_ctx->full_rate;
-
-	LOGPFSML(fi, LOGL_DEBUG, "MGW proceeding assignment request...\n");
-	rc = gsm0808_assign_req(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 = data;
-	struct gsm_subscriber_connection *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);
-
-	switch (event) {
-	case EV_ASS_COMPLETE:
-		break;
-	default:
-		handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
-		return;
-	}
-
-	mgcp = mgcp_ctx->mgcp;
-	OSMO_ASSERT(mgcp);
-	lchan = mgcp_ctx->lchan;
-	OSMO_ASSERT(lchan);
-
-	rtp_endpoint = mgcp_ctx->rtp_endpoint;
-
-	addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip);
-	LOGPFSML(fi, LOGL_DEBUG,
-		 "MDCX/BTS: completing connection for the BTS side on MGW endpoint:0x%x, BTS expects RTP input on address %s:%u\n",
-		 rtp_endpoint, 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->sccp.conn_id,
-		.conn_id = mgcp_ctx->conn_id_bts,
-		.conn_mode = MGCP_CONN_RECV_SEND,
-		.audio_ip = inet_ntoa(addr),
-		.audio_port = lchan->abis_ip.bound_port
-	};
-	if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
-	    sizeof(mgcp_msg.endpoint)) {
-		handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
-		return;
-	}
-	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
-	OSMO_ASSERT(msg);
-
-	/* Transmit MGCP message to MGW */
-	mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
-	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(fi, 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 (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR,
-		     "MDCX/BTS: late MGW response, FSM already terminated -- ignoring...\n");
-		return;
-	}
-
-	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 = data;
-	struct gsm_subscriber_connection *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);
-
-	switch (event) {
-	case EV_MDCX_BTS_RESP:
-		break;
-	default:
-		handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
-		return;
-	}
-
-	rtp_endpoint = mgcp_ctx->rtp_endpoint;
-
-	LOGPFSML(fi, LOGL_DEBUG,
-		 "CRCX/NET: creating connection for the NET side on MGW endpoint:0x%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 MGW does not support IPv6 yet. This is
-	 * why we stop here in case some MSC tries to signal IPv6 AoIP
-	 * transport identifiers */
-	if (conn->user_plane.aoip_rtp_addr_remote.ss_family != AF_INET) {
-		LOGPFSML(fi, LOGL_ERROR,
-			 "CRCX/NET: 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->user_plane.aoip_rtp_addr_remote;
-	addr = inet_ntoa(sin->sin_addr);
-	port = osmo_ntohs(sin->sin_port);
-	LOGPFSML(fi, LOGL_DEBUG, "CRCX/NET: 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_MODE |
-			     MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
-		.call_id = conn->sccp.conn_id,
-		.conn_mode = MGCP_CONN_RECV_SEND,
-		.audio_ip = addr,
-		.audio_port = port
-	};
-	if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
-	    sizeof(mgcp_msg.endpoint)) {
-		handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
-		return;
-	}
-	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
-	OSMO_ASSERT(msg);
-
-	/* Transmit MGCP message to MGW */
-	mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
-	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(fi, 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 gsm_subscriber_connection *conn;
-	struct gsm_lchan *lchan;
-	struct sockaddr_in *sin;
-	uint32_t addr;
-
-	OSMO_ASSERT(mgcp_ctx);
-	conn = mgcp_ctx->conn;
-	OSMO_ASSERT(conn);
-	lchan = mgcp_ctx->lchan;
-	OSMO_ASSERT(lchan);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR,
-		     "CRCX/NET: late MGW response, FSM already terminated -- ignoring...\n");
-		return;
-	}
-
-	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;
-	}
-
-	/* memorize connection identifier */
-	osmo_strlcpy(mgcp_ctx->conn_id_net, r->head.conn_id, sizeof(mgcp_ctx->conn_id_net));
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: MGW responded with CI: %s\n", mgcp_ctx->conn_id_net);
-
-	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;
-	}
-
-	addr = inet_addr(r->audio_ip);
-	if (addr == INADDR_NONE) {
-		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/NET: Cannot parse response (invalid IP-address)\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->user_plane.aoip_rtp_addr_local;
-	sin->sin_family = AF_INET;
-	sin->sin_addr.s_addr = addr;
-	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 = data;
-	struct gsm_lchan *lchan;
-
-	OSMO_ASSERT(mgcp_ctx);
-
-	switch (event) {
-	case EV_CRCX_NET_RESP:
-		break;
-	default:
-		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(fi, LOGL_DEBUG, "call in progress, waiting for call end...\n");
-
-	osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
-}
-
-static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv);
-static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv);
-
-/* Helper function to perform a connection teardown. This function may be
- * called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state
- * change to ST_HALT when teardown is done. */
-static void handle_teardown(struct mgcp_ctx *mgcp_ctx)
-{
-	struct gsm_subscriber_connection *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);
-
-	rtp_endpoint = mgcp_ctx->rtp_endpoint;
-
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
-		 "DLCX: removing connection for the BTS and NET side on MGW endpoint:0x%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->sccp.conn_id
-	};
-	if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
-	    sizeof(mgcp_msg.endpoint)) {
-		handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
-		return;
-	}
-	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
-	OSMO_ASSERT(msg);
-
-	/* Transmit MGCP message to MGW */
-	mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
-	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);
-}
-
-/* Helper function to perform a handover (MDCX). This function may be
- * called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state
- * change to ST_CALL when teardown is done. */
-static void handle_handover(struct mgcp_ctx *mgcp_ctx)
-{
-	struct gsm_subscriber_connection *conn;
-	struct msgb *msg;
-	struct mgcp_msg mgcp_msg;
-	struct mgcp_client *mgcp;
-	struct gsm_lchan *ho_lchan;
-	uint16_t rtp_endpoint;
-	struct in_addr addr;
-	int rc;
-
-	OSMO_ASSERT(mgcp_ctx);
-	conn = mgcp_ctx->conn;
-	OSMO_ASSERT(conn);
-	mgcp = mgcp_ctx->mgcp;
-	OSMO_ASSERT(mgcp);
-	ho_lchan = mgcp_ctx->ho_lchan;
-	OSMO_ASSERT(ho_lchan);
-
-	rtp_endpoint = mgcp_ctx->rtp_endpoint;
-
-	addr.s_addr = osmo_ntohl(ho_lchan->abis_ip.bound_ip);
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
-		 "MDCX/BTS/HO: handover connection from old BTS to new BTS side on MGW endpoint:0x%x, new BTS expects RTP input on address %s:%u\n\n",
-		 rtp_endpoint, inet_ntoa(addr), ho_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->sccp.conn_id,
-		.conn_id = mgcp_ctx->conn_id_bts,
-		.conn_mode = MGCP_CONN_RECV_SEND,
-		.audio_ip = inet_ntoa(addr),
-		.audio_port = ho_lchan->abis_ip.bound_port};
-	if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
-	    sizeof(mgcp_msg.endpoint)) {
-		handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
-		return;
-	}
-	msg = mgcp_msg_gen(mgcp, &mgcp_msg);
-	OSMO_ASSERT(msg);
-
-	/* Transmit MGCP message to MGW */
-	mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
-	rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_ho_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_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
-}
-
-/* Callback for ST_CALL: Handle call teardown and Handover */
-static void fsm_active_call_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
-	struct mgcp_ctx *mgcp_ctx = data;
-
-	OSMO_ASSERT(mgcp_ctx);
-
-	switch (event) {
-	case EV_TEARDOWN:
-		handle_teardown(mgcp_ctx);
-		break;
-	case EV_HANDOVER:
-		handle_handover(mgcp_ctx);
-		break;
-	}
-
-}
-
-/* Callback for MGCP-Client: handle response for BTS/Handover associated MDCX */
-static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv)
-{
-	struct mgcp_ctx *mgcp_ctx = priv;
-
-	OSMO_ASSERT(mgcp_ctx);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR, "MDCX/BTS/HO: late MGW response, FSM already terminated -- ignoring...\n");
-		return;
-	}
-
-	if (r->head.response_code != 200) {
-		LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
-			 "MDCX/BTS/HO: response yields error: %d %s\n", r->head.response_code, r->head.comment);
-		handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
-		return;
-	}
-
-	/* Notify the FSM that we got the response. */
-	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_HO_RESP, mgcp_ctx);
-}
-
-/* Callback for ST_MDCX_BTS_HO: Complete updating the connection data after
- * handoverin the call to another BTS */
-static void fsm_complete_handover(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
-	struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
-
-	OSMO_ASSERT(mgcp_ctx);
-
-	switch (event) {
-	case EV_MDCX_BTS_HO_RESP:
-		/* The response from the MGW arrived, the connection pointing
-		 * towards the BTS is now updated, so we now change back to
-		 * ST_CALL, where we will wait for the call-end (or another
-		 * handover) */
-		LOGPFSML(fi, LOGL_DEBUG, "MDCX/BTS/HO: handover done, waiting for call end...\n");
-		osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
-		break;
-	case EV_HANDOVER:
-		/* This handles the rare, but possible situation where another
-		 * handover is happening while we still wait for the the MGW to
-		 * complete the current one. In this case we will stop waiting
-		 * for the response and directly move on with that second
-		 * handover */
-		handle_handover(mgcp_ctx);
-		break;
-	case EV_TEARDOWN:
-		/* It may happen that the BSS wants to teardown all connections
-		 * while we are still waiting for the MGW to respond. In this
-		 * case we start to teard down the connection immediately */
-		handle_teardown(mgcp_ctx);
-		break;
-	}
-}
-
-/* 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 gsm_subscriber_connection *conn;
-	struct mgcp_client *mgcp;
-
-	OSMO_ASSERT(mgcp_ctx);
-	conn = mgcp_ctx->conn;
-	OSMO_ASSERT(conn);
-	mgcp = mgcp_ctx->mgcp;
-	OSMO_ASSERT(mgcp);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR,
-		     "DLCX: late MGW response, FSM already terminated -- ignoring...\n");
-		return;
-	}
-
-	/* 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 gsm_subscriber_connection *conn;
-
-	OSMO_ASSERT(mgcp_ctx);
-	conn = mgcp_ctx->conn;
-	OSMO_ASSERT(conn);
-
-	/* Send pending sigtran message */
-	if (mgcp_ctx->resp) {
-		LOGPFSML(fi, LOGL_DEBUG, "sending pending sigtran response message...\n");
-		osmo_bsc_sigtran_send(conn, mgcp_ctx->resp);
-		mgcp_ctx->resp = NULL;
-	}
-
-	/* Destroy the state machine and all context information */
-	osmo_fsm_inst_free(mgcp_ctx->fsm);
-	mgcp_ctx->fsm = NULL;
-}
-
-/* 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);
-
-	/* 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 MGW,
-		 * unfortunately there is no meaningful action we can take
-		 * now other than giving up. */
-
-		/* At least release the occupied endpoint ID */
-		mgcp_client_release_endpoint(mgcp_ctx->rtp_endpoint, mgcp);
-
-		/* Cancel the transaction that timed out */
-		mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans);
-
-		/* Initiate self destruction of the FSM */
-		osmo_fsm_inst_state_chg(fi, ST_HALT, 0, 0);
-		osmo_fsm_inst_dispatch(fi, 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 MGW, 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_bsc_mgcp_states[] = {
-
-	/* Startup state machine, send CRCX to BTS. */
-	[ST_CRCX_BTS] = {
-			 .in_event_mask = S(EV_INIT),
-			 .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_ASSIGN_PROC),
-			 .name = OSMO_STRINGIFY(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 = S(EV_TEARDOWN) | S(EV_CRCX_BTS_RESP),
-			    .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_BTS),
-			    .name = OSMO_STRINGIFY(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 = S(EV_TEARDOWN) | S(EV_ASS_COMPLETE),
-			 .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_CRCX_NET),
-			 .name = OSMO_STRINGIFY(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 = S(EV_TEARDOWN) | S(EV_MDCX_BTS_RESP),
-			 .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_ASSIGN_COMPL),
-			 .name = OSMO_STRINGIFY(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 = S(EV_TEARDOWN) | S(EV_CRCX_NET_RESP),
-			     .out_state_mask = S(ST_HALT) | S(ST_CALL),
-			     .name = OSMO_STRINGIFY(ST_ASSIGN_COMPL),
-			     .action = fsm_send_assignment_complete,
-			     },
-
-	/* When the call ends, remove all RTP connections from the
-	 * MGW by sending a wildcarded DLCX. In case of a handover,
-	 * go for an extra MDCX to update the connection and land in
-	 * this state again when done. */
-	[ST_CALL] = {
-		     .in_event_mask = S(EV_TEARDOWN) | S(EV_HANDOVER),
-		     .out_state_mask = S(ST_HALT) | S(ST_MDCX_BTS_HO),
-		     .name = OSMO_STRINGIFY(ST_CALL),
-		     .action = fsm_active_call_cb,
-		     },
-
-	/* A handover is in progress. When the response to the respective
-	 * MDCX is received, then go back to ST_CALL and wait for the
-	 * call end */
-	[ST_MDCX_BTS_HO] = {
-			    .in_event_mask = S(EV_TEARDOWN) | S(EV_HANDOVER) | S(EV_MDCX_BTS_HO_RESP),
-			    .out_state_mask = S(ST_HALT) | S(ST_CALL),
-			    .name = OSMO_STRINGIFY(ST_MDCX_BTS_HO),
-			    .action = fsm_complete_handover,
-			    },
-
-	/* When the MGW confirms that the connections are terminated,
-	 * then halt the state machine. */
-	[ST_HALT] = {
-		     .in_event_mask = S(EV_TEARDOWN) | S(EV_DLCX_ALL_RESP),
-		     .out_state_mask = 0,
-		     .name = OSMO_STRINGIFY(ST_HALT),
-		     .action = fsm_halt_cb,
-		     },
-};
-
-/* State machine definition */
-static struct osmo_fsm fsm_bsc_mgcp = {
-	.name = "MGW",
-	.states = fsm_bsc_mgcp_states,
-	.num_states = ARRAY_SIZE(fsm_bsc_mgcp_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 MGW 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 gsm_subscriber_connection *conn,
-				  enum gsm48_chan_mode chan_mode, bool full_rate)
-{
-	struct mgcp_ctx *mgcp_ctx;
-	char name[32];
-
-	OSMO_ASSERT(mgcp);
-	OSMO_ASSERT(conn);
-
-	OSMO_ASSERT(snprintf(name, sizeof(name), "MGW_%i", conn->sccp.conn_id) < sizeof(name));
-
-	/* Allocate and configure a new fsm instance */
-	mgcp_ctx = talloc_zero(ctx, struct mgcp_ctx);
-	OSMO_ASSERT(mgcp_ctx);
-
-	mgcp_ctx->fsm = osmo_fsm_inst_alloc(&fsm_bsc_mgcp, NULL, ctx, LOGL_DEBUG, name);
-	OSMO_ASSERT(mgcp_ctx->fsm);
-	mgcp_ctx->fsm->priv = mgcp_ctx;
-	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 MGW,
- * 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)
-{
-	struct gsm_subscriber_connection *conn;
-
-	OSMO_ASSERT(mgcp_ctx);
-	OSMO_ASSERT(resp);
-	conn = mgcp_ctx->conn;
-	OSMO_ASSERT(conn);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR,
-		     "clear completion attemted on already terminated FSM -- forwarding directly...\n");
-		osmo_bsc_sigtran_send(conn, resp);
-		mgcp_ctx->resp = NULL;
-		return;
-	}
-
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating call end...\n");
-
-	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);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR, "assignment completion attemted on already terminated FSM -- ignored\n");
-		mgcp_ctx->lchan = NULL;
-		return;
-	}
-
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating assignment completion...\n");
-
-	mgcp_ctx->lchan = lchan;
-
-	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASS_COMPLETE, mgcp_ctx);
-
-	return;
-}
-
-/* Notify that the call got handovered to another BTS, update the connection
- * that is pointing to the BTS side with the connection data for the new bts.
- * Parameter:
- * mgcp_ctx: context information (FSM, and pointer to external system data)
- * ho_lchan: the lchan on the new BTS */
-static void mgcp_handover(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *ho_lchan)
-{
-	OSMO_ASSERT(mgcp_ctx);
-	OSMO_ASSERT(ho_lchan);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_ERROR, "handover attemted on already terminated FSM -- ignored\n");
-		mgcp_ctx->ho_lchan = NULL;
-		return;
-	}
-
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating handover...\n");
-
-	mgcp_ctx->ho_lchan = ho_lchan;
-
-	osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_HANDOVER, mgcp_ctx);
-
-	return;
-}
-
-/* GSM 08.58 HANDOVER DETECT has been received */
-static int mgcp_sig_ho_detect(struct gsm_lchan *new_lchan)
-{
-	struct gsm_subscriber_connection *conn;
-	if (!new_lchan) {
-		LOGP(DHO, LOGL_ERROR, "HO Detect signal for NULL lchan\n");
-		return -EINVAL;
-	}
-
-	conn = new_lchan->conn;
-
-	if (!conn) {
-		LOGP(DHO, LOGL_ERROR, "%s HO Detect for lchan without conn\n",
-		     gsm_lchan_name(new_lchan));
-		return -EINVAL;
-	}
-
-	if (!conn->sccp.conn_id) {
-		LOGP(DHO, LOGL_ERROR, "%s HO Detect for conn without sccp_conn_id\n",
-		     gsm_lchan_name(new_lchan));
-		return -EINVAL;
-	}
-
-	if (!conn->user_plane.mgcp_ctx) {
-		LOGP(DHO, LOGL_ERROR, "%s HO Detect for conn without MGCP ctx\n",
-		     gsm_lchan_name(new_lchan));
-		return -EINVAL;
-	}
-
-	mgcp_handover(conn->user_plane.mgcp_ctx, new_lchan);
-	return 0;
-}
-
-static int mgcp_sig_cb(unsigned int subsys, unsigned int signal,
-				void *handler_data, void *signal_data)
-{
-	struct lchan_signal_data *lchan_data;
-
-	switch (subsys) {
-	case SS_LCHAN:
-		lchan_data = signal_data;
-		switch (signal) {
-		case S_LCHAN_HANDOVER_DETECT:
-			return mgcp_sig_ho_detect(lchan_data->lchan);
-		}
-		break;
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-/* Free an existing mgcp context gracefully
- * Parameter:
- * mgcp_ctx: context information (FSM, and pointer to external system data) */
-void mgcp_free_ctx(struct mgcp_ctx *mgcp_ctx)
-{
-	OSMO_ASSERT(mgcp_ctx);
-
-	if (mgcp_ctx->fsm == NULL) {
-		LOGP(DMGCP, LOGL_DEBUG, "fsm already terminated, freeing only related context information...\n");
-		talloc_free(mgcp_ctx);
-		return;
-	}
-
-	LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "terminating fsm and freeing related context information...\n");
-
-	osmo_fsm_inst_free(mgcp_ctx->fsm);
-	talloc_free(mgcp_ctx);
-}
-
-void mgcp_init(struct gsm_network *net)
-{
-	osmo_fsm_register(&fsm_bsc_mgcp);
-	osmo_signal_register_handler(SS_LCHAN, mgcp_sig_cb, NULL);
-}
diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c
index ab903b6..1a31a7c 100644
--- a/src/osmo-bsc/osmo_bsc_sigtran.c
+++ b/src/osmo-bsc/osmo_bsc_sigtran.c
@@ -1,7 +1,6 @@
-/* (C) 2017 by sysmocom s.f.m.c. GmbH
+/* (C) 2017 by sysmocom s.f.m.c. GmbH, Author: Philipp Maier
+ * (C) 2017 by Harald Welte <laforge at gnumonks.org>
  * 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
@@ -20,6 +19,7 @@
 
 #include <osmocom/core/utils.h>
 #include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
 #include <osmocom/sigtran/osmo_ss7.h>
 #include <osmocom/sigtran/sccp_sap.h>
 #include <osmocom/core/linuxlist.h>
@@ -32,7 +32,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>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
 
 /* A pointer to a list with all involved MSCs
  * (a copy of the pointer location submitted with osmo_bsc_sigtran_init() */
@@ -127,18 +127,10 @@
 }
 
 /* Send data to MSC, use the connection id which MSC it is */
-static int handle_data_from_msc(int conn_id, struct msgb *msg)
+static int handle_data_from_msc(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
-	struct gsm_subscriber_connection *conn = get_bsc_conn_by_conn_id(conn_id);
-	int rc = -EINVAL;
-
-	if (conn) {
-		msg->l3h = msgb_l2(msg);
-		rc = bsc_handle_dt(conn, msg, msgb_l2len(msg));
-	} else
-		LOGP(DMSC, LOGL_NOTICE, "incoming data from unknown connection id: %i\n", conn_id);
-
-	return rc;
+	msg->l3h = msgb_l2(msg);
+	return bsc_handle_dt(conn, msg, msgb_l2len(msg));
 }
 
 /* Sent unitdata to MSC, use the point code to determine which MSC it is */
@@ -177,23 +169,32 @@
 		break;
 
 	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
-		/* Handle (Reject) inbound connections */
+		/* Handle inbound connections */
 		DEBUGP(DMSC, "N-CONNECT.ind(X->%u)\n", scu_prim->u.connect.conn_id);
-		LOGP(DMSC, LOGL_DEBUG, "Rejecting inbound SCCP connection...\n");
-		rc = osmo_sccp_tx_disconn(scu, scu_prim->u.connect.conn_id, &scu_prim->u.connect.called_addr, 0);
+		conn = bsc_subscr_con_allocate(bsc_gsmnet);
+		if (conn) {
+			conn->sccp.msc = get_msc_by_addr(&scu_prim->u.connect.calling_addr);
+			/* MSC may be NULL, let the FSM deal with it */
+			osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_IND, scu_prim);
+		} else
+			LOGP(DMSC, LOGL_ERROR, "Unable to alloc subscr_conn for inbound N-CONNECT.ind\n");
 		break;
 
 	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
 		/* Handle outbound connection confirmation */
+		DEBUGP(DMSC, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id,
+		       osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
 		conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id);
-		if (conn)
+		if (conn) {
+			osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, scu_prim);
 			conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
-		if (msgb_l2len(oph->msg) > 0) {
-			DEBUGP(DMSC, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id,
-			       osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
-			rc = handle_data_from_msc(scu_prim->u.connect.conn_id, oph->msg);
-		} else
-			DEBUGP(DMSC, "N-CONNECT.cnf(%u)\n", scu_prim->u.connect.conn_id);
+			if (msgb_l2len(oph->msg) > 0)
+				handle_data_from_msc(conn, oph->msg);
+		} else {
+			LOGP(DMSC, LOGL_ERROR, "N-CONNET.cfm(%u, %s) for unknown conn?!?\n",
+				scu_prim->u.connect.conn_id, osmo_hexdump(msgb_l2(oph->msg),
+				msgb_l2len(oph->msg)));
+		}
 		break;
 
 	case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
@@ -202,28 +203,25 @@
 		       osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
 
 		/* Incoming data is a sign of a vital connection */
-		conn = get_bsc_conn_by_conn_id(scu_prim->u.disconnect.conn_id);
-		if (conn)
+		conn = get_bsc_conn_by_conn_id(scu_prim->u.data.conn_id);
+		if (conn) {
 			a_reset_conn_success(conn->sccp.msc->a.reset);
-
-		rc = handle_data_from_msc(scu_prim->u.data.conn_id, oph->msg);
+			handle_data_from_msc(conn, oph->msg);
+		}
 		break;
 
 	case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
+		DEBUGP(DMSC, "N-DISCONNECT.ind(%u, %s, cause=%i)\n", scu_prim->u.disconnect.conn_id,
+		       osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)),
+		       scu_prim->u.disconnect.cause);
 		/* indication of disconnect */
 		conn = get_bsc_conn_by_conn_id(scu_prim->u.disconnect.conn_id);
-		if (conn)
+		if (conn) {
 			conn->sccp.state = SUBSCR_SCCP_ST_NONE;
-		if (msgb_l2len(oph->msg) > 0) {
-			DEBUGP(DMSC, "N-DISCONNECT.ind(%u, %s, cause=%i)\n", scu_prim->u.disconnect.conn_id,
-			       osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)), scu_prim->u.disconnect.cause);
-			handle_data_from_msc(scu_prim->u.disconnect.conn_id, oph->msg);
-		} else {
-			DEBUGP(DMSC, "N-DISCONNECT.ind(%u, cause=%i)\n", scu_prim->u.disconnect.conn_id,
-			       scu_prim->u.disconnect.cause);
+			if (msgb_l2len(oph->msg) > 0)
+				handle_data_from_msc(conn, oph->msg);
+			osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_DISC_IND, scu_prim);
 		}
-		if (conn)
-			rc = osmo_bsc_sigtran_del_conn(conn);
 		break;
 
 	default:
@@ -352,31 +350,6 @@
 	return rc;
 }
 
-/* Delete a connection from the list with open connections
- * (called by osmo_bsc_api.c on failing open connections and
- * locally, when a connection is closed by the MSC */
-int osmo_bsc_sigtran_del_conn(struct gsm_subscriber_connection *conn)
-{
-	if (!conn)
-		return 0;
-
-	LOGP(DMSC, LOGL_ERROR,
-	     "sccp connection (id=%i) not cleared (gsm subscriber connection still active) -- forcefully clearing it now!\n", conn->sccp.conn_id);
-
-	/* This bahaviour might be caused by a bad connection. Maybe we
-	 * will have to go through the reset procedure again */
-	a_reset_conn_fail(conn->sccp.msc->a.reset);
-
-	/* Remove mgcp context if existant */
-	if (conn->user_plane.mgcp_ctx)
-		mgcp_free_ctx(conn->user_plane.mgcp_ctx);
-
-	/* free the "conn" and make sure any pending lchans are also free'd */
-	bsc_subscr_con_free(conn);
-
-	return 0;
-}
-
 /* Send an USSD notification in case we loose the connection to the MSC */
 static void bsc_notify_msc_lost(struct gsm_subscriber_connection *conn)
 {
@@ -411,13 +384,9 @@
 			bsc_notify_msc_lost(conn);
 
 			/* Take down all occopied RF channels */
-			gsm0808_clear(conn);
-
 			/* Disconnect all Sigtran connections */
-			osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn_id, &msc->a.bsc_addr, 0);
-
 			/* Delete subscriber connection */
-			osmo_bsc_sigtran_del_conn(conn);
+			osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL);
 		}
 	}
 }
diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am
index 983a3bb..0c681a5 100644
--- a/src/utils/Makefile.am
+++ b/src/utils/Makefile.am
@@ -22,7 +22,6 @@
 	$(NULL)
 
 bin_PROGRAMS = \
-	bs11_config \
 	isdnsync \
 	meas_json \
 	$(NULL)
diff --git a/tests/abis/abis_test.c b/tests/abis/abis_test.c
index bb70a15..7deb0f0 100644
--- a/tests/abis/abis_test.c
+++ b/tests/abis/abis_test.c
@@ -182,3 +182,7 @@
 
 	return EXIT_SUCCESS;
 }
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) {
+	OSMO_ASSERT(0);
+}
diff --git a/tests/bsc-nat/bsc_nat_test.c b/tests/bsc-nat/bsc_nat_test.c
index 09378ae..bb287ec 100644
--- a/tests/bsc-nat/bsc_nat_test.c
+++ b/tests/bsc-nat/bsc_nat_test.c
@@ -1590,3 +1590,5 @@
 {
 	abort();
 }
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *network) { return NULL; }
diff --git a/tests/bsc/bsc_test.c b/tests/bsc/bsc_test.c
index 541a44c..744b9e1 100644
--- a/tests/bsc/bsc_test.c
+++ b/tests/bsc/bsc_test.c
@@ -235,3 +235,7 @@
 	printf("Testing execution completed.\n");
 	return 0;
 }
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) {
+	OSMO_ASSERT(0);
+}
diff --git a/tests/bssap/Makefile.am b/tests/bssap/Makefile.am
index e4fe38b..30a9246 100644
--- a/tests/bssap/Makefile.am
+++ b/tests/bssap/Makefile.am
@@ -29,7 +29,6 @@
 	$(top_srcdir)/src/osmo-bsc/osmo_bsc_sigtran.c \
 	$(top_srcdir)/src/osmo-bsc/osmo_bsc_filter.c \
 	$(top_srcdir)/src/osmo-bsc/osmo_bsc_grace.c \
-	$(top_srcdir)/src/osmo-bsc/osmo_bsc_mgcp.c \
 	$(NULL)
 
 bssap_test_LDADD = \
diff --git a/tests/bssap/bssap_test.c b/tests/bssap/bssap_test.c
index cf60e38..5601e29 100644
--- a/tests/bssap/bssap_test.c
+++ b/tests/bssap/bssap_test.c
@@ -150,3 +150,7 @@
 
 	return 0;
 }
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) {
+	OSMO_ASSERT(0);
+}
diff --git a/tests/channel/channel_test.c b/tests/channel/channel_test.c
index b41e3d6..78db1d4 100644
--- a/tests/channel/channel_test.c
+++ b/tests/channel/channel_test.c
@@ -119,5 +119,6 @@
 void ipa_client_conn_send() {}
 void ipa_msg_push_header() {}
 void ipaccess_bts_handle_ccm() {}
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *network) { return NULL; }
 
 struct tlv_definition nm_att_tlvdef;
diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c
index e8d5501..5a4c168 100644
--- a/tests/gsm0408/gsm0408_test.c
+++ b/tests/gsm0408/gsm0408_test.c
@@ -861,3 +861,7 @@
 
 	return EXIT_SUCCESS;
 }
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) {
+	OSMO_ASSERT(0);
+}
diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am
index 7133fcd..957bbee 100644
--- a/tests/handover/Makefile.am
+++ b/tests/handover/Makefile.am
@@ -9,6 +9,8 @@
 	$(LIBOSMOCORE_CFLAGS) \
 	$(LIBOSMOGSM_CFLAGS) \
 	$(LIBOSMOABIS_CFLAGS) \
+	$(LIBOSMOSIGTRAN_CFLAGS) \
+	$(LIBOSMOMGCPCLIENT_CFLAGS) \
 	$(NULL)
 
 AM_LDFLAGS = \
@@ -28,7 +30,7 @@
 	$(NULL)
 
 handover_test_LDFLAGS =\
-	-Wl,--wrap=abis_rsl_sendmsg \
+	-Wl,--wrap=abis_rsl_sendmsg,--wrap=mgcp_conn_modify,--wrap=mgcp_conn_delete\
 	$(NULL)
 
 handover_test_LDADD = \
@@ -36,4 +38,6 @@
 	$(LIBOSMOCORE_LIBS) \
 	$(LIBOSMOGSM_LIBS) \
 	$(LIBOSMOABIS_LIBS) \
+	$(LIBOSMOSIGTRAN_LIBS) \
+	$(LIBOSMOMGCPCLIENT_LIBS) \
 	$(NULL)
diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c
index bf7350c..8207121 100644
--- a/tests/handover/handover_test.c
+++ b/tests/handover/handover_test.c
@@ -39,8 +39,48 @@
 #include <osmocom/bsc/bss.h>
 #include <osmocom/bsc/bsc_api.h>
 #include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
 
 struct gsm_network *bsc_gsmnet;
+
+/* override, requires '-Wl,--wrap=mgcp_conn_modify'.
+ * Catch modification of an MGCP connection. */
+int __real_mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer);
+int __wrap_mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer)
+{
+	/* CAUTION HACK:
+	 * This function is called from gscon_fsm_wait_ho_compl() from
+	 * bsc_subscr_conn_fsm.c when GSCON_EV_HO_COMPL is dispatched to the
+	 * GSCON FSM. By then, the GSCON FSM has already changed to the state
+	 * ST_WAIT_MDCX_BTS_HO (see gscon_fsm_wait_mdcx_bts_ho()) and waits for
+	 * GSCON_EV_MGW_MDCX_RESP_BTS. The signal GSCON_EV_MGW_MDCX_RESP_BTS
+	 * is sent to this function using the parameter parent_evt. So we
+	 * implicitly know the event that is needed to simulate a successful
+	 * MGW negotiation to the GSCON FSM. All we need to do is to dispatch
+	 * parent_evt back to the GSCON FSM in order to make it think that the
+	 * MGW negotiation is done.
+	 *
+	 * Unfortunately, the fi pointer we get here is (normally) unpopulated
+	 * because in this setup we do not allocate any MGCP Client FSM. In
+	 * order to resolve this we have exceptionally populated the fi
+	 * pointer with the reference to the GSCON FSM itsself. So we may use
+	 * fi to dispatch the parent_evt event.
+	 *
+	 * See also note in function create_conn() */
+
+	osmo_fsm_inst_dispatch(fi, parent_evt, NULL);
+	return 0;
+}
+
+/* override, requires '-Wl,--wrap=mgcp_conn_delete'.
+ * Catch deletion of an MGCP connection. */
+int __real_mgcp_conn_delete(struct osmo_fsm_inst *fi);
+int __wrap_mgcp_conn_delete(struct osmo_fsm_inst *fi)
+{
+	/* Just do nothing and pretend that everything went well.
+	 * We never have allocatec any MGCP connections. */
+	return 0;
+}
 
 /* measurement report */
 
@@ -186,7 +226,24 @@
 
 void create_conn(struct gsm_lchan *lchan)
 {
-	lchan->conn = bsc_subscr_con_allocate(lchan);
+	struct gsm_subscriber_connection *conn;
+	conn = bsc_subscr_con_allocate(lchan->ts->trx->bts->network);
+
+	/* CAUTION HACK: When __real_mgcp_conn_modify() is called by the GSCON
+	 * FSM, then we need to know the reference to caller FSM (GSCON FSM).
+	 * Unfortunately the function __real_mgcp_conn_modify() is called with
+	 * fi_bts, which is unpopulated in this setup. The real function would
+	 * perform the communication with the MGW and then dispatch a signal
+	 * back to the parent FSM. Since we do not have all that in this setup
+	 * we populate the fi_bts pointer with a reference to the GSCON FSM in
+	 * order to have it available later in __real_mgcp_conn_modify(). */
+	conn->user_plane.fi_bts = conn->fi;
+
+	lchan->conn = conn;
+	conn->lchan = lchan;
+	/* kick the FSM from INIT through to the ACTIVE state */
+	osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_REQ, NULL);
+	osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, NULL);
 }
 
 /* create lchan */
@@ -1256,6 +1313,11 @@
 		.color = "\033[1;35m",
 		.enabled = 1, .loglevel = LOGL_DEBUG,
 	},
+	[DMSC] = {
+		.name = "DMSC",
+		.description = "Mobile Switching Center",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
 };
 
 const struct log_info log_info = {
@@ -1592,7 +1654,7 @@
 		struct gsm_subscriber_connection *conn = lchan[i]->conn;
 		lchan[i]->conn = NULL;
 		conn->lchan = NULL;
-		bsc_subscr_con_free(conn);
+		osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REGULAR, NULL);
 		lchan_free(lchan[i]);
 	}
 
@@ -1615,3 +1677,5 @@
 void trau_mux_map_lchan() {}
 void trau_recv_lchan() {}
 void trau_send_frame() {}
+int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg) { return 0; }
+int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg) { return 0; }
diff --git a/tests/nanobts_omlattr/nanobts_omlattr_test.c b/tests/nanobts_omlattr/nanobts_omlattr_test.c
index 6fa221c..663594c 100644
--- a/tests/nanobts_omlattr/nanobts_omlattr_test.c
+++ b/tests/nanobts_omlattr/nanobts_omlattr_test.c
@@ -292,3 +292,7 @@
 {
 	abort();
 }
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) {
+	OSMO_ASSERT(0);
+}

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

Gerrit-MessageType: newpatchset
Gerrit-Change-Id: I68286d26e2014048b054f39ef29c35fef420cc97
Gerrit-PatchSet: 3
Gerrit-Project: osmo-bsc
Gerrit-Branch: master
Gerrit-Owner: dexter <pmaier at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder



More information about the gerrit-log mailing list