Implement AoIP, port to M3UA SIGTRAN (large addition and refactoring)

This was originally a long series of commits converging to the final result
seen in this patch. It does not make much sense to review the smaller steps'
trial and error, we need to review this entire change as a whole.

Implement AoIP in osmo-msc and osmo-bsc.

Change over to the new libosmo-sigtran API with support for proper
SCCP/M3UA/SCTP stacking, as mandated by 3GPP specifications for the IuCS and
IuPS interfaces.

From here on, a separate osmo-stp process is required for SCCP routing between
OsmoBSC / OsmoHNBGW <-> OsmoMSC / OsmoSGSN

jenkins.sh: build from libosmo-sccp and osmo-iuh master branches now for new
M3UA SIGTRAN.

Patch-by: pmaier, nhofmeyr, laforge
Change-Id: I5ae4e05ee7c57cad341ea5e86af37c1f6b0ffa77
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index 4726bbe..9f246b3 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -13,6 +13,7 @@
 	$(LIBCRYPTO_CFLAGS) \
 	$(LIBSMPP34_CFLAGS) \
 	$(LIBASN1C_CFLAGS) \
+	$(LIBOSMOSIGTRAN_CFLAGS) \
 	$(NULL)
 
 noinst_HEADERS = \
@@ -25,6 +26,7 @@
 
 libmsc_a_SOURCES = \
 	a_iface.c \
+	a_iface_bssap.c \
 	auth.c \
 	msc_vty.c \
 	db.c \
diff --git a/src/libmsc/a_iface.c b/src/libmsc/a_iface.c
index caf9d4b..93e8ab5 100644
--- a/src/libmsc/a_iface.c
+++ b/src/libmsc/a_iface.c
@@ -1,9 +1,8 @@
-/* A-interface implementation, from MSC to BSC */
-
-/* (C) 2016 by sysmocom s.m.f.c GmbH <info@sysmocom.de>
- *
+/* (C) 2017 by sysmocom s.f.m.c. GmbH
  * All Rights Reserved
  *
+ * Author: Philipp Maier
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * the Free Software Foundation; either version 3 of the License, or
@@ -19,35 +18,574 @@
  *
  */
 
+#include <osmocom/core/utils.h>
 #include <osmocom/core/msgb.h>
 #include <osmocom/core/logging.h>
-
+#include <osmocom/sigtran/sccp_helpers.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/protocol/m3ua.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0808_utils.h>
 #include <openbsc/debug.h>
-
-#include <openbsc/gsm_data.h>
 #include <openbsc/msc_ifaces.h>
-#include <openbsc/debug.h>
+#include <openbsc/a_iface.h>
+#include <openbsc/a_iface_bssap.h>
+#include <openbsc/transaction.h>
+#include <openbsc/mgcpgw_client.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/sccp/sccp_types.h>
+#include <openbsc/a_reset.h>
+#include <openbsc/osmo_msc.h>
 
-int a_tx(struct msgb *msg)
+/* A pointer to the GSM network we work with. By the current paradigm,
+ * there can only be one gsm_network per MSC. The pointer is set once
+ * when calling a_init() */
+static struct gsm_network *gsm_network = NULL;
+
+/* A struct to track currently active connections. We need that information
+ * to handle failure sitautions. In case of a problem, we must know which
+ * connections are currently open and which BSC is responsible. We also need
+ * the data to perform our connection checks (a_reset). All other logic will
+ * look at the connection ids and addresses that are supplied by the
+ * primitives */
+struct bsc_conn {
+	struct llist_head list;
+	uint32_t conn_id;			/* Connection identifier */
+};
+
+/* Internal list with connections we currently maintain. This
+ * list is of type struct bsc_conn (see above) */
+static LLIST_HEAD(active_connections);
+
+/* Record info of a new active connection in the active connection list */
+static void record_bsc_con(const void *ctx, uint32_t conn_id)
 {
-	LOGP(DMSC, LOGL_ERROR, "message to be sent to BSC, but A-interface"
-	     " not implemented.\n%s\n", osmo_hexdump(msg->data, msg->len));
-	return -1;
+	struct bsc_conn *conn;
+
+	conn = talloc_zero(ctx, struct bsc_conn);
+	OSMO_ASSERT(conn);
+
+	conn->conn_id = conn_id;
+
+	llist_add_tail(&conn->list, &active_connections);
 }
 
-int a_page(const char *imsi, uint32_t tmsi, uint16_t lac)
+/* Delete info of a closed connection from the active connection list */
+void a_delete_bsc_con(uint32_t conn_id)
 {
-	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;
+	struct bsc_conn *conn;
+	struct bsc_conn *conn_temp;
+
+	LOGP(DMSC, LOGL_DEBUG,
+	     "Removing connection from active sccp-connection list (conn_id=%i)\n",
+	     conn_id);
+
+	llist_for_each_entry_safe(conn, conn_temp, &active_connections, list) {
+		if (conn->conn_id == conn_id) {
+			llist_del(&conn->list);
+			talloc_free(conn);
+		}
+	}
 }
 
-int msc_gsm0808_tx_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
-			       const uint8_t *key, int len, int include_imeisv)
+/* Check if a specified connection id has an active SCCP connection */
+static bool check_connection_active(uint32_t conn_id)
+{
+	struct bsc_conn *conn;
+
+	/* Find the address for the current connection id */
+	llist_for_each_entry(conn, &active_connections, list) {
+		if (conn->conn_id == conn_id) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Get the reset context for a specifiec calling (BSC) address */
+static struct a_reset_ctx *get_reset_ctx_by_sccp_addr(const struct osmo_sccp_addr *addr)
+{
+	struct bsc_context *bsc_ctx;
+	struct osmo_ss7_instance *ss7;
+
+	if (!addr)
+		return NULL;
+
+	llist_for_each_entry(bsc_ctx, &gsm_network->a.bscs, list) {
+		if (memcmp(&bsc_ctx->bsc_addr, addr, sizeof(*addr)) == 0)
+			return bsc_ctx->reset;
+	}
+
+	ss7 = osmo_ss7_instance_find(gsm_network->a.cs7_instance);
+	OSMO_ASSERT(ss7);
+	LOGP(DMSC, LOGL_ERROR, "The calling BSC (%s) is unknown to this MSC ...\n",
+	     osmo_sccp_addr_name(ss7, addr));
+	return NULL;
+}
+
+/* Send DTAP message via A-interface */
+int a_iface_tx_dtap(struct msgb *msg)
+{
+	struct gsm_subscriber_connection *conn;
+	struct msgb *msg_resp;
+
+	/* FIXME: Set this to some meaninful value! */
+	uint8_t link_id = 0x00;
+	OSMO_ASSERT(msg);
+	conn = (struct gsm_subscriber_connection *)msg->dst;
+	OSMO_ASSERT(conn);
+	OSMO_ASSERT(conn->a.scu);
+
+	LOGP(DMSC, LOGL_DEBUG, "Passing DTAP message from MSC to BSC (conn_id=%i)\n", conn->a.conn_id);
+
+	msg->l3h = msg->data;
+	msg_resp = gsm0808_create_dtap(msg, link_id);
+	if (!msg_resp) {
+		LOGP(DMSC, LOGL_ERROR, "Unable to generate BSSMAP DTAP message!\n");
+		return -EINVAL;
+	} else
+		LOGP(DMSC, LOGL_DEBUG, "Massage will be sent as BSSMAP DTAP message!\n");
+
+	LOGP(DMSC, LOGL_DEBUG, "N-DATA.req(%u, %s)\n", conn->a.conn_id, osmo_hexdump(msg_resp->data, msg_resp->len));
+	return osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg_resp);
+}
+
+/* Send Cipher mode command via A-interface */
+int a_iface_tx_cipher_mode(const struct gsm_subscriber_connection *conn,
+			   int cipher, const const uint8_t *key, int len, int include_imeisv)
 {
 	/* TODO generalize for A- and Iu interfaces, don't name after 08.08 */
-	LOGP(DMSC, LOGL_ERROR, "gsm0808_cipher_mode(): message to be sent to"
-	     " BSC, but A interface not yet implemented.\n");
-	return -1;
+	struct msgb *msg_resp;
+	struct gsm0808_encrypt_info ei;
+
+	OSMO_ASSERT(conn);
+
+	LOGP(DMSC, LOGL_DEBUG, "Passing Cipher mode command message from MSC to BSC (conn_id=%i)\n", conn->a.conn_id);
+	uint8_t crm = 0x01;
+	uint8_t *crm_ptr = NULL;
+
+	/* Setup encryption information */
+	if (len > ENCRY_INFO_KEY_MAXLEN || !key) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "Cipher mode command message could not be generated due to invalid key! (conn_id=%i)\n",
+		     conn->a.conn_id);
+		return -EINVAL;
+	} else {
+		memcpy(&ei.key, key, len);
+		ei.key_len = len;
+	}
+
+	if (include_imeisv)
+		crm_ptr = &crm;
+
+	ei.perm_algo[0] = (uint8_t) (1 << cipher);
+	ei.perm_algo_len = 1;
+
+	msg_resp = gsm0808_create_cipher(&ei, crm_ptr);
+	LOGP(DMSC, LOGL_DEBUG, "N-DATA.req(%u, %s)\n", conn->a.conn_id, osmo_hexdump(msg_resp->data, msg_resp->len));
+
+	return osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg_resp);
+}
+
+/* Page a subscriber via A-interface */
+int a_iface_tx_paging(const char *imsi, uint32_t tmsi, uint16_t lac)
+{
+	struct bsc_context *bsc_ctx;
+	struct gsm0808_cell_id_list cil;
+	struct msgb *msg;
+	int page_count = 0;
+	struct osmo_ss7_instance *ss7;
+
+	OSMO_ASSERT(imsi);
+
+	cil.id_discr = CELL_IDENT_LAC;
+	cil.id_list_lac[0] = lac;
+	cil.id_list_len = 1;
+
+	ss7 = osmo_ss7_instance_find(gsm_network->a.cs7_instance);
+	OSMO_ASSERT(ss7);
+
+	/* Deliver paging request to all known BSCs */
+	llist_for_each_entry(bsc_ctx, &gsm_network->a.bscs, list) {
+		if (a_reset_conn_ready(bsc_ctx->reset)) {
+			LOGP(DMSC, LOGL_DEBUG,
+			     "Passing paging message from MSC %s to BSC %s (imsi=%s, tmsi=0x%08x, lac=%u)\n",
+			     osmo_sccp_addr_name(ss7, &bsc_ctx->msc_addr),
+			     osmo_sccp_addr_name(ss7, &bsc_ctx->bsc_addr), imsi, tmsi, lac);
+			msg = gsm0808_create_paging(imsi, &tmsi, &cil, NULL);
+			osmo_sccp_tx_unitdata_msg(bsc_ctx->sccp_user,
+						  &bsc_ctx->msc_addr, &bsc_ctx->bsc_addr, msg);
+			page_count++;
+		} else {
+			LOGP(DMSC, LOGL_DEBUG,
+			     "Connection down, dropping paging from MSC %s to BSC %s (imsi=%s, tmsi=0x%08x, lac=%u)\n",
+			     osmo_sccp_addr_name(ss7, &bsc_ctx->msc_addr),
+			     osmo_sccp_addr_name(ss7, &bsc_ctx->bsc_addr), imsi, tmsi, lac);
+		}
+	}
+
+	if (page_count <= 0)
+		LOGP(DMSC, LOGL_ERROR, "Could not deliver paging because none of the associated BSCs is available!\n");
+
+	return page_count;
+}
+
+/* Convert speech version field */
+static uint8_t convert_Abis_sv_to_A_sv(int speech_ver)
+{
+	/* The speech versions that are transmitted in the Bearer capability
+	 * information element, that is transmitted on the Abis interfece
+	 * use a different encoding than the permitted speech version
+	 * identifier, that is signalled in the channel type element on the A
+	 * interface. (See also 3GPP TS 48.008, 3.2.2.1 and 3GPP TS 24.008,
+	 * 10.5.103 */
+
+	switch (speech_ver) {
+	case GSM48_BCAP_SV_FR:
+		return GSM0808_PERM_FR1;
+		break;
+	case GSM48_BCAP_SV_HR:
+		return GSM0808_PERM_HR1;
+		break;
+	case GSM48_BCAP_SV_EFR:
+		return GSM0808_PERM_FR2;
+		break;
+	case GSM48_BCAP_SV_AMR_F:
+		return GSM0808_PERM_FR3;
+		break;
+	case GSM48_BCAP_SV_AMR_H:
+		return GSM0808_PERM_HR3;
+		break;
+	case GSM48_BCAP_SV_AMR_OFW:
+		return GSM0808_PERM_FR4;
+		break;
+	case GSM48_BCAP_SV_AMR_OHW:
+		return GSM0808_PERM_HR4;
+		break;
+	case GSM48_BCAP_SV_AMR_FW:
+		return GSM0808_PERM_FR5;
+		break;
+	case GSM48_BCAP_SV_AMR_OH:
+		return GSM0808_PERM_HR6;
+		break;
+	}
+
+	/* If nothing matches, tag the result as invalid */
+	LOGP(DMSC, LOGL_ERROR, "Invalid permitted speech version / rate detected, discarding.\n");
+	return 0xFF;
+}
+
+/* Convert speech preference field */
+static uint8_t convert_Abis_prev_to_A_pref(int radio)
+{
+	/* The Radio channel requirement field that is transmitted in the
+	 * Bearer capability information element, that is transmitted on the
+	 * Abis interfece uses a different encoding than the Channel rate and
+	 * type field that is signalled in the channel type element on the A
+	 * interface. (See also 3GPP TS 48.008, 3.2.2.1 and 3GPP TS 24.008,
+	 * 10.5.102 */
+
+	switch (radio) {
+	case GSM48_BCAP_RRQ_FR_ONLY:
+		return GSM0808_SPEECH_FULL_BM;
+	case GSM48_BCAP_RRQ_DUAL_FR:
+		return GSM0808_SPEECH_FULL_PREF;
+	case GSM48_BCAP_RRQ_DUAL_HR:
+		return GSM0808_SPEECH_HALF_PREF;
+	}
+
+	LOGP(DMSC, LOGL_ERROR, "Invalid speech version / rate combination preference, defaulting to full rate.\n");
+	return GSM0808_SPEECH_FULL_BM;
+}
+
+/* Assemble the channel type field */
+static int enc_channel_type(struct gsm0808_channel_type *ct, const struct gsm_mncc_bearer_cap *bc)
+{
+	unsigned int i;
+	uint8_t sv;
+	unsigned int count = 0;
+	bool only_gsm_hr = true;
+
+	OSMO_ASSERT(ct);
+	OSMO_ASSERT(bc);
+
+	ct->ch_indctr = GSM0808_CHAN_SPEECH;
+
+	for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) {
+		if (bc->speech_ver[i] == -1)
+			break;
+		sv = convert_Abis_sv_to_A_sv(bc->speech_ver[i]);
+		if (sv != 0xFF) {
+			/* Detect if something else than
+			 * GSM HR V1 is supported */
+			if (sv == GSM0808_PERM_HR2 ||
+			    sv == GSM0808_PERM_HR3 || sv == GSM0808_PERM_HR4 || sv == GSM0808_PERM_HR6)
+				only_gsm_hr = false;
+
+			ct->perm_spch[count] = sv;
+			count++;
+		}
+	}
+	ct->perm_spch_len = count;
+
+	if (only_gsm_hr)
+		/* Note: We must avoid the usage of GSM HR1 as this
+		 * codec only offers very poor audio quality. If the
+		 * MS only supports GSM HR1 (and full rate), and has
+		 * a preference for half rate. Then we will ignore the
+		 * preference and assume a preference for full rate. */
+		ct->ch_rate_type = GSM0808_SPEECH_FULL_BM;
+	else
+		ct->ch_rate_type = convert_Abis_prev_to_A_pref(bc->radio);
+
+	if (count)
+		return 0;
+	else
+		return -EINVAL;
+}
+
+/* Assemble the speech codec field */
+static int enc_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct gsm0808_channel_type *ct)
+{
+	unsigned int i;
+	int rc;
+
+	memset(scl, 0, sizeof(*scl));
+	for (i = 0; i < ct->perm_spch_len; i++) {
+		rc = gsm0808_speech_codec_from_chan_type(&scl->codec[i], ct->perm_spch[i]);
+		if (rc != 0)
+			return -EINVAL;
+	}
+	scl->len = i;
+
+	return 0;
+}
+
+/* Send assignment request via A-interface */
+int a_iface_tx_assignment(const struct gsm_trans *trans)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm0808_channel_type ct;
+	struct gsm0808_speech_codec_list scl;
+	uint32_t *ci_ptr = NULL;
+	struct msgb *msg;
+	struct sockaddr_storage rtp_addr;
+	struct sockaddr_in rtp_addr_in;
+	int rc;
+
+	OSMO_ASSERT(trans);
+	conn = trans->conn;
+	OSMO_ASSERT(conn);
+
+	LOGP(DMSC, LOGL_ERROR, "Sending assignment command to BSC (conn_id %u)\n", conn->a.conn_id);
+
+	/* Channel type */
+	rc = enc_channel_type(&ct, &trans->bearer_cap);
+	if (rc < 0) {
+		LOGP(DMSC, LOGL_ERROR, "Faild to generate channel type -- assignment not sent!\n");
+		return -EINVAL;
+	}
+
+	/* Speech codec list */
+	rc = enc_speech_codec_list(&scl, &ct);
+	if (rc < 0) {
+		LOGP(DMSC, LOGL_ERROR, "Faild to generate Speech codec list -- assignment not sent!\n");
+		return -EINVAL;
+	}
+
+	/* Package RTP-Address data */
+	memset(&rtp_addr_in, 0, sizeof(rtp_addr_in));
+	rtp_addr_in.sin_family = AF_INET;
+	rtp_addr_in.sin_port = osmo_htons(conn->rtp.port_subscr);
+	rtp_addr_in.sin_addr.s_addr = osmo_htonl(mgcpgw_client_remote_addr_n(gsm_network->mgcpgw.client));
+
+	memset(&rtp_addr, 0, sizeof(rtp_addr));
+	memcpy(&rtp_addr, &rtp_addr_in, sizeof(rtp_addr_in));
+
+	msg = gsm0808_create_ass(&ct, NULL, &rtp_addr, &scl, ci_ptr);
+
+	LOGP(DMSC, LOGL_DEBUG, "N-DATA.req(%u, %s)\n", conn->a.conn_id, osmo_hexdump(msg->data, msg->len));
+	return osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg);
+}
+
+/* Send clear command via A-interface */
+int a_iface_tx_clear_cmd(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg;
+
+	LOGP(DMSC, LOGL_NOTICE, "Sending clear command to BSC (conn_id=%u)\n", conn->a.conn_id);
+
+	msg = gsm0808_create_clear_command(GSM0808_CAUSE_CALL_CONTROL);
+	return osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg);
+}
+
+/* Callback function: Close all open connections */
+static void a_reset_cb(const void *priv)
+{
+	struct msgb *msg;
+	struct bsc_context *bsc_ctx = (struct bsc_context*) priv;
+	struct osmo_ss7_instance *ss7;
+
+	/* Skip if the A interface is not properly initalized yet */
+	if (!gsm_network)
+		return;
+
+	/* Clear all now orphaned subscriber connections */
+	a_clear_all(bsc_ctx->sccp_user, &bsc_ctx->bsc_addr);
+
+	/* Send reset to the remote BSC */
+	ss7 = osmo_ss7_instance_find(gsm_network->a.cs7_instance);
+	OSMO_ASSERT(ss7);
+	LOGP(DMSC, LOGL_NOTICE, "Sending RESET to BSC %s\n", osmo_sccp_addr_name(ss7, &bsc_ctx->bsc_addr));
+	msg = gsm0808_create_reset();
+	osmo_sccp_tx_unitdata_msg(bsc_ctx->sccp_user, &bsc_ctx->msc_addr,
+				  &bsc_ctx->bsc_addr, msg);
+}
+
+/* Add a new BSC connection to our internal list with known BSCs */
+static void add_bsc(const struct osmo_sccp_addr *msc_addr, const struct osmo_sccp_addr *bsc_addr,
+		    struct osmo_sccp_user *scu)
+{
+	struct bsc_context *bsc_ctx;
+	struct osmo_ss7_instance *ss7;
+
+	OSMO_ASSERT(bsc_addr);
+	OSMO_ASSERT(msc_addr);
+	OSMO_ASSERT(scu);
+
+	/* Check if we already know this BSC, if yes, skip adding it. */
+	if (get_reset_ctx_by_sccp_addr(bsc_addr))
+		return;
+
+	ss7 = osmo_ss7_instance_find(gsm_network->a.cs7_instance);
+	OSMO_ASSERT(ss7);
+	LOGP(DMSC, LOGL_NOTICE, "Adding new BSC connection for BSC %s...\n", osmo_sccp_addr_name(ss7, bsc_addr));
+
+	/* Generate and fill up a new bsc context */
+	bsc_ctx = talloc_zero(gsm_network, struct bsc_context);
+	OSMO_ASSERT(bsc_ctx);
+	memcpy(&bsc_ctx->bsc_addr, bsc_addr, sizeof(*bsc_addr));
+	memcpy(&bsc_ctx->msc_addr, msc_addr, sizeof(*msc_addr));
+	bsc_ctx->sccp_user = scu;
+	llist_add_tail(&bsc_ctx->list, &gsm_network->a.bscs);
+
+	/* Start reset procedure to make the new connection active */
+	bsc_ctx->reset = a_reset_alloc(bsc_ctx, osmo_sccp_addr_name(ss7, bsc_addr), a_reset_cb, bsc_ctx);
+}
+
+/* Callback function, called by the SSCP stack when data arrives */
+static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
+{
+	struct osmo_sccp_user *scu = _scu;
+	struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *)oph;
+	int rc = 0;
+	struct a_conn_info a_conn_info;
+	memset(&a_conn_info, 0, sizeof(a_conn_info));
+	a_conn_info.network = gsm_network;
+	a_conn_info.reset = NULL;
+
+	switch (OSMO_PRIM_HDR(&scu_prim->oph)) {
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
+		/* Handle inbound connection indication */
+		add_bsc(&scu_prim->u.connect.called_addr, &scu_prim->u.connect.calling_addr, scu);
+		a_conn_info.conn_id = scu_prim->u.connect.conn_id;
+		a_conn_info.msc_addr = &scu_prim->u.connect.called_addr;
+		a_conn_info.bsc_addr = &scu_prim->u.connect.calling_addr;
+		a_conn_info.reset = get_reset_ctx_by_sccp_addr(&scu_prim->u.unitdata.calling_addr);
+
+		if (a_reset_conn_ready(a_conn_info.reset) == false) {
+			rc = osmo_sccp_tx_disconn(scu, a_conn_info.conn_id, a_conn_info.msc_addr,
+						  SCCP_RETURN_CAUSE_UNQUALIFIED);
+			break;
+		}
+
+		osmo_sccp_tx_conn_resp(scu, scu_prim->u.connect.conn_id, &scu_prim->u.connect.called_addr, NULL, 0);
+		if (msgb_l2len(oph->msg) > 0) {
+			LOGP(DMSC, LOGL_DEBUG, "N-CONNECT.ind(%u, %s)\n",
+			     scu_prim->u.connect.conn_id, osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+			rc = sccp_rx_dt(scu, &a_conn_info, oph->msg);
+		} else
+			LOGP(DMSC, LOGL_DEBUG, "N-CONNECT.ind(%u)\n", scu_prim->u.connect.conn_id);
+
+		record_bsc_con(scu, scu_prim->u.connect.conn_id);
+		break;
+
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
+		/* Handle incoming connection oriented data */
+		a_conn_info.conn_id = scu_prim->u.data.conn_id;
+		LOGP(DMSC, LOGL_DEBUG, "N-DATA.ind(%u, %s)\n",
+		     scu_prim->u.data.conn_id, osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+		sccp_rx_dt(scu, &a_conn_info, oph->msg);
+		break;
+
+	case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
+		/* Handle inbound UNITDATA */
+		add_bsc(&scu_prim->u.unitdata.called_addr, &scu_prim->u.unitdata.calling_addr, scu);
+		a_conn_info.msc_addr = &scu_prim->u.unitdata.called_addr;
+		a_conn_info.bsc_addr = &scu_prim->u.unitdata.calling_addr;
+		a_conn_info.reset = get_reset_ctx_by_sccp_addr(&scu_prim->u.unitdata.calling_addr);
+		DEBUGP(DMSC, "N-UNITDATA.ind(%s)\n", osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+		sccp_rx_udt(scu, &a_conn_info, oph->msg);
+		break;
+
+	default:
+		LOGP(DMSC, LOGL_ERROR, "Unhandled SIGTRAN primitive: %u:%u\n", oph->primitive, oph->operation);
+		break;
+	}
+
+	return rc;
+}
+
+/* Clear all subscriber connections on a specified BSC */
+void a_clear_all(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *bsc_addr)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_subscriber_connection *conn_temp;
+	struct gsm_network *network = gsm_network;
+
+	OSMO_ASSERT(scu);
+	OSMO_ASSERT(bsc_addr);
+
+	llist_for_each_entry_safe(conn, conn_temp, &network->subscr_conns, entry) {
+		/* Clear only A connections and connections that actually
+		 * belong to the specified BSC */
+		if (conn->via_ran == RAN_GERAN_A && memcmp(bsc_addr, &conn->a.bsc_addr, sizeof(conn->a.bsc_addr)) == 0) {
+			LOGP(DMSC, LOGL_NOTICE, "Dropping orphaned subscriber connection (conn_id %i)\n",
+			     conn->a.conn_id);
+			msc_clear_request(conn, GSM48_CC_CAUSE_SWITCH_CONG);
+
+			/* If there is still an SCCP connection active, remove it now */
+			if (check_connection_active(conn->a.conn_id)) {
+				osmo_sccp_tx_disconn(scu, conn->a.conn_id, bsc_addr,
+						     SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+				a_delete_bsc_con(conn->a.conn_id);
+			}
+		}
+	}
+}
+
+/* Initalize A interface connection between to MSC and BSC */
+int a_init(struct osmo_sccp_instance *sccp, struct gsm_network *network)
+{
+	OSMO_ASSERT(sccp);
+	OSMO_ASSERT(network);
+
+	/* FIXME: Remove hardcoded parameters, use parameters in parameter list */
+	LOGP(DMSC, LOGL_NOTICE, "Initalizing SCCP connection to stp...\n");
+
+	/* Set GSM network variable, there can only be
+	 * one network by design */
+	if (gsm_network != NULL) {
+		OSMO_ASSERT(gsm_network == network);
+	} else
+		gsm_network = network;
+
+	/* SCCP Protocol stack */
+	osmo_sccp_user_bind(sccp, "OsmoMSC-A", sccp_sap_up, SCCP_SSN_BSSAP);
+
+	return 0;
 }
diff --git a/src/libmsc/a_iface_bssap.c b/src/libmsc/a_iface_bssap.c
new file mode 100644
index 0000000..561ccde
--- /dev/null
+++ b/src/libmsc/a_iface_bssap.c
@@ -0,0 +1,717 @@
+/* (C) 2017 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+#include <osmocom/sccp/sccp_types.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/a_iface_bssap.h>
+#include <openbsc/a_iface.h>
+#include <openbsc/iu.h>
+#include <openbsc/osmo_msc.h>
+#include <osmocom/core/byteswap.h>
+#include <openbsc/a_reset.h>
+
+#define IP_V4_ADDR_LEN 4
+
+/*
+ * Helper functions to lookup and allocate subscribers
+ */
+
+/* Allocate a new subscriber connection */
+static struct gsm_subscriber_connection *subscr_conn_allocate_a(const struct a_conn_info *a_conn_info,
+								struct gsm_network *network,
+								uint16_t lac, struct osmo_sccp_user *scu, int conn_id)
+{
+	struct gsm_subscriber_connection *conn;
+
+	LOGP(DMSC, LOGL_NOTICE, "Allocating A-Interface subscriber conn: lac %i, conn_id %i\n", lac, conn_id);
+
+	conn = talloc_zero(network, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
+
+	conn->network = network;
+	conn->via_ran = RAN_GERAN_A;
+	conn->lac = lac;
+
+	conn->a.conn_id = conn_id;
+	conn->a.scu = scu;
+
+	/* Also backup the calling address of the BSC, this allows us to
+	 * identify later which BSC is responsible for this subscriber connection */
+	memcpy(&conn->a.bsc_addr, a_conn_info->bsc_addr, sizeof(conn->a.bsc_addr));
+
+	llist_add_tail(&conn->entry, &network->subscr_conns);
+	LOGP(DMSC, LOGL_NOTICE, "A-Interface subscriber connection successfully allocated!\n");
+	return conn;
+}
+
+/* Return an existing A subscriber connection record for the given
+ * connection IDs, or return NULL if not found. */
+static struct gsm_subscriber_connection *subscr_conn_lookup_a(const struct gsm_network *network, int conn_id)
+{
+	struct gsm_subscriber_connection *conn;
+
+	OSMO_ASSERT(network);
+
+	DEBUGP(DMSC, "Looking for A subscriber: conn_id %i\n", conn_id);
+
+	/* FIXME: log_subscribers() is defined in iucs.c as static inline, if
+	 * maybe this function should be public to reach it from here? */
+	/* log_subscribers(network); */
+
+	llist_for_each_entry(conn, &network->subscr_conns, entry) {
+		if (conn->via_ran == RAN_GERAN_A && conn->a.conn_id == conn_id) {
+			DEBUGP(DIUCS, "Found A subscriber for conn_id %i\n", conn_id);
+			return conn;
+		}
+	}
+	DEBUGP(DMSC, "No A subscriber found for conn_id %i\n", conn_id);
+	return NULL;
+}
+
+/*
+ * BSSMAP handling for UNITDATA
+ */
+
+/* Endpoint to handle BSSMAP reset */
+static void bssmap_rx_reset(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct osmo_ss7_instance *ss7;
+
+	ss7 = osmo_ss7_instance_find(network->a.cs7_instance);
+	OSMO_ASSERT(ss7);
+
+	LOGP(DMSC, LOGL_NOTICE, "Rx RESET from BSC %s, sending RESET ACK\n",
+	     osmo_sccp_addr_name(ss7, a_conn_info->bsc_addr));
+	osmo_sccp_tx_unitdata_msg(scu, a_conn_info->msc_addr, a_conn_info->bsc_addr, gsm0808_create_reset_ack());
+
+	/* Make sure all orphand subscriber connections will be cleard */
+	a_clear_all(scu, a_conn_info->bsc_addr);
+
+	msgb_free(msg);
+}
+
+/* Endpoint to handle BSSMAP reset acknowlegement */
+static void bssmap_rx_reset_ack(const struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info,
+				struct msgb *msg)
+{
+
+	struct gsm_network *network = a_conn_info->network;
+	struct osmo_ss7_instance *ss7;
+
+	ss7 = osmo_ss7_instance_find(network->a.cs7_instance);
+	OSMO_ASSERT(ss7);
+
+	if (a_conn_info->reset == NULL) {
+		LOGP(DMSC, LOGL_ERROR, "Received RESET ACK from an unknown BSC %s, ignoring...\n",
+		     osmo_sccp_addr_name(ss7, a_conn_info->bsc_addr));
+		goto fail;
+	}
+
+	LOGP(DMSC, LOGL_NOTICE, "Received RESET ACK from BSC %s\n", osmo_sccp_addr_name(ss7, a_conn_info->bsc_addr));
+
+	/* Confirm that we managed to get the reset ack message
+	 * towards the connection reset logic */
+	a_reset_ack_confirm(a_conn_info->reset);
+
+fail:
+	msgb_free(msg);
+}
+
+/* Handle UNITDATA BSSMAP messages */
+static void bssmap_rcvmsg_udt(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	/* Note: When in the MSC role, RESET ACK is the only valid message that
+	 * can be received via UNITDATA */
+
+	if (msgb_l3len(msg) < 1) {
+		LOGP(DMSC, LOGL_NOTICE, "Error: No data received -- discarding message!\n");
+		return;
+	}
+
+	LOGP(DMSC, LOGL_NOTICE, "Rx BSC UDT BSSMAP %s\n", gsm0808_bssmap_name(msg->l3h[0]));
+
+	switch (msg->l3h[0]) {
+	case BSS_MAP_MSG_RESET:
+		bssmap_rx_reset(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_RESET_ACKNOWLEDGE:
+		bssmap_rx_reset_ack(scu, a_conn_info, msg);
+		break;
+	default:
+		LOGP(DMSC, LOGL_NOTICE, "Unimplemented message format: %s -- message discarded!\n",
+		     gsm0808_bssmap_name(msg->l3h[0]));
+		msgb_free(msg);
+	}
+}
+
+/* Receive incoming connection less data messages via sccp */
+void sccp_rx_udt(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	/* Note: The only valid message type that can be received
+	 * via UNITDATA are BSS Management messages */
+	struct bssmap_header *bs;
+
+	OSMO_ASSERT(scu);
+	OSMO_ASSERT(a_conn_info);
+	OSMO_ASSERT(msg);
+
+	LOGP(DMSC, LOGL_NOTICE, "Rx BSC UDT: %s\n", osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+	if (msgb_l2len(msg) < sizeof(*bs)) {
+		LOGP(DMSC, LOGL_ERROR, "Error: Header is too short -- discarding message!\n");
+		msgb_free(msg);
+		return;
+	}
+
+	bs = (struct bssmap_header *)msgb_l2(msg);
+	if (bs->length < msgb_l2len(msg) - sizeof(*bs)) {
+		LOGP(DMSC, LOGL_ERROR, "Error: Message is too short -- discarding message!\n");
+		msgb_free(msg);
+		return;
+	}
+
+	switch (bs->type) {
+	case BSSAP_MSG_BSS_MANAGEMENT:
+		msg->l3h = &msg->l2h[sizeof(struct bssmap_header)];
+		bssmap_rcvmsg_udt(scu, a_conn_info, msg);
+		break;
+	default:
+		LOGP(DMSC, LOGL_ERROR,
+		     "Error: Unimplemented message type: %s -- message discarded!\n", gsm0808_bssmap_name(bs->type));
+		msgb_free(msg);
+	}
+}
+
+/*
+ * BSSMAP handling for connection oriented data
+ */
+
+/* Endpoint to handle BSSMAP clear request */
+static int bssmap_rx_clear_rqst(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct tlv_parsed tp;
+	int rc;
+	struct msgb *msg_resp;
+	uint8_t cause;
+	struct gsm_subscriber_connection *conn;
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC requested to clear connection (conn_id=%i)\n", a_conn_info->conn_id);
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CAUSE)) {
+		LOGP(DMSC, LOGL_ERROR, "Cause code is missing -- discarding message!\n");
+		goto fail;
+	}
+	cause = TLVP_VAL(&tp, GSM0808_IE_CAUSE)[0];
+
+	/* Respond with clear command */
+	msg_resp = gsm0808_create_clear_command(GSM0808_CAUSE_CALL_CONTROL);
+	rc = osmo_sccp_tx_data_msg(scu, a_conn_info->conn_id, msg_resp);
+
+	/* If possible, inform the MSC about the clear request */
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn)
+		goto fail;
+	msc_clear_request(conn, cause);
+
+	msgb_free(msg);
+	return rc;
+
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Endpoint to handle BSSMAP clear complete */
+static int bssmap_rx_clear_complete(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	int rc;
+
+	LOGP(DMSC, LOGL_NOTICE, "Releasing connection (conn_id=%i)\n", a_conn_info->conn_id);
+	rc = osmo_sccp_tx_disconn(scu, a_conn_info->conn_id,
+				  a_conn_info->msc_addr, SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+
+	/* Remove the record from the list with active connections. */
+	a_delete_bsc_con(a_conn_info->conn_id);
+
+	msgb_free(msg);
+	return rc;
+}
+
+/* Endpoint to handle layer 3 complete messages */
+static int bssmap_rx_l3_compl(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	struct tlv_parsed tp;
+	struct {
+		uint8_t ident;
+		struct gsm48_loc_area_id lai;
+		uint16_t ci;
+	} __attribute__ ((packed)) lai_ci;
+	uint16_t mcc;
+	uint16_t mnc;
+	uint16_t lac;
+	uint8_t data_length;
+	const uint8_t *data;
+	int rc;
+
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC has completed layer 3 connection (conn_id=%i)\n", a_conn_info->conn_id);
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandatory CELL IDENTIFIER not present -- discarding message!\n");
+		goto fail;
+	}
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandatory LAYER 3 INFORMATION not present -- discarding message!\n");
+		goto fail;
+	}
+
+	/* Parse Cell ID element */
+	/* FIXME: Encapsulate this in a parser/generator function inside
+	 * libosmocore, add support for all specified cell identification
+	 * discriminators (see 3GPP ts 3.2.2.17 Cell Identifier) */
+	data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER);
+	data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER);
+	if (sizeof(lai_ci) != data_length) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "Unable to parse element CELL IDENTIFIER (wrong field length) -- discarding message!\n");
+		goto fail;
+	}
+	memcpy(&lai_ci, data, sizeof(lai_ci));
+	if (lai_ci.ident != CELL_IDENT_WHOLE_GLOBAL) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "Unable to parse element CELL IDENTIFIER (wrong cell identification discriminator) -- discarding message!\n");
+		goto fail;
+	}
+	if (gsm48_decode_lai(&lai_ci.lai, &mcc, &mnc, &lac) != 0) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "Unable to parse element CELL IDENTIFIER (lai decoding failed) -- discarding message!\n");
+		goto fail;
+	}
+
+	/* Parse Layer 3 Information element */
+	/* FIXME: This is probably to hackish, compiler also complains "assignment discards ‘const’ qualifier..." */
+	msg->l3h = TLVP_VAL(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+	msg->tail = msg->l3h + TLVP_LEN(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+
+	/* Create new subscriber context */
+	conn = subscr_conn_allocate_a(a_conn_info, network, lac, scu, a_conn_info->conn_id);
+
+	/* Handover location update to the MSC code */
+	/* msc_compl_l3() takes ownership of dtap_msg
+	 * message buffer */
+	rc = msc_compl_l3(conn, msg, 0);
+	if (rc == MSC_CONN_ACCEPT) {
+		LOGP(DMSC, LOGL_NOTICE, "User has been accepted by MSC.\n");
+		return 0;
+	} else if (rc == MSC_CONN_REJECT)
+		LOGP(DMSC, LOGL_NOTICE, "User has been rejected by MSC.\n");
+	else
+		LOGP(DMSC, LOGL_NOTICE, "User has been rejected by MSC (unknown error)\n");
+
+	return -EINVAL;
+
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Endpoint to handle BSSMAP classmark update */
+static int bssmap_rx_classmark_upd(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+	struct tlv_parsed tp;
+	const uint8_t *cm2 = NULL;
+	const uint8_t *cm3 = NULL;
+	uint8_t cm2_len = 0;
+	uint8_t cm3_len = 0;
+
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn)
+		goto fail;
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC sends clasmark update (conn_id=%i)\n", conn->a.conn_id);
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CLASSMARK_INFORMATION_T2)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandatory Classmark Information Type 2 not present -- discarding message!\n");
+		goto fail;
+	}
+
+	cm2 = TLVP_VAL(&tp, GSM0808_IE_CLASSMARK_INFORMATION_T2);
+	cm2_len = TLVP_LEN(&tp, GSM0808_IE_CLASSMARK_INFORMATION_T2);
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_CLASSMARK_INFORMATION_T3)) {
+		cm3 = TLVP_VAL(&tp, GSM0808_IE_CLASSMARK_INFORMATION_T3);
+		cm3_len = TLVP_LEN(&tp, GSM0808_IE_CLASSMARK_INFORMATION_T3);
+	}
+
+	/* Inform MSC about the classmark change */
+	msc_classmark_chg(conn, cm2, cm2_len, cm3, cm3_len);
+
+	msgb_free(msg);
+	return 0;
+
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Endpoint to handle BSSMAP cipher mode complete */
+static int bssmap_rx_ciph_compl(const struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info,
+				struct msgb *msg)
+{
+	/* FIXME: The field GSM0808_IE_LAYER_3_MESSAGE_CONTENTS is optional by
+	 * means of the specification. So there can be messages without L3 info.
+	 * In this case, the code will crash becrause msc_cipher_mode_compl()
+	 * is not able to deal with msg = NULL and apperently
+	 * msc_cipher_mode_compl() was never meant to be used without L3 data.
+	 * This needs to be discussed further! */
+
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+	struct tlv_parsed tp;
+	uint8_t alg_id = 1;
+
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn)
+		goto fail;
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC sends cipher mode complete (conn_id=%i)\n", conn->a.conn_id);
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_CHOSEN_ENCR_ALG)) {
+		alg_id = TLVP_VAL(&tp, GSM0808_IE_CHOSEN_ENCR_ALG)[0] - 1;
+	}
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_MESSAGE_CONTENTS)) {
+		msg->l3h = TLVP_VAL(&tp, GSM0808_IE_LAYER_3_MESSAGE_CONTENTS);
+		msg->tail = msg->l3h + TLVP_LEN(&tp, GSM0808_IE_LAYER_3_MESSAGE_CONTENTS);
+	} else {
+		msgb_free(msg);
+		msg = NULL;
+	}
+
+	/* Hand over cipher mode complete message to the MSC,
+	 * msc_cipher_mode_compl() takes ownership for msg */
+	msc_cipher_mode_compl(conn, msg, alg_id);
+
+	return 0;
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Endpoint to handle BSSMAP cipher mode reject */
+static int bssmap_rx_ciph_rej(const struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+	struct tlv_parsed tp;
+	uint8_t cause;
+
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn)
+		goto fail;
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC sends cipher mode reject (conn_id=%i)\n", conn->a.conn_id);
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, BSS_MAP_MSG_CIPHER_MODE_REJECT)) {
+		LOGP(DMSC, LOGL_ERROR, "Cause code is missing -- discarding message!\n");
+		goto fail;
+	}
+
+	cause = TLVP_VAL(&tp, BSS_MAP_MSG_CIPHER_MODE_REJECT)[0];
+	LOGP(DMSC, LOGL_NOTICE, "Cipher mode rejection cause: %i\n", cause);
+
+	/* FIXME: Can we do something meaningful here? e.g. report to the
+	 * msc code somehow that the cipher mode command has failed. */
+
+	msgb_free(msg);
+	return 0;
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Endpoint to handle BSSMAP assignment failure */
+static int bssmap_rx_ass_fail(const struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+	struct tlv_parsed tp;
+	uint8_t cause;
+	uint8_t *rr_cause_ptr = NULL;
+	uint8_t rr_cause;
+
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn)
+		goto fail;
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC sends assignment failure message (conn_id=%i)\n", conn->a.conn_id);
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CAUSE)) {
+		LOGP(DMSC, LOGL_ERROR, "Cause code is missing -- discarding message!\n");
+		goto fail;
+	}
+	cause = TLVP_VAL(&tp, GSM0808_IE_CAUSE)[0];
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_RR_CAUSE)) {
+		rr_cause = TLVP_VAL(&tp, GSM0808_IE_RR_CAUSE)[0];
+		rr_cause_ptr = &rr_cause;
+	}
+
+	/* FIXME: In AoIP, the Assignment failure will carry also an optional
+	 * Codec List (BSS Supported) element. It has to be discussed if we
+	 * can ignore this element. If not, The msc_assign_fail() function
+	 * call has to change. However msc_assign_fail() does nothing in the
+	 * end. So probably we can just leave it as it is. Even for AoIP */
+
+	/* Inform the MSC about the assignment failure event */
+	msc_assign_fail(conn, cause, rr_cause_ptr);
+
+	msgb_free(msg);
+	return 0;
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Endpoint to handle sapi "n" reject */
+static int bssmap_rx_sapi_n_rej(const struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info,
+				struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+	struct tlv_parsed tp;
+	uint8_t dlci;
+
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn)
+		goto fail;
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC sends sapi \"n\" reject message (conn_id=%i)\n", conn->a.conn_id);
+
+	/* Note: The MSC code seems not to care about the cause code, but by
+	 * the specification it is mandatory, so we check its presence. See
+	 * also 3GPP TS 48.008 3.2.1.34 SAPI "n" REJECT */
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CAUSE)) {
+		LOGP(DMSC, LOGL_ERROR, "Cause code is missing -- discarding message!\n");
+		goto fail;
+	}
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_DLCI)) {
+		LOGP(DMSC, LOGL_ERROR, "DLCI is missing -- discarding message!\n");
+		goto fail;
+	}
+	dlci = TLVP_VAL(&tp, GSM0808_IE_DLCI)[0];
+
+	/* Inform the MSC about the sapi "n" reject event */
+	msc_sapi_n_reject(conn, dlci);
+
+	msgb_free(msg);
+	return 0;
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Endpoint to handle assignment complete */
+static int bssmap_rx_ass_compl(const struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info,
+			       struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+	struct mgcpgw_client *mgcp;
+	struct tlv_parsed tp;
+	struct sockaddr_storage rtp_addr;
+	struct sockaddr_in *rtp_addr_in;
+	int rc;
+
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn)
+		goto fail;
+
+	mgcp = conn->network->mgcpgw.client;
+	OSMO_ASSERT(mgcp);
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC sends assignment complete message (conn_id=%i)\n", conn->a.conn_id);
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
+		LOGP(DMSC, LOGL_ERROR, "AoIP transport identifier missing -- discarding message!\n");
+		goto fail;
+	}
+
+	/* 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");
+		goto fail;
+	}
+
+	/* use address / port supplied with the AoIP
+	 * transport address element */
+	if (rtp_addr.ss_family == AF_INET) {
+		rtp_addr_in = (struct sockaddr_in *)&rtp_addr;
+		conn->rtp.port_subscr = osmo_ntohs(rtp_addr_in->sin_port);
+		/* FIXME: We also get the IP-Address of the remote (e.g. BTS)
+		 * end with the response. Currently we just ignore that address.
+		 * Instead we expect that our local MGCP gateway and the code
+		 * controlling it, magically knows the IP of the remote end. */
+	} else {
+		LOGP(DMSC, LOGL_ERROR, "Unsopported addressing scheme. (supports only IPV4)\n");
+		goto fail;
+	}
+
+	/* FIXME: Seems to be related to authentication or,
+	   encryption. Is this really in the right place? */
+	msc_rx_sec_mode_compl(conn);
+
+	msgb_free(msg);
+	return 0;
+fail:
+	msgb_free(msg);
+	return -EINVAL;
+}
+
+/* Handle incoming connection oriented BSSMAP messages */
+static int rx_bssmap(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	if (msgb_l3len(msg) < 1) {
+		LOGP(DMSC, LOGL_NOTICE, "Error: No data received -- discarding message!\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	LOGP(DMSC, LOGL_NOTICE, "Rx MSC DT1 BSSMAP %s\n", gsm0808_bssmap_name(msg->l3h[0]));
+
+	switch (msg->l3h[0]) {
+	case BSS_MAP_MSG_CLEAR_RQST:
+		return bssmap_rx_clear_rqst(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_CLEAR_COMPLETE:
+		return bssmap_rx_clear_complete(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_COMPLETE_LAYER_3:
+		return bssmap_rx_l3_compl(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_CLASSMARK_UPDATE:
+		return bssmap_rx_classmark_upd(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_CIPHER_MODE_COMPLETE:
+		return bssmap_rx_ciph_compl(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_CIPHER_MODE_REJECT:
+		return bssmap_rx_ciph_rej(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_ASSIGMENT_FAILURE:
+		return bssmap_rx_ass_fail(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_SAPI_N_REJECT:
+		return bssmap_rx_sapi_n_rej(scu, a_conn_info, msg);
+		break;
+	case BSS_MAP_MSG_ASSIGMENT_COMPLETE:
+		return bssmap_rx_ass_compl(scu, a_conn_info, msg);
+		break;
+	default:
+		LOGP(DMSC, LOGL_ERROR, "Unimplemented msg type: %s\n", gsm0808_bssmap_name(msg->l3h[0]));
+		msgb_free(msg);
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+/* Endpoint to handle regular BSSAP DTAP messages */
+static int rx_dtap(const struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	struct gsm_network *network = a_conn_info->network;
+	struct gsm_subscriber_connection *conn;
+
+	conn = subscr_conn_lookup_a(network, a_conn_info->conn_id);
+	if (!conn) {
+		msgb_free(msg);
+		return -EINVAL;
+	}
+
+	LOGP(DMSC, LOGL_NOTICE, "BSC sends layer 3 dtap (conn_id=%i)\n", conn->a.conn_id);
+
+	/* msc_dtap expects the dtap payload in l3h */
+	msg->l3h = msg->l2h + 3;
+
+	/* Forward dtap payload into the msc,
+	 * msc_dtap() takes ownership for msg */
+	msc_dtap(conn, conn->a.conn_id, msg);
+
+	return 0;
+}
+
+/* Handle incoming connection oriented messages */
+int sccp_rx_dt(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg)
+{
+	OSMO_ASSERT(scu);
+	OSMO_ASSERT(a_conn_info);
+	OSMO_ASSERT(msg);
+
+	LOGP(DMSC, LOGL_NOTICE, "Rx BSC DT: %s\n", osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+	if (msgb_l2len(msg) < sizeof(struct bssmap_header)) {
+		LOGP(DMSC, LOGL_NOTICE, "The header is too short -- discarding message!\n");
+		msgb_free(msg);
+	}
+
+	switch (msg->l2h[0]) {
+	case BSSAP_MSG_BSS_MANAGEMENT:
+		msg->l3h = &msg->l2h[sizeof(struct bssmap_header)];
+		return rx_bssmap(scu, a_conn_info, msg);
+		break;
+	case BSSAP_MSG_DTAP:
+		return rx_dtap(scu, a_conn_info, msg);
+		break;
+	default:
+		LOGP(DMSC, LOGL_ERROR, "Unimplemented BSSAP msg type: %s\n", gsm0808_bssap_name(msg->l2h[0]));
+		msgb_free(msg);
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index 28cba5b..b6746a5 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -79,6 +79,8 @@
 #include <openbsc/iu.h>
 #endif
 
+#include <openbsc/a_iface.h>
+
 #include <assert.h>
 
 
@@ -1267,6 +1269,8 @@
 	struct msgb *msg;
 	unsigned char *data;
 
+	DEBUGP(DMNCC, "transmit message %s\n", get_mncc_name(msg_type));
+
 #if BEFORE_MSCSPLIT
 	/* Re-enable this log output once we can obtain this information via
 	 * A-interface, see OS#2391. */
@@ -1324,6 +1328,9 @@
 {
 	gsm48_stop_cc_timer(trans);
 
+	/* Make sure call also gets released on the mgcp side */
+	msc_call_release(trans);
+
 	/* send release to L4, if callref still exists */
 	if (trans->callref) {
 		/* Ressource unavailable */
@@ -1549,6 +1556,12 @@
 		setup.fields |= MNCC_F_BEARER_CAP;
 		gsm48_decode_bearer_cap(&setup.bearer_cap,
 				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+
+		/* Create a copy of the bearer capability
+		 * in the transaction struct, so we can use
+		 * this information later */
+		memcpy(&trans->bearer_cap,&setup.bearer_cap,
+		       sizeof(trans->bearer_cap));
 	}
 	/* facility */
 	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
@@ -1682,6 +1695,7 @@
 	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
 	struct tlv_parsed tp;
 	struct gsm_mncc call_conf;
+	int rc;
 
 	gsm48_stop_cc_timer(trans);
 	gsm48_start_cc_timer(trans, 0x310, GSM48_T310);
@@ -1702,6 +1716,12 @@
 		call_conf.fields |= MNCC_F_BEARER_CAP;
 		gsm48_decode_bearer_cap(&call_conf.bearer_cap,
 				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+
+		/* Create a copy of the bearer capability
+		 * in the transaction struct, so we can use
+		 * this information later */
+		memcpy(&trans->bearer_cap,&call_conf.bearer_cap,
+		       sizeof(trans->bearer_cap));
 	}
 	/* cause */
 	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
@@ -1721,7 +1741,18 @@
 
 	new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
 
-	msc_call_assignment(trans);
+	/* Assign call (if not done yet) */
+	if (trans->assignment_done == false) {
+		rc = msc_call_assignment(trans);
+		trans->assignment_done = true;
+	}
+	else
+		rc = 0;
+
+	/* don't continue, if there were problems with
+	 * the call assignment. */
+	if (rc)
+		return rc;
 
 	return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND,
 			    &call_conf);
@@ -1752,7 +1783,15 @@
 	if (rc)
 		return rc;
 
-	return msc_call_assignment(trans);
+	/* Assign call (if not done yet) */
+	if (trans->assignment_done == false) {
+		rc = msc_call_assignment(trans);
+		trans->assignment_done = true;
+	}
+	else
+		rc = 0;
+
+	return rc;
 }
 
 static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
@@ -2398,6 +2437,12 @@
 		modify.fields |= MNCC_F_BEARER_CAP;
 		gsm48_decode_bearer_cap(&modify.bearer_cap,
 				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+
+		/* Create a copy of the bearer capability
+		 * in the transaction struct, so we can use
+		 * this information later */
+		memcpy(&trans->bearer_cap,&modify.bearer_cap,
+		       sizeof(trans->bearer_cap));
 	}
 
 	new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
@@ -2440,6 +2485,12 @@
 		modify.fields |= MNCC_F_BEARER_CAP;
 		gsm48_decode_bearer_cap(&modify.bearer_cap,
 				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+
+		/* Create a copy of the bearer capability
+		 * in the transaction struct, so we can use
+		 * this information later */
+		memcpy(&trans->bearer_cap,&modify.bearer_cap,
+		       sizeof(trans->bearer_cap));
 	}
 
 	new_cc_state(trans, GSM_CSTATE_ACTIVE);
@@ -2480,6 +2531,12 @@
 		modify.fields |= GSM48_IE_BEARER_CAP;
 		gsm48_decode_bearer_cap(&modify.bearer_cap,
 				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+
+		/* Create a copy of the bearer capability
+		 * in the transaction struct, so we can use
+		 * this information later */
+		memcpy(&trans->bearer_cap,&modify.bearer_cap,
+		       sizeof(trans->bearer_cap));
 	}
 	/* cause */
 	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
@@ -2582,6 +2639,139 @@
 	return mncc_recvmsg(trans->net, trans, MNCC_USERINFO_IND, &user);
 }
 
+static void mncc_recv_rtp(struct gsm_network *net, uint32_t callref,
+		int cmd, uint32_t addr, uint16_t port, uint32_t payload_type,
+		uint32_t payload_msg_type)
+{
+	uint8_t data[sizeof(struct gsm_mncc)];
+	struct gsm_mncc_rtp *rtp;
+
+	memset(&data, 0, sizeof(data));
+	rtp = (struct gsm_mncc_rtp *) &data[0];
+
+	rtp->callref = callref;
+	rtp->msg_type = cmd;
+	rtp->ip = addr;
+	rtp->port = port;
+	rtp->payload_type = payload_type;
+	rtp->payload_msg_type = payload_msg_type;
+	mncc_recvmsg(net, NULL, cmd, (struct gsm_mncc *)data);
+}
+
+static void mncc_recv_rtp_sock(struct gsm_network *net, struct gsm_trans *trans, int cmd)
+{
+	int msg_type;
+
+	/* FIXME This has to be set to some meaningful value.
+	 * Possible options are:
+	 * GSM_TCHF_FRAME, GSM_TCHF_FRAME_EFR,
+	 * GSM_TCHH_FRAME, GSM_TCH_FRAME_AMR
+	 * (0 if unknown) */
+	msg_type = GSM_TCHF_FRAME;
+
+	uint32_t addr = mgcpgw_client_remote_addr_n(net->mgcpgw.client);
+	uint16_t port = trans->conn->rtp.port_cn;
+
+	/* FIXME: This has to be set to some meaningful value,
+	 * before the MSC-Split, this value was pulled from
+	 * lchan->abis_ip.rtp_payload */
+	uint32_t payload_type = 0;
+
+	return mncc_recv_rtp(net, trans->callref, cmd,
+			addr,
+			port,
+		        payload_type,
+			msg_type);
+}
+
+static void mncc_recv_rtp_err(struct gsm_network *net, uint32_t callref, int cmd)
+{
+	return mncc_recv_rtp(net, callref, cmd, 0, 0, 0, 0);
+}
+
+static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
+{
+	struct gsm_trans *trans;
+	int rc;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, callref);
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP create for non-existing trans\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return -EIO;
+	}
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
+	if (!trans->conn) {
+		LOGP(DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return 0;
+	}
+
+	trans->conn->mncc_rtp_bridge = 1;
+
+	/* When we call msc_call_assignment() we will trigger, depending
+	 * on the RAN type the call assignment on the A or Iu interface.
+	 * msc_call_assignment() also takes care about sending the CRCX
+	 * command to the MGCP-GW. The CRCX will return the port number,
+	 * where the PBX (e.g. Asterisk) will send its RTP stream to. We
+	 * have to return this port number back to the MNCC by sending
+	 * it back with the TCH_RTP_CREATE message. To make sure that
+	 * this message is sent AFTER the response to CRCX from the
+	 * MGCP-GW has arrived, we need will instruct msc_call_assignment()
+	 * to take care of this by setting trans->tch_rtp_create to true.
+	 * This will make sure that gsm48_tch_rtp_create() (below) is
+	 * called as soon as the local port number has become known. */
+	trans->tch_rtp_create = true;
+
+	/* Assign call (if not done yet) */
+	if (trans->assignment_done == false) {
+		rc = msc_call_assignment(trans);
+		trans->assignment_done = true;
+	}
+	else
+		rc = 0;
+
+	return rc;
+}
+
+/* Trigger TCH_RTP_CREATE acknowledgement */
+int gsm48_tch_rtp_create(struct gsm_trans *trans)
+{
+	/* This function is called as soon as the port, on which the
+	 * mgcp-gw expects the incoming RTP stream from the remote
+	 * end (e.g. Asterisk) is known. */
+
+	struct gsm_subscriber_connection *conn = trans->conn;
+	struct gsm_network *network = conn->network;
+
+	mncc_recv_rtp_sock(network, trans, MNCC_RTP_CREATE);
+	return 0;
+}
+
+static int tch_rtp_connect(struct gsm_network *net, void *arg)
+{
+	struct gsm_trans *trans;
+	struct gsm_mncc_rtp *rtp = arg;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, rtp->callref);
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect for non-existing trans\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+		return -EIO;
+	}
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
+	if (!trans->conn) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+		return 0;
+	}
+
+	msc_call_connect(trans,rtp->port,rtp->ip);
+	return 0;
+}
+
 static struct downstate {
 	uint32_t	states;
 	int		type;
@@ -2657,11 +2847,16 @@
 		if (rc < 0)
 			disconnect_bridge(net, arg, -rc);
 		return rc;
+	case MNCC_RTP_CREATE:
+		return tch_rtp_create(net, data->callref);
+	case MNCC_RTP_CONNECT:
+		return tch_rtp_connect(net, arg);
+	case MNCC_RTP_FREE:
+		/* unused right now */
+		return -EIO;
+
 	case MNCC_FRAME_DROP:
 	case MNCC_FRAME_RECV:
-	case MNCC_RTP_CREATE:
-	case MNCC_RTP_CONNECT:
-	case MNCC_RTP_FREE:
 	case GSM_TCHF_FRAME:
 	case GSM_TCHF_FRAME_EFR:
 	case GSM_TCHH_FRAME:
@@ -3211,8 +3406,8 @@
 	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);
+		return a_iface_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",
diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c
index ac6c96a..73361a1 100644
--- a/src/libmsc/gsm_subscriber.c
+++ b/src/libmsc/gsm_subscriber.c
@@ -43,6 +43,7 @@
 #include <openbsc/iu.h>
 #include <openbsc/osmo_msc.h>
 #include <openbsc/msc_ifaces.h>
+#include <openbsc/a_iface.h>
 
 int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
 			   struct msgb *msg, void *data, void *param)
@@ -105,7 +106,7 @@
 	 * 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);
+		return a_iface_tx_paging(vsub->imsi, vsub->tmsi, vsub->lac);
 	case RAN_UTRAN_IU:
 		return iu_page_cs(vsub->imsi,
 				  vsub->tmsi == GSM_RESERVED_TMSI?
diff --git a/src/libmsc/iucs.c b/src/libmsc/iucs.c
index aeda140..be026c8 100644
--- a/src/libmsc/iucs.c
+++ b/src/libmsc/iucs.c
@@ -40,8 +40,8 @@
 {
 	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);
+	DEBUGP(DIUCS, "Allocating IuCS subscriber conn: lac %d, conn_id %" PRIx32 "\n",
+	       lac, ue->conn_id);
 
 	conn = talloc_zero(network, struct gsm_subscriber_connection);
 	if (!conn)
@@ -61,8 +61,7 @@
 {
 	if (a == b)
 		return 1;
-	return (a->link == b->link)
-		&& (a->conn_id == b->conn_id);
+	return (a->conn_id == b->conn_id);
 }
 
 static inline void log_subscribers(struct gsm_network *network)
@@ -78,8 +77,7 @@
 		case RAN_UTRAN_IU:
 			DEBUGPC(DIUCS, " Iu");
 			if (conn->iu.ue_ctx) {
-				DEBUGPC(DIUCS, " link %p, conn_id %d",
-					conn->iu.ue_ctx->link,
+				DEBUGPC(DIUCS, " conn_id %d",
 					conn->iu.ue_ctx->conn_id
 				       );
 			}
@@ -101,7 +99,7 @@
 	DEBUGP(DIUCS, "subscribers registered: %d\n", i);
 }
 
-/* Return an existing IuCS subscriber connection record for the given link and
+/* Return an existing IuCS subscriber connection record for the given
  * connection IDs, or return NULL if not found. */
 struct gsm_subscriber_connection *subscr_conn_lookup_iu(
 						struct gsm_network *network,
@@ -109,8 +107,8 @@
 {
 	struct gsm_subscriber_connection *conn;
 
-	DEBUGP(DIUCS, "Looking for IuCS subscriber: link_id %p, conn_id %" PRIx32 "\n",
-	       ue->link, ue->conn_id);
+	DEBUGP(DIUCS, "Looking for IuCS subscriber: conn_id %" PRIx32 "\n",
+	       ue->conn_id);
 	log_subscribers(network);
 
 	llist_for_each_entry(conn, &network->subscr_conns, entry) {
@@ -118,12 +116,12 @@
 			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);
+		DEBUGP(DIUCS, "Found IuCS subscriber for conn_id %" PRIx32 "\n",
+		       ue->conn_id);
 		return conn;
 	}
-	DEBUGP(DIUCS, "No IuCS subscriber found for link_id %p, conn_id %" PRIx32 "\n",
-	       ue->link, ue->conn_id);
+	DEBUGP(DIUCS, "No IuCS subscriber found for conn_id %" PRIx32 "\n",
+	       ue->conn_id);
 	return NULL;
 }
 
diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c
index 56cbd49..7d2e898 100644
--- a/src/libmsc/msc_ifaces.c
+++ b/src/libmsc/msc_ifaces.c
@@ -29,6 +29,7 @@
 #include <openbsc/mgcp.h>
 #include <openbsc/mgcpgw_client.h>
 #include <openbsc/vlr.h>
+#include <openbsc/a_iface.h>
 
 #include "../../bscconfig.h"
 
@@ -41,13 +42,18 @@
 
 static int msc_tx(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
+	if (!conn)
+		return -EINVAL;
+	if (!msg)
+		return -EINVAL;
+
 	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) {
 	case RAN_GERAN_A:
 		msg->dst = conn;
-		return a_tx(msg);
+		return a_iface_tx_dtap(msg);
 
 	case RAN_UTRAN_IU:
 		msg->dst = conn->iu.ue_ctx;
@@ -72,9 +78,15 @@
 /* 9.2.5 CM service accept */
 int msc_gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn)
 {
-	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERV ACC");
-	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct msgb *msg;
+	struct gsm48_hdr *gh;
 
+	if (!conn)
+		return -EINVAL;
+
+	msg = gsm48_msgb_alloc_name("GSM 04.08 SERV ACC");
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
 	gh->proto_discr = GSM48_PDISC_MM;
 	gh->msg_type = GSM48_MT_MM_CM_SERV_ACC;
 
@@ -89,6 +101,10 @@
 			     enum gsm48_reject_value value)
 {
 	struct msgb *msg;
+
+	if (!conn)
+		return -EINVAL;
+
 	conn->received_cm_service_request = false;
 
 	msg = gsm48_create_mm_serv_rej(value);
@@ -104,6 +120,9 @@
 
 int msc_tx_common_id(struct gsm_subscriber_connection *conn)
 {
+	if (!conn)
+		return -EINVAL;
+
 	/* Common ID is only sent over IuCS */
 	if (conn->via_ran != RAN_UTRAN_IU) {
 		LOGP(DMM, LOGL_INFO,
@@ -118,10 +137,10 @@
 	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)
+static int iu_rab_act_cs(struct ue_conn_ctx *uectx, uint8_t rab_id,
+			 uint32_t rtp_ip, uint16_t rtp_port)
 {
+#ifdef BUILD_IU
 	struct msgb *msg;
 	bool use_x213_nsap;
 	uint32_t conn_id = uectx->conn_id;
@@ -140,6 +159,11 @@
 		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);
+	return 0;
+#else
+	LOGP(DMSC, LOGL_ERROR, "Cannot send Iu RAB Assignment: built without Iu support\n");
+	return -ENOTSUP;
+#endif
 }
 
 static void mgcp_response_rab_act_cs_crcx(struct mgcp_response *r, void *priv)
@@ -165,71 +189,75 @@
 		goto rab_act_cs_error;
 	}
 
-	conn->iu.mgcp_rtp_port_cn = r->audio_port;
+	conn->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 */
+
+	if (trans->conn->via_ran == RAN_UTRAN_IU) {
+		/* Assign a voice channel via RANAP on 3G */
+		if (iu_rab_act_cs(uectx, conn->iu.rab_id, rtp_ip, conn->rtp.port_subscr))
+			goto rab_act_cs_error;
+	} else if (trans->conn->via_ran == RAN_GERAN_A) {
+		/* Assign a voice channel via A on 2G */
+		if (a_iface_tx_assignment(trans))
+			goto rab_act_cs_error;
+	} else
+		goto rab_act_cs_error;
+
+	/* Respond back to MNCC (if requested) */
+	if (trans->tch_rtp_create) {
+		if (gsm48_tch_rtp_create(trans))
+			goto rab_act_cs_error;
+	}
+	return;
 
 rab_act_cs_error:
 	/* FIXME abort call, invalidate conn, ... */
+	LOGP(DMSC, LOGL_ERROR, "%s: failure during assignment\n",
+	     vlr_subscr_name(trans->vsub));
 	return;
 }
 
-static int conn_iu_rab_act_cs(struct gsm_trans *trans)
+int msc_call_assignment(struct gsm_trans *trans)
 {
-	struct gsm_subscriber_connection *conn = trans->conn;
-	struct mgcpgw_client *mgcp = conn->network->mgcpgw.client;
+	struct gsm_subscriber_connection *conn;
+	struct mgcpgw_client *mgcp;
 	struct msgb *msg;
+	uint16_t bts_base;
 
-	/* 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 ++;
+	if (!trans)
+		return -EINVAL;
+	if (!trans->conn)
+		return -EINVAL;
 
-	conn->iu.mgcp_rtp_endpoint =
+	conn = trans->conn;
+	mgcp = conn->network->mgcpgw.client;
+
+#ifdef BUILD_IU
+	/* FIXME: HACK. where to scope the RAB Id? At the conn / subscriber / ue_conn_ctx? */
+	static uint8_t next_iu_rab_id = 1;
+	if (conn->via_ran == RAN_UTRAN_IU)
+		conn->iu.rab_id = next_iu_rab_id ++;
+#endif
+
+	conn->rtp.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;
+
+	/* This will calculate the port we assign to the BTS via AoIP
+	 * assignment command (or rab-assignment on 3G) The BTS will send
+	 * its RTP traffic to that port on the MGCPGW side. The MGCPGW only
+	 * gets the endpoint ID via the CRCX. It will do the same calculation
+	 * on his side too to get knowledge of the rtp port. */
+	bts_base = mgcp->actual.bts_base;
+	conn->rtp.port_subscr = bts_base + 2 * conn->rtp.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);
+	msg = mgcp_msg_crcx(mgcp, conn->rtp.mgcp_rtp_endpoint,
+			    conn->rtp.mgcp_rtp_endpoint, 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);
 
@@ -252,8 +280,8 @@
 	ip = mgcpgw_client_remote_addr_str(mgcp);
 
 	msg = mgcp_msg_mdcx(mgcp,
-			    conn1->iu.mgcp_rtp_endpoint,
-			    ip, conn2->iu.mgcp_rtp_port_cn,
+			    conn1->rtp.mgcp_rtp_endpoint,
+			    ip, conn2->rtp.port_cn,
 			    mode);
 	if (mgcpgw_client_tx(mgcp, msg, mgcp_response_bridge_mdcx, from))
 		LOGP(DMGCP, LOGL_ERROR,
@@ -302,8 +330,58 @@
 	}
 }
 
+int msc_call_connect(struct gsm_trans *trans, uint16_t port, uint32_t ip)
+{
+	/* With this function we inform the MGCP-GW  where (ip/port) it
+	 * has to send its outgoing voic traffic. The receiving end will
+	 * usually be a PBX (e.g. Asterisk). The IP-Address we tell, will
+	 * not only be used to direct the traffic, it will also be used
+	 * as a filter to make sure only RTP packets from the right
+	 * remote end will reach the BSS. This is also the reason why
+	 * inbound audio will not work until this step is performed */
+
+	/* NOTE: This function is used when msc_call_bridge(), is not
+	 * applicable. This is usually the case when an external MNCC
+	 * is in use */
+
+	struct gsm_subscriber_connection *conn;
+	struct mgcpgw_client *mgcp;
+	struct msgb *msg;
+
+	if (!trans)
+		return -EINVAL;
+	if (!trans->conn)
+		return -EINVAL;
+	if (!trans->conn->network)
+		return -EINVAL;
+	if (!trans->conn->network->mgcpgw.client)
+		return -EINVAL;
+
+	mgcp = trans->conn->network->mgcpgw.client;
+
+	struct in_addr ip_addr;
+	ip_addr.s_addr = ntohl(ip);
+
+	conn = trans->conn;
+
+	msg = mgcp_msg_mdcx(mgcp,
+			    conn->rtp.mgcp_rtp_endpoint,
+			    inet_ntoa(ip_addr), port, MGCP_CONN_RECV_SEND);
+	if (mgcpgw_client_tx(mgcp, msg, NULL, trans))
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Failed to send MDCX message for %s\n",
+		     vlr_subscr_name(trans->vsub));
+
+	return 0;
+}
+
 int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2)
 {
+	if (!trans1)
+		return -EINVAL;
+	if (!trans2)
+		return -EINVAL;
+
 	/* 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. */
@@ -314,3 +392,31 @@
 
 	return 0;
 }
+
+void msc_call_release(struct gsm_trans *trans)
+{
+	struct msgb *msg;
+	struct gsm_subscriber_connection *conn;
+	struct mgcpgw_client *mgcp;
+
+	if (!trans)
+		return;
+	if (!trans->conn)
+		return;
+	if (!trans->conn->network)
+		return;
+
+	conn = trans->conn;
+	mgcp = conn->network->mgcpgw.client;
+
+	/* Send DLCX */
+	msg = mgcp_msg_dlcx(mgcp, conn->rtp.mgcp_rtp_endpoint,
+			    conn->rtp.mgcp_rtp_endpoint);
+	if (mgcpgw_client_tx(mgcp, msg, NULL, NULL))
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Failed to send DLCX message for %s\n",
+		     vlr_subscr_name(trans->vsub));
+
+	/* Release endpoint id */
+	mgcpgw_client_release_endpoint(conn->rtp.mgcp_rtp_endpoint, mgcp);
+}
diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c
index 82dc7d6..01e7e82 100644
--- a/src/libmsc/msc_vty.c
+++ b/src/libmsc/msc_vty.c
@@ -64,6 +64,26 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_msc_cs7_instance_a,
+      cfg_msc_cs7_instance_a_cmd,
+      "cs7-instance-a <0-15>",
+      "Set SS7 to be used by the A-Interface.\n" "SS7 instance reference number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->a.cs7_instance = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_cs7_instance_iu,
+      cfg_msc_cs7_instance_iu_cmd,
+      "cs7-instance-iu <0-15>",
+      "Set SS7 to be used by the Iu-Interface.\n" "SS7 instance reference number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->iu.cs7_instance = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
 static int config_write_msc(struct vty *vty)
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
@@ -72,6 +92,11 @@
 	vty_out(vty, " %sassign-tmsi%s",
 		gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE);
 
+	vty_out(vty, " cs7-instance-a %u%s", gsmnet->a.cs7_instance,
+		VTY_NEWLINE);
+	vty_out(vty, " cs7-instance-iu %u%s", gsmnet->iu.cs7_instance,
+		VTY_NEWLINE);
+
 	mgcpgw_client_config_write(vty, " ");
 #ifdef BUILD_IU
 	iu_vty_config_write(vty, " ");
@@ -123,6 +148,9 @@
 	vty_install_default(MSC_NODE);
 	install_element(MSC_NODE, &cfg_msc_assign_tmsi_cmd);
 	install_element(MSC_NODE, &cfg_msc_no_assign_tmsi_cmd);
+	install_element(MSC_NODE, &cfg_msc_cs7_instance_a_cmd);
+	install_element(MSC_NODE, &cfg_msc_cs7_instance_iu_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);
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c
index ddc3836..866cfbd 100644
--- a/src/libmsc/osmo_msc.c
+++ b/src/libmsc/osmo_msc.c
@@ -29,11 +29,12 @@
 #include <openbsc/vlr.h>
 #include <openbsc/osmo_msc.h>
 #include <openbsc/iu.h>
+#include <openbsc/a_iface.h>
 
 #include <openbsc/gsm_04_11.h>
 
 /* Receive a SAPI-N-REJECT from BSC */
-static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
 {
 	int sapi = dlci & 0x7;
 
@@ -106,24 +107,24 @@
 }
 
 /* Receive an ASSIGNMENT COMPLETE from BSC */
-static void msc_assign_compl(struct gsm_subscriber_connection *conn,
-			     uint8_t rr_cause, uint8_t chosen_channel,
-			     uint8_t encr_alg_id, uint8_t speec)
+void msc_assign_compl(struct gsm_subscriber_connection *conn,
+		      uint8_t rr_cause, uint8_t chosen_channel,
+		      uint8_t encr_alg_id, uint8_t speec)
 {
 	LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n");
 }
 
 /* Receive an ASSIGNMENT FAILURE from BSC */
-static void msc_assign_fail(struct gsm_subscriber_connection *conn,
-			    uint8_t cause, uint8_t *rr_cause)
+void msc_assign_fail(struct gsm_subscriber_connection *conn,
+		     uint8_t cause, uint8_t *rr_cause)
 {
 	LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n");
 }
 
 /* Receive a CLASSMARK CHANGE from BSC */
-static void msc_classmark_chg(struct gsm_subscriber_connection *conn,
-			      const uint8_t *cm2, uint8_t cm2_len,
-			      const uint8_t *cm3, uint8_t cm3_len)
+void msc_classmark_chg(struct gsm_subscriber_connection *conn,
+		       const uint8_t *cm2, uint8_t cm2_len,
+		       const uint8_t *cm3, uint8_t cm3_len)
 {
 	if (cm2 && cm2_len) {
 		if (cm2_len > sizeof(conn->classmark.classmark2)) {
@@ -250,7 +251,7 @@
 }
 
 /* Receive a CLEAR REQUEST from BSC */
-static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
 {
 	msc_subscr_conn_close(conn, cause);
 	return 1;
@@ -290,7 +291,7 @@
 		 * says "unknown UE" for each release outcome. */
 		break;
 	case RAN_GERAN_A:
-		/* future: a_iface_tx_clear_cmd(conn); */
+		a_iface_tx_clear_cmd(conn);
 		break;
 	default:
 		LOGP(DMM, LOGL_ERROR, "%s: Unknown RAN type, cannot tx release/clear\n",
diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c
index 31decc7..cdeeae9 100644
--- a/src/libmsc/subscr_conn.c
+++ b/src/libmsc/subscr_conn.c
@@ -31,6 +31,8 @@
 #include <openbsc/transaction.h>
 #include <openbsc/signal.h>
 #include <openbsc/iu.h>
+#include <openbsc/a_iface.h>
+
 
 #define SUBSCR_CONN_TIMEOUT 5 /* seconds */
 
@@ -223,7 +225,6 @@
 
 	if (!conn)
 		return;
-
 	conn->conn_fsm = NULL;
  	msc_subscr_conn_close(conn, cause);
 	msc_subscr_conn_put(conn);