Implement IuCS (large refactoring and addition)

osmo-nitb becomes osmo-msc
add DIUCS debug log constant
add iucs.[hc]
add msc vty, remove nitb vty
add libiudummy, to avoid linking Iu deps in tests
Use new msc_tx_dtap() instead of gsm0808_submit_dtap()
libmgcp: add mgcpgw client API
bridge calls via mgcpgw

Enable MSC specific CTRL commands, bsc_base_ctrl_cmds_install() still needs to
be split up.

Change-Id: I5b5b6a9678b458affa86800afb1ec726e66eed88
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index 3c06514..16154ff 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -12,6 +12,7 @@
 	$(COVERAGE_CFLAGS) \
 	$(LIBCRYPTO_CFLAGS) \
 	$(LIBSMPP34_CFLAGS) \
+	$(LIBASN1C_CFLAGS) \
 	$(NULL)
 
 noinst_HEADERS = \
@@ -25,11 +26,14 @@
 libmsc_a_SOURCES = \
 	a_iface.c \
 	auth.c \
+	msc_vty.c \
 	db.c \
 	gsm_04_08.c \
 	gsm_04_11.c \
 	gsm_04_80.c \
 	gsm_subscriber.c \
+	iucs.c \
+	iucs_ranap.c \
 	mncc.c \
 	mncc_builtin.c \
 	mncc_sock.c \
@@ -45,6 +49,11 @@
 	meas_feed.c \
 	subscr_conn.c \
 	$(NULL)
+if !BUILD_IU
+libmsc_a_SOURCES += \
+	iu_dummy.c \
+	$(NULL)
+endif
 
 if BUILD_SMPP
 noinst_HEADERS += \
diff --git a/src/libmsc/a_iface.c b/src/libmsc/a_iface.c
index 1f471f9..caf9d4b 100644
--- a/src/libmsc/a_iface.c
+++ b/src/libmsc/a_iface.c
@@ -35,6 +35,14 @@
 	return -1;
 }
 
+int a_page(const char *imsi, uint32_t tmsi, uint16_t lac)
+{
+	LOGP(DMSC, LOGL_ERROR, "Paging to be sent to BSC, but A-interface"
+	     " not implemented: IMSI %s TMSI 0x%08x LAC %u\n",
+	     imsi, tmsi, lac);
+	return -1;
+}
+
 int msc_gsm0808_tx_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
 			       const uint8_t *key, int len, int include_imeisv)
 {
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index 21ffaaa..3f3f905 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -31,6 +31,7 @@
 #include <netinet/in.h>
 #include <regex.h>
 #include <sys/types.h>
+#include <openssl/rand.h>
 
 #include "bscconfig.h"
 
@@ -69,6 +70,13 @@
 #include <osmocom/core/talloc.h>
 #include <osmocom/core/utils.h>
 #include <osmocom/gsm/tlv.h>
+#include <osmocom/crypt/auth.h>
+
+#include <openbsc/msc_ifaces.h>
+
+#ifdef BUILD_IU
+#include <openbsc/iu.h>
+#endif
 
 #include <assert.h>
 
@@ -105,7 +113,7 @@
 		gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
 	}
 
-	return gsm0808_submit_dtap(conn, msg, 0, 0);
+	return msc_tx_dtap(conn, msg);
 }
 
 int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message)
@@ -141,7 +149,7 @@
 }
 
 /* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */
-int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, uint8_t cause)
+static int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, uint8_t cause)
 {
 	struct msgb *msg;
 
@@ -184,12 +192,17 @@
 		len = gsm48_generate_mid_from_imsi(mi, conn->vsub->imsi);
 		mid = msgb_put(msg, len);
 		memcpy(mid, mi, len);
+		DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT\n",
+		       vlr_subscr_name(conn->vsub));
 	} else {
 		/* Include the TMSI, which means that the MS will send a
 		 * TMSI REALLOCATION COMPLETE, and we should wait for
 		 * that until T3250 expiration */
 		mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
 		gsm48_generate_mid_from_tmsi(mid, send_tmsi);
+		DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT (TMSI = 0x%08x)\n",
+		       vlr_subscr_name(conn->vsub),
+		       send_tmsi);
 	}
 	/* TODO: Follow-on proceed */
 	/* TODO: CTS permission */
@@ -197,7 +210,6 @@
 	/* TODO: Emergency Number List */
 	/* TODO: Per-MS T3312 */
 
-	DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
 
 	return gsm48_conn_sendmsg(msg, conn, NULL);
 }
@@ -257,11 +269,11 @@
 	uint8_t mi_type;
 	char mi_string[GSM48_MI_SIZE];
 	enum vlr_lu_type vlr_lu_type = VLR_LU_TYPE_REGULAR;
-
 	uint32_t tmsi;
 	char *imsi;
 	struct osmo_location_area_id old_lai, new_lai;
 	struct osmo_fsm_inst *lu_fsm;
+	bool is_utran;
 	int rc;
 
  	lu = (struct gsm48_loc_upd_req *) gh->data;
@@ -327,16 +339,18 @@
 	new_lai.lac = conn->lac;
 	DEBUGP(DMM, "LU/new-LAC: %u/%u\n", old_lai.lac, new_lai.lac);
 
+	is_utran = (conn->via_ran == RAN_UTRAN_IU);
 	lu_fsm = vlr_loc_update(conn->conn_fsm,
 				SUBSCR_CONN_E_ACCEPTED,
 				SUBSCR_CONN_E_CN_CLOSE,
 				(void*)&conn_from_lu,
 				net->vlr, conn, vlr_lu_type, tmsi, imsi,
 				&old_lai, &new_lai,
-				conn->network->authentication_required,
-				conn->network->a5_encryption,
+				is_utran || conn->network->authentication_required,
+				is_utran? VLR_CIPH_A5_3
+					: conn->network->a5_encryption,
 				classmark_is_r99(&conn->classmark),
-				conn->via_ran == RAN_UTRAN_IU,
+				is_utran,
 				net->vlr->cfg.assign_tmsi);
 	if (!lu_fsm) {
 		DEBUGP(DRR, "%s: Can't start LU FSM\n", mi_string);
@@ -629,6 +643,7 @@
 	uint8_t mi_len = *(classmark2 + classmark2_len);
 	uint8_t *mi = (classmark2 + classmark2_len + 1);
 	struct osmo_location_area_id lai;
+	bool is_utran;
 	int rc;
 
 	lai.plmn.mcc = conn->network->country_code;
@@ -685,16 +700,18 @@
 		return rc;
 	}
 
+	is_utran = (conn->via_ran == RAN_UTRAN_IU);
 	vlr_proc_acc_req(conn->conn_fsm,
 			 SUBSCR_CONN_E_ACCEPTED,
 			 SUBSCR_CONN_E_CN_CLOSE,
 			 (void*)&conn_from_cm_service_req,
 			 net->vlr, conn,
 			 VLR_PR_ARQ_T_CM_SERV_REQ, mi-1, &lai,
-			 conn->network->authentication_required,
-			 conn->network->a5_encryption,
+			 is_utran || conn->network->authentication_required,
+			 is_utran? VLR_CIPH_A5_3
+				 : conn->network->a5_encryption,
 			 classmark_is_r99(&conn->classmark),
-			 conn->via_ran == RAN_UTRAN_IU);
+			 is_utran);
 
 	return 0;
 }
@@ -1038,6 +1055,7 @@
 	char mi_string[GSM48_MI_SIZE];
 	int rc = 0;
 	struct osmo_location_area_id lai;
+	bool is_utran;
 
 	lai.plmn.mcc = conn->network->country_code;
 	lai.plmn.mnc = conn->network->network_code;
@@ -1063,18 +1081,20 @@
 	memcpy(conn->classmark.classmark2, classmark2_lv+1, *classmark2_lv);
 	conn->classmark.classmark2_len = *classmark2_lv;
 
+	is_utran = (conn->via_ran == RAN_UTRAN_IU);
 	vlr_proc_acc_req(conn->conn_fsm,
 			 SUBSCR_CONN_E_ACCEPTED,
 			 SUBSCR_CONN_E_CN_CLOSE,
 			 (void*)&conn_from_paging_resp,
 			 net->vlr, conn,
 			 VLR_PR_ARQ_T_PAGING_RESP, mi_lv, &lai,
-			 conn->network->authentication_required,
-			 conn->network->a5_encryption,
+			 is_utran || conn->network->authentication_required,
+			 is_utran? VLR_CIPH_A5_3
+				 : conn->network->a5_encryption,
 			 classmark_is_r99(&conn->classmark),
-			 conn->via_ran == RAN_UTRAN_IU);
+			 is_utran);
 
-	return rc;
+	return 0;
 }
 
 static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg)
@@ -1365,8 +1385,7 @@
 	/* Which subscriber do we want to track trans1 or trans2? */
 	log_set_context(LOG_CTX_VLR_SUBSCR, trans1->vsub);
 
-	/* future: msc_call_bridge(trans1, trans2); */
-	return -1;
+	return msc_call_bridge(trans1, trans2);
 }
 
 static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
@@ -1694,15 +1713,18 @@
 
 	new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
 
+	msc_call_assignment(trans);
+
 	return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND,
 			    &call_conf);
 }
 
-static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg)
+static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg)
 {
 	struct gsm_mncc *proceeding = arg;
 	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROC");
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	int rc;
 
 	gh->msg_type = GSM48_MT_CC_CALL_PROC;
 
@@ -1718,7 +1740,11 @@
 	if (proceeding->fields & MNCC_F_PROGRESS)
 		gsm48_encode_progress(msg, 0, &proceeding->progress);
 
-	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+	rc = gsm48_conn_sendmsg(msg, trans->conn, trans);
+	if (rc)
+		return rc;
+
+	return msc_call_assignment(trans);
 }
 
 static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
@@ -2555,7 +2581,7 @@
 } downstatelist[] = {
 	/* mobile originating call establishment */
 	{SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */
-	 MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc},
+	 MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc_and_assign},
 	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */
 	 MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
 	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */
@@ -2732,15 +2758,15 @@
 				trans_free(trans);
 				return 0;
 			}
-			/* store setup informations until paging was successfull */
+			/* store setup information until paging succeeds */
 			memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
 
 			/* Request a channel */
 			trans->paging_request = subscr_request_conn(
 							vsub,
-							RSL_CHANNEED_TCH_F,
 							setup_trig_pag_evt,
-							trans);
+							trans,
+							"MNCC: establish call");
 			if (!trans->paging_request) {
 				LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n");
 				vlr_subscr_put(vsub);
@@ -3007,6 +3033,16 @@
 		return -EACCES;
 	}
 
+	if (conn->vsub && conn->vsub->cs.attached_via_ran != conn->via_ran) {
+		LOGP(DMM, LOGL_ERROR,
+		     "%s: Illegal situation: RAN type mismatch:"
+		     " attached via %s, received message via %s\n",
+		     vlr_subscr_name(conn->vsub),
+		     ran_type_name(conn->vsub->cs.attached_via_ran),
+		     ran_type_name(conn->via_ran));
+		return -EACCES;
+	}
+
 #if 0
 	if (silent_call_reroute(conn, msg))
 		return silent_call_rx(conn, msg);
@@ -3090,7 +3126,13 @@
 static int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref)
 {
 	struct gsm_subscriber_connection *conn = msc_conn_ref;
-	return gsm48_tx_mm_serv_ack(conn);
+	return msc_gsm48_tx_mm_serv_ack(conn);
+}
+
+static int msc_vlr_tx_common_id(void *msc_conn_ref)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	return msc_tx_common_id(conn);
 }
 
 /* VLR asks us to transmit a CM Service Reject */
@@ -3124,7 +3166,7 @@
 		break;
 	};
 
-	return gsm48_tx_mm_serv_rej(conn, cause);
+	return msc_gsm48_tx_mm_serv_rej(conn, cause);
 }
 
 /* VLR asks us to start using ciphering */
@@ -3152,9 +3194,47 @@
 		return -EINVAL;
 	}
 
-	/* TODO: MSCSPLIT: don't directly push BSC buttons */
-	return gsm0808_cipher_mode(conn, ciph, tuple->vec.kc, 8,
-				   retrieve_imeisv);
+	switch (conn->via_ran) {
+	case RAN_GERAN_A:
+		DEBUGP(DMM, "-> CIPHER MODE COMMAND %s\n",
+		       vlr_subscr_name(conn->vsub));
+		return msc_gsm0808_tx_cipher_mode(conn, ciph, tuple->vec.kc, 8,
+						  retrieve_imeisv);
+	case RAN_UTRAN_IU:
+#ifdef BUILD_IU
+		DEBUGP(DMM, "-> SECURITY MODE CONTROL %s\n",
+		       vlr_subscr_name(conn->vsub));
+		return iu_tx_sec_mode_cmd(conn->iu.ue_ctx, tuple, 0, 1);
+#else
+		LOGP(DMM, LOGL_ERROR, "Cannot send Security Mode Control over RAN_UTRAN_IU,"
+		     " built without Iu support\n");
+		return -ENOTSUP;
+#endif
+
+	default:
+		break;
+	}
+	LOGP(DMM, LOGL_ERROR,
+	     "%s: cannot start ciphering, unknown RAN type %d\n",
+	     vlr_subscr_name(conn->vsub), conn->via_ran);
+	return -ENOTSUP;
+}
+
+void msc_rx_sec_mode_compl(struct gsm_subscriber_connection *conn)
+{
+	struct vlr_ciph_result vlr_res = {};
+
+	if (!conn || !conn->vsub) {
+		LOGP(DMM, LOGL_ERROR,
+		     "Rx Security Mode Complete for invalid conn\n");
+		return;
+	}
+
+	DEBUGP(DMM, "<- SECURITY MODE COMPLETE %s\n",
+	       vlr_subscr_name(conn->vsub));
+
+	vlr_res.cause = VLR_CIPH_COMPL;
+	vlr_subscr_rx_ciph_res(conn->vsub, &vlr_res);
 }
 
 /* VLR informs us that the subscriber data has somehow been modified */
@@ -3170,6 +3250,7 @@
 	struct gsm_subscriber_connection *conn = msc_conn_ref;
 	OSMO_ASSERT(!conn->vsub);
 	conn->vsub = vlr_subscr_get(vsub);
+	conn->vsub->cs.attached_via_ran = conn->via_ran;
 }
 
 /* operations that we need to implement for libvlr */
@@ -3182,6 +3263,7 @@
 	.tx_cm_serv_acc = msc_vlr_tx_cm_serv_acc,
 	.tx_cm_serv_rej = msc_vlr_tx_cm_serv_rej,
 	.set_ciph_mode = msc_vlr_set_ciph_mode,
+	.tx_common_id = msc_vlr_tx_common_id,
 	.subscr_update = msc_vlr_subscr_update,
 	.subscr_assoc = msc_vlr_subscr_assoc,
 };
@@ -3203,19 +3285,3 @@
 	return vlr_start("MSC", net->vlr, net->gsup_server_addr_str,
 			 net->gsup_server_port);
 }
-
-/* This is a temporary shim merely to ensure that the unit tests still work. It
- * shall be removed as soon as Iu and A interface paging is implemented. */
-int msc_fake_paging_request(struct vlr_subscr *vsub)
-{
-	LOGP(DMM, LOGL_ERROR, "Paging currently not implemented in the MSC.\n");
-	OSMO_ASSERT(false);
-}
-
-/* This is a temporary shim merely to ensure that the unit tests still work. It
- * shall be removed as soon as Iu and A interface paging is implemented. */
-void msc_fake_paging_request_stop(struct vlr_subscr *vsub)
-{
-	LOGP(DMM, LOGL_ERROR, "Paging currently not implemented in the MSC.\n");
-	OSMO_ASSERT(false);
-}
diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c
index 3255a3b..bdf2ad7 100644
--- a/src/libmsc/gsm_04_11.c
+++ b/src/libmsc/gsm_04_11.c
@@ -55,7 +55,7 @@
 #include <openbsc/paging.h>
 #include <openbsc/bsc_rll.h>
 #include <openbsc/chan_alloc.h>
-#include <openbsc/bsc_api.h>
+#include <openbsc/msc_ifaces.h>
 #include <openbsc/osmo_msc.h>
 #include <openbsc/vlr.h>
 
@@ -128,7 +128,7 @@
 {
 	DEBUGP(DLSMS, "GSM4.11 TX %s\n", osmo_hexdump(msg->data, msg->len));
 	msg->l3h = msg->data;
-	return gsm0808_submit_dtap(conn, msg, UM_SAPI_SMS, 1);
+	return msc_tx_dtap(conn, msg);
 }
 
 /* Prefix msg with a 04.08/04.11 CP header */
@@ -1016,8 +1016,7 @@
 	/* if not, we have to start paging */
 	LOGP(DLSMS, LOGL_DEBUG, "Sending SMS: no connection open, start paging %s\n",
 	     vlr_subscr_name(vsub));
-	res = subscr_request_conn(vsub, RSL_CHANNEED_SDCCH, paging_cb_send_sms,
-				  sms);
+	res = subscr_request_conn(vsub, paging_cb_send_sms, sms, "send SMS");
 	if (!res) {
 		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, GSM_PAGING_BUSY);
 		sms_free(sms);
diff --git a/src/libmsc/gsm_04_80.c b/src/libmsc/gsm_04_80.c
index 479d6fb..bec1d26 100644
--- a/src/libmsc/gsm_04_80.c
+++ b/src/libmsc/gsm_04_80.c
@@ -32,7 +32,7 @@
 #include <openbsc/gsm_data.h>
 #include <openbsc/gsm_04_08.h>
 #include <openbsc/gsm_04_80.h>
-#include <openbsc/bsc_api.h>
+#include <openbsc/msc_ifaces.h>
 
 #include <osmocom/gsm/gsm0480.h>
 #include <osmocom/gsm/gsm_utils.h>
@@ -106,7 +106,7 @@
 					| (1<<7);  /* TI direction = 1 */
 	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
 
-	return gsm0808_submit_dtap(conn, msg, 0, 0);
+	return msc_tx_dtap(conn, msg);
 }
 
 int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn,
@@ -135,7 +135,7 @@
 	gh->proto_discr |= req->transaction_id | (1<<7);  /* TI direction = 1 */
 	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
 
-	return gsm0808_submit_dtap(conn, msg, 0, 0);
+	return msc_tx_dtap(conn, msg);
 }
 
 int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, const char *text)
@@ -143,7 +143,7 @@
 	struct msgb *msg = gsm0480_create_ussd_notify(level, text);
 	if (!msg)
 		return -1;
-	return gsm0808_submit_dtap(conn, msg, 0, 0);
+	return msc_tx_dtap(conn, msg);
 }
 
 int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn)
@@ -151,5 +151,5 @@
 	struct msgb *msg = gsm0480_create_ussd_release_complete();
 	if (!msg)
 		return -1;
-	return gsm0808_submit_dtap(conn, msg, 0, 0);
+	return msc_tx_dtap(conn, msg);
 }
diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c
index 69d79b0..ac6c96a 100644
--- a/src/libmsc/gsm_subscriber.c
+++ b/src/libmsc/gsm_subscriber.c
@@ -40,6 +40,9 @@
 #include <openbsc/db.h>
 #include <openbsc/chan_alloc.h>
 #include <openbsc/vlr.h>
+#include <openbsc/iu.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/msc_ifaces.h>
 
 int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
 			   struct msgb *msg, void *data, void *param)
@@ -49,27 +52,41 @@
 	struct vlr_subscr *vsub = param;
 	struct paging_signal_data sig_data;
 
-	OSMO_ASSERT(vsub && vsub->cs.is_paging);
+	OSMO_ASSERT(vsub);
+	OSMO_ASSERT(hooknum == GSM_HOOK_RR_PAGING);
+	OSMO_ASSERT(!(conn && (conn->vsub != vsub)));
+	OSMO_ASSERT(!((event == GSM_PAGING_SUCCEEDED) && !conn));
 
-	/* FIXME: implement stop paging in libmsc;
-	 * faking it for the unit tests to still work */
-	msc_fake_paging_request_stop(vsub);
+	LOGP(DPAG, LOGL_DEBUG, "Paging %s for %s (event=%d)\n",
+	     event == GSM_PAGING_SUCCEEDED ? "success" : "failure",
+	     vlr_subscr_name(vsub), event);
+
+	if (!vsub->cs.is_paging) {
+		LOGP(DPAG, LOGL_ERROR,
+		     "Paging Response received for subscriber"
+		     " that is not paging.\n");
+		return -EINVAL;
+	}
+
+	if (event == GSM_PAGING_SUCCEEDED)
+		msc_stop_paging(vsub);
 
 	/* Inform parts of the system we don't know */
-	sig_data.vsub	= vsub;
-	sig_data.bts	= conn ? conn->bts : NULL;
-	sig_data.conn	= conn;
+	sig_data.vsub = vsub;
+	sig_data.conn = conn;
 	sig_data.paging_result = event;
-	osmo_signal_dispatch(
-		SS_PAGING,
-		event == GSM_PAGING_SUCCEEDED ?
-			S_PAGING_SUCCEEDED : S_PAGING_EXPIRED,
-		&sig_data
-	);
+	osmo_signal_dispatch(SS_PAGING,
+			     event == GSM_PAGING_SUCCEEDED ?
+				S_PAGING_SUCCEEDED : S_PAGING_EXPIRED,
+			     &sig_data);
 
 	llist_for_each_entry_safe(request, tmp, &vsub->cs.requests, entry) {
 		llist_del(&request->entry);
-		request->cbfn(hooknum, event, msg, data, request->param);
+		if (request->cbfn) {
+			LOGP(DPAG, LOGL_DEBUG, "Calling paging cbfn.\n");
+			request->cbfn(hooknum, event, msg, data, request->param);
+		} else
+			LOGP(DPAG, LOGL_DEBUG, "Paging without action.\n");
 		talloc_free(request);
 	}
 
@@ -79,21 +96,48 @@
 	return 0;
 }
 
-struct subscr_request *subscr_request_conn(struct vlr_subscr *vsub, int channel_type, gsm_cbfn *cbfn,
-					   void *param)
+int msc_paging_request(struct vlr_subscr *vsub)
+{
+	/* The subscriber was last seen in subscr->lac. Find out which
+	 * BSCs/RNCs are responsible and send them a paging request via open
+	 * SCCP connections (if any). */
+	/* TODO Implementing only RNC paging, since this is code on the iu branch.
+	 * Need to add BSC paging at some point. */
+	switch (vsub->cs.attached_via_ran) {
+	case RAN_GERAN_A:
+		return a_page(vsub->imsi, vsub->tmsi, vsub->lac);
+	case RAN_UTRAN_IU:
+		return iu_page_cs(vsub->imsi,
+				  vsub->tmsi == GSM_RESERVED_TMSI?
+				  NULL : &vsub->tmsi,
+				  vsub->lac);
+	default:
+		break;
+	}
+
+	LOGP(DPAG, LOGL_ERROR, "%s: Cannot page, subscriber not attached\n",
+	     vlr_subscr_name(vsub));
+	return -EINVAL;
+}
+
+/*! \brief Start a paging request for vsub, call cbfn(param) when done.
+ * \param vsub  subscriber to page.
+ * \param cbfn  function to call when the conn is established.
+ * \param param  caller defined param to pass to cbfn().
+ * \param label  human readable label of the request kind used for logging.
+ */
+struct subscr_request *subscr_request_conn(struct vlr_subscr *vsub,
+					   gsm_cbfn *cbfn, void *param,
+					   const char *label)
 {
 	int rc;
 	struct subscr_request *request;
 
 	/* Start paging.. we know it is async so we can do it before */
 	if (!vsub->cs.is_paging) {
-		LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet.\n",
+		LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet, start paging.\n",
 		     vlr_subscr_name(vsub));
-
-		/* FIXME: implement paging in libmsc;
-		 * faking it for the unit tests to still work */
-		rc = msc_fake_paging_request(vsub);
-
+		rc = msc_paging_request(vsub);
 		if (rc <= 0) {
 			LOGP(DMM, LOGL_ERROR, "Subscriber %s paging failed: %d\n",
 			     vlr_subscr_name(vsub), rc);
@@ -102,6 +146,9 @@
 		/* reduced on the first paging callback */
 		vlr_subscr_get(vsub);
 		vsub->cs.is_paging = true;
+	} else {
+		LOGP(DMM, LOGL_DEBUG, "Subscriber %s already paged.\n",
+			vlr_subscr_name(vsub));
 	}
 
 	/* TODO: Stop paging in case of memory allocation failure */
diff --git a/src/libmsc/iu_dummy.c b/src/libmsc/iu_dummy.c
new file mode 100644
index 0000000..1f5dffb
--- /dev/null
+++ b/src/libmsc/iu_dummy.c
@@ -0,0 +1,93 @@
+/* Trivial switch-off of external Iu dependencies,
+ * allowing to run full unit tests even when built without Iu support. */
+
+/*
+ * (C) 2016,2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * 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 "../../bscconfig.h"
+#ifndef BUILD_IU
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/core/msgb.h>
+
+struct msgb;
+struct ue_conn_ctx;
+struct gsm_auth_tuple;
+struct RANAP_Cause;
+
+int iu_tx(struct msgb *msg, uint8_t sapi)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "iu_tx() dummy called, NOT transmitting %d bytes: %s\n",
+	     msg->len, osmo_hexdump(msg->data, msg->len));
+	return 0;
+}
+
+int iu_tx_sec_mode_cmd(struct ue_conn_ctx *uectx, struct gsm_auth_tuple *tp,
+		       int send_ck)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_sec_mode_cmd() dummy called, NOT transmitting Security Mode Command\n");
+	return 0;
+}
+
+int iu_page_cs(const char *imsi, const uint32_t *tmsi, uint16_t lac)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "iu_page_cs() dummy called, NOT paging\n");
+	return 23;
+}
+
+int iu_page_ps(const char *imsi, const uint32_t *ptmsi, uint16_t lac, uint8_t rac)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "iu_page_ps() dummy called, NOT paging\n");
+	return 0;
+}
+
+struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id, uint32_t rtp_ip,
+					    uint16_t rtp_port,
+					    bool use_x213_nsap)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "ranap_new_msg_rab_assign_voice() dummy called, NOT composing RAB Assignment\n");
+	return NULL;
+}
+
+int iu_rab_act(struct ue_conn_ctx *ue_ctx, struct msgb *msg)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "iu_rab_act() dummy called, NOT activating RAB\n");
+	return 0;
+}
+
+int iu_tx_common_id(struct ue_conn_ctx *uectx, const char *imsi)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_common_id() dummy called, NOT sending CommonID\n");
+	return 0;
+}
+
+int iu_tx_release(struct ue_conn_ctx *ctx, const struct RANAP_Cause *cause)
+{
+	LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_release() dummy called, NOT sending Release\n");
+	return 0;
+}
+
+#endif
diff --git a/src/libmsc/iucs.c b/src/libmsc/iucs.c
new file mode 100644
index 0000000..aeda140
--- /dev/null
+++ b/src/libmsc/iucs.c
@@ -0,0 +1,191 @@
+/* Code to manage MSC subscriber connections over IuCS interface */
+
+/*
+ * (C) 2016,2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/logging.h>
+#include <openbsc/debug.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/iu.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/vlr.h>
+
+/* For A-interface see libbsc/bsc_api.c subscr_con_allocate() */
+static struct gsm_subscriber_connection *subscr_conn_allocate_iu(struct gsm_network *network,
+								 struct ue_conn_ctx *ue,
+								 uint16_t lac)
+{
+	struct gsm_subscriber_connection *conn;
+
+	DEBUGP(DIUCS, "Allocating IuCS subscriber conn: lac %d, link_id %p, conn_id %" PRIx32 "\n",
+	       lac, ue->link, ue->conn_id);
+
+	conn = talloc_zero(network, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
+
+	conn->network = network;
+	conn->via_ran = RAN_UTRAN_IU;
+	conn->iu.ue_ctx = ue;
+	conn->iu.ue_ctx->rab_assign_addr_enc = network->iu.rab_assign_addr_enc;
+	conn->lac = lac;
+
+	llist_add_tail(&conn->entry, &network->subscr_conns);
+	return conn;
+}
+
+static int same_ue_conn(struct ue_conn_ctx *a, struct ue_conn_ctx *b)
+{
+	if (a == b)
+		return 1;
+	return (a->link == b->link)
+		&& (a->conn_id == b->conn_id);
+}
+
+static inline void log_subscribers(struct gsm_network *network)
+{
+	if (!log_check_level(DIUCS, LOGL_DEBUG))
+		return;
+
+	struct gsm_subscriber_connection *conn;
+	int i = 0;
+	llist_for_each_entry(conn, &network->subscr_conns, entry) {
+		DEBUGP(DIUCS, "%3d: %s", i, vlr_subscr_name(conn->vsub));
+		switch (conn->via_ran) {
+		case RAN_UTRAN_IU:
+			DEBUGPC(DIUCS, " Iu");
+			if (conn->iu.ue_ctx) {
+				DEBUGPC(DIUCS, " link %p, conn_id %d",
+					conn->iu.ue_ctx->link,
+					conn->iu.ue_ctx->conn_id
+				       );
+			}
+			break;
+		case RAN_GERAN_A:
+			DEBUGPC(DIUCS, " A");
+			/* TODO log A-interface connection details */
+			break;
+		case RAN_UNKNOWN:
+			DEBUGPC(DIUCS, " ?");
+			break;
+		default:
+			DEBUGPC(DIUCS, " invalid");
+			break;
+		}
+		DEBUGPC(DIUCS, "\n");
+		i++;
+	}
+	DEBUGP(DIUCS, "subscribers registered: %d\n", i);
+}
+
+/* Return an existing IuCS subscriber connection record for the given link and
+ * connection IDs, or return NULL if not found. */
+struct gsm_subscriber_connection *subscr_conn_lookup_iu(
+						struct gsm_network *network,
+						struct ue_conn_ctx *ue)
+{
+	struct gsm_subscriber_connection *conn;
+
+	DEBUGP(DIUCS, "Looking for IuCS subscriber: link_id %p, conn_id %" PRIx32 "\n",
+	       ue->link, ue->conn_id);
+	log_subscribers(network);
+
+	llist_for_each_entry(conn, &network->subscr_conns, entry) {
+		if (conn->via_ran != RAN_UTRAN_IU)
+			continue;
+		if (!same_ue_conn(conn->iu.ue_ctx, ue))
+			continue;
+		DEBUGP(DIUCS, "Found IuCS subscriber for link_id %p, conn_id %" PRIx32 "\n",
+		       ue->link, ue->conn_id);
+		return conn;
+	}
+	DEBUGP(DIUCS, "No IuCS subscriber found for link_id %p, conn_id %" PRIx32 "\n",
+	       ue->link, ue->conn_id);
+	return NULL;
+}
+
+/* Receive MM/CC/... message from IuCS (SCCP user SAP).
+ * msg->dst must reference a struct ue_conn_ctx, which identifies the peer that
+ * sent the msg.
+ *
+ * For A-interface see libbsc/bsc_api.c gsm0408_rcvmsg(). */
+int gsm0408_rcvmsg_iucs(struct gsm_network *network, struct msgb *msg,
+			uint16_t *lac)
+{
+	int rc;
+	struct ue_conn_ctx *ue_ctx;
+	struct gsm_subscriber_connection *conn;
+
+	ue_ctx = (struct ue_conn_ctx*)msg->dst;
+
+	/* TODO: are there message types that could allow us to skip this
+	 * search? */
+	conn = subscr_conn_lookup_iu(network, ue_ctx);
+
+	if (conn && lac && (conn->lac != *lac)) {
+		LOGP(DIUCS, LOGL_ERROR, "IuCS subscriber has changed LAC"
+		     " within the same connection, discarding connection:"
+		     " %s from LAC %d to %d\n",
+		     vlr_subscr_name(conn->vsub), conn->lac, *lac);
+		/* Deallocate conn with previous LAC */
+		msc_subscr_conn_close(conn, GSM_CAUSE_INV_MAND_INFO);
+		/* At this point we could be tolerant and allocate a new
+		 * connection, but changing the LAC within the same connection
+		 * is shifty. Rather cancel everything. */
+		return -1;
+	}
+
+	if (conn) {
+		/* Make sure we don't receive RR over IuCS; otherwise all
+		 * messages handled by gsm0408_dispatch() are of interest (CC,
+		 * MM, SMS, NS_SS, maybe even MM_GPRS and SM_GPRS). */
+		struct gsm48_hdr *gh = msgb_l3(msg);
+		uint8_t pdisc = gh->proto_discr & 0x0f;
+		OSMO_ASSERT(pdisc != GSM48_PDISC_RR);
+
+		msc_dtap(conn, ue_ctx->conn_id, msg);
+		rc = 0;
+	} else {
+		/* allocate a new connection */
+
+		if (!lac) {
+			LOGP(DIUCS, LOGL_ERROR, "New IuCS subscriber"
+			     " but no LAC available. Expecting an InitialUE"
+			     " message containing a LAI IE."
+			     " Dropping connection.\n");
+			return -1;
+		}
+
+		conn = subscr_conn_allocate_iu(network, ue_ctx, *lac);
+		if (!conn)
+			abort();
+
+		/* ownership of conn hereby goes to the MSC: */
+		rc = msc_compl_l3(conn, msg, 0);
+	}
+
+	return rc;
+}
diff --git a/src/libmsc/iucs_ranap.c b/src/libmsc/iucs_ranap.c
new file mode 100644
index 0000000..c016474
--- /dev/null
+++ b/src/libmsc/iucs_ranap.c
@@ -0,0 +1,104 @@
+/* Implementation of RANAP messages to/from an MSC via an Iu-CS interface.
+ * This keeps direct RANAP dependencies out of libmsc. */
+
+/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ *
+ * 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 "../../bscconfig.h"
+
+#ifdef BUILD_IU
+
+#include <osmocom/core/logging.h>
+
+#include <osmocom/ranap/ranap_ies_defs.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/iu.h>
+#include <openbsc/iucs.h>
+#include <openbsc/vlr.h>
+#include <openbsc/iucs_ranap.h>
+#include <openbsc/osmo_msc.h>
+
+/* To continue authorization after a Security Mode Complete */
+int gsm0408_authorize(struct gsm_subscriber_connection *conn);
+
+static int iucs_rx_rab_assign(struct gsm_subscriber_connection *conn,
+			      RANAP_RAB_SetupOrModifiedItemIEs_t *setup_ies)
+{
+	uint8_t rab_id;
+	RANAP_RAB_SetupOrModifiedItem_t *item = &setup_ies->raB_SetupOrModifiedItem;
+
+	rab_id = item->rAB_ID.buf[0];
+
+	LOGP(DIUCS, LOGL_NOTICE,
+	     "Received RAB assignment event for %s rab_id=%hhd\n",
+	     vlr_subscr_name(conn->vsub), rab_id);
+
+	return 0;
+}
+
+int iucs_rx_sec_mode_compl(struct gsm_subscriber_connection *conn,
+			   RANAP_SecurityModeCompleteIEs_t *ies)
+{
+	OSMO_ASSERT(conn->via_ran == RAN_UTRAN_IU);
+
+	/* TODO evalute ies */
+
+	msc_rx_sec_mode_compl(conn);
+	return 0;
+}
+
+int iucs_rx_ranap_event(struct gsm_network *network,
+			struct ue_conn_ctx *ue_ctx, int type, void *data)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = subscr_conn_lookup_iu(network, ue_ctx);
+
+	if (!conn) {
+		LOGP(DRANAP, LOGL_ERROR, "Cannot find subscriber for IU event %u\n", type);
+		return -1;
+	}
+
+	switch (type) {
+	case IU_EVENT_IU_RELEASE:
+	case IU_EVENT_LINK_INVALIDATED:
+		LOGP(DIUCS, LOGL_INFO, "IuCS release for %s\n",
+		     vlr_subscr_name(conn->vsub));
+		msc_subscr_conn_close(conn, 0);
+		return 0;
+
+	case IU_EVENT_SECURITY_MODE_COMPLETE:
+		LOGP(DIUCS, LOGL_INFO, "IuCS security mode complete for %s\n",
+		     vlr_subscr_name(conn->vsub));
+		return iucs_rx_sec_mode_compl(conn,
+					      (RANAP_SecurityModeCompleteIEs_t*)data);
+	case IU_EVENT_RAB_ASSIGN:
+		return iucs_rx_rab_assign(conn,
+				(RANAP_RAB_SetupOrModifiedItemIEs_t*)data);
+	default:
+		LOGP(DIUCS, LOGL_NOTICE, "Unknown message received:"
+		     " RANAP event: %i\n", type);
+		return -1;
+	}
+}
+
+#endif /* BUILD_IU */
diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c
index 001fcba..56cbd49 100644
--- a/src/libmsc/msc_ifaces.c
+++ b/src/libmsc/msc_ifaces.c
@@ -23,11 +23,28 @@
 #include <openbsc/debug.h>
 #include <openbsc/gsm_data.h>
 #include <openbsc/msc_ifaces.h>
+#include <openbsc/iu.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/transaction.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcpgw_client.h>
+#include <openbsc/vlr.h>
+
+#include "../../bscconfig.h"
+
+#ifdef BUILD_IU
+extern struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id,
+						   uint32_t rtp_ip,
+						   uint16_t rtp_port,
+						   bool use_x213_nsap);
+#endif /* BUILD_IU */
 
 static int msc_tx(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
+	DEBUGP(DMSC, "msc_tx %u bytes to %s via %s\n",
+	       msg->len, vlr_subscr_name(conn->vsub),
+	       ran_type_name(conn->via_ran));
 	switch (conn->via_ran) {
-	/* FUTURE 
 	case RAN_GERAN_A:
 		msg->dst = conn;
 		return a_tx(msg);
@@ -35,7 +52,7 @@
 	case RAN_UTRAN_IU:
 		msg->dst = conn->iu.ue_ctx;
 		return iu_tx(msg, 0);
-	*/
+
 	default:
 		LOGP(DMSC, LOGL_ERROR,
 		     "msc_tx(): conn->via_ran invalid (%d)\n",
@@ -61,7 +78,8 @@
 	gh->proto_discr = GSM48_PDISC_MM;
 	gh->msg_type = GSM48_MT_MM_CM_SERV_ACC;
 
-	DEBUGP(DMM, "-> CM SERVICE ACCEPT\n");
+	DEBUGP(DMM, "-> CM SERVICE ACCEPT %s\n",
+	       vlr_subscr_name(conn->vsub));
 
 	return msc_tx_dtap(conn, msg);
 }
@@ -71,6 +89,7 @@
 			     enum gsm48_reject_value value)
 {
 	struct msgb *msg;
+	conn->received_cm_service_request = false;
 
 	msg = gsm48_create_mm_serv_rej(value);
 	if (!msg) {
@@ -82,3 +101,216 @@
 
 	return msc_tx_dtap(conn, msg);
 }
+
+int msc_tx_common_id(struct gsm_subscriber_connection *conn)
+{
+	/* Common ID is only sent over IuCS */
+	if (conn->via_ran != RAN_UTRAN_IU) {
+		LOGP(DMM, LOGL_INFO,
+		     "%s: Asked to transmit Common ID, but skipping"
+		     " because this is not on UTRAN\n",
+		     vlr_subscr_name(conn->vsub));
+		return 0;
+	}
+
+	DEBUGP(DIUCS, "%s: tx CommonID %s\n",
+	       vlr_subscr_name(conn->vsub), conn->vsub->imsi);
+	return iu_tx_common_id(conn->iu.ue_ctx, conn->vsub->imsi);
+}
+
+#ifdef BUILD_IU
+static void iu_rab_act_cs(struct ue_conn_ctx *uectx, uint8_t rab_id,
+			  uint32_t rtp_ip, uint16_t rtp_port)
+{
+	struct msgb *msg;
+	bool use_x213_nsap;
+	uint32_t conn_id = uectx->conn_id;
+
+	use_x213_nsap = (uectx->rab_assign_addr_enc == NSAP_ADDR_ENC_X213);
+
+	LOGP(DIUCS, LOGL_DEBUG, "Assigning RAB: conn_id=%u, rab_id=%d,"
+	     " rtp=%x:%u, use_x213_nsap=%d\n", conn_id, rab_id, rtp_ip,
+	     rtp_port, use_x213_nsap);
+
+	msg = ranap_new_msg_rab_assign_voice(rab_id, rtp_ip, rtp_port,
+					     use_x213_nsap);
+	msg->l2h = msg->data;
+
+	if (iu_rab_act(uectx, msg))
+		LOGP(DIUCS, LOGL_ERROR, "Failed to send RAB Assignment:"
+		     " conn_id=%d rab_id=%d rtp=%x:%u\n",
+		     conn_id, rab_id, rtp_ip, rtp_port);
+}
+
+static void mgcp_response_rab_act_cs_crcx(struct mgcp_response *r, void *priv)
+{
+	struct gsm_trans *trans = priv;
+	struct gsm_subscriber_connection *conn = trans->conn;
+	struct ue_conn_ctx *uectx = conn->iu.ue_ctx;
+	uint32_t rtp_ip;
+	int rc;
+
+	if (r->head.response_code != 200) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "MGCPGW response yields error: %d %s\n",
+		     r->head.response_code, r->head.comment);
+		goto rab_act_cs_error;
+	}
+
+	rc = mgcp_response_parse_params(r);
+	if (rc) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot parse MGCP response, for %s\n",
+		     vlr_subscr_name(trans->vsub));
+		goto rab_act_cs_error;
+	}
+
+	conn->iu.mgcp_rtp_port_cn = r->audio_port;
+
+	rtp_ip = mgcpgw_client_remote_addr_n(conn->network->mgcpgw.client);
+	iu_rab_act_cs(uectx, conn->iu.rab_id, rtp_ip,
+		      conn->iu.mgcp_rtp_port_ue);
+	/* use_x213_nsap == 0 for ip.access nano3G */
+
+rab_act_cs_error:
+	/* FIXME abort call, invalidate conn, ... */
+	return;
+}
+
+static int conn_iu_rab_act_cs(struct gsm_trans *trans)
+{
+	struct gsm_subscriber_connection *conn = trans->conn;
+	struct mgcpgw_client *mgcp = conn->network->mgcpgw.client;
+	struct msgb *msg;
+
+	/* HACK. where to scope the RAB Id? At the conn / subscriber /
+	 * ue_conn_ctx? */
+	static uint8_t next_rab_id = 1;
+	conn->iu.rab_id = next_rab_id ++;
+
+	conn->iu.mgcp_rtp_endpoint =
+		mgcpgw_client_next_endpoint(conn->network->mgcpgw.client);
+	/* HACK: the addresses should be known from CRCX response
+	 * and config. */
+	conn->iu.mgcp_rtp_port_ue = 4000 + 2 * conn->iu.mgcp_rtp_endpoint;
+
+	/* Establish the RTP stream first as looping back to the originator.
+	 * The MDCX will patch through to the counterpart. TODO: play a ring
+	 * tone instead. */
+	msg = mgcp_msg_crcx(mgcp, conn->iu.mgcp_rtp_endpoint, trans->callref,
+			    MGCP_CONN_LOOPBACK);
+	return mgcpgw_client_tx(mgcp, msg, mgcp_response_rab_act_cs_crcx, trans);
+}
+#endif
+
+int msc_call_assignment(struct gsm_trans *trans)
+{
+	struct gsm_subscriber_connection *conn = trans->conn;
+
+	switch (conn->via_ran) {
+	case RAN_GERAN_A:
+		LOGP(DMSC, LOGL_ERROR,
+		     "msc_call_assignment(): A-interface BSSMAP Assignment"
+		     " Request not yet implemented\n");
+		return -ENOTSUP;
+
+	case RAN_UTRAN_IU:
+#ifdef BUILD_IU
+		return conn_iu_rab_act_cs(trans);
+#else
+		LOGP(DMSC, LOGL_ERROR,
+		     "msc_call_assignment(): cannot send RAB Activation, built without Iu support\n");
+		return -ENOTSUP;
+#endif
+
+	default:
+		LOGP(DMSC, LOGL_ERROR,
+		     "msc_tx(): conn->via_ran invalid (%d)\n",
+		     conn->via_ran);
+		return -EINVAL;
+	}
+}
+
+static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv);
+
+static void mgcp_bridge(struct gsm_trans *from, struct gsm_trans *to,
+			enum bridge_state state,
+			enum mgcp_connection_mode mode)
+{
+	struct gsm_subscriber_connection *conn1 = from->conn;
+	struct gsm_subscriber_connection *conn2 = to->conn;
+	struct mgcpgw_client *mgcp = conn1->network->mgcpgw.client;
+	const char *ip;
+	struct msgb *msg;
+
+	OSMO_ASSERT(mgcp);
+
+	from->bridge.peer = to;
+	from->bridge.state = state;
+
+	/* Loop back to the same MGCP GW */
+	ip = mgcpgw_client_remote_addr_str(mgcp);
+
+	msg = mgcp_msg_mdcx(mgcp,
+			    conn1->iu.mgcp_rtp_endpoint,
+			    ip, conn2->iu.mgcp_rtp_port_cn,
+			    mode);
+	if (mgcpgw_client_tx(mgcp, msg, mgcp_response_bridge_mdcx, from))
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Failed to send MDCX message for %s\n",
+		     vlr_subscr_name(from->vsub));
+}
+
+static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv)
+{
+	struct gsm_trans *trans = priv;
+	struct gsm_trans *peer = trans->bridge.peer;
+
+	switch (trans->bridge.state) {
+	case BRIDGE_STATE_LOOPBACK_PENDING:
+		trans->bridge.state = BRIDGE_STATE_LOOPBACK_ESTABLISHED;
+
+		switch (peer->bridge.state) {
+		case BRIDGE_STATE_LOOPBACK_PENDING:
+			/* Wait until the other is done as well. */
+			return;
+		case BRIDGE_STATE_LOOPBACK_ESTABLISHED:
+			/* Now that both are in loopback, switch both to
+			 * forwarding. */
+			mgcp_bridge(trans, peer, BRIDGE_STATE_BRIDGE_PENDING,
+				    MGCP_CONN_RECV_SEND);
+			mgcp_bridge(peer, trans, BRIDGE_STATE_BRIDGE_PENDING,
+				    MGCP_CONN_RECV_SEND);
+			break;
+		default:
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Unexpected bridge state: %d for %s\n",
+			     trans->bridge.state, vlr_subscr_name(trans->vsub));
+			break;
+		}
+		break;
+
+	case BRIDGE_STATE_BRIDGE_PENDING:
+		trans->bridge.state = BRIDGE_STATE_BRIDGE_ESTABLISHED;
+		break;
+
+	default:
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Unexpected bridge state: %d for %s\n",
+		     trans->bridge.state, vlr_subscr_name(trans->vsub));
+		break;
+	}
+}
+
+int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2)
+{
+	/* First setup as loopback and configure the counterparts' endpoints,
+	 * so that when transmission starts the originating addresses are
+	 * already known to be valid. The mgcp callback will continue. */
+	mgcp_bridge(trans1, trans2, BRIDGE_STATE_LOOPBACK_PENDING,
+		    MGCP_CONN_LOOPBACK);
+	mgcp_bridge(trans2, trans1, BRIDGE_STATE_LOOPBACK_PENDING,
+		    MGCP_CONN_LOOPBACK);
+
+	return 0;
+}
diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c
new file mode 100644
index 0000000..82dc7d6
--- /dev/null
+++ b/src/libmsc/msc_vty.c
@@ -0,0 +1,130 @@
+/* MSC interface to quagga VTY */
+/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c)
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther
+ * 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/>.
+ *
+ */
+
+/* NOTE: I would have liked to call this the MSC_NODE instead of the MSC_NODE,
+ * but MSC_NODE already exists to configure a remote MSC for osmo-bsc. */
+
+#include <inttypes.h>
+
+#include <osmocom/vty/command.h>
+
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/vlr.h>
+#include <openbsc/iu.h>
+
+static struct cmd_node msc_node = {
+	MSC_NODE,
+	"%s(config-msc)# ",
+	1,
+};
+
+DEFUN(cfg_msc, cfg_msc_cmd,
+      "msc", "Configure MSC options")
+{
+	vty->node = MSC_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_assign_tmsi, cfg_msc_assign_tmsi_cmd,
+      "assign-tmsi",
+      "Assign TMSI during Location Updating.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->vlr->cfg.assign_tmsi = true;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_no_assign_tmsi, cfg_msc_no_assign_tmsi_cmd,
+      "no assign-tmsi",
+      NO_STR "Assign TMSI during Location Updating.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->vlr->cfg.assign_tmsi = false;
+	return CMD_SUCCESS;
+}
+
+static int config_write_msc(struct vty *vty)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	vty_out(vty, "msc%s", VTY_NEWLINE);
+	vty_out(vty, " %sassign-tmsi%s",
+		gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE);
+
+	mgcpgw_client_config_write(vty, " ");
+#ifdef BUILD_IU
+	iu_vty_config_write(vty, " ");
+#endif
+
+	return CMD_SUCCESS;
+}
+
+static int config_write_net(struct vty *vty)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	vty_out(vty, "network%s", VTY_NEWLINE);
+	vty_out(vty, " network country code %u%s", gsmnet->country_code, VTY_NEWLINE);
+	vty_out(vty, " mobile network code %u%s", gsmnet->network_code, VTY_NEWLINE);
+	vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE);
+	vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE);
+	vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE);
+	vty_out(vty, " location updating reject cause %u%s",
+		gsmnet->reject_cause, VTY_NEWLINE);
+	vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE);
+	vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode),
+		VTY_NEWLINE);
+	vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE);
+	if (gsmnet->tz.override != 0) {
+		if (gsmnet->tz.dst)
+			vty_out(vty, " timezone %d %d %d%s",
+				gsmnet->tz.hr, gsmnet->tz.mn, gsmnet->tz.dst,
+				VTY_NEWLINE);
+		else
+			vty_out(vty, " timezone %d %d%s",
+				gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE);
+	}
+	if (gsmnet->t3212 == 0)
+		vty_out(vty, " no periodic location update%s", VTY_NEWLINE);
+	else
+		vty_out(vty, " periodic location update %u%s",
+			gsmnet->t3212 * 6, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+void msc_vty_init(struct gsm_network *msc_network)
+{
+	common_cs_vty_init(msc_network, config_write_net);
+
+	install_element(CONFIG_NODE, &cfg_msc_cmd);
+	install_node(&msc_node, config_write_msc);
+	vty_install_default(MSC_NODE);
+	install_element(MSC_NODE, &cfg_msc_assign_tmsi_cmd);
+	install_element(MSC_NODE, &cfg_msc_no_assign_tmsi_cmd);
+	mgcpgw_client_vty_init(MSC_NODE, &msc_network->mgcpgw.conf);
+#ifdef BUILD_IU
+	iu_vty_init(MSC_NODE, &msc_network->iu.rab_assign_addr_enc);
+#endif
+}
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c
index c847b78..ddc3836 100644
--- a/src/libmsc/osmo_msc.c
+++ b/src/libmsc/osmo_msc.c
@@ -28,6 +28,7 @@
 #include <openbsc/db.h>
 #include <openbsc/vlr.h>
 #include <openbsc/osmo_msc.h>
+#include <openbsc/iu.h>
 
 #include <openbsc/gsm_04_11.h>
 
@@ -40,24 +41,6 @@
 		gsm411_sapi_n_reject(conn);
 }
 
-static bool keep_conn(struct gsm_subscriber_connection *conn)
-{
-	/* TODO: what about a silent call? */
-
-	if (!conn->conn_fsm) {
-		DEBUGP(DMM, "No conn_fsm, release conn\n");
-		return false;
-	}
-
-	switch (conn->conn_fsm->state) {
-	case SUBSCR_CONN_S_NEW:
-	case SUBSCR_CONN_S_ACCEPTED:
-		return true;
-	default:
-		return false;
-	}
-}
-
 static void subscr_conn_bump(struct gsm_subscriber_connection *conn)
 {
 	if (!conn)
@@ -65,39 +48,32 @@
 	if (!conn->conn_fsm)
 		return;
 	if (!(conn->conn_fsm->state == SUBSCR_CONN_S_ACCEPTED
-	      || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING))
+	      || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING)) {
+		DEBUGP(DMM, "%s: bump: conn still being established (%s)\n",
+		       vlr_subscr_name(conn->vsub),
+		       osmo_fsm_inst_state_name(conn->conn_fsm));
 		return;
+	}
 	osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_BUMP, NULL);
 }
 
 /* receive a Level 3 Complete message and return MSC_CONN_ACCEPT or
  * MSC_CONN_REJECT */
-static int msc_compl_l3(struct gsm_subscriber_connection *conn,
-			struct msgb *msg, uint16_t chosen_channel)
+int msc_compl_l3(struct gsm_subscriber_connection *conn,
+		 struct msgb *msg, uint16_t chosen_channel)
 {
-	/* Ownership of the gsm_subscriber_connection is still a bit mucky
-	 * between libbsc and libmsc. In libmsc, we use ref counting, but not
-	 * in libbsc. This will become simpler with the MSCSPLIT. */
-
-	/* reserve for the duration of this function */
 	msc_subscr_conn_get(conn);
-
 	gsm0408_dispatch(conn, msg);
 
-	if (!keep_conn(conn)) {
-		DEBUGP(DMM, "compl_l3: Discarding conn\n");
-		/* keep the use_count reserved, libbsc will discard. If we
-		 * released the ref count and discarded here, libbsc would
-		 * double-free. And we will not change bsc_api semantics. */
-		return MSC_CONN_REJECT;
-	}
-	DEBUGP(DMM, "compl_l3: Keeping conn\n");
-
 	/* Bump whether the conn wants to be closed */
 	subscr_conn_bump(conn);
 
 	/* If this should be kept, the conn->conn_fsm has placed a use_count */
 	msc_subscr_conn_put(conn);
+
+	/* Always return acceptance, because even if the conn was not accepted,
+	 * we assumed ownership of it and the caller shall not interfere with
+	 * that. We may even already have discarded the conn. */
 	return MSC_CONN_ACCEPT;
 
 #if 0
@@ -119,7 +95,7 @@
 }
 
 /* Receive a DTAP message from BSC */
-static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
 {
 	msc_subscr_conn_get(conn);
 	gsm0408_dispatch(conn, msg);
@@ -170,8 +146,8 @@
 }
 
 /* Receive a CIPHERING MODE COMPLETE from BSC */
-static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn,
-			     struct msgb *msg, uint8_t alg_id)
+void msc_cipher_mode_compl(struct gsm_subscriber_connection *conn,
+			   struct msgb *msg, uint8_t alg_id)
 {
 	struct gsm48_hdr *gh = msgb_l3(msg);
 	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
@@ -289,7 +265,7 @@
 	.assign_compl = msc_assign_compl,
 	.assign_fail = msc_assign_fail,
 	.classmark_chg = msc_classmark_chg,
-	.cipher_mode_compl = msc_ciph_m_compl,
+	.cipher_mode_compl = msc_cipher_mode_compl,
 	.conn_cleanup = msc_subscr_con_cleanup,
 };
 
@@ -308,7 +284,10 @@
 
 	switch (conn->via_ran) {
 	case RAN_UTRAN_IU:
-		/* future: iu_tx_release(conn->iu.ue_ctx, NULL); */
+		iu_tx_release(conn->iu.ue_ctx, NULL);
+		/* FIXME: keep the conn until the Iu Release Outcome is
+		 * received from the UE, or a timeout expires. For now, the log
+		 * says "unknown UE" for each release outcome. */
 		break;
 	case RAN_GERAN_A:
 		/* future: a_iface_tx_clear_cmd(conn); */
@@ -390,8 +369,12 @@
 		"%s: MSC conn use - 1 == %u\n",
 		vlr_subscr_name(conn->vsub), conn->use_count);
 
-	if (conn->use_count == 0) {
-		gsm0808_clear(conn);
-		bsc_subscr_con_free(conn);
-	}
+	if (conn->use_count == 0)
+		msc_subscr_con_free(conn);
+}
+
+void msc_stop_paging(struct vlr_subscr *vsub)
+{
+	DEBUGP(DPAG, "Paging can stop for %s\n", vlr_subscr_name(vsub));
+	/* tell BSCs and RNCs to stop paging? How? */
 }
diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c
index 5fad4f4..7af7a80 100644
--- a/src/libmsc/silent_call.c
+++ b/src/libmsc/silent_call.c
@@ -133,7 +133,8 @@
 	/* FIXME the VTY command allows selecting a silent call channel type.
 	 * This doesn't apply to the situation after MSCSPLIT with an
 	 * A-interface. */
-	req = subscr_request_conn(vsub, type, paging_cb_silent, data);
+	req = subscr_request_conn(vsub, paging_cb_silent, data,
+				  "establish silent call");
 	return req != NULL;
 }
 
diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c
index b28a511..31decc7 100644
--- a/src/libmsc/subscr_conn.c
+++ b/src/libmsc/subscr_conn.c
@@ -30,6 +30,7 @@
 #include <openbsc/debug.h>
 #include <openbsc/transaction.h>
 #include <openbsc/signal.h>
+#include <openbsc/iu.h>
 
 #define SUBSCR_CONN_TIMEOUT 5 /* seconds */
 
@@ -52,8 +53,8 @@
 	{ 0, NULL }
 };
 
-static void paging_resp(struct gsm_subscriber_connection *conn,
-			       enum gsm_paging_event pe)
+static void paging_event(struct gsm_subscriber_connection *conn,
+			 enum gsm_paging_event pe)
 {
 	subscr_paging_dispatch(GSM_HOOK_RR_PAGING, pe, NULL, conn, conn->vsub);
 }
@@ -85,11 +86,17 @@
 
 	case SUBSCR_CONN_E_MO_CLOSE:
 	case SUBSCR_CONN_E_CN_CLOSE:
+		if (data)
+			LOGPFSM(fi, "Close event, cause %u\n",
+				*(uint32_t*)data);
+		/* will release further below, see
+		 * 'if (fi->state != SUBSCR_CONN_S_ACCEPTED)' */
 		break;
 
 	default:
-		LOGPFSM(fi, "Unexpected event: %d %s\n",
-			event, osmo_fsm_event_name(fi->fsm, event));
+		LOGPFSML(fi, LOGL_ERROR,
+			 "Unexpected event: %d %s\n", event,
+			 osmo_fsm_event_name(fi->fsm, event));
 		break;
 	}
 
@@ -102,21 +109,24 @@
 
 	/* signal paging success or failure in case this was a paging */
 	if (from == SUBSCR_CONN_FROM_PAGING_RESP)
-		paging_resp(conn,
-			    success ? GSM_PAGING_SUCCEEDED
-			    	    : GSM_PAGING_EXPIRED);
+		paging_event(conn,
+			     success ? GSM_PAGING_SUCCEEDED
+			     	     : GSM_PAGING_EXPIRED);
+
+	/* FIXME rate counters */
+	/*rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED]);*/
 
 	/* On failure, discard the conn */
 	if (!success) {
 		/* TODO: on MO_CLOSE or CN_CLOSE, first go to RELEASING and
-		 * await BSC confirmation? */
+		 * await BSC/RNC confirmation? */
 		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 		return;
 	}
 
 	if (from == SUBSCR_CONN_FROM_CM_SERVICE_REQ) {
 		conn->received_cm_service_request = true;
-		LOGPFSM(fi, "received_cm_service_request = true\n");
+		LOGPFSML(fi, LOGL_DEBUG, "received_cm_service_request = true\n");
 	}
 
 	osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_BUMP, data);
@@ -125,19 +135,37 @@
 static void subscr_conn_fsm_bump(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	struct gsm_subscriber_connection *conn = fi->priv;
+	struct gsm_trans *trans;
 
-	if (conn->silent_call)
+	if (conn->silent_call) {
+		LOGPFSML(fi, LOGL_DEBUG, "bump: silent call still active\n");
 		return;
+	}
 
-	if (conn->received_cm_service_request)
+	if (conn->received_cm_service_request) {
+		LOGPFSML(fi, LOGL_DEBUG, "bump: still awaiting first request after a CM Service Request\n");
 		return;
+	}
 
-	if (conn->vsub && !llist_empty(&conn->vsub->cs.requests))
+	if (conn->vsub && !llist_empty(&conn->vsub->cs.requests)) {
+		struct subscr_request *sr;
+		if (!log_check_level(fi->fsm->log_subsys, LOGL_DEBUG)) {
+			llist_for_each_entry(sr, &conn->vsub->cs.requests, entry) {
+				LOGPFSML(fi, LOGL_DEBUG, "bump: still active: %s\n",
+					 sr->label);
+			}
+		}
 		return;
+	}
 
-	if (trans_has_conn(conn))
+	if ((trans = trans_has_conn(conn))) {
+		LOGPFSML(fi, LOGL_DEBUG,
+			 "bump: connection still has active transaction: %s\n",
+			 gsm48_pdisc_name(trans->protocol));
 		return;
+	}
 
+	LOGPFSML(fi, LOGL_DEBUG, "bump: releasing conn\n");
 	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 }
 
@@ -269,7 +297,7 @@
 	.num_states = ARRAY_SIZE(subscr_conn_fsm_states),
 	.allstate_event_mask = 0,
 	.allstate_action = NULL,
-	.log_subsys = DVLR,
+	.log_subsys = DMM,
 	.event_names = subscr_conn_fsm_event_names,
 	.cleanup = subscr_conn_fsm_cleanup,
 	.timer_cb = subscr_conn_fsm_timeout,
diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c
index d157f54..7289a8f 100644
--- a/src/libmsc/transaction.c
+++ b/src/libmsc/transaction.c
@@ -180,15 +180,15 @@
  * \param[in] conn Connection to check
  * \returns 1 in case there is a transaction, 0 otherwise
  */
-int trans_has_conn(const struct gsm_subscriber_connection *conn)
+struct gsm_trans *trans_has_conn(const struct gsm_subscriber_connection *conn)
 {
 	struct gsm_trans *trans;
 
 	llist_for_each_entry(trans, &conn->network->trans_list, entry)
 		if (trans->conn == conn)
-			return 1;
+			return trans;
 
-	return 0;
+	return NULL;
 }
 
 /*! Free all transactions associated with a connection, presumably when the
diff --git a/src/libmsc/vty_interface_layer3.c b/src/libmsc/vty_interface_layer3.c
index 0106f91..d1bf6b3 100644
--- a/src/libmsc/vty_interface_layer3.c
+++ b/src/libmsc/vty_interface_layer3.c
@@ -21,9 +21,8 @@
 #include <stdlib.h>
 #include <limits.h>
 #include <unistd.h>
-#include <stdbool.h>
-#include <inttypes.h>
 #include <time.h>
+#include <inttypes.h>
 
 #include <osmocom/vty/command.h>
 #include <osmocom/vty/buffer.h>
@@ -474,17 +473,6 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(ena_subscr_delete,
-      ena_subscr_delete_cmd,
-      "subscriber " SUBSCR_TYPES " ID delete",
-	SUBSCR_HELP "Delete subscriber in VLR\n")
-{
-	vty_out(vty, "%% 'subscriber delete' is no longer supported.%s"
-		"%% This is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
 DEFUN(ena_subscr_expire,
       ena_subscr_expire_cmd,
       "subscriber " SUBSCR_TYPES " ID expire",
@@ -516,43 +504,6 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(ena_subscr_authorized,
-      ena_subscr_authorized_cmd,
-      "subscriber " SUBSCR_TYPES " ID authorized (0|1)",
-	SUBSCR_HELP "(De-)Authorize subscriber in HLR\n"
-	"Subscriber should NOT be authorized\n"
-	"Subscriber should be authorized\n")
-{
-	vty_out(vty, "%% 'subscriber authorized' is no longer supported.%s"
-		"%% Authorization is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
-DEFUN(ena_subscr_name,
-      ena_subscr_name_cmd,
-      "subscriber " SUBSCR_TYPES " ID name .NAME",
-	SUBSCR_HELP "Set the name of the subscriber\n"
-	"Name of the Subscriber\n")
-{
-	vty_out(vty, "%% 'subscriber name' is no longer supported.%s"
-		"%% This is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
-DEFUN(ena_subscr_extension,
-      ena_subscr_extension_cmd,
-      "subscriber " SUBSCR_TYPES " ID extension EXTENSION",
-	SUBSCR_HELP "Set the extension (phone number) of the subscriber\n"
-	"Extension (phone number)\n")
-{
-	vty_out(vty, "%% 'subscriber extension' is no longer supported.%s"
-		"%% This is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
 #define A3A8_ALG_TYPES "(none|xor|comp128v1)"
 #define A3A8_ALG_HELP 			\
 	"Use No A3A8 algorithm\n"	\
@@ -571,18 +522,6 @@
 	return CMD_WARNING;
 }
 
-DEFUN(subscriber_purge,
-      subscriber_purge_cmd,
-      "subscriber purge-inactive",
-      "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n")
-{
-	/* TODO: does this still have a use with the VLR? */
-	vty_out(vty, "%% 'subscriber purge-inactive' is no longer supported.%s"
-		"%% This is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
 DEFUN(subscriber_update,
       subscriber_update_cmd,
       "subscriber " SUBSCR_TYPES " ID update",
@@ -834,7 +773,6 @@
       "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
 {
 	struct vlr_subscr *vlr_subscr;
-	struct bsc_subscr *bsc_subscr;
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
 	struct log_target *tgt = osmo_log_vty2tgt(vty);
 	const char *imsi = argv[0];
@@ -843,16 +781,14 @@
 		return CMD_WARNING;
 
 	vlr_subscr = vlr_subscr_find_by_imsi(gsmnet->vlr, imsi);
-	bsc_subscr = bsc_subscr_find_by_imsi(gsmnet->bsc_subscribers, imsi);
 
-	if (!vlr_subscr && !bsc_subscr) {
+	if (!vlr_subscr) {
 		vty_out(vty, "%%no subscriber with IMSI(%s)%s",
 			argv[0], VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
 	log_set_filter_vlr_subscr(tgt, vlr_subscr);
-	log_set_filter_bsc_subscr(tgt, bsc_subscr);
 	return CMD_SUCCESS;
 }
 
@@ -900,81 +836,6 @@
 	return CMD_SUCCESS;
 }
 
-static struct cmd_node nitb_node = {
-	NITB_NODE,
-	"%s(config-nitb)# ",
-	1,
-};
-
-DEFUN(cfg_nitb, cfg_nitb_cmd,
-      "nitb", "Configure NITB options")
-{
-	vty->node = NITB_NODE;
-	return CMD_SUCCESS;
-}
-
-/* Note: limit on the parameter length is set by internal vty code limitations */
-DEFUN(cfg_nitb_subscr_random, cfg_nitb_subscr_random_cmd,
-      "subscriber-create-on-demand random <1-9999999999> <2-9999999999>",
-      "Set random parameters for a new record when a subscriber is first seen.\n"
-      "Set random parameters for a new record when a subscriber is first seen.\n"
-      "Minimum for subscriber extension\n""Maximum for subscriber extension\n")
-{
-	vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s"
-		"%% This is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
-DEFUN(cfg_nitb_subscr_create, cfg_nitb_subscr_create_cmd,
-      "subscriber-create-on-demand [no-extension]",
-      "Make a new record when a subscriber is first seen.\n"
-      "Do not automatically assign extension to created subscribers\n")
-{
-	vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s"
-		"%% This is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
-DEFUN(cfg_nitb_no_subscr_create, cfg_nitb_no_subscr_create_cmd,
-      "no subscriber-create-on-demand",
-      NO_STR "Make a new record when a subscriber is first seen.\n")
-{
-	vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s"
-		"%% This is now up to osmo-hlr.%s",
-		VTY_NEWLINE, VTY_NEWLINE);
-	return CMD_WARNING;
-}
-
-DEFUN(cfg_nitb_assign_tmsi, cfg_nitb_assign_tmsi_cmd,
-      "assign-tmsi",
-      "Assign TMSI during Location Updating.\n")
-{
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	gsmnet->vlr->cfg.assign_tmsi = true;
-	return CMD_SUCCESS;
-}
-
-DEFUN(cfg_nitb_no_assign_tmsi, cfg_nitb_no_assign_tmsi_cmd,
-      "no assign-tmsi",
-      NO_STR "Assign TMSI during Location Updating.\n")
-{
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	gsmnet->vlr->cfg.assign_tmsi = false;
-	return CMD_SUCCESS;
-}
-
-static int config_write_nitb(struct vty *vty)
-{
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-
-	vty_out(vty, "nitb%s", VTY_NEWLINE);
-	vty_out(vty, " %sassign-tmsi%s",
-		gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE);
-	return CMD_SUCCESS;
-}
-
 int bsc_vty_init_extra(void)
 {
 	osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL);
@@ -995,13 +856,8 @@
 	install_element_ve(&show_smsqueue_cmd);
 	install_element_ve(&logging_fltr_imsi_cmd);
 
-	install_element(ENABLE_NODE, &ena_subscr_delete_cmd);
 	install_element(ENABLE_NODE, &ena_subscr_expire_cmd);
-	install_element(ENABLE_NODE, &ena_subscr_name_cmd);
-	install_element(ENABLE_NODE, &ena_subscr_extension_cmd);
-	install_element(ENABLE_NODE, &ena_subscr_authorized_cmd);
 	install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd);
-	install_element(ENABLE_NODE, &subscriber_purge_cmd);
 	install_element(ENABLE_NODE, &smsqueue_trigger_cmd);
 	install_element(ENABLE_NODE, &smsqueue_max_cmd);
 	install_element(ENABLE_NODE, &smsqueue_clear_cmd);
@@ -1025,13 +881,5 @@
 	install_element(HLR_NODE, &cfg_hlr_remote_ip_cmd);
 	install_element(HLR_NODE, &cfg_hlr_remote_port_cmd);
 
-	install_element(CONFIG_NODE, &cfg_nitb_cmd);
-	install_node(&nitb_node, config_write_nitb);
-	install_element(NITB_NODE, &cfg_nitb_subscr_create_cmd);
-	install_element(NITB_NODE, &cfg_nitb_subscr_random_cmd);
-	install_element(NITB_NODE, &cfg_nitb_no_subscr_create_cmd);
-	install_element(NITB_NODE, &cfg_nitb_assign_tmsi_cmd);
-	install_element(NITB_NODE, &cfg_nitb_no_assign_tmsi_cmd);
-
 	return 0;
 }