diff options
Diffstat (limited to 'src/libmsc')
-rw-r--r-- | src/libmsc/Makefile.am | 2 | ||||
-rw-r--r-- | src/libmsc/a_iface.c | 582 | ||||
-rw-r--r-- | src/libmsc/a_iface_bssap.c | 717 | ||||
-rw-r--r-- | src/libmsc/gsm_04_08.c | 207 | ||||
-rw-r--r-- | src/libmsc/gsm_subscriber.c | 3 | ||||
-rw-r--r-- | src/libmsc/iucs.c | 24 | ||||
-rw-r--r-- | src/libmsc/msc_ifaces.c | 214 | ||||
-rw-r--r-- | src/libmsc/msc_vty.c | 28 | ||||
-rw-r--r-- | src/libmsc/osmo_msc.c | 23 | ||||
-rw-r--r-- | src/libmsc/subscr_conn.c | 3 |
10 files changed, 1695 insertions, 108 deletions
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am index 4726bbe4b..9f246b3d8 100644 --- a/src/libmsc/Makefile.am +++ b/src/libmsc/Makefile.am @@ -13,6 +13,7 @@ AM_CFLAGS = \ $(LIBCRYPTO_CFLAGS) \ $(LIBSMPP34_CFLAGS) \ $(LIBASN1C_CFLAGS) \ + $(LIBOSMOSIGTRAN_CFLAGS) \ $(NULL) noinst_HEADERS = \ @@ -25,6 +26,7 @@ noinst_LIBRARIES = \ 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 caf9d4b06..93e8ab5e9 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> + +/* 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) +{ + 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); +} + +/* Delete info of a closed connection from the active connection list */ +void a_delete_bsc_con(uint32_t conn_id) +{ + 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); + } + } +} + +/* 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; +} -int a_tx(struct msgb *msg) +/* 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) { - 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_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; } -int a_page(const char *imsi, uint32_t tmsi, uint16_t lac) +/* Send DTAP message via A-interface */ +int a_iface_tx_dtap(struct msgb *msg) { - 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 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); } -int msc_gsm0808_tx_cipher_mode(struct gsm_subscriber_connection *conn, int cipher, - const uint8_t *key, int len, int include_imeisv) +/* 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 000000000..561ccdeb0 --- /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 28cba5b85..b6746a564 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 @@ static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans, 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 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans) { 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 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) 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 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) 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 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) 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 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) 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 @@ static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg) 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 @@ static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg) 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 @@ static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg 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 @@ static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg) 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 @@ static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg) 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 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg) if (rc < 0) disconnect_bridge(net, arg, -rc); return rc; - case MNCC_FRAME_DROP: - case MNCC_FRAME_RECV: 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 GSM_TCHF_FRAME: case GSM_TCHF_FRAME_EFR: case GSM_TCHH_FRAME: @@ -3211,8 +3406,8 @@ static int msc_vlr_set_ciph_mode(void *msc_conn_ref, 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 ac6c96a88..73361a169 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 @@ int msc_paging_request(struct vlr_subscr *vsub) * 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 aeda1406a..be026c857 100644 --- a/src/libmsc/iucs.c +++ b/src/libmsc/iucs.c @@ -40,8 +40,8 @@ static struct gsm_subscriber_connection *subscr_conn_allocate_iu(struct gsm_netw { 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 @@ 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); + return (a->conn_id == b->conn_id); } static inline void log_subscribers(struct gsm_network *network) @@ -78,8 +77,7 @@ static inline void log_subscribers(struct gsm_network *network) 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 @@ static inline void log_subscribers(struct gsm_network *network) 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 *subscr_conn_lookup_iu( { 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 @@ struct gsm_subscriber_connection *subscr_conn_lookup_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); + 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 56cbd49d4..7d2e8981b 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 @@ extern struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id, 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 @@ int msc_tx_dtap(struct gsm_subscriber_connection *conn, /* 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 @@ int msc_gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, 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_gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, 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 @@ int msc_tx_common_id(struct gsm_subscriber_connection *conn) 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 @@ static void iu_rab_act_cs(struct ue_conn_ctx *uectx, uint8_t rab_id, 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 @@ static void mgcp_response_rab_act_cs_crcx(struct mgcp_response *r, void *priv) 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; + + if (!trans) + return -EINVAL; + if (!trans->conn) + return -EINVAL; - /* 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 = trans->conn; + mgcp = conn->network->mgcpgw.client; - conn->iu.mgcp_rtp_endpoint = +#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 @@ static void mgcp_bridge(struct gsm_trans *from, struct gsm_trans *to, 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 @@ static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv) } } +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 @@ int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2) 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 82dc7d679..01e7e8295 100644 --- a/src/libmsc/msc_vty.c +++ b/src/libmsc/msc_vty.c @@ -64,6 +64,26 @@ DEFUN(cfg_msc_no_assign_tmsi, cfg_msc_no_assign_tmsi_cmd, 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 @@ static int config_write_msc(struct vty *vty) 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 @@ void msc_vty_init(struct gsm_network *msc_network) 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 ddc383612..866cfbd07 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 @@ void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct ms } /* 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 @@ void msc_subscr_con_free(struct gsm_subscriber_connection *conn) } /* 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 @@ static void msc_subscr_conn_release_all(struct gsm_subscriber_connection *conn, * 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 31decc7b3..cdeeae903 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 @@ static void subscr_conn_fsm_cleanup(struct osmo_fsm_inst *fi, if (!conn) return; - conn->conn_fsm = NULL; msc_subscr_conn_close(conn, cause); msc_subscr_conn_put(conn); |